Skip to content

Conversation

@kasya
Copy link
Collaborator

@kasya kasya commented Dec 27, 2025

Updated backend logic for issue assignment. The flow now also assigns the issue on GitHub via the GB API. If the GitHub assignment fails, the app-level assignment will not proceed.
We also create a Task on issue assignment now.

For unassignment, the issue is first unassigned on GitHub, followed by updating the app state upon a successful response.

Validation and error handling improvements will be addressed in a separate PR.

Resolves #2861

Checklist

  • Required: I read and followed the contributing guidelines
  • Required: I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 27, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • GitHub integration for automatic issue assignment synchronization
    • Enhanced task deadline management with per-assignee deadline support
    • Added "CLOSED" status option for task tracking
  • Improvements

    • Tasks now automatically created when assigning issues
    • Enhanced error handling with improved validation messages
    • Streamlined issue assignment and unassignment workflows

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds GitHub client integration and error-message constants to module mutations; synchronizes assignees with GitHub when assigning/unassigning issues, creates/updates per-assignee Tasks (including new CLOSED status), manages per-assignee deadlines, and tightens module/issue/mentor validations.

Changes

Cohort / File(s) Summary
Module mutations & GitHub integration
backend/apps/mentorship/api/internal/mutations/module.py
Added GithubException and get_github_client imports; introduced MODULE_NOT_FOUND_MSG, ISSUE_NOT_FOUND_IN_MODULE_MSG, ASSIGNEE_NOT_FOUND_MSG; added get_github_issue(...); updated flows: assign_issue_to_user / unassign_issue_from_user now call GitHub to add/remove assignees, create/update MenteeModule and per-assignee Task records, mark tasks CLOSED on unassign, translate GitHub errors to ValidationError; extended set_task_deadline / clear_task_deadline to validate mentors/assignees and update per-assignee deadlines; tightened update_module error handling and date/mentor validation.
Task model (status choices)
backend/apps/mentorship/models/task.py
Added Task.Status.CLOSED = "CLOSED", "Closed" enum value to represent closed tasks when assignees are removed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main change: adding GitHub API integration for issue assignment and unassignment workflows.
Description check ✅ Passed The description is well-related to the changeset, explaining the GitHub API integration, assignment/unassignment flows, and Task creation—all reflected in the code changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch github-api-assign-issue

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: 1

🧹 Nitpick comments (2)
backend/apps/mentorship/api/internal/mutations/module.py (2)

168-179: Avoid catching bare Exception.

The generic Exception handler (lines 176-179) is overly broad and could mask unexpected errors like AttributeError or KeyError that indicate bugs. Consider removing it or limiting to specific expected exceptions.

🔎 Proposed fix
         try:
             gh_client = get_github_client()
             gh_repository = gh_client.get_repo(issue.repository.path)
             gh_issue = gh_repository.get_issue(number=issue.number)
             gh_issue.add_to_assignees(user_login)
         except GithubException as e:
             raise ValidationError(message=f"Failed to assign issue on GitHub: {e}") from e
-
-        except Exception as e:
-            raise ValidationError(
-                message=f"Unexpected error while assigning issue on GitHub: {e}"
-            ) from e

256-267: Same broad Exception catch as noted above.

Consider removing the generic Exception handler for consistency and to avoid masking bugs.

🔎 Proposed fix
         try:
             gh_client = get_github_client()
             gh_repository = gh_client.get_repo(issue.repository.path)
             gh_issue = gh_repository.get_issue(number=issue.number)
             gh_issue.remove_from_assignees(user_login)
         except GithubException as e:
             raise ValidationError(message=f"Failed to unassign issue on GitHub: {e}") from e
-
-        except Exception as e:
-            raise ValidationError(
-                message=f"Unexpected error while unassigning issue on GitHub: {e}"
-            ) from e
📜 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 a74ee97 and 9b1341e.

