Skip to content

Conversation

Rajgupta36
Copy link
Collaborator

@Rajgupta36 Rajgupta36 commented Oct 8, 2025

Proposed change


Resolves #2239

Tasks

  • created issues list page
  • created seperate issue page
  • created command to link pr with related issue.
  • created graphql queries and mutationg related to issue,task and pr

Steps

  • Create a program.
  • Create a module and add the appropriate label based on the repository (I used gssoc25 in OWASP Nest).
  • Run make mentorship-sync-module-issues.
  • Then run make github-update-pull-requests.
  • For interested user . add /interested in the github issue and then run make mentorship-update-comments cmd to sync latest comments update
  • You’ll see the linked issue on the Issues page and its details on the issues/:issueId page.

Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Summary by CodeRabbit

  • New Features

    • Added issue detail pages with assignment management and deadline tracking
    • Added module issues listing with label filtering
    • Added GitHub pull request linking to issues via closing keywords
    • Added labels support for mentorship modules
    • Added user interest tracking for module issues
    • Added issue comment synchronization
  • Bug Fixes

    • Improved admin autocomplete for related issues
  • Documentation

    • Added GraphQL mutations for issue management operations

Walkthrough

Adds GitHub comment model and syncing; links pull requests to issues; introduces issue-level and module-issue relationships and user-interest tracking; expands GraphQL fields and mentorship mutations; adds management commands, migrations, frontend issue list/detail pages, components, types, and admin tweaks.

Changes