📒 Files selected for processing (2)
  • backend/apps/mentorship/api/internal/mutations/module.py
  • backend/apps/mentorship/models/task.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
🧬 Code graph analysis (1)
backend/apps/mentorship/api/internal/mutations/module.py (9)
backend/apps/mentorship/api/internal/nodes/module.py (3)
  • CreateModuleInput (163-177)
  • UpdateModuleInput (181-196)
  • issues (76-87)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/apps/mentorship/models/mentee_module.py (1)
  • MenteeModule (11-45)
backend/apps/mentorship/models/module.py (2)
  • Module (17-99)
  • save (92-99)
backend/apps/mentorship/models/program.py (2)
  • Program (18-87)
  • save (83-87)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/apps/mentorship/models/task.py (1)
  • Task (10-76)
backend/apps/github/models/repository.py (1)
  • path (157-159)
backend/apps/github/api/internal/nodes/issue.py (1)
  • assignees (45-47)
⏰ 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). (5)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (python)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (7)
backend/apps/mentorship/models/task.py (1)

26-26: LGTM!

The new CLOSED status is a valid addition that supports the task lifecycle when issues are unassigned. The value fits within the max_length=20 constraint.

backend/apps/mentorship/api/internal/mutations/module.py (6)

10-29: LGTM!

Good use of centralized error message constants and appropriate imports for GitHub API integration.


234-238: Verify permission asymmetry between assign and unassign.

assign_issue_to_user requires only mentor status (lines 149-151), while unassign_issue_from_user additionally requires program admin status. If this is intentional, consider adding a comment explaining the rationale.


289-311: LGTM!

Good use of centralized error message constants.


349-371: LGTM!

Consistent use of centralized error message constants.


401-402: LGTM!

Consistent use of centralized error message constant.


183-192: LGTM!

The Mentee and MenteeModule creation with appropriate defaults handles the case where module.started_at is None. Based on learnings, mentors can be created with only github_user initially, and this pattern correctly applies to mentees as well.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/apps/mentorship/api/internal/mutations/module.py (1)

310-319: Align task creation behavior across the mentorship system.

The code creates a Task for each assignee on an issue (lines 310-317), but this contradicts the documented system design. The mentorship_sync_module_issues command intentionally uses issue.assignees.first() to create only one task per issue for the first assignee. Either update set_task_deadline to also use the first assignee only (for consistency with the sync command and documented design), or update the sync command to handle all assignees—not just the first one.

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

362-371: Verify multiple-task-per-issue behavior against system design.

Similar to set_task_deadline, this function processes tasks for all assignees (lines 362-366), which contradicts the documented single-assignee-per-issue design principle.

Note: The conditional bulk_update logic at lines 368-371 is a valid micro-optimization, though the single .save() call for one task and bulk_update() for multiple tasks could be simplified to always use bulk_update() for consistency.


247-258: GitHub API call inside transaction may cause state inconsistency.

Similar to assign_issue_to_user, the GitHub unassignment at line 249 executes inside a @transaction.atomic block. If the GitHub operation succeeds but the database transaction rolls back, the states will be inconsistent. The same architectural solution mentioned for assignment should apply here.

📜 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 9b1341e and 59ba400.

📒 Files selected for processing (1)
  • backend/apps/mentorship/api/internal/mutations/module.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
🧬 Code graph analysis (1)
backend/apps/mentorship/api/internal/mutations/module.py (8)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/apps/mentorship/models/mentee_module.py (1)
  • MenteeModule (11-45)
backend/apps/mentorship/models/mentor.py (1)
  • Mentor (11-40)
backend/apps/mentorship/models/module.py (2)
  • Module (17-99)
  • save (92-99)
backend/apps/mentorship/models/program.py (2)
  • Program (18-87)
  • save (83-87)
backend/apps/mentorship/models/task.py (2)
  • Task (10-76)
  • Status (19-26)
backend/apps/github/models/repository.py (1)
  • path (157-159)
backend/apps/owasp/api/internal/nodes/member_snapshot.py (1)
  • github_user (32-34)
⏰ 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 backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (3)
backend/apps/mentorship/api/internal/mutations/module.py (3)

10-12: LGTM!

The GitHub client imports and centralized error message constants are well-structured and support the new GitHub integration features.

Also applies to: 27-29


185-209: LGTM!

The MenteeModule and Task creation logic is correct. The re-assignment handling at lines 206-209 properly resets assigned_at and status to IN_PROGRESS when re-assigning a previously closed task, addressing the concern from past reviews.


45-47: GitHub client authentication is properly implemented.

The get_github_client() function authenticates using server-level credentials configured via GitHub App (App ID, Installation ID, private key) or falls back to a PAT token from the GITHUB_TOKEN environment variable. Both approaches are validated during initialization and raise BadCredentialsException if credentials are missing. No changes required.

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 (3)
backend/apps/mentorship/api/internal/mutations/module.py (3)

178-182: GitHub API call inside transaction can cause state inconsistency.

This concern was already raised in previous reviews. The GitHub API call executes inside the @transaction.atomic block, which can cause inconsistency if the external call succeeds but the database transaction subsequently fails and rolls back. The PR description indicates this architectural concern is being tracked for a separate PR.


248-252: GitHub API call inside transaction can cause state inconsistency.

Same architectural concern as in assign_issue_to_user (lines 178-182): the GitHub API call executes inside the @transaction.atomic block, which can cause inconsistency if the external call succeeds but the database transaction fails and rolls back.


197-210: Critical: Re-assignment doesn't update CLOSED tasks to IN_PROGRESS.

The current logic only updates the task when not created and task.assigned_at is None, but this doesn't handle the case where a task was previously assigned (assigned_at set), then unassigned (status set to CLOSED at lines 256-259 without clearing assigned_at), and is now being re-assigned. In this scenario:

  • created is False (task exists)
  • task.assigned_at is NOT None (was set during first assignment)
  • The condition evaluates to False
  • The task status remains CLOSED instead of being updated to IN_PROGRESS
🔎 Proposed fix
 task, created = Task.objects.get_or_create(
     module=module,
     issue=issue,
     assignee=gh_user,
     defaults={
         "assigned_at": now,
         "status": Task.Status.IN_PROGRESS,
     },
 )
 
-if not created and task.assigned_at is None:
+if not created:
+    if task.assigned_at is None:
+        task.assigned_at = now
+    if task.status == Task.Status.CLOSED:
+        task.status = Task.Status.IN_PROGRESS
-    task.assigned_at = now
-    task.status = Task.Status.IN_PROGRESS
-    task.save(update_fields=["assigned_at", "status"])
+    task.save(update_fields=["assigned_at", "status"])

Or more concisely:

 task, created = Task.objects.get_or_create(
     module=module,
     issue=issue,
     assignee=gh_user,
     defaults={
         "assigned_at": now,
         "status": Task.Status.IN_PROGRESS,
     },
 )
 
-if not created and task.assigned_at is None:
-    task.assigned_at = now
+if not created:
+    needs_update = False
+    if task.assigned_at is None:
+        task.assigned_at = now
+        needs_update = True
+    if task.status != Task.Status.IN_PROGRESS:
+        task.status = Task.Status.IN_PROGRESS
+        needs_update = True
+    if needs_update:
-    task.status = Task.Status.IN_PROGRESS
-    task.save(update_fields=["assigned_at", "status"])
+        task.save(update_fields=["assigned_at", "status"])
🧹 Nitpick comments (1)
backend/apps/mentorship/api/internal/mutations/module.py (1)

33-50: Consider adding explicit return type hint.

The helper function is well-structured and centralizes GitHub issue retrieval logic. For improved clarity and IDE support, consider adding an explicit return type annotation.

🔎 Proposed enhancement
+from apps.github.models import Issue
+from github import GithubObject
+
-def get_github_issue(module: Module, issue_number: int) -> tuple:
+def get_github_issue(module: Module, issue_number: int) -> tuple[Issue, GithubObject.Issue]:
     """Get local issue and GitHub issue objects for a module issue."""

Note: Adjust the GitHub issue type import based on your PyGithub usage patterns.

📜 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 59ba400 and ce2e1f8.

📒 Files selected for processing (1)
  • backend/apps/mentorship/api/internal/mutations/module.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
🔇 Additional comments (4)
backend/apps/mentorship/api/internal/mutations/module.py (4)

10-10: LGTM! GitHub integration imports added.

The imports of GithubException and get_github_client properly support the new GitHub API integration for issue assignment and unassignment workflows.

Also applies to: 12-12


27-31: LGTM! Error message constants improve consistency.

Centralizing error messages as module-level constants is a good practice that improves maintainability and ensures consistent messaging across the codebase.


256-259: Note: Unassignment preserves assigned_at, contributing to re-assignment bug.

When unassigning, the task status is set to CLOSED but assigned_at is not cleared. This preserves historical assignment data but requires the re-assignment logic (lines 197-210) to check for CLOSED status explicitly, which it currently doesn't do correctly. This is addressed in the critical issue comment on lines 197-210.

If the intent is to preserve assignment history, the current approach is reasonable, but ensure the re-assignment logic properly handles CLOSED tasks as flagged above.


184-195: LGTM! Mentee and MenteeModule creation logic is correct.

The code properly creates or retrieves the Mentee and MenteeModule records when assigning an issue, with appropriate defaults for started_at and ended_at based on module dates.

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 (1)
backend/apps/mentorship/api/internal/mutations/module.py (1)

197-210: Fix incomplete re-assignment logic for CLOSED tasks.

The current logic only handles tasks where assigned_at is None (line 207), but when a task is unassigned (lines 258-261), only the status is set to CLOSEDassigned_at is not cleared. Therefore, when re-assigning a previously unassigned task:

  1. get_or_create returns the existing task with created=False
  2. task.assigned_at is NOT None (it still has the old timestamp)
  3. The condition on line 207 evaluates to False
  4. The task status remains CLOSED instead of being updated to IN_PROGRESS

Solution: Check if the task status is CLOSED rather than checking if assigned_at is None, or update both fields unconditionally when re-assigning.

🔎 Proposed fix to handle re-assignment of CLOSED tasks
-            if not created and task.assigned_at is None:
+            if not created and task.status == Task.Status.CLOSED:
                 task.assigned_at = now
                 task.status = Task.Status.IN_PROGRESS
                 task.save(update_fields=["assigned_at", "status"])

Alternatively, always update when re-assigning:

-            if not created and task.assigned_at is None:
+            if not created:
                 task.assigned_at = now
                 task.status = Task.Status.IN_PROGRESS
                 task.save(update_fields=["assigned_at", "status"])
🧹 Nitpick comments (3)
backend/apps/mentorship/api/internal/mutations/module.py (3)

33-50: Consider adding error handling for GitHub API calls.

The helper function get_github_issue calls GitHub APIs (lines 46-48) but doesn't wrap them in error handling. While GithubException is imported and caught at call sites, GitHub API calls can fail due to network issues, rate limiting, authentication failures, or repository access issues. Consider whether centralized error handling in this helper would improve code maintainability.

🔎 Optional refactor to centralize GitHub API error handling
 def get_github_issue(module: Module, issue_number: int) -> tuple:
     """Get local issue and GitHub issue objects for a module issue."""
     issue = (
         module.issues.select_related("repository", "repository__owner")
         .filter(number=issue_number)
         .first()
     )
     if issue is None:
         raise ObjectDoesNotExist(ISSUE_NOT_FOUND_IN_MODULE_MSG)
 
     if not issue.repository:
         raise ValidationError(message="Issue has no repository.")
 
-    gh_client = get_github_client()
-    gh_repository = gh_client.get_repo(issue.repository.path)
-    gh_issue = gh_repository.get_issue(number=issue.number)
+    try:
+        gh_client = get_github_client()
+        gh_repository = gh_client.get_repo(issue.repository.path)
+        gh_issue = gh_repository.get_issue(number=issue.number)
+    except GithubException as e:
+        raise ValidationError(message=f"Failed to fetch issue from GitHub: {e}") from e
 
     return issue, gh_issue

177-181: Architectural note: GitHub-first approach can cause state inconsistency.

The GitHub API call (line 179) executes before the database transaction (line 183). If the GitHub operation succeeds but the database transaction subsequently fails, GitHub will have the assignment while the database won't, creating an inconsistent state. The PR description notes that error-handling improvements are deferred to a separate PR, but this architectural concern should be tracked.

Consider implementing one of these approaches in the follow-up PR:

  1. Compensating transaction: If the DB transaction fails, roll back the GitHub assignment
  2. DB-first with retry: Persist to DB first, then update GitHub; if GitHub fails, roll back DB
  3. Idempotency: Ensure operations can be safely retried to achieve eventual consistency

249-253: Same architectural concern applies to unassign operation.

Similar to the assign operation, the GitHub API call (line 251) executes before the database transaction (line 255). If the GitHub unassignment succeeds but the database transaction fails, state inconsistency will occur. This should be addressed in the same follow-up PR that handles the assign operation's transaction boundaries.

📜 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 ce2e1f8 and 401ca72.

📒 Files selected for processing (1)
  • backend/apps/mentorship/api/internal/mutations/module.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
🧬 Code graph analysis (1)
backend/apps/mentorship/api/internal/mutations/module.py (6)
backend/apps/mentorship/api/internal/nodes/module.py (4)
  • CreateModuleInput (163-177)
  • ModuleNode (19-159)
  • UpdateModuleInput (181-196)
  • issues (76-87)
backend/apps/mentorship/models/mentee.py (1)
  • Mentee (11-49)
backend/apps/mentorship/models/mentee_module.py (1)
  • MenteeModule (11-45)
backend/apps/mentorship/models/module.py (2)
  • Module (17-99)
  • save (92-99)
backend/apps/mentorship/models/issue_user_interest.py (1)
  • IssueUserInterest (6-36)
backend/apps/mentorship/models/task.py (2)
  • Task (10-76)
  • Status (19-26)
⏰ 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 unit tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (6)
backend/apps/mentorship/api/internal/mutations/module.py (6)

10-10: LGTM! GitHub integration imports are appropriate.

The new imports support GitHub API operations and task/mentee management functionality.

Also applies to: 12-12, 19-19


27-30: LGTM! Error message constants improve maintainability.

Centralizing error messages promotes consistency and makes updates easier.


314-322: Clarify expected behavior when setting deadline on CLOSED tasks.

When setting a deadline, the code creates or retrieves tasks for each assignee (lines 314-316) and updates assigned_at if it's None (lines 317-319), but it doesn't check or update the task status. If a task is in CLOSED status (e.g., after unassignment), setting a deadline won't reopen it.

Please verify: should setting a deadline on a CLOSED task automatically change its status to IN_PROGRESS or another active status?


364-376: LGTM! Efficient deadline clearing with smart bulk update optimization.

The logic correctly handles clearing deadlines for all assignees, and the optimization at lines 371-374 (using save() for a single task and bulk_update() for multiple tasks) is a nice touch for performance.


389-389: LGTM! Consistent error messaging.

Using the centralized MODULE_NOT_FOUND_MSG constant improves maintainability.


169-171: Add admin permission check to assign_issue_to_user for consistency with unassign_issue_from_user.

The assign_issue_to_user mutation (lines 169-171) only checks if the user is a mentor, while unassign_issue_from_user (lines 242-243) requires the mentor to also be a program admin. Both operations modify module and GitHub state, making this asymmetry inconsistent. Either both mutations should require admin privileges, or the admin check should be removed from unassign. No documentation explains the design intent for this difference.

@sonarqubecloud
Copy link

@kasya kasya marked this pull request as ready for review December 28, 2025 00:31
@kasya kasya requested a review from arkid15r as a code owner December 28, 2025 00:31
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 (3)
backend/apps/mentorship/api/internal/mutations/module.py (3)

116-116: Consider adding an error message for clarity.

The PermissionDenied exception is raised without a message. Adding a descriptive message would improve debugging and user experience.

🔎 Suggested improvement
-        raise PermissionDenied
+        raise PermissionDenied("Only program admins can create modules.")

241-243: Consider adding error messages for clarity.

Both PermissionDenied exceptions are raised without messages. Adding descriptive messages would improve debugging and user experience.

🔎 Suggested improvements
         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.")

404-404: Consider adding an error message for clarity.

The PermissionDenied exception is raised without a message. Adding a descriptive message would improve debugging and user experience.

🔎 Suggested improvement
-        raise PermissionDenied
+        raise PermissionDenied("Only program admins can update modules.")
📜 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 401ca72 and 0d64cc2.

📒 Files selected for processing (1)
  • backend/apps/mentorship/api/internal/mutations/module.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.
📚 Learning: 2025-08-17T11:55:55.990Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 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.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
📚 Learning: 2025-07-16T13:49:58.648Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: backend/apps/mentorship/graphql/mutations/module.py:28-29
Timestamp: 2025-07-16T13:49:58.648Z
Learning: In the OWASP Nest mentorship system, mentors can be created with only github_user initially (without nest_user) when assigned to modules. This allows mentors to be assigned before they've signed into the system. When these users eventually sign in, the nest_user is automatically linked to the existing github_user, creating a complete mentor profile. This design provides flexibility in mentor assignment workflows.

Applied to files:

  • backend/apps/mentorship/api/internal/mutations/module.py
⏰ 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: Run frontend e2e tests
🔇 Additional comments (2)
backend/apps/mentorship/api/internal/mutations/module.py (2)

177-183: GitHub-then-DB flow partially addresses past architectural concern.

The GitHub API call now executes outside the transaction block (improvement from past review), ensuring GitHub assignment happens first. However, the reverse inconsistency risk remains: if the GitHub assignment succeeds but the database transaction fails, GitHub will have the assignment while the application state won't reflect it. Since the PR description indicates error-handling improvements are deferred to a separate PR, this architectural concern should continue to be tracked for future work.


442-457: LGTM: Experience level management is well-implemented.

The logic correctly maintains the program's experience levels as the union of all module experience levels. When a module's experience level changes, the new level is added and the old level is removed only if no other modules use it. The exclusion query (lines 449-453) properly prevents premature removal.


mentor = Mentor.objects.filter(nest_user=user).first()
if mentor is None:
raise PermissionDenied(msg="Only mentors can assign issues.")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix incorrect PermissionDenied syntax.

PermissionDenied doesn't accept a msg= keyword argument. The message should be passed as a positional argument, otherwise it won't be displayed when the exception is raised.

🔎 Proposed fix
-        raise PermissionDenied(msg="Only mentors can assign issues.")
+        raise PermissionDenied("Only mentors can assign issues.")

This same issue appears at lines 290 and 350. All instances should be fixed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
raise PermissionDenied(msg="Only mentors can assign issues.")
raise PermissionDenied("Only mentors can assign issues.")
🤖 Prompt for AI Agents
In backend/apps/mentorship/api/internal/mutations/module.py around line 171
(also apply same fix at lines ~290 and ~350), the PermissionDenied exception is
being constructed with a keyword argument msg= which is invalid; replace
PermissionDenied(msg="...") with PermissionDenied("...") so the message is
passed as the positional argument and will be displayed when raised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend Module Assignment Functionality with GitHub API Integration

2 participants