Cohort / File(s) Summary
Build & Makefiles
\backend/Makefile`, `backend/apps/github/Makefile`, `backend/apps/mentorship/Makefile``
Added mentorship Makefile include and new targets: github-update-pull-requests, mentorship-sync-module-issues, mentorship-sync-issue-levels, mentorship-update-comments.
GitHub models, migrations & admin
\backend/apps/github/models/comment.py`, `backend/apps/github/models/issue.py`, `backend/apps/github/models/pull_request.py`, `backend/apps/github/models/init.py`, `backend/apps/github/migrations/0037_.py`, `backend/apps/github/migrations/0038_.py`, `backend/apps/github/admin/*.py``
Add Comment model and registration, expose Comment/Label, add Issue.level FK, Issue.comments GenericRelation and latest_comment, and PullRequest.related_issues M2M; adjust admin displays/autocomplete.
GitHub utils & management command
\backend/apps/github/common.py`, `backend/apps/github/management/commands/github_update_pull_requests.py``
Add sync_issue_comments(gh_client, issue) and a management command to link PRs to issues using closing-keyword regex; bulk update relations.
GraphQL (backend)
\backend/apps/github/api/internal/nodes/issue.py`, `backend/apps/github/api/internal/nodes/pull_request.py`, `backend/tests/apps/github/api/internal/nodes/*.py` ``
Expose new fields on IssueNode (number, body, assignees, labels, is_merged, interested_users, pull_requests) and on PullRequestNode (state, merged_at); update tests.
Mentorship models, managers & migrations
\backend/apps/mentorship/models/module.py`, `backend/apps/mentorship/models/task.py`, `backend/apps/mentorship/models/issue_user_interest.py`, `backend/apps/mentorship/models/managers/`, `backend/apps/mentorship/migrations/0005_.py`, `backend/apps/mentorship/migrations/*` ``
Add Module.issues M2M, add IssueUserInterest model, add PublishedModuleManager and Module.published_modules, remove Task.level, make Task.assigned_at nullable.
Mentorship admin, utils & management commands
\backend/apps/mentorship/admin/.py`, `backend/apps/mentorship/utils.py`, `backend/apps/mentorship/management/commands/.py`, `cspell/custom-dict.txt` ``
Admin exports/registrations for IssueUserInterest and minor admin tweaks; add normalize_name() util; add commands: sync_module_issues, sync_issue_levels, mentorship_update_comments; add cspell token.
Mentorship API (mutations & nodes)
\backend/apps/mentorship/api/internal/mutations/module.py`, `backend/apps/mentorship/api/internal/nodes/module.py` ``
Add mutations: assign_issue_to_user, unassign_issue_from_user, set_task_deadline; ModuleNode gains issues, issues_count, available_labels, issue_by_number, interested_users, task_deadline/task_assigned_at, and labels input fields.
Frontend — queries, components, pages & types
\frontend/src/server/queries/.ts`, `frontend/src/server/queries/issueQueries.ts`, `frontend/src/server/queries/moduleQueries.ts`, `frontend/src/server/mutations/.ts`, `frontend/src/components/.tsx`, `frontend/src/components/LabelList.tsx`, `frontend/src/app//modules//page.tsx`, `frontend/src/app//issues//page.tsx`, `frontend/src/app/**/issues/[issueId]/page.tsx`, `frontend/src/types/.ts`, `frontend/src/components/ModuleForm.tsx` ``
Add GET_MODULE_ISSUES and GET_MODULE_ISSUE_VIEW queries and assignment/deadline mutations; add LabelList component; add labels prop to DetailsCard and Module forms; implement module issues list and single-issue page with assign/unassign/deadline and PR lists; update frontend types (Issue, PullRequest, Module labels).
Frontend tests & fixtures
\frontend/tests/unit/**`, `frontend/tests/unit/data/.ts`, `backend/tests/apps/github/api/internal/nodes/_test.py` ``
Update test fixtures to include PR id, state, mergedAt; adjust module/issue test data and backend node tests to expect new GraphQL fields.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • arkid15r
  • kasya

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "UI/UX issue and issue details page" is directly related to the main deliverables of the changeset. The title clearly highlights the creation of the issues list page and issue details page, which are the primary frontend UI/UX components mentioned in the linked issue #2239. However, the title focuses on the user-facing UI aspects and does not convey the broader scope of backend infrastructure changes, including new data models, GraphQL mutations, management commands, and database migrations that support these pages. While the title accurately represents a significant portion of the work, it is somewhat partial as it emphasizes only the frontend UI/UX aspect rather than the complete changeset.
Linked Issues Check ✅ Passed The changeset comprehensively addresses the requirements in linked issue #2239. For the Issues List Page, the PR implements title and labels display, assignee rendering, and clickable navigation to individual issues. For the Single Issue Page, it provides issue title and description, assignee information linked to user profiles, labels/levels, interested users list with the ability to assign and unassign issues, and a link to view the issue on GitHub. The backend implements necessary models (Comment, IssueUserInterest), GraphQL nodes with resolvers for assignees, labels, and pull requests, mutations for assign/unassign operations, and management commands to synchronize issue data. The only potentially incomplete requirements are an explicit deadline display on the issue page and a direct link to an open PR for the current assignee, though PR data appears to be fetched and displayed based on the schema changes.
Out of Scope Changes Check ✅ Passed All changes in this pull request are directly scoped to implementing the issues view and single issue page functionality described in linked issue #2239. Backend changes include new models (Comment, IssueUserInterest), field additions (Issue.level, PullRequest.related_issues), and GraphQL schema extensions to expose issue and PR data. Frontend changes include new pages for issues listing and issue details, type definitions, GraphQL queries and mutations, and supporting components. Makefile targets and management commands are added to support data synchronization workflows. Admin configurations are updated for new models. Database migrations establish the necessary schema. All changes are instrumental to achieving the stated objectives and represent no out-of-scope additions.
Description Check ✅ Passed The pull request description is clearly related to the changeset. It references the linked issue (#2239), lists completed tasks (issues list page, issue page, PR linking command, GraphQL queries and mutations), provides clear steps to use the new features, and describes the end-to-end workflow for viewing linked issues and interested users. The description adequately explains what was implemented and how to test the functionality.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fde2800 and 10da824.

📒 Files selected for processing (1)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-28T14:51:14.736Z
Learnt from: adithya-naik
PR: OWASP/Nest#1894
File: frontend/src/components/TopContributorsList.tsx:74-74
Timestamp: 2025-07-28T14:51:14.736Z
Learning: In the OWASP/Nest project, the maintainer adithya-naik prefers not to create separate components for code that's only used in two specific cases, following the YAGNI principle to avoid over-engineering when the duplication is limited and manageable.

Applied to files:

  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx
🧬 Code graph analysis (1)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (4)
frontend/src/server/queries/issueQueries.ts (4)
  • GET_MODULE_ISSUE_VIEW (3-49)
  • ASSIGN_ISSUE_TO_USER (51-67)
  • UNASSIGN_ISSUE_FROM_USER (69-85)
  • SET_TASK_DEADLINE (87-103)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (4-17)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (28)
backend/apps/mentorship/utils.py (1)

4-6: Consider adding type hints for clarity.

The normalization logic is correct, but adding type hints would improve code documentation and IDE support.

Apply this diff to add type hints:

-def normalize_name(name):
+def normalize_name(name: str | None) -> str:
     """Normalize a string by stripping whitespace and converting to lowercase."""
     return (name or "").strip().casefold()
backend/apps/mentorship/management/commands/sync_issue_levels.py (3)

20-38: Clarify label collision handling behavior.

When multiple TaskLevels within the same module share the same normalized label (lines 35-37), the last TaskLevel processed will overwrite previous mappings. This "last-wins" behavior might be intentional, but it could lead to unexpected results if label collisions are not anticipated.

Consider:

  1. Adding a check to detect and warn about label collisions across TaskLevels in the same module.
  2. Documenting this behavior explicitly in the method's docstring.

Example implementation to detect collisions:

 def _build_module_level_maps(self, all_levels):
     """Build a mapping from module ID to a dictionary of data.

     The dictionary contains a 'label_to_level_map' for normalized label/level
     names to TaskLevel objects.
+
+    Note: If multiple TaskLevels in the same module have the same normalized label,
+    the last one processed will be used (last-wins behavior).
     """
     module_data_map = {}
     for level in all_levels:
         module_id = level.module_id
         level_map_container = module_data_map.setdefault(module_id, {"label_to_level_map": {}})
         level_map = level_map_container["label_to_level_map"]

         normalized_level_name = normalize_name(level.name)
         level_map[normalized_level_name] = level

         for label_name in level.labels:
             normalized_label = normalize_name(label_name)
+            if normalized_label in level_map:
+                self.stdout.write(
+                    self.style.WARNING(
+                        f"Label collision: '{label_name}' maps to multiple TaskLevels "
+                        f"in module {level.module.name}"
+                    )
+                )
             level_map[normalized_label] = level
     return module_data_map

40-53: Rename method to reflect "first match" behavior.

The method name _find_best_match_level suggests a prioritization or scoring mechanism, but it actually returns the first matching TaskLevel found. The result depends on:

  1. The order of modules in issue_mentorship_modules.
  2. The order of labels in issue_labels_normalized.

This could lead to non-deterministic behavior if an issue belongs to multiple modules with different matching TaskLevels.

Consider renaming to _find_first_match_level for accuracy:

-    def _find_best_match_level(
+    def _find_first_match_level(
         self,
         issue_labels_normalized,
         issue_mentorship_modules,
         module_data_map,
     ):
-        """Find the best matching TaskLevel for an issue based on its labels and modules."""
+        """Find the first matching TaskLevel for an issue based on its labels and modules.
+        
+        Returns the first TaskLevel that matches any of the issue's labels within any
+        of the issue's modules. The order depends on module and label iteration order.
+        """

And update the call site:

-            best_match_level = self._find_best_match_level(
+            best_match_level = self._find_first_match_level(

72-88: Consider implementing batch processing for scalability.

The current implementation loads all issues into memory at once (lines 72-88). For repositories with thousands of issues, this could lead to memory pressure and slow performance.

Consider implementing batch processing using Django's iterator() with chunking:

# Process issues in batches to reduce memory usage
BATCH_SIZE = 500
issues_to_update = []

issues_query = Issue.objects.prefetch_related(
    Prefetch("labels", queryset=Label.objects.only("name")),
    "mentorship_modules",
).select_related("level").iterator(chunk_size=BATCH_SIZE)

for issue in issues_query:
    issue_labels_normalized = {normalize_name(label.name) for label in issue.labels.all()}
    
    best_match_level = self._find_first_match_level(
        issue_labels_normalized,
        list(issue.mentorship_modules.all()),
        module_data_map,
    )
    
    if issue.level != best_match_level:
        issue.level = best_match_level
        issues_to_update.append(issue)
    
    # Bulk update when batch is full
    if len(issues_to_update) >= BATCH_SIZE:
        Issue.objects.bulk_update(issues_to_update, ["level"])
        self.stdout.write(f"Updated batch of {len(issues_to_update)} issues...")
        issues_to_update = []

# Update remaining issues
if issues_to_update:
    Issue.objects.bulk_update(issues_to_update, ["level"])

Note: Be aware that iterator() with prefetch may require Django 4.1+ for optimal performance. Test this change with your Django version.

backend/apps/github/models/comment.py (4)

30-32: Consider indexing for GenericForeignKey performance.

The content_type and object_id fields form a generic foreign key, which is frequently queried when accessing related comments. Without an index on object_id, queries filtering by content object may suffer performance degradation as the comments table grows.

Apply this diff to add a compound index:

 class Meta:
     db_table = "github_comments"
     verbose_name = "Comment"
     verbose_name_plural = "Comments"
     ordering = ("-nest_created_at",)
+    indexes = [
+        models.Index(fields=["content_type", "object_id"]),
+    ]

20-20: Consider indexing github_id for lookup performance.

The github_id field is used in update_data for lookups (line 73) and is marked as unique. While uniqueness provides an implicit index in most databases, explicitly adding db_index=True improves code clarity and ensures consistent behavior across database backends.

Apply this diff:

-github_id = models.BigIntegerField(unique=True, verbose_name="Github ID")
+github_id = models.BigIntegerField(unique=True, db_index=True, verbose_name="Github ID")

38-51: Add defensive attribute checking in from_github.

The method uses getattr(gh_comment, gh_field, None) with a default, but doesn't validate that gh_comment is the expected type or has the required structure. If called with an invalid object, silent failures could occur.

Consider adding a type check or validation at the method entry:

def from_github(self, gh_comment, author=None):
    """Populate fields from a GitHub API comment object."""
    if not hasattr(gh_comment, 'body'):
        raise ValueError("Invalid GitHub comment object")
    
    field_mapping = {
        "body": "body",
        "created_at": "created_at",
        "updated_at": "updated_at",
    }
    # ... rest of method

72-75: Verify gh_comment.id attribute exists before access.

Line 73 accesses gh_comment.id directly without validation. If the GitHub API returns an unexpected object structure, this could raise an AttributeError.

Apply this diff to add defensive checks:

+if not hasattr(gh_comment, 'id') or gh_comment.id is None:
+    raise ValueError("GitHub comment object missing required 'id' field")
+
 try:
     comment = Comment.objects.get(github_id=gh_comment.id)
 except Comment.DoesNotExist:
     comment = Comment(github_id=gh_comment.id)
backend/apps/mentorship/admin/__init__.py (1)

3-3: Consider alphabetically ordering imports.

The import of IssueUserInterest is placed between existing imports but doesn't follow alphabetical ordering. For consistency and maintainability, consider ordering all imports alphabetically.

Apply this diff:

-from .issue_user_interest import IssueUserInterest
 from .mentee import MenteeAdmin
 from .mentee_program import MenteeProgramAdmin
 from .mentor import MentorAdmin
 from .module import ModuleAdmin
 from .program import ProgramAdmin
 from .task import TaskAdmin
 from .task_level import TaskLevelAdmin
+from .issue_user_interest import IssueUserInterestAdmin

Note: Ensure the imported class name matches the actual export from issue_user_interest.py (likely IssueUserInterestAdmin rather than IssueUserInterest).

backend/apps/mentorship/models/__init__.py (1)

1-1: Consider maintaining alphabetical import order.

The IssueUserInterest import is placed at the top but breaks alphabetical ordering. For consistency with the rest of the imports in this file, consider placing it in alphabetical order.

Apply this diff:

-from .issue_user_interest import IssueUserInterest
 from .mentee import Mentee
 from .mentee_module import MenteeModule
 from .mentee_program import MenteeProgram
 from .mentor import Mentor
 from .mentor_module import MentorModule
 from .module import Module
 from .program import Program
 from .task import Task
 from .task_level import TaskLevel
+from .issue_user_interest import IssueUserInterest

This places the import between MentorModule and Module in alphabetical order.

frontend/src/components/CardDetailsPage.tsx (1)

92-92: Consider using Next.js router pathname instead of window.location.pathname.

Using window.location.pathname directly can be fragile and is not the idiomatic Next.js approach. The useRouter hook from next/navigation provides a pathname property that should be used for path construction.

However, note that in the Next.js App Router (next/navigation), useRouter() doesn't expose a pathname property. If you need the current path, you should use the usePathname() hook:

import { useRouter, usePathname } from 'next/navigation'

// Inside component:
const router = useRouter()
const pathname = usePathname()

// Then use pathname instead of window.location.pathname:
<button
  type="button"
  onClick={() => {
    router.push(`${pathname}/issues`)
  }}
>
  View Issues
</button>

Also applies to: 101-101

backend/apps/mentorship/admin/issue_user_interest.py (1)

11-11: Consider adding user to list_display for better admin UX.

The list_display currently shows only module and issue, but the search_fields includes user__login. For better admin usability, consider adding user to list_display so admins can see which user is interested in each issue at a glance.

Apply this diff:

-    list_display = ("module", "issue")
+    list_display = ("module", "issue", "user")
backend/apps/github/models/pull_request.py (1)

64-69: Add guardrails to prevent cross‑repository relations in related_issues.

Nothing stops linking an Issue from a different repository. Add a validation hook (e.g., m2m_changed receiver) to enforce issue.repository_id == pull_request.repository_id.

Example (place in a signals module or apps.py-ready hook):

from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from apps.github.models import PullRequest, Issue

@receiver(m2m_changed, sender=PullRequest.related_issues.through)
def validate_related_issues(sender, instance: PullRequest, action, pk_set, **kwargs):
    if action == "pre_add" and instance.repository_id and pk_set:
        invalid = Issue.objects.filter(pk__in=pk_set).exclude(repository_id=instance.repository_id).exists()
        if invalid:
            raise ValueError("All related issues must belong to the same repository as the pull request.")
backend/apps/github/admin/comment.py (1)

8-19: Optimize admin list rendering for performance.

Select related author to cut N+1 queries when listing.

 class CommentAdmin(admin.ModelAdmin):
@@
     list_filter = ("nest_created_at", "nest_updated_at")
     search_fields = ("body", "author__login")
+    list_select_related = ("author",)
backend/apps/github/management/commands/github_update_pull_requests.py (1)

24-33: Remove redundant bulk_save and prefetch related_issues to avoid N+1.

  • pr.related_issues.add() writes the M2M through table; no need to bulk update PRs.
  • Prefetch related_issues to avoid a query per PR when computing existing_ids.
-        linked = 0
-        updated_prs = []
+        linked = 0
@@
-        queryset = PullRequest.objects.select_related("repository").all()
+        queryset = (
+            PullRequest.objects.select_related("repository")
+            .prefetch_related("related_issues")
+            .all()
+        )
@@
-            if new_ids:
-                pr.related_issues.add(*new_ids)
-                linked += len(new_ids)
-                updated_prs.append(pr)
+            if new_ids:
+                pr.related_issues.add(*new_ids)
+                linked += len(new_ids)
@@
-        if updated_prs:
-            PullRequest.bulk_save(updated_prs)
-
         self.stdout.write(f"Linked: {linked}")

Also applies to: 47-60

backend/apps/github/api/internal/nodes/issue.py (2)

55-59: Avoid N+1 in interested_users; add select_related + ordering.

Use select_related("user") and deterministic ordering.

-    def interested_users(self) -> list[UserNode]:
+    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)]
+        interests = (
+            IssueUserInterest.objects.select_related("user")
+            .filter(issue=self)
+            .order_by("user__login")
+        )
+        return [i.user for i in interests]

50-53: Return labels in a stable order.

Current order is DB-dependent; sort for consistent UI.

-        return list(self.labels.values_list("name", flat=True))
+        return list(self.labels.order_by("name").values_list("name", flat=True))
backend/apps/mentorship/models/issue_user_interest.py (1)

9-14: Prefer UniqueConstraint over unique_together.

Future-proof the uniqueness rule and name the constraint explicitly.

 class IssueUserInterest(models.Model):
@@
     class Meta:
         db_table = "mentorship_issue_user_interests"
         verbose_name = "Issue User Interest"
         verbose_name_plural = "Issue User Interests"
-        unique_together = ("module", "issue", "user")
+        constraints = [
+            models.UniqueConstraint(
+                fields=("module", "issue", "user"),
+                name="uniq_interest_module_issue_user",
+            )
+        ]
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (2)

29-36: Call useParams once and destructure.

Avoid duplicate hooks and conversions.

-  const { issueId } = useParams() as { issueId: string }
-
-  const { programKey, moduleKey } = useParams() as {
-    programKey: string
-    moduleKey: string
-    issueId: string
-  }
+  const { programKey, moduleKey, issueId } = useParams() as {
+    programKey: string
+    moduleKey: string
+    issueId: string
+  }

133-141: Use non-interactive element and stable key for label chips.

Buttons without handlers hinder a11y; avoid index keys.

-            {visibleLabels.map((label, index) => (
-              <button key={index} className={labelButtonClassName}>
-                {label}
-              </button>
-            ))}
+            {visibleLabels.map((label) => (
+              <span key={label} className={labelButtonClassName}>
+                {label}
+              </span>
+            ))}
             {remainingLabels > 0 && (
-              <button className={labelButtonClassName}>+{remainingLabels} more</button>
+              <span className={labelButtonClassName}>+{remainingLabels} more</span>
             )}
backend/apps/mentorship/api/internal/mutations/module.py (2)

146-149: Use case-insensitive login lookup for GitHub users.

GitHub logins are case-insensitive. Align with resolve_mentors_from_logins to avoid false negatives.

-        gh_user = GithubUser.objects.filter(login=user_login).first()
+        gh_user = GithubUser.objects.filter(login__iexact=user_login).first()

Apply in both assign and unassign.

Also applies to: 188-191


17-17: Import consistency (optional).

Elsewhere you import models via from apps.mentorship.models import IssueUserInterest. Consider the same here for consistency.

backend/apps/mentorship/management/commands/mentorship_update_comments.py (2)

17-19: Anchor the interest command to avoid false positives.

Current r"/interested" matches "not /interested". Anchor and use word boundary.

-INTEREST_PATTERNS = [
-    re.compile(r"/interested", re.IGNORECASE),
-]
+INTEREST_PATTERNS = [
+    re.compile(r"(?m)^\s*/interested\b", re.IGNORECASE),
+]

70-76: Scope issues to module-linked ones to reduce load.

Filtering by all open issues in project repos may process unrelated issues. Prefer the M2M if it’s maintained.

-        relevant_issues = Issue.objects.filter(
-            repository_id__in=module_repos, state=Issue.State.OPEN
-        ).distinct()
+        relevant_issues = module.issues.filter(state=Issue.State.OPEN).distinct()

If Module.issues isn’t fully populated yet, keep current logic; otherwise adopt the M2M for accuracy/perf. Confirm intended scope.

backend/apps/mentorship/api/internal/nodes/module.py (3)

61-73: Deduplicate interested users when multiple repos share the same issue number.

If a module links issues from different repos with the same number, users may appear twice.

-        interests = (
-            IssueUserInterest.objects.select_related("user")
-            .filter(module=self, issue_id__in=issue_ids)
-            .order_by("user__login")
-        )
-        return [i.user for i in interests]
+        interests = (
+            IssueUserInterest.objects.select_related("user")
+            .filter(module=self, issue_id__in=issue_ids)
+        )
+        users = {i.user for i in interests}
+        return sorted(users, key=lambda u: u.login)

Alternatively, add repo as an argument or return interests per specific IssueNode to avoid cross-repo ambiguity.


74-86: Disambiguate by issue ID(s), not just number, to avoid cross-repo mix.

Numbers collide across repos. Align with the approach used above.

-        return (
-            Task.objects.filter(
-                module=self,
-                issue__number=issue_number,
-                deadline_at__isnull=False,
-            )
+        issue_ids = list(self.issues.filter(number=issue_number).values_list("id", flat=True))
+        return (
+            Task.objects.filter(
+                module=self,
+                issue_id__in=issue_ids,
+                deadline_at__isnull=False,
+            )
             .order_by("deadline_at")
             .values_list("deadline_at", flat=True)
             .first()
         )

88-101: Same cross-repo ambiguity for assigned_at; filter by issue IDs.

-        return (
-            Task.objects.filter(
-                module=self,
-                issue__number=issue_number,
-                assigned_at__isnull=False,
-            )
+        issue_ids = list(self.issues.filter(number=issue_number).values_list("id", flat=True))
+        return (
+            Task.objects.filter(
+                module=self,
+                issue_id__in=issue_ids,
+                assigned_at__isnull=False,
+            )
             .order_by("assigned_at")
             .values_list("assigned_at", flat=True)
             .first()
         )
backend/apps/github/migrations/0037_issue_level_comment.py (1)

27-73: Add composite index for GenericRelation lookups.

issue.comments queries hit content_type and object_id. A composite index significantly improves performance at scale.

             options={
                 "verbose_name": "Comment",
                 "verbose_name_plural": "Comments",
                 "db_table": "github_comments",
                 "ordering": ("-nest_created_at",),
+                "indexes": [
+                    models.Index(fields=["content_type", "object_id"], name="github_comments_ct_obj_idx"),
+                ],
             },

If you prefer a separate migration, use migrations.AddIndex on Comment after this migration.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e49e117 and ce1c571.

📒 Files selected for processing (43)
  • backend/Makefile (1 hunks)
  • backend/apps/github/Makefile (1 hunks)
  • backend/apps/github/admin/__init__.py (1 hunks)
  • backend/apps/github/admin/comment.py (1 hunks)
  • backend/apps/github/admin/issue.py (1 hunks)
  • backend/apps/github/admin/pull_request.py (1 hunks)
  • backend/apps/github/api/internal/nodes/issue.py (2 hunks)
  • backend/apps/github/common.py (2 hunks)
  • backend/apps/github/management/commands/github_update_pull_requests.py (1 hunks)
  • backend/apps/github/migrations/0037_issue_level_comment.py (1 hunks)
  • backend/apps/github/migrations/0038_pullrequest_related_issues.py (1 hunks)
  • backend/apps/github/models/__init__.py (1 hunks)
  • backend/apps/github/models/comment.py (1 hunks)
  • backend/apps/github/models/issue.py (3 hunks)
  • backend/apps/github/models/pull_request.py (1 hunks)
  • backend/apps/mentorship/Makefile (1 hunks)
  • backend/apps/mentorship/admin/__init__.py (1 hunks)
  • backend/apps/mentorship/admin/issue_user_interest.py (1 hunks)
  • backend/apps/mentorship/admin/module.py (1 hunks)
  • backend/apps/mentorship/admin/task.py (1 hunks)
  • backend/apps/mentorship/api/internal/mutations/module.py (3 hunks)
  • backend/apps/mentorship/api/internal/nodes/module.py (2 hunks)
  • backend/apps/mentorship/management/commands/mentorship_update_comments.py (1 hunks)
  • backend/apps/mentorship/management/commands/sync_issue_levels.py (1 hunks)
  • backend/apps/mentorship/management/commands/sync_module_issues.py (1 hunks)
  • backend/apps/mentorship/migrations/0005_remove_task_level_module_issues_and_more.py (1 hunks)
  • backend/apps/mentorship/models/__init__.py (1 hunks)
  • backend/apps/mentorship/models/issue_user_interest.py (1 hunks)
  • backend/apps/mentorship/models/managers/__init__.py (1 hunks)
  • backend/apps/mentorship/models/managers/module.py (1 hunks)
  • backend/apps/mentorship/models/module.py (2 hunks)
  • backend/apps/mentorship/models/task.py (1 hunks)
  • backend/apps/mentorship/utils.py (1 hunks)
  • cspell/custom-dict.txt (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1 hunks)
  • frontend/src/components/CardDetailsPage.tsx (1 hunks)
  • frontend/src/components/ModuleCard.tsx (2 hunks)
  • frontend/src/components/ProgramCard.tsx (0 hunks)
  • frontend/src/server/queries/issueQueries.ts (1 hunks)
  • frontend/src/server/queries/moduleQueries.ts (1 hunks)
  • frontend/src/types/issue.ts (2 hunks)
  • frontend/src/types/pullRequest.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/components/ProgramCard.tsx
🧰 Additional context used
🧬 Code graph analysis (25)
backend/apps/mentorship/models/module.py (2)
backend/apps/mentorship/models/managers/module.py (1)
  • PublishedModuleManager (8-13)
backend/apps/mentorship/api/internal/nodes/module.py (1)
  • issues (43-49)
backend/apps/mentorship/models/managers/__init__.py (1)
backend/apps/mentorship/models/managers/module.py (1)
  • PublishedModuleManager (8-13)
backend/apps/mentorship/models/__init__.py (1)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/github/admin/__init__.py (1)
backend/apps/github/admin/comment.py (1)
  • CommentAdmin (8-18)
backend/apps/mentorship/management/commands/mentorship_update_comments.py (6)
backend/apps/common/utils.py (1)
  • truncate (181-193)
backend/apps/github/common.py (1)
  • sync_issue_comments (237-299)
backend/apps/github/models/issue.py (1)
  • Issue (19-234)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/models/module.py (1)
  • Module (17-104)
backend/apps/github/models/generic_issue_model.py (1)
  • State (18-20)
backend/apps/mentorship/models/managers/module.py (1)
backend/apps/mentorship/models/program.py (1)
  • ProgramStatus (25-28)
backend/apps/github/admin/comment.py (1)
backend/apps/github/models/comment.py (1)
  • Comment (11-85)
backend/apps/mentorship/utils.py (1)
backend/apps/mentorship/api/internal/nodes/mentor.py (1)
  • name (18-20)
backend/apps/github/api/internal/nodes/issue.py (3)
backend/apps/github/api/internal/nodes/pull_request.py (1)
  • PullRequestNode (17-42)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/api/internal/nodes/module.py (1)
  • interested_users (62-72)
backend/apps/mentorship/api/internal/nodes/module.py (3)
backend/apps/github/api/internal/nodes/issue.py (2)
  • IssueNode (23-63)
  • interested_users (56-58)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/models/task.py (1)
  • Task (10-80)
backend/apps/mentorship/api/internal/mutations/module.py (3)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/api/internal/nodes/module.py (2)
  • ModuleNode (17-100)
  • issues (43-49)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/apps/mentorship/management/commands/sync_issue_levels.py (4)
backend/apps/github/models/issue.py (1)
  • Issue (19-234)
backend/apps/github/models/label.py (1)
  • Label (9-77)
backend/apps/mentorship/models/task_level.py (1)
  • TaskLevel (8-61)
backend/apps/mentorship/utils.py (1)
  • normalize_name (4-6)
backend/apps/github/migrations/0037_issue_level_comment.py (1)
backend/apps/mentorship/migrations/0005_remove_task_level_module_issues_and_more.py (1)
  • Migration (7-79)
backend/apps/mentorship/models/issue_user_interest.py (2)
backend/apps/github/models/issue.py (1)
  • Meta (25-31)
backend/apps/mentorship/models/module.py (1)
  • Meta (23-31)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (5)
frontend/src/server/queries/moduleQueries.ts (1)
  • GET_MODULE_ISSUES (79-120)
frontend/src/app/global-error.tsx (2)
  • handleAppError (66-86)
  • ErrorDisplay (28-51)
backend/apps/mentorship/api/internal/nodes/module.py (1)
  • issues (43-49)
backend/apps/github/api/internal/nodes/issue.py (2)
  • labels (51-53)
  • assignees (46-48)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
backend/apps/mentorship/admin/__init__.py (1)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/github/models/__init__.py (1)
backend/apps/github/models/comment.py (1)
  • Comment (11-85)
frontend/src/types/issue.ts (1)
frontend/src/types/pullRequest.ts (1)
  • PullRequest (3-11)
backend/apps/mentorship/management/commands/sync_module_issues.py (5)
backend/apps/github/models/issue.py (2)
  • Issue (19-234)
  • save (187-196)
backend/apps/mentorship/models/module.py (2)
  • Module (17-104)
  • save (97-104)
backend/apps/mentorship/models/task.py (2)
  • Task (10-80)
  • Status (19-25)
backend/apps/mentorship/utils.py (1)
  • normalize_name (4-6)
backend/apps/github/models/repository.py (1)
  • path (154-156)
backend/apps/github/management/commands/github_update_pull_requests.py (1)
backend/apps/github/models/pull_request.py (2)
  • PullRequest (11-152)
  • bulk_save (114-116)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (3)
frontend/src/server/queries/issueQueries.ts (3)
  • GET_MODULE_ISSUE_VIEW (3-53)
  • ASSIGN_ISSUE_TO_USER (55-71)
  • UNASSIGN_ISSUE_FROM_USER (73-89)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
backend/apps/github/common.py (5)
backend/apps/github/models/comment.py (3)
  • Comment (11-85)
  • update_data (59-85)
  • bulk_save (54-56)
backend/apps/github/models/issue.py (4)
  • latest_comment (99-106)
  • update_data (210-234)
  • save (187-196)
  • bulk_save (199-201)
backend/apps/github/models/repository.py (2)
  • path (154-156)
  • update_data (307-349)
backend/apps/github/models/user.py (3)
  • User (19-159)
  • update_data (130-159)
  • bulk_save (110-112)
backend/apps/common/models.py (1)
  • bulk_save (19-34)
backend/apps/mentorship/admin/issue_user_interest.py (1)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/migrations/0005_remove_task_level_module_issues_and_more.py (1)
backend/apps/github/migrations/0037_issue_level_comment.py (1)
  • Migration (7-74)
backend/apps/github/models/comment.py (3)
backend/apps/common/models.py (2)
  • BulkSaveModel (10-34)
  • TimestampedModel (37-46)
backend/apps/common/utils.py (1)
  • truncate (181-193)
backend/apps/github/models/issue.py (5)
  • Meta (25-31)
  • from_github (108-147)
  • bulk_save (199-201)
  • update_data (210-234)
  • save (187-196)
🪛 checkmake (0.2.2)
backend/apps/mentorship/Makefile

[warning] 1-1: Missing required phony target "all"

(minphony)


[warning] 1-1: Missing required phony target "clean"

(minphony)


[warning] 1-1: Missing required phony target "test"

(minphony)

🪛 GitHub Actions: Run CI/CD
frontend/src/components/ModuleCard.tsx

[warning] 15-15: ESLint import/order warning: 'next/navigation' import should occur before import of 'react'. (Step: pnpm run lint:check)


[warning] 18-18: ESLint import/order warning: 'next/dist/shared/lib/hooks-client-context.shared-runtime' import should occur before import of 'next/link'. (Step: pnpm run lint:check)


[error] 18-18: ESLint error: 'PathnameContext' is defined but never used. (no-unused-vars) (Step: pnpm run lint:check)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (26)
frontend/src/components/ModuleCard.tsx (1)

73-73: LGTM! Correct migration to Next.js navigation hook.

The replacement of window.location.pathname with usePathname() follows Next.js best practices and correctly constructs the module link path. This change properly integrates with the new module/issue navigation flows introduced in this PR.

Also applies to: 77-77

backend/Makefile (1)

3-3: LGTM!

The include directive correctly integrates the mentorship Makefile into the backend build workflow, following the established pattern for other app-specific Makefiles.

cspell/custom-dict.txt (1)

123-123: LGTM!

The addition of "unassigning" to the custom dictionary correctly supports the new unassignment functionality introduced in this PR.

backend/apps/mentorship/models/managers/__init__.py (1)

1-1: LGTM!

The package initialization correctly exposes PublishedModuleManager as part of the managers module's public API, following standard Python conventions.

backend/apps/mentorship/admin/task.py (1)

28-28: LGTM!

The ordering by -assigned_at appropriately displays the most recently assigned tasks first in the admin interface. Django will place tasks with null assigned_at values at the end, which is the expected behavior.

backend/apps/mentorship/Makefile (1)

1-9: LGTM!

All three Makefile targets follow the established exec-backend-command pattern consistently and correctly integrate with the corresponding Django management commands.

Note: The static analysis warnings about missing phony targets (all, clean, test) are false positives. These are not required in an included Makefile that's meant to extend the parent backend/Makefile.

backend/apps/mentorship/models/task.py (1)

27-31: Verify nullable assigned_at usage and handling
Ensure all Task creation paths explicitly set assigned_at, no code (views, serializers, filters, etc.) assumes it’s non-null, and the migration backfills or otherwise handles existing records.

backend/apps/mentorship/migrations/0005_remove_task_level_module_issues_and_more.py (1)

38-78: LGTM!

The IssueUserInterest model is well-designed with:

  • Appropriate foreign key relationships with CASCADE deletion behavior
  • unique_together constraint preventing duplicate interest records
  • Clear verbose names for admin display
  • Proper db_table naming convention

The model structure aligns with the requirements for tracking user interest in issues within modules.

backend/apps/github/admin/issue.py (1)

22-22: LGTM!

Adding level to list_display provides visibility into issue difficulty levels in the admin interface, which aligns with the new Issue.level field added in migration 0037.

backend/apps/github/admin/__init__.py (1)

3-3: LGTM!

The CommentAdmin import follows the established pattern for admin registrations in the GitHub app and maintains alphabetical ordering with other admin imports.

backend/apps/mentorship/admin/module.py (1)

16-16: LGTM!

Adding autocomplete_fields for the issues M2M relationship improves the admin UI experience when linking issues to modules. The IssueAdmin already has search_fields = ("title",) configured, which enables the autocomplete functionality to work correctly.

frontend/src/types/issue.ts (2)

1-1: LGTM!

The import is necessary for the new pullRequests field and follows TypeScript conventions.


13-13: LGTM!

The optional pullRequests field correctly models the many-to-many relationship between issues and pull requests established in the backend.

backend/apps/github/models/__init__.py (1)

3-3: LGTM!

The Comment import follows the established pattern for exposing models publicly.

backend/apps/github/admin/pull_request.py (1)

17-17: LGTM!

Adding related_issues to autocomplete_fields enables efficient admin selection of related issues and is consistent with the existing pattern for other relational fields.

frontend/src/server/queries/moduleQueries.ts (1)

79-120: LGTM!

The GET_MODULE_ISSUES query is well-structured and fetches all necessary fields for displaying module issues with their related pull requests, assignees, and labels. The nested structure aligns with the type definitions and backend schema.

backend/apps/github/migrations/0038_pullrequest_related_issues.py (1)

1-19: LGTM!

The migration correctly adds a many-to-many relationship between PullRequest and Issue. The blank=True configuration is appropriate since not all pull requests are linked to issues, and the related_name="pull_requests" establishes a clear reverse relationship.

backend/apps/github/Makefile (1)

21-23: Management command exists: Verified github_update_pull_requests is implemented in backend/apps/github/management/commands/github_update_pull_requests.py.

backend/apps/mentorship/models/managers/module.py (1)

11-13: LGTM.

Clean queryset override targeting published programs.

backend/apps/mentorship/models/module.py (1)

20-22: LGTM.

Manager exposure and issues M2M look consistent with the API nodes using select/prefetch.

Also applies to: 72-78

frontend/src/server/queries/issueQueries.ts (1)

3-53: LGTM.

Queries and mutations align with the described schema and UI needs.

Also applies to: 55-71, 73-89

backend/apps/github/models/issue.py (1)

98-107: Ordering by nest_created_at is valid. Comment inherits nest_created_at via TimestampedModel.

backend/apps/mentorship/management/commands/sync_module_issues.py (1)

128-169: Create/update tasks for all assignees, not just the first.

Using first() drops additional assignees; iterate over all.

-                for issue in issues:
-                    assignee = issue.assignees.first()
-                    if not assignee:
-                        continue
-
-                    status = self._get_status(issue, assignee)
-                    task, created = Task.objects.get_or_create(
-                        issue=issue,
-                        assignee=assignee,
-                        defaults={"module": module, "status": status},
-                    )
+                for issue in issues:
+                    for assignee in issue.assignees.all():
+                        status = self._get_status(issue, assignee)
+                        task, created = Task.objects.get_or_create(
+                            issue=issue,
+                            assignee=assignee,
+                            defaults={"module": module, "status": status},
+                        )
@@
-                    if (created or task.assigned_at is None) and issue.repository:
+                        if (created or task.assigned_at is None) and issue.repository:
@@
-                    if created:
-                        num_tasks_created += 1
-                        self.stdout.write(
-                            self.style.SUCCESS(
-                                f"Task created for user '{assignee.login}' on issue "
-                                f"{issue.repository.name}#{issue.number} "
-                                f"in module '{module.name}'"
-                            )
-                        )
+                        if created:
+                            num_tasks_created += 1
+                            self.stdout.write(
+                                self.style.SUCCESS(
+                                    f"Task created for user '{assignee.login}' on issue "
+                                    f"{issue.repository.name}#{issue.number} "
+                                    f"in module '{module.name}'"
+                                )
+                            )
@@
-                    if updates:
-                        for field, value in updates.items():
-                            setattr(task, field, value)
-                        task.save(update_fields=list(updates.keys()))
+                        if updates:
+                            for field, value in updates.items():
+                                setattr(task, field, value)
+                            task.save(update_fields=list(updates.keys()))
⛔ Skipped due to learnings
Learnt from: Rajgupta36
PR: OWASP/Nest#2000
File: backend/apps/mentorship/management/commands/sync_module_issues.py:109-145
Timestamp: 2025-08-17T11:55:55.990Z
Learning: In the OWASP/Nest mentorship system, tasks are designed to be assigned to only one assignee per issue, even if GitHub issues can have multiple assignees. The sync_module_issues command correctly uses issue.assignees.first() to create one task per issue for the first assignee only.
backend/apps/github/api/internal/nodes/issue.py (1)

60-63: Resolver pull_requests is valid PullRequest model declares its ManyToManyField to Issue with related_name="pull_requests", so this resolver will work as intended.

backend/apps/mentorship/api/internal/nodes/module.py (2)

42-50: Good resolver with prefetch/select_related.


51-59: Single issue lookup looks good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
frontend/src/components/ModuleCard.tsx (1)

11-12: Fix import order to comply with linting rules.

The next/navigation import on line 11 should be placed before the react import on line 12, as flagged in the previous review and indicated by the linting error.

Apply this diff to fix the import order:

 import Link from 'next/link'
+import { usePathname } from 'next/navigation'
 import { useState } from 'react'
 import type { Module } from 'types/mentorship'
 import { formatDate } from 'utils/dateFormatter'
 import { TextInfoItem } from 'components/InfoItem'
-import { usePathname } from 'next/navigation'
 import SingleModuleCard from 'components/SingleModuleCard'
🧹 Nitpick comments (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (2)

174-174: Prefer displaying name over login for better readability.

Line 174 displays a.login || a.name, which prefers the technical login over the more human-readable name. Consider reversing the precedence to a.name || a.login for improved user experience.

Apply this diff:

-                  <span className="text-sm font-medium">{a.login || a.name}</span>
+                  <span className="text-sm font-medium">{a.name || a.login}</span>

110-118: Consider using consistent date formatting with locale options.

The date formatting uses toLocaleDateString() without arguments, which relies on the browser's default locale. For consistent presentation and better internationalization support, consider passing explicit locale and format options.

Example with consistent formatting:

const formatDate = (dateString: string) => {
  return new Date(dateString).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'short',
    day: 'numeric'
  })
}

// Then use:
{data?.getModule?.taskAssignedAt
  ? formatDate(data.getModule.taskAssignedAt)
  : 'Not assigned'}

Alternatively, you could use a library like date-fns or dayjs for more robust date formatting with i18n support.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce1c571 and 857041c.

📒 Files selected for processing (11)
  • frontend/__tests__/unit/components/CardDetailsPage.test.tsx (1 hunks)
  • frontend/__tests__/unit/components/ItemCardList.test.tsx (1 hunks)
  • frontend/__tests__/unit/components/RecentPullRequests.test.tsx (2 hunks)
  • frontend/__tests__/unit/data/mockOrganizationData.ts (2 hunks)
  • frontend/__tests__/unit/data/mockProjectDetailsData.ts (2 hunks)
  • frontend/__tests__/unit/data/mockRepositoryData.ts (2 hunks)
  • frontend/__tests__/unit/data/mockUserDetails.ts (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1 hunks)
  • frontend/src/components/CardDetailsPage.tsx (1 hunks)
  • frontend/src/components/ModuleCard.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/tests/unit/data/mockProjectDetailsData.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/CardDetailsPage.tsx
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (3)
frontend/src/server/queries/issueQueries.ts (3)
  • GET_MODULE_ISSUE_VIEW (3-53)
  • ASSIGN_ISSUE_TO_USER (55-71)
  • UNASSIGN_ISSUE_FROM_USER (73-89)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
🔇 Additional comments (7)
frontend/src/components/ModuleCard.tsx (1)

72-76: Correct migration to Next.js usePathname hook.

The replacement of window.location.pathname with the Next.js usePathname hook is the correct approach for constructing module links in Next.js 15. The hook is properly called at the top level of the component, and the href construction is accurate.

frontend/__tests__/unit/data/mockOrganizationData.ts (1)

79-79: LGTM! Test data augmentation is consistent.

The addition of id fields to the pull request mock data aligns with the updated PullRequest type shape and is consistent with similar changes across other test files in this PR.

Also applies to: 89-89

frontend/__tests__/unit/components/CardDetailsPage.test.tsx (1)

521-521: LGTM! Test data updated correctly.

The id field addition to mock pull request data is consistent with the PullRequest type changes and aligns with similar updates across the test suite.

frontend/__tests__/unit/components/ItemCardList.test.tsx (1)

151-151: LGTM! Mock data aligned with type definition.

The id field addition to the mock pull request object is a straightforward update that aligns with the PullRequest type augmentation.

frontend/__tests__/unit/data/mockUserDetails.ts (1)

48-48: LGTM! Consistent test data update.

The id field addition to the pull request mock data is consistent with the pattern established across all test fixtures in this PR.

frontend/__tests__/unit/data/mockRepositoryData.ts (1)

65-65: LGTM! Test data properly augmented.

The addition of id fields to both pull request entries is consistent with the updated PullRequest type and follows the same descriptive naming pattern used in other test files.

Also applies to: 75-75

frontend/__tests__/unit/components/RecentPullRequests.test.tsx (1)

43-43: LGTM! Test fixtures updated correctly.

The id field additions to the test data objects align with the PullRequest type changes. Both objects using the same id value is acceptable since they're used in separate, isolated test cases.

Also applies to: 55-55

@Rajgupta36 Rajgupta36 self-assigned this Oct 8, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
backend/apps/mentorship/api/internal/mutations/module.py (2)

139-196: Stop passing msg= into Django exceptions

Every raise ObjectDoesNotExist(msg=...) / raise PermissionDenied(msg=...) here will immediately crash with TypeError: __init__() got an unexpected keyword argument 'msg'. Pass the message positionally (and add one where it’s currently missing). Example fix:

-        if module is None:
-            raise ObjectDoesNotExist(msg="Module not found.")
+        if module is None:
+            raise ObjectDoesNotExist("Module not found.")
...
-        if mentor is None:
-            raise PermissionDenied(msg="Only mentors can assign issues.")
+        if mentor is None:
+            raise PermissionDenied("Only mentors can assign issues.")
...
-        if gh_user is None:
-            raise ObjectDoesNotExist(msg="Assignee not found.")
+        if gh_user is None:
+            raise ObjectDoesNotExist("Assignee not found.")
...
-        if issue is None:
-            raise ObjectDoesNotExist(msg="Issue not found in this module.")
+        if issue is None:
+            raise ObjectDoesNotExist("Issue not found in this module.")

Please update the remaining occurrences in this file the same way.


212-223: Same msg= bug in update flow

raise ObjectDoesNotExist(msg="Module not found.") and raise PermissionDenied(msg="Only mentors can edit modules.") will also throw TypeError at runtime. Switch to positional strings:

-        except Module.DoesNotExist as e:
-            raise ObjectDoesNotExist(msg="Module not found.") from e
+        except Module.DoesNotExist as e:
+            raise ObjectDoesNotExist("Module not found.") from e
...
-        except Mentor.DoesNotExist as err:
-            ...
-            raise PermissionDenied(msg="Only mentors can edit modules.") from err
+        except Mentor.DoesNotExist as err:
+            ...
+            raise PermissionDenied("Only mentors can edit modules.") from err
🧹 Nitpick comments (2)
frontend/src/server/queries/moduleQueries.ts (1)

80-121: Consider adding pagination to the issues query.

The GET_MODULE_ISSUES query fetches all issues for a module without pagination. For modules with many issues, this could lead to large response sizes and slower query performance.

Consider adding pagination parameters (e.g., first, after) to the query:

query GetModuleIssues(
  $programKey: String!
  $moduleKey: String!
  $first: Int
  $after: String
) {
  getModule(moduleKey: $moduleKey, programKey: $programKey) {
    id
    name
    key
    issues(first: $first, after: $after) {
      edges {
        node {
          # ... issue fields
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
backend/apps/mentorship/api/internal/nodes/module.py (1)

43-50: Consider adding pagination to the issues resolver.

The issues() resolver returns all issues for a module without pagination. For modules with many issues, this could lead to performance issues and large response sizes.

Consider implementing cursor-based or limit/offset pagination:

@strawberry.field
def issues(self, first: int | None = None, after: str | None = None) -> list[IssueNode]:
    """Return issues linked to this module."""
    queryset = (
        self.issues.select_related("repository", "author")
        .prefetch_related("assignees", "labels")
        .order_by("-created_at")
    )
    if first:
        queryset = queryset[:first]
    return list(queryset)

Or return a connection type with page info for full pagination support.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2838aa0 and ca49e13.

📒 Files selected for processing (15)
  • backend/apps/mentorship/api/internal/mutations/module.py (5 hunks)
  • backend/apps/mentorship/api/internal/nodes/module.py (5 hunks)
  • frontend/__tests__/unit/components/SingleModuleCard.test.tsx (1 hunks)
  • frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/edit/page.tsx (2 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx (2 hunks)
  • frontend/src/components/CardDetailsPage.tsx (3 hunks)
  • frontend/src/components/LabelList.tsx (1 hunks)
  • frontend/src/components/ModuleCard.tsx (3 hunks)
  • frontend/src/components/ModuleForm.tsx (2 hunks)
  • frontend/src/server/mutations/moduleMutations.ts (2 hunks)
  • frontend/src/server/queries/moduleQueries.ts (2 hunks)
  • frontend/src/types/card.ts (1 hunks)
  • frontend/src/types/mentorship.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/ModuleCard.tsx
  • frontend/src/components/CardDetailsPage.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
backend/apps/mentorship/api/internal/mutations/module.py (7)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/github/api/internal/nodes/issue.py (2)
  • labels (51-53)
  • assignees (46-48)
backend/apps/nest/api/internal/permissions.py (1)
  • IsAuthenticated (10-21)
backend/apps/mentorship/api/internal/nodes/module.py (2)
  • ModuleNode (17-101)
  • issues (44-50)
backend/apps/github/api/internal/queries/user.py (1)
  • user (40-53)
backend/apps/mentorship/models/module.py (1)
  • Module (17-104)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx (1)
frontend/src/utils/parser.ts (1)
  • parseCommaSeparated (1-6)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/edit/page.tsx (1)
frontend/src/utils/parser.ts (1)
  • parseCommaSeparated (1-6)
frontend/src/components/LabelList.tsx (2)
backend/apps/github/models/label.py (1)
  • Label (9-77)
backend/apps/github/api/internal/nodes/issue.py (1)
  • labels (51-53)
backend/apps/mentorship/api/internal/nodes/module.py (3)
backend/apps/github/api/internal/nodes/issue.py (3)
  • IssueNode (23-63)
  • labels (51-53)
  • interested_users (56-58)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/models/task.py (1)
  • Task (10-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (10)
frontend/src/components/ModuleForm.tsx (1)

19-19: LGTM!

The labels field is correctly added to the form data structure and the UI input follows the same pattern as the existing domains and tags fields.

Also applies to: 189-202

frontend/src/server/mutations/moduleMutations.ts (1)

15-15: LGTM!

The labels field is correctly added to both mutation selection sets, consistent with other fields like tags and domains.

Also applies to: 39-39

frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx (1)

42-42: LGTM!

The labels field initialization and parsing follows the same pattern as domains and tags, maintaining consistency across the codebase.

Also applies to: 92-92

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx (1)

68-68: LGTM!

The labels prop is correctly passed to DetailsCard, following the same pattern as tags and domains.

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/edit/page.tsx (1)

85-85: LGTM!

The labels field is correctly initialized by joining the array into a comma-separated string and parsed back into an array for the mutation, following the same pattern as domains and tags. The null/undefined handling with || [] is appropriate.

Also applies to: 107-107

frontend/src/server/queries/moduleQueries.ts (1)

63-63: LGTM!

The labels field is correctly added to the query selection set.

backend/apps/mentorship/api/internal/nodes/module.py (4)

27-27: LGTM!

The labels field is correctly added to ModuleNode and both input types, with appropriate default values.

Also applies to: 113-113, 133-133


52-60: LGTM!

The issue_by_number resolver correctly fetches a single issue with proper query optimization using select_related and prefetch_related.


62-73: LGTM!

The interested_users resolver correctly handles the case where a module might have issues from multiple repositories with the same issue number. The logic appropriately fetches all matching issue IDs and returns the interested users.


75-87: LGTM!

Both task_deadline and task_assigned_at resolvers correctly fetch the earliest timestamp for tasks linked to the module and issue number, with proper null filtering.

Also applies to: 89-101

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

Good start.
Left some comments ⬇️

</button>
</div>
))}
{assignees.length === 0 && <span className="text-sm text-gray-400">Unassigned</span>}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's just not show anything if the issue is not assigned - I feel like that will be clearer visually.

<SecondaryCard title={<AnchorTitle title="Task Timeline" />}>
<div className="space-y-1 text-sm text-gray-700 dark:text-gray-300">
<div>
<span className="font-medium">Assigned:</span>{' '}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is confusing...
So right now for one of the issues I see Assignee and at the same time this block says Not Assigned. 🤔

Image

Did you mean to show when the issue was assigned? If so, why does it not show anything even though we show Assignee.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@Rajgupta36 so about this... Will the new synced data help with this too? Or was it addressed in changes you pushed since review?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I updated the command so that it always syncs the latest assignee date, ensuring that both the assignee and the assigned date are included if an assignee is present

Copy link
Collaborator

Choose a reason for hiding this comment

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

How are these ordered right now? Can we show the most recent ones at the top?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right now, they are ordered by the created_at attribute. Do we want to use another field?
image

Copy link
Collaborator

Choose a reason for hiding this comment

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

@Rajgupta36 as I can see right now the oldest issues are at the top, which is not the best idea.

Also, not really relevant, but I can't open images you attach. When I click on them - they lead to the address for some private-user and fallback to 404. Not a huge deal and not sure if you can attach them differently. But that would be good to be able to click into these.
Screenshot 2025-10-12 at 11 01 46 AM

I've never had this issue before 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, I synced everything again and I see a different order now.
Not sure how these are ordered tbh.
Screenshot 2025-10-12 at 1 53 32 PM
Screenshot 2025-10-12 at 1 53 42 PM

Copy link
Collaborator

@kasya kasya Oct 10, 2025

Choose a reason for hiding this comment

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

Something is off here.
Look at issue #1766

Current assignee is @trucodd. Your page shows assignee as the first person who was assigned for the issue. Since then they have been removed as an assignee and the issue was reassigned.

Image Image

Copy link
Collaborator

Choose a reason for hiding this comment

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

Same for #1385
Page shows no assignee at all and shows PR from first assignee of the issue. That PR was closed and never merged in, though this is not the point.
After that the issue was reassigned and another person completed the task - but it's never shown on this page, not in Assignee nor in the PR section.

This needs to be fixed:

Image

Also this case shows one important thing - we should probably add a label to the PR view about if the PR was merged or closed - that's important.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right now, it loads old data using the make load-data command.
If you want to see the current data, please use this command:

make github-update-owasp-organization --repository Nest

My personal access token got rate-limited halfway 😭 through the syncing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image image Assignee date is wrong in second image. got rate limited while fetching

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is better 👌🏼 Thanks!
Can we change the merged color for traditional GitHub color for merge (which is purple shade)? I do think those colors are something people are already used to and understand without even needing to read words like merged or closed.

I would keep green shade for Open PRs.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm syncing new data locally and will try that out when it's done 👍🏼

Copy link
Collaborator

Choose a reason for hiding this comment

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

ok, everything synced properly. I did not run into any rate limiting issues, so I assume everything was synced and should show up.
I see Assigned at timestamp now, but I still don't see Deadline date.
For example for issue #1385 :
Screenshot 2025-10-12 at 1 57 20 PM
Screenshot 2025-10-12 at 1 59 41 PM

I do see correct PRs (with a proper state) and and correct assignee now 👍🏼 . So the only issue would be the deadline date here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Regarding this matter, I have spoken with Ark. Could you please review it and share your feedback?
https://owasp.slack.com/archives/C08RHMHHZ5K/p1755798724749389?thread_ts=1755720622.747399&cid=C08RHMHHZ5K

<div className="flex flex-row items-center gap-2">
<FontAwesomeIcon icon={faUsers} className="mr-2 h-5 w-5" />
</div>
<span>Interested Users</span>
Copy link
Collaborator

@kasya kasya Oct 10, 2025

Choose a reason for hiding this comment

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

UPD: Realized we need to have something like /interested on the issue comment to mark user as interested.
How did you test this locally?


I'm yet to find an issue that would show some interested users.. 🤔
Do you have any examples? I can see we have issues from gssoc25 label that were not taken yet.
This one for example - #2177

I don't even see this issue in the list we pull for some reason 🤔 This is also a problem.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also, suppose we have issue #2177 in our project locally.
Just try commenting /interested on that issue.
Then, run this command

make mentorship-update-comments

to update the comment locally — you’ll get the list of interested users in the array.
image
image

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for pointing that out. This should have been added to the steps to test though - I'm not always checking all of the backend logic to know which commands to run to check if this works.

Could you update the description with the rest of the steps required to fully test the implementation? Thanks!

@kasya
Copy link
Collaborator

kasya commented Oct 10, 2025

Also, can we add a way to view Issues from this dropdown for a Module?
Screenshot 2025-10-09 at 9 32 28 PM

@Rajgupta36
Copy link
Collaborator Author

Also, can we add a way to view Issues from this dropdown for a Module? Screenshot 2025-10-09 at 9 32 28 PM

done

@Rajgupta36 Rajgupta36 requested a review from kasya October 12, 2025 11:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ede2bc7 and fb70d24.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/components/CardDetailsPage.test.tsx (1 hunks)
  • frontend/__tests__/unit/components/ItemCardList.test.tsx (2 hunks)
  • frontend/__tests__/unit/components/RecentPullRequests.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/tests/unit/components/RecentPullRequests.test.tsx
  • frontend/tests/unit/components/ItemCardList.test.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

Thanks for clarifying some commands. I'll need to check the dates and assignees/PRs after my sync is done again.
But for now I have a few more comments ⬇️

)}
</div>
) : (
<span className="text-gray-400">Unassigned</span>
Copy link
Collaborator

Choose a reason for hiding this comment

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

This was not resolved as per by prev. request.
I can still see Unassigned if the issue is not assigned instead an of empty space there.

Image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ohh.sorry for that

id
number
title
summary
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do believe we'd better show an actual description of the issue, instead of the summary. Is this the same summary we show on the Contribute page?

Do we have actual description available? That would make more sense to show the full thing here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's same summary as contribute page.
this is how body looks
image

Comment on lines 94 to 123
issues(limit: $limit, offset: $offset, label: $label) {
id
number
createdAt
title
summary
url
author {
id
avatarUrl
login
name
}
assignees {
id
avatarUrl
login
name
}
labels
pullRequests {
id
title
url
createdAt
author {
id
login
name
avatarUrl
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we need ALL of this info at this level. Noticed because of the summary which we never use on this page, but I think there is a lot of other properties as well that we don't need.

Can we clean this up?

className="block max-w-xl cursor-pointer text-left hover:underline"
>
<TruncatedText
text={`${issue.title.slice(0, 50)}${issue.title.length > 50 ? '…' : ''}`}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do believe we better show the full title on hover in Tooltip/title property. Otherwise there's no sense in showing truncated text on truncated text...

Image

Comment on lines 256 to 259
<ActionButton url={pr.url} tooltipLabel="View PR">
<FontAwesomeIcon icon={faLink} />
<span className="hidden sm:inline">View PR</span>
</ActionButton>
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is redundant and can be removed completely. The link tot he PR in the title leads to the same place. Plus it will look nicer, since styles for PR state and this button don't really look nice together:

Image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image image colors took it from here https://github.com/microsoft/vscode-pull-request-github/pull/555/files

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think these are old ones 🤔

You should be able to get the colors from inspecting issues pages, then you can transform that into hex code:
Screenshot 2025-10-17 at 7 02 47 PM

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm syncing new data locally and will try that out when it's done 👍🏼

<SecondaryCard title={<AnchorTitle title="Task Timeline" />}>
<div className="space-y-1 text-sm text-gray-700 dark:text-gray-300">
<div>
<span className="font-medium">Assigned:</span>{' '}
Copy link
Collaborator

Choose a reason for hiding this comment

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

@Rajgupta36 so about this... Will the new synced data help with this too? Or was it addressed in changes you pushed since review?

Copy link
Collaborator

Choose a reason for hiding this comment

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

ok, everything synced properly. I did not run into any rate limiting issues, so I assume everything was synced and should show up.
I see Assigned at timestamp now, but I still don't see Deadline date.
For example for issue #1385 :
Screenshot 2025-10-12 at 1 57 20 PM
Screenshot 2025-10-12 at 1 59 41 PM

I do see correct PRs (with a proper state) and and correct assignee now 👍🏼 . So the only issue would be the deadline date here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we also add a label tag to show current state of the issue? Is it open or closed.
We could show that next to a title on the Issue page.
Or even underneath the title, where we have the Org/repo and the Issue ID. Whatever you prefer.

Image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

image image

does it looks good ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@Rajgupta36 Sorry, I still can't open your images for some reason. Are those the same colors GitHub uses? If so, they look great 👍🏼
Also, it's hard to tell from the small image, but do both have the same border radius? The one on the issue page looks a bit more pill-shaped - can we make them consistent?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (2)

30-36: Remove duplicate useParams destructuring.

Extract all params once.

Apply this diff:

-  const { issueId } = useParams() as { issueId: string }
-
-  const { programKey, moduleKey } = useParams() as {
-    programKey: string
-    moduleKey: string
-    issueId: string
-  }
+  const { programKey, moduleKey, issueId } = useParams() as {
+    programKey: string
+    moduleKey: string
+    issueId: string
+  }

303-327: Add fallback for missing login and aria-label to Assign button.

Prevents rendering “@undefined” and improves accessibility.

Apply this diff:

-                  <span className="text-sm font-medium">@{u.login}</span>
+                  <span className="text-sm font-medium">@{u.login || u.name || 'Unknown'}</span>
@@
-                <button
+                <button
                   type="button"
+                  aria-label={`Assign issue to @${u.login || u.name || 'Unknown'}`}
                   disabled={!issueId || assigning}
🧹 Nitpick comments (4)
backend/apps/github/api/internal/nodes/issue.py (1)

45-48: Consider prefetch optimization for nested field access.

If nested UserNode fields are frequently accessed (e.g., user login or email), consider prefetching to prevent potential N+1 queries.

Apply this diff to add prefetch optimization if needed:

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

Note: Only apply if profiling shows N+1 queries in production.

frontend/src/types/issue.ts (1)

6-11: Align Issue.number and timestamps with backend types (or split types).

GraphQL Issue.number is Int, and timestamps are typically ISO strings. Here, number?: string and createdAt/updatedAt: number can cause subtle bugs if reused for GraphQL data. Either:

  • Update to number?: number and createdAt/updatedAt: string, or
  • Keep this as an Algolia/search shape and define a separate GraphQL Issue type.

Please verify usages and adjust accordingly.

Also applies to: 16-18

frontend/src/server/queries/moduleQueries.ts (1)

80-106: Prefer cursor-based pagination and include assignee id.

  • Offset pagination can duplicate/skip items when the list changes; consider cursor-based pagination for stability.
  • Include assignees.id to support stable React keys downstream.

Example (shape only):

  • issues(after: $cursor, first: $first) { edges { node { …, assignees { id login name avatarUrl } } pageInfo { endCursor hasNextPage } } }
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1)

118-134: Hide Task Timeline when unassigned (per maintainer feedback).

Only render when assignedAt or deadline exists.

Apply this diff:

-        <SecondaryCard title={<AnchorTitle title="Task Timeline" />}>
-          <div className="space-y-1 text-sm text-gray-700 dark:text-gray-300">
-            <div>
-              <span className="font-medium">Assigned:</span>{' '}
-              {data?.getModule?.taskAssignedAt
-                ? new Date(data.getModule.taskAssignedAt).toLocaleDateString()
-                : 'Not assigned'}
-            </div>
-            <div>
-              <span className="font-medium">Deadline:</span>{' '}
-              {data?.getModule?.taskDeadline
-                ? new Date(data.getModule.taskDeadline).toLocaleDateString()
-                : 'No deadline set'}
-            </div>
-          </div>
-        </SecondaryCard>
+        {(data?.getModule?.taskAssignedAt || data?.getModule?.taskDeadline) && (
+          <SecondaryCard title={<AnchorTitle title="Task Timeline" />}>
+            <div className="space-y-1 text-sm text-gray-700 dark:text-gray-300">
+              {data?.getModule?.taskAssignedAt && (
+                <div>
+                  <span className="font-medium">Assigned:</span>{' '}
+                  {new Date(data.getModule.taskAssignedAt).toLocaleDateString()}
+                </div>
+              )}
+              {data?.getModule?.taskDeadline && (
+                <div>
+                  <span className="font-medium">Deadline:</span>{' '}
+                  {new Date(data.getModule.taskDeadline).toLocaleDateString()}
+                </div>
+              )}
+            </div>
+          </SecondaryCard>
+        )}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb70d24 and 6e070a9.

⛔ Files ignored due to path filters (5)
  • frontend/src/types/__generated__/graphql.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/issueQueries.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/moduleMutations.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/moduleQueries.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/programsQueries.generated.ts is excluded by !**/__generated__/**
📒 Files selected for processing (9)
  • backend/.env.example (0 hunks)
  • backend/apps/github/Makefile (2 hunks)
  • backend/apps/github/api/internal/nodes/issue.py (2 hunks)
  • frontend/__tests__/unit/components/CardDetailsPage.test.tsx (2 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1 hunks)
  • frontend/src/server/queries/issueQueries.ts (1 hunks)
  • frontend/src/server/queries/moduleQueries.ts (2 hunks)
  • frontend/src/types/issue.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • backend/.env.example
🚧 Files skipped from review as they are similar to previous changes (4)
  • backend/apps/github/Makefile
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx
  • frontend/src/server/queries/issueQueries.ts
  • frontend/tests/unit/components/CardDetailsPage.test.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-28T14:51:14.736Z
Learnt from: adithya-naik
PR: OWASP/Nest#1894
File: frontend/src/components/TopContributorsList.tsx:74-74
Timestamp: 2025-07-28T14:51:14.736Z
Learning: In the OWASP/Nest project, the maintainer adithya-naik prefers not to create separate components for code that's only used in two specific cases, following the YAGNI principle to avoid over-engineering when the duplication is limited and manageable.

Applied to files:

  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx
🧬 Code graph analysis (3)
backend/apps/github/api/internal/nodes/issue.py (5)
backend/apps/github/api/internal/nodes/pull_request.py (1)
  • PullRequestNode (19-44)
frontend/src/types/__generated__/graphql.ts (2)
  • PullRequestNode (557-569)
  • UserNode (927-948)
frontend/src/types/issue.ts (1)
  • Issue (4-21)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/api/internal/nodes/module.py (1)
  • interested_users (90-100)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (4)
frontend/src/server/queries/issueQueries.ts (3)
  • GET_MODULE_ISSUE_VIEW (3-48)
  • ASSIGN_ISSUE_TO_USER (50-66)
  • UNASSIGN_ISSUE_FROM_USER (68-84)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (4-17)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
frontend/src/types/issue.ts (2)
frontend/src/types/pullRequest.ts (1)
  • PullRequest (3-13)
frontend/src/types/user.ts (1)
  • RepositoryDetails (5-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (3)
backend/apps/github/api/internal/nodes/issue.py (1)

60-63: LGTM! Good use of select_related for performance.

The resolver correctly uses select_related("author", "repository") to prevent N+1 queries when accessing nested PullRequestNode fields.

frontend/src/server/queries/moduleQueries.ts (1)

63-63: Adding labels looks good.

This enables filtering/rendering on the module page. No issues.

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1)

176-186: Good fix: guarded Next/Image src usage.

Avatar src is now conditionally rendered with a fallback placeholder across assignees, PR authors, and interested users. This prevents Next/Image crashes when URLs are missing.

Also applies to: 224-237, 292-302

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/types/issue.ts (1)

16-16: Clarify the distinction between body and summary.

Both body and summary fields are present, which could be confusing without clear documentation of their purposes. Based on the PR objectives mentioning truncating descriptions after 10–15 rows, it appears body may hold the full description while summary holds a truncated version—but this should be explicitly documented.

Consider adding JSDoc comments to clarify:

  • What content each field contains
  • When to use one vs. the other
  • Whether they're mutually exclusive or complementary

Example documentation:

   projectUrl: string
   pullRequests?: PullRequest[]
   repository?: RepositoryDetails
   repositoryLanguages?: string[]
+  /** Full issue description/body text from GitHub */
   body?: string
   title: string
   state?: string
+  /** Truncated version of the issue description for list/preview display */
   summary?: string
   updatedAt: number

Also applies to: 19-19

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f43cad7 and 0851ebf.

📒 Files selected for processing (3)
  • backend/manage.py (1 hunks)
  • backend/tests/apps/github/api/internal/nodes/issue_test.py (1 hunks)
  • frontend/src/types/issue.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/github/api/internal/nodes/issue_test.py
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/types/issue.ts (2)
frontend/src/types/pullRequest.ts (1)
  • PullRequest (3-13)
frontend/src/types/user.ts (1)
  • RepositoryDetails (5-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (3)
frontend/src/types/issue.ts (3)

1-1: LGTM!

The import statement correctly brings in the PullRequest type, which is used for the new pullRequests field below.


13-13: LGTM!

The pullRequests field addition aligns with the PR objectives to link pull requests with issues. The optional array type is appropriate for representing zero or more related PRs.


18-18: LGTM!

The state field is a standard addition for tracking issue status (e.g., "open", "closed"). The optional type is appropriate for backward compatibility.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
backend/apps/github/api/internal/nodes/issue.py (1)

54-62: Past review comment properly addressed!

The interested_users() resolver now correctly uses the reverse relationship participant_interests with select_related("user") and applies order_by("user__login") for consistency. This eliminates the N+1 query issue flagged in the previous review.

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (3)

31-37: Remove redundant destructuring of issueId.

issueId is extracted twice: once on line 31 and again on line 36. Consolidate into a single destructuring call.

Apply this diff:

-  const { issueId } = useParams() as { issueId: string }
-
-  const { programKey, moduleKey } = useParams() as {
+  const { programKey, moduleKey, issueId } = useParams() as {
     programKey: string
     moduleKey: string
     issueId: string
   }

347-347: Add fallback for missing login.

Displaying @{u.login} without a fallback will render "@undefined" when login is missing.

Apply this diff:

-                  <span className="text-sm font-medium">@{u.login}</span>
+                  <span className="text-sm font-medium">@{u.login || u.name || 'Unknown'}</span>

349-374: Add aria-label to assign button for accessibility.

The assign button lacks an aria-label attribute. The unassign button (line 231) properly includes one.

Apply this diff:

                <button
                  type="button"
+                 aria-label={
+                   !issueId ? 'Loading issue…' : assigning ? 'Assigning…' : `Assign to @${u.login}`
+                 }
                  disabled={!issueId || assigning}
🧹 Nitpick comments (1)
backend/apps/github/api/internal/nodes/issue.py (1)

44-52: Consider query-level prefetching for list views.

The assignees() and labels() resolvers are appropriate for single-issue access. However, if these resolvers are called when fetching multiple issues in list views, consider adding query-level prefetching (e.g., using strawberry-django's query optimization features or dataloaders) to avoid N+1 queries.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0851ebf and d7de361.

📒 Files selected for processing (2)
  • backend/apps/github/api/internal/nodes/issue.py (3 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-28T14:51:14.736Z
Learnt from: adithya-naik
PR: OWASP/Nest#1894
File: frontend/src/components/TopContributorsList.tsx:74-74
Timestamp: 2025-07-28T14:51:14.736Z
Learning: In the OWASP/Nest project, the maintainer adithya-naik prefers not to create separate components for code that's only used in two specific cases, following the YAGNI principle to avoid over-engineering when the duplication is limited and manageable.

Applied to files:

  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx
🧬 Code graph analysis (2)
backend/apps/github/api/internal/nodes/issue.py (2)
backend/apps/github/api/internal/nodes/pull_request.py (1)
  • PullRequestNode (19-44)
backend/apps/mentorship/api/internal/nodes/module.py (1)
  • interested_users (90-100)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (4)
frontend/src/server/queries/issueQueries.ts (3)
  • GET_MODULE_ISSUE_VIEW (3-48)
  • ASSIGN_ISSUE_TO_USER (50-66)
  • UNASSIGN_ISSUE_FROM_USER (68-84)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (4-17)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
🔇 Additional comments (1)
backend/apps/github/api/internal/nodes/issue.py (1)

64-67: Well-optimized resolver!

The pull_requests() resolver correctly uses select_related("author", "repository") to prefetch related objects, preventing N+1 queries when accessing pull request details.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (6)
backend/apps/mentorship/api/internal/mutations/module.py (4)

121-161: Fix exception instantiation: use positional args, not msg= keyword.

Django's ObjectDoesNotExist and PermissionDenied do not accept a msg= keyword argument. Passing it raises TypeError at runtime. Pass the message as the first positional argument, and add explicit messages to all bare raises.

Apply this diff:

         if module is None:
-            raise ObjectDoesNotExist(msg="Module not found.")
+            raise ObjectDoesNotExist("Module not found.")
 
         mentor = Mentor.objects.filter(nest_user=user).first()
         if mentor is None:
-            raise PermissionDenied(msg="Only mentors can assign issues.")
+            raise PermissionDenied("Only mentors can assign issues.")
         if not module.program.admins.filter(id=mentor.id).exists():
-            raise PermissionDenied
+            raise PermissionDenied("Only program admins can assign issues.")
 
         gh_user = GithubUser.objects.filter(login=user_login).first()
         if gh_user is None:
-            raise ObjectDoesNotExist(msg="Assignee not found.")
+            raise ObjectDoesNotExist("Assignee not found.")
 
         issue = module.issues.filter(number=issue_number).first()
         if issue is None:
-            raise ObjectDoesNotExist(msg="Issue not found in this module.")
+            raise ObjectDoesNotExist("Issue not found in this module.")

163-201: Fix exception instantiation: use positional args and add missing messages.

Same issue as assign_issue_to_user: use positional arguments instead of msg= keyword, and add explicit messages to all bare raises.

Apply this diff:

         if module is None:
-            raise ObjectDoesNotExist
+            raise ObjectDoesNotExist("Module not found.")
 
         mentor = Mentor.objects.filter(nest_user=user).first()
         if mentor is None:
-            raise PermissionDenied
+            raise PermissionDenied("Only mentors can unassign issues.")
         if not module.program.admins.filter(id=mentor.id).exists():
-            raise PermissionDenied
+            raise PermissionDenied("Only program admins can unassign issues.")
 
         gh_user = GithubUser.objects.filter(login=user_login).first()
         if gh_user is None:
-            raise ObjectDoesNotExist(msg="Assignee not found.")
+            raise ObjectDoesNotExist("Assignee not found.")
 
         issue = module.issues.filter(number=issue_number).first()
         if issue is None:
-            raise ObjectDoesNotExist(msg=f"Issue {issue_number} not found in this module.")
+            raise ObjectDoesNotExist(f"Issue {issue_number} not found in this module.")

214-236: Fix exception instantiation: use positional args and add missing message.

Same critical issue: use positional arguments instead of msg= keyword, and add explicit message to the bare raise.

Apply this diff:

         if module is None:
-            raise ObjectDoesNotExist(msg="Module not found.")
+            raise ObjectDoesNotExist("Module not found.")
 
         mentor = Mentor.objects.filter(nest_user=user).first()
         if mentor is None:
-            raise PermissionDenied(msg="Only mentors can set deadlines.")
+            raise PermissionDenied("Only mentors can set deadlines.")
         if not module.program.admins.filter(id=mentor.id).exists():
-            raise PermissionDenied
+            raise PermissionDenied("Only program admins can set deadlines.")
 
         issue = (
             module.issues.select_related("repository").prefetch_related("assignees")
             .filter(number=issue_number)
             .first()
         )
         if issue is None:
-            raise ObjectDoesNotExist(msg="Issue not found in this module.")
+            raise ObjectDoesNotExist("Issue not found in this module.")

275-285: Fix exception instantiation in update_module: use positional args.

Same critical issue in the update_module method: use positional arguments instead of msg= keyword.

Apply this diff:

         except Module.DoesNotExist as e:
-            raise ObjectDoesNotExist(msg="Module not found.") from e
+            raise ObjectDoesNotExist("Module not found.") from e
 
         try:
             creator_as_mentor = Mentor.objects.get(nest_user=user)
         except Mentor.DoesNotExist as err:
             logger.warning(
                 "User '%s' is not a mentor and cannot edit modules.",
                 user.username,
                 exc_info=True,
             )
-            raise PermissionDenied(msg="Only mentors can edit modules.") from err
+            raise PermissionDenied("Only mentors can edit modules.") from err
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (2)

457-457: Add fallback for missing login.

Line 457 displays @{u.login} without a fallback. If u.login is missing or undefined, this will render "@undefined", which is confusing.

Apply this diff to add a fallback:

-                  <span className="text-sm font-medium">@{u.login}</span>
+                  <span className="text-sm font-medium">@{u.login || u.name || 'Unknown'}</span>

459-481: Add aria-label to assign button for accessibility.

The assign button lacks an aria-label attribute, which is important for screen reader users. The unassign button on line 345 properly includes an aria-label.

Apply this diff to add the aria-label:

                <button
                  type="button"
+                  aria-label={`Assign issue to @${u.login}`}
                  disabled={!issueId || assigning}
🧹 Nitpick comments (5)
backend/manage.py (1)

14-14: Add trailing newline.

Static analysis (Ruff W292) flags that the file is missing a newline at the end. This is a minor style issue but aligns with Python conventions.

Apply this diff to add the trailing newline:

     execute_from_command_line(sys.argv)
+
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (4)

100-102: Consider timezone implications for deadline initialization.

The deadline input is initialized using .slice(0, 10) to extract the date portion. While this works for ISO strings, it assumes the taskDeadline is always in ISO format and doesn't account for timezone conversions that might be needed for user-local display.

Consider adding a comment or using a more explicit date formatting utility:

  const [deadlineInput, setDeadlineInput] = useState<string>(
-    taskDeadline ? new Date(taskDeadline).toISOString().slice(0, 10) : ''
+    // Extract YYYY-MM-DD from ISO timestamp for date input
+    taskDeadline ? new Date(taskDeadline).toISOString().split('T')[0] : ''
  )

247-272: Extract complex deadline display logic to a helper function.

The inline deadline display logic is 25 lines of nested conditionals and calculations. This reduces readability and makes the JSX harder to scan.

Extract to a helper function:

const formatDeadlineDisplay = (deadline: string | undefined): { text: string; className: string } => {
  if (!deadline) {
    return {
      text: 'No deadline set',
      className: 'text-xs font-medium text-gray-600 dark:text-gray-300'
    }
  }
  
  const d = new Date(deadline)
  const today = new Date()
  const isPast = d.setHours(0, 0, 0, 0) < today.setHours(0, 0, 0, 0)
  const diffDays = Math.ceil((d.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))
  
  const suffix = isPast ? '(overdue)' : diffDays > 0 ? `(in ${diffDays} days)` : '(today)'
  
  return {
    text: `${d.toLocaleDateString()} ${suffix}`,
    className: 'text-xs font-medium text-red-700 dark:text-red-300'
  }
}

Then use it in the JSX:

{(() => {
  const { text, className } = formatDeadlineDisplay(taskDeadline)
  return <span className={className}>{text}</span>
})()}

257-257: Mutating date object in comparison affects subsequent logic.

Line 257 uses d.setHours(0, 0, 0, 0) which mutates the date object d. This mutation affects the subsequent d.toLocaleDateString() call on line 264, though in this case it only resets the time component so the date display should still be correct.

For clarity and to avoid potential bugs, use a separate date object for comparison:

-                      const isPast = d.setHours(0, 0, 0, 0) < today.setHours(0, 0, 0, 0)
+                      const dNormalized = new Date(d).setHours(0, 0, 0, 0)
+                      const todayNormalized = new Date(today).setHours(0, 0, 0, 0)
+                      const isPast = dNormalized < todayNormalized

32-491: Consider breaking down this large component.

The component is 460 lines long and handles multiple concerns: data fetching, mutations, state management, and rendering multiple UI sections. This makes it harder to test, maintain, and reuse.

Consider extracting sections into smaller components:

  • IssueHeader (lines 159-182)
  • IssueDescription (lines 184-188)
  • TaskTimeline (lines 190-287)
  • IssueLabels (lines 289-308)
  • IssueAssignees (lines 310-367)
  • IssuePullRequests (lines 369-428)
  • InterestedUsers (lines 430-487)

Each component could receive the necessary data and callbacks as props, improving testability and following the Single Responsibility Principle. Based on learnings from the OWASP/Nest project, you may want to defer this refactoring until these sections are reused elsewhere to follow the YAGNI principle.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7de361 and 86be65d.

📒 Files selected for processing (4)
  • backend/apps/mentorship/api/internal/mutations/module.py (5 hunks)
  • backend/manage.py (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
  • frontend/src/server/queries/issueQueries.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/server/queries/issueQueries.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-28T14:51:14.736Z
Learnt from: adithya-naik
PR: OWASP/Nest#1894
File: frontend/src/components/TopContributorsList.tsx:74-74
Timestamp: 2025-07-28T14:51:14.736Z
Learning: In the OWASP/Nest project, the maintainer adithya-naik prefers not to create separate components for code that's only used in two specific cases, following the YAGNI principle to avoid over-engineering when the duplication is limited and manageable.

Applied to files:

  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx
🧬 Code graph analysis (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (4)
frontend/src/server/queries/issueQueries.ts (4)
  • GET_MODULE_ISSUE_VIEW (3-48)
  • ASSIGN_ISSUE_TO_USER (50-66)
  • UNASSIGN_ISSUE_FROM_USER (68-84)
  • SET_TASK_DEADLINE (86-102)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (4-17)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
backend/apps/mentorship/api/internal/mutations/module.py (4)
backend/apps/mentorship/models/task.py (1)
  • Task (10-80)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/api/internal/nodes/module.py (2)
  • ModuleNode (18-128)
  • issues (45-56)
backend/apps/mentorship/models/module.py (1)
  • Module (17-104)
🪛 GitHub Actions: Run CI/CD
backend/apps/mentorship/api/internal/mutations/module.py

[error] 205-205: ruff: Missing docstring in public method (D102) in set_task_deadline. Add a docstring to the public method.


[warning] 1-1: ruff-format: 1 file reformatted during pre-commit; code style updated. Run 'pre-commit run --all-files' or re-commit to apply changes.

🪛 Ruff (0.14.0)
backend/apps/mentorship/api/internal/mutations/module.py

205-205: Missing docstring in public method

(D102)

backend/manage.py

14-14: No newline at end of file

Add trailing newline

(W292)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (4)
backend/manage.py (1)

14-14: Inconsistency: AI summary mentions dotenv integration, but no dotenv code is present.

The AI-generated summary states that this file "adds dotenv integration: imports load_dotenv from dotenv and calls load_dotenv() inside the main entry point," but the provided code contains no dotenv imports or load_dotenv() calls. The past review comment also referenced load_dotenv() usage around lines 8-11, yet the current code shows only standard Django setup with os.environ.setdefault calls.

Please clarify:

  • Was the dotenv integration removed in this change?
  • Is the AI summary describing changes from a different commit?
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (3)

185-187: Good use of Markdown component for issue description.

The description now uses the Markdown component with the line-clamp-[15] class for truncation, addressing the feedback to support markdown rendering.

The implementation correctly sanitizes and renders markdown content while limiting the display to approximately 15 lines as specified.


168-176: Issue state label properly implemented.

The issue state (Open/Closed) is now displayed as a colored badge next to the issue number, using GitHub-style colors (green for open, red for closed). This addresses the feedback to show the current state of the issue.


408-420: PR state colors align with GitHub conventions.

The PR states use appropriate colors: purple for merged, red for closed, and green for open. This follows GitHub's familiar color scheme and improves user recognition without needing to read the labels.

This addresses the feedback to use traditional GitHub colors for PR states, making the interface more intuitive for users familiar with GitHub.

@Rajgupta36 Rajgupta36 requested a review from kasya October 16, 2025 13:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (7)
backend/apps/mentorship/api/internal/mutations/module.py (4)

141-141: Critical: Remove msg= keyword and add missing exception messages.

Django's ObjectDoesNotExist and PermissionDenied do not accept a msg= keyword argument. This will raise TypeError at runtime. Additionally, Line 147 raises PermissionDenied without a message.

Apply this diff:

-            raise ObjectDoesNotExist(msg="Module not found.")
+            raise ObjectDoesNotExist("Module not found.")

-            raise PermissionDenied(msg="Only mentors can assign issues.")
+            raise PermissionDenied("Only mentors can assign issues.")
         if not module.program.admins.filter(id=mentor.id).exists():
-            raise PermissionDenied
+            raise PermissionDenied("Only program admins can assign issues.")

-            raise ObjectDoesNotExist(msg="Assignee not found.")
+            raise ObjectDoesNotExist("Assignee not found.")

-            raise ObjectDoesNotExist(msg="Issue not found in this module.")
+            raise ObjectDoesNotExist("Issue not found in this module.")

Also applies to: 145-145, 147-147, 151-151, 155-155


183-183: Critical: Remove msg= keyword and add missing exception messages.

Same issue as in assign_issue_to_user: ObjectDoesNotExist and PermissionDenied do not accept msg= keyword arguments. Lines 183, 187, and 189 raise exceptions without messages.

Apply this diff:

-            raise ObjectDoesNotExist
+            raise ObjectDoesNotExist("Module not found.")

         mentor = Mentor.objects.filter(nest_user=user).first()
         if mentor is None:
-            raise PermissionDenied
+            raise PermissionDenied("Only mentors can unassign issues.")
         if not module.program.admins.filter(id=mentor.id).exists():
-            raise PermissionDenied
+            raise PermissionDenied("Only program admins can unassign issues.")

-            raise ObjectDoesNotExist(msg="Assignee not found.")
+            raise ObjectDoesNotExist("Assignee not found.")

-            raise ObjectDoesNotExist(msg=f"Issue {issue_number} not found in this module.")
+            raise ObjectDoesNotExist(f"Issue {issue_number} not found in this module.")

Also applies to: 187-187, 189-189, 193-193, 197-197


223-223: Critical: Remove msg= keyword and add missing exception message.

Same msg= keyword issue present in this mutation. Line 229 also raises PermissionDenied without a message.

Apply this diff:

-            raise ObjectDoesNotExist(msg="Module not found.")
+            raise ObjectDoesNotExist("Module not found.")

-            raise PermissionDenied(msg="Only mentors can set deadlines.")
+            raise PermissionDenied("Only mentors can set deadlines.")
         if not module.program.admins.filter(id=mentor.id).exists():
-            raise PermissionDenied
+            raise PermissionDenied("Only program admins can set deadlines.")

-            raise ObjectDoesNotExist(msg="Issue not found in this module.")
+            raise ObjectDoesNotExist("Issue not found in this module.")

Also applies to: 227-227, 229-229, 238-238


277-277: Critical: Remove msg= keyword from exceptions.

Same msg= keyword issue in update_module.

Apply this diff:

-            raise ObjectDoesNotExist(msg="Module not found.") from e
+            raise ObjectDoesNotExist("Module not found.") from e

-            raise PermissionDenied(msg="Only mentors can edit modules.") from err
+            raise PermissionDenied("Only mentors can edit modules.") from err

Also applies to: 287-287

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (3)

33-34: Consolidate params destructuring.

Lines 33-34 perform redundant destructuring. Extract all three keys in a single statement.

Apply this diff:

-  const params = useParams() as { programKey: string; moduleKey: string; issueId: string }
-  const { programKey, moduleKey, issueId } = params
+  const { programKey, moduleKey, issueId } = useParams() as {
+    programKey: string
+    moduleKey: string
+    issueId: string
+  }

465-465: Add fallback for missing login.

If u.login is undefined, line 465 will render "@undefined". Provide a fallback value.

Apply this diff:

-                  <span className="text-sm font-medium">@{u.login}</span>
+                  <span className="text-sm font-medium">@{u.login || u.name || 'Unknown'}</span>

467-489: Add aria-label to assign button for accessibility.

The assign button lacks an aria-label, while the unassign button on line 344 properly includes one. Add a descriptive label for screen reader users.

Apply this diff:

                 <button
                   type="button"
+                  aria-label={`Assign issue to @${u.login || u.name || 'user'}`}
                   disabled={!issueId || assigning}
🧹 Nitpick comments (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (2)

174-189: Simplify tooltip isDisabled logic.

The ternary on line 180 can be simplified. A tooltip should be disabled when the title is short enough to display fully (≤50 chars).

Apply this diff:

-                      isDisabled={issue.title.length > 50 ? false : true}
+                      isDisabled={issue.title.length <= 50}

228-251: Simplify assignee data access.

The assignee lookup on lines 249-250 searches through data?.getModule?.issues to find matching issue by objectID. Since you're already mapping moduleData?.issues into moduleIssues, consider including assignees in that normalized structure (lines 43-62) to avoid this nested lookup.

If assignees are available in the server response, add them to the Issue mapping:

   const moduleIssues: Issue[] = useMemo(() => {
     return (moduleData?.issues || []).map((i) => ({
       author: i.author,
       createdAt: i.createdAt,
       hint: '',
       labels: i.labels || [],
       number: i.number,
       organizationName: i.organizationName,
       projectName: moduleData?.projectName || '',
       projectUrl: '',
       repository: undefined,
       repositoryLanguages: [],
       body: i.body || '',
       title: i.title,
       state: i.state,
       updatedAt: i.createdAt,
       url: i.url,
       objectID: i.id,
+      assignees: i.assignees || [],
     }))
   }, [moduleData])

Then access assignees directly from issue.assignees in the table row.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86be65d and 42e41dd.

📒 Files selected for processing (3)
  • backend/apps/mentorship/api/internal/mutations/module.py (6 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-28T14:51:14.736Z
Learnt from: adithya-naik
PR: OWASP/Nest#1894
File: frontend/src/components/TopContributorsList.tsx:74-74
Timestamp: 2025-07-28T14:51:14.736Z
Learning: In the OWASP/Nest project, the maintainer adithya-naik prefers not to create separate components for code that's only used in two specific cases, following the YAGNI principle to avoid over-engineering when the duplication is limited and manageable.

Applied to files:

  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx
🧬 Code graph analysis (3)
backend/apps/mentorship/api/internal/mutations/module.py (4)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-31)
backend/apps/mentorship/models/task.py (1)
  • Task (10-80)
backend/apps/mentorship/api/internal/nodes/module.py (2)
  • ModuleNode (18-128)
  • issues (45-56)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (3)
frontend/src/server/queries/moduleQueries.ts (1)
  • GET_MODULE_ISSUES (80-106)
frontend/src/app/global-error.tsx (2)
  • handleAppError (66-86)
  • ErrorDisplay (28-51)
frontend/src/types/issue.ts (1)
  • Issue (4-23)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (4)
frontend/src/server/queries/issueQueries.ts (4)
  • GET_MODULE_ISSUE_VIEW (3-48)
  • ASSIGN_ISSUE_TO_USER (50-66)
  • UNASSIGN_ISSUE_FROM_USER (68-84)
  • SET_TASK_DEADLINE (86-102)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (4-17)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
🔇 Additional comments (1)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1)

298-305: Labels correctly rendered as non-interactive elements.

The labels are now <span> elements rather than <button> elements. Since this is the single-issue detail view and labels don't need to be interactive here (unlike in a list view where they might filter), this is the appropriate semantic choice.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (2)

170-186: Simplify inverted boolean logic.

The isDisabled prop uses inverted ternary logic that can be simplified for better readability.

Apply this diff:

                     <Tooltip
                       closeDelay={100}
                       delay={100}
                       showArrow
                       content={issue.title}
                       placement="bottom"
-                      isDisabled={issue.title.length > 50 ? false : true}
+                      isDisabled={issue.title.length <= 50}
                     >

224-248: Simplify assignee rendering after including assignees in ModuleIssueRow.

After adding assignees to the ModuleIssueRow type (as suggested in the previous comment), this complex lookup can be replaced with direct access to issue.assignees.

After applying the previous fix, replace this entire cell content with:

                   <td className="px-6 py-4 text-sm whitespace-nowrap text-gray-700 dark:text-gray-300">
-                    {((assignees) =>
-                      assignees?.length ? (
+                    {issue.assignees?.length ? (
                         <div className="flex items-center gap-2">
                           <div className="flex items-center gap-2">
                             <Image
                               height={24}
                               width={24}
-                              src={assignees[0].avatarUrl}
-                              alt={assignees[0].login}
+                              src={issue.assignees[0].avatarUrl}
+                              alt={issue.assignees[0].login}
                               className="rounded-full"
                             />
-                            <span>{assignees[0].login || assignees[0].name}</span>
+                            <span>{issue.assignees[0].login || issue.assignees[0].name}</span>
                           </div>
-                          {assignees.length > 1 && (
+                          {issue.assignees.length > 1 && (
                             <div className="flex h-6 w-6 items-center justify-center rounded-full bg-gray-200 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300">
-                              +{assignees.length - 1}
+                              +{issue.assignees.length - 1}
                             </div>
                           )}
                         </div>
-                      ) : null)(
-                      (data?.getModule?.issues || []).find((i) => i.id === issue.objectID)
-                        ?.assignees
-                    )}
+                      ) : null}
                   </td>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42e41dd and 77fa3bb.

📒 Files selected for processing (1)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (3)
frontend/src/server/queries/moduleQueries.ts (1)
  • GET_MODULE_ISSUES (80-106)
frontend/src/app/global-error.tsx (2)
  • handleAppError (66-86)
  • ErrorDisplay (28-51)
backend/apps/github/api/internal/nodes/issue.py (2)
  • labels (50-52)
  • assignees (45-47)
🪛 GitHub Actions: Run CI/CD
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx

[warning] 51-51: Unexpected any. Specify a different type (no-explicit-any).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1)

172-187: LGTM! Tooltip implementation addresses previous feedback.

The tooltip correctly displays the full title on hover for truncated text, addressing the previous review comment.

Consider these minor simplifications:

                      isDisabled={issue.title.length > 50 ? false : true}
+                     isDisabled={issue.title.length <= 50}
-                       className="block max-w-md cursor-pointer truncate text-left hover:underline"
+                       className="block max-w-md truncate text-left hover:underline"

Note: Buttons already have cursor: pointer by default, so the class is redundant.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94af2ca and fd72300.

📒 Files selected for processing (1)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (2)
frontend/src/server/queries/moduleQueries.ts (1)
  • GET_MODULE_ISSUES (80-106)
frontend/src/app/global-error.tsx (2)
  • handleAppError (66-86)
  • ErrorDisplay (28-51)
🔇 Additional comments (12)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (12)

1-16: LGTM! Clean imports and constants.

The imports are appropriate and the constants (LABEL_ALL, ITEMS_PER_PAGE, MAX_VISIBLE_LABELS) are well-defined for managing the issues list page.


18-24: LGTM! State management initialized correctly.

The component properly initializes state from URL query parameters, enabling shareable filtered URLs.


25-35: LGTM! GraphQL query configured correctly.

The query variables are properly computed, the skip condition prevents unnecessary requests, and the cache-and-network fetch policy is appropriate for potentially changing data.


37-39: LGTM! Error handling implemented correctly.

The error effect properly delegates to the centralized handleAppError utility, which handles toast notifications and Sentry logging.


41-60: LGTM! Data transformation improved based on previous reviews.

The ModuleIssueRow type now includes the assignees field, the map function is properly typed (no longer using any), and the mapping correctly extracts only the fields returned by the GraphQL query.


62-75: LGTM! Robust label and pagination computation.

The code correctly calculates pagination and provides a solid fallback for label extraction when availableLabels is not provided by the server.


77-95: LGTM! Event handlers implemented correctly.

The handlers properly manage state, synchronize URL parameters, and reset pagination when filters change. Navigation follows Next.js patterns.


97-99: LGTM! Proper loading and error state handling.

The component correctly shows a loading spinner during data fetch and displays a 404 error page when the module is not found.


107-135: LGTM! HeroUI Select component fixed correctly.

The Select component now properly uses selectedKeys as a Set and onSelectionChange to handle selection changes, addressing the critical issue from the previous review.


200-224: LGTM! Label rendering with overflow indicator.

The labels are correctly displayed with a "+N more" indicator when there are more than MAX_VISIBLE_LABELS, providing good UX for issues with many labels.


226-246: LGTM! Assignee display fixed per previous feedback.

The assignee column now correctly displays nothing when there are no assignees (instead of showing "Unassigned"), addressing the previous review comment. Multiple assignees are handled well with the count badge.


249-270: LGTM! Empty state and pagination implemented.

The empty state provides clear feedback when no issues match the filter. The Pagination component addresses the previous review request for pagination support on this page.

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

Added a few more comments and requests! Thank you!


<div className="flex flex-col gap-2">
<div className="flex flex-wrap items-center gap-2">
<span className="font-medium">Deadline:</span>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we simplify this logic so that the edit form opens up as soon as the user clicks the "No deadline set" or Current deadline date? Meaning that whole thing would be like a button trigger.
We won't need edit button this way.

We also won't need close button for editing. To cancel the change user can click out of the "calendar" view, use Clear on it or edit manually.
And we will accept the edit as soon as user selects the date. This should set it to deadline automatically, without having to click Save.

This will simplify the view a lot!

: 'bg-[#cb2431] text-white'
}`}
>
{issue.state === 'open' ? 'Open' : 'Closed'}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we center these?

Image

: 'bg-[#cb2431] text-white'
}`}
>
{issue.state === 'open' ? 'Open' : 'Closed'}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also I do believe that we should be able to show the Merged state as well in case if the attached PR was merged 🤔
It would make sense to differentiate these 2 views, since Closed more of a negative label, where Merged is definitely a positive one.

alt={issue.assignees[0].login}
className="rounded-full"
/>
<span>{issue.assignees[0].login || issue.assignees[0].name}</span>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we truncate the name in case it's longer?

Image

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated
image

issue.state === 'open'
? 'bg-[#2cbe4e] text-white'
: 'bg-[#cb2431] text-white'
}`}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also I don't think these are the color that GitHub uses for these states. I wanted these to be exactly GitHub shades so that there's instant recognition of the state.

I inspected the GitHub labels for these and colors look different (GitHub shades on the right):

Image Image

These would need to be updated on single Issue page too. and for Merged state I'd also prefer the GiHub shade.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I updated the hex code.

Comment on lines 256 to 259
<ActionButton url={pr.url} tooltipLabel="View PR">
<FontAwesomeIcon icon={faLink} />
<span className="hidden sm:inline">View PR</span>
</ActionButton>
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think these are old ones 🤔

You should be able to get the colors from inspecting issues pages, then you can transform that into hex code:
Screenshot 2025-10-17 at 7 02 47 PM

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (2)

455-455: Add fallback for missing login.

Line 455 displays @{u.login} without a fallback. If u.login is missing or undefined, this will render "@undefined", which is confusing for users.

Apply this diff to add a fallback:

-                  <span className="text-sm font-medium">@{u.login}</span>
+                  <span className="text-sm font-medium">@{u.login || u.name || 'Unknown'}</span>

</invoke_comment_end -->


457-479: Add aria-label to assign button for accessibility.

The assign button lacks an aria-label attribute, which is important for screen reader users. The unassign button properly includes an aria-label (line 334).

Apply this diff to add the aria-label:

                <button
                  type="button"
+                  aria-label={
+                    !issueId ? 'Loading issue…' : assigning ? 'Assigning…' : `Assign to ${u.login || u.name || 'user'}`
+                  }
                  disabled={!issueId || assigning}

</invoke_comment_end -->

🧹 Nitpick comments (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1)

174-191: Consider alignment between truncation and tooltip threshold.

The Tooltip is enabled when title.length > 50, but the visual truncation is controlled by max-w-md width on the button. These two thresholds may not align—a 40-character title with wide characters might truncate visually, while a 55-character title with narrow characters might fit. This could result in truncated titles without tooltips or non-truncated titles with unnecessary tooltips.

Consider one of these approaches:

  1. Use a ref and measure actual overflow to conditionally enable the tooltip.
  2. Use a more generous character threshold (e.g., 30-40) to reduce false negatives.
  3. Always show the tooltip (remove isDisabled) since it only appears on hover anyway.

</invoke_comment_end -->

frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1)

36-54: Non-overdue deadlines are styled the same as overdue ones.

The formatDeadline function returns color: 'text-[#DA3633]' (red) for all deadlines, regardless of whether they're overdue, today, or in the future. This may confuse users—red typically signals urgency or a problem.

Consider using different colors based on deadline status:

  • Overdue: red (text-[#DA3633])
  • Today: yellow/orange
  • Future: gray or neutral

Apply this diff:

    return {
      text: `${deadlineDate.toLocaleDateString()} ${statusText}`,
-      color: 'text-[#DA3633]',
+      color: isOverdue
+        ? 'text-[#DA3633]'
+        : daysLeft === 0
+          ? 'text-yellow-600 dark:text-yellow-500'
+          : 'text-gray-600 dark:text-gray-300',
    }

</invoke_comment_end -->

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd72300 and b322dc2.

📒 Files selected for processing (5)
  • backend/apps/github/api/internal/nodes/issue.py (3 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (1 hunks)
  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (1 hunks)
  • frontend/src/server/queries/issueQueries.ts (1 hunks)
  • frontend/src/server/queries/moduleQueries.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/server/queries/issueQueries.ts
  • frontend/src/server/queries/moduleQueries.ts
  • backend/apps/github/api/internal/nodes/issue.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-28T14:51:14.736Z
Learnt from: adithya-naik
PR: OWASP/Nest#1894
File: frontend/src/components/TopContributorsList.tsx:74-74
Timestamp: 2025-07-28T14:51:14.736Z
Learning: In the OWASP/Nest project, the maintainer adithya-naik prefers not to create separate components for code that's only used in two specific cases, following the YAGNI principle to avoid over-engineering when the duplication is limited and manageable.

Applied to files:

  • frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx
🧬 Code graph analysis (2)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx (4)
frontend/src/server/queries/issueQueries.ts (4)
  • GET_MODULE_ISSUE_VIEW (3-49)
  • ASSIGN_ISSUE_TO_USER (51-67)
  • UNASSIGN_ISSUE_FROM_USER (69-85)
  • SET_TASK_DEADLINE (87-103)
frontend/src/app/global-error.tsx (1)
  • ErrorDisplay (28-51)
frontend/src/components/MarkdownWrapper.tsx (1)
  • Markdown (4-17)
frontend/src/components/TruncatedText.tsx (1)
  • TruncatedText (3-45)
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx (2)
frontend/src/server/queries/moduleQueries.ts (1)
  • GET_MODULE_ISSUES (80-107)
frontend/src/app/global-error.tsx (2)
  • handleAppError (66-86)
  • ErrorDisplay (28-51)
🪛 GitHub Actions: Run CI/CD
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx

[error] 274-274: Unstaged changes detected. Run 'make check' and use 'git add' to address it. Diff shows a className order change in the IssuesPage component.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL (javascript-typescript)

Copy link

@Rajgupta36 Rajgupta36 requested a review from kasya October 18, 2025 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants