Skip to content

refactor: migrate ModuleNode to use strawberry-django decorator for consistency#4256

Open
Jpeg-create wants to merge 10 commits intoOWASP:mainfrom
Jpeg-create:refactor/module-node-strawberry-django
Open

refactor: migrate ModuleNode to use strawberry-django decorator for consistency#4256
Jpeg-create wants to merge 10 commits intoOWASP:mainfrom
Jpeg-create:refactor/module-node-strawberry-django

Conversation

@Jpeg-create
Copy link
Contributor

Resolves #4226

Migrates ModuleNode from @strawberry.type to @strawberry_django.type(Module, ...) for consistency with other nodes in the codebase.

Changes:

  • Replace @strawberry.type decorator with @strawberry_django.type(Module, fields=[...])
  • Move simple model fields into the fields list
  • Update all resolver methods to use root: Module parameter pattern
  • Add from future import annotations and noqa comments following program.py pattern
  • Update tests to pass root parameter to resolver functions
  • Remove TODO comment left by @arkid15r

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran make check-test locally: all warnings addressed, tests passed
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • Refactor
    • Optimized module data resolution with improved query efficiency and context handling.
    • Enhanced type consistency for experience level field handling in the frontend.
    • Strengthened internal API reliability through better resolver architecture.

Walkthrough

ModuleNode was migrated to use @strawberry_django.type(Module, fields=[...]). Resolvers were rewritten to accept root: Module and perform module-scoped queries for mentors, mentees, issues, labels, PRs, and task fields. Tests and a frontend type were updated to match the new signatures.

Changes

Cohort / File(s) Summary
GraphQL Node Refactor
backend/apps/mentorship/api/internal/nodes/module.py
Replaced @strawberry.type with @strawberry_django.type(Module, fields=[...]). Converted resolvers to accept root: Module, added program, project_id, project_name fields, and migrated logic for mentors, mentees, issue_mentees, issues, issues_count, available_labels, issue_by_number, interested_users, task_deadline, task_assigned_at, recent_pull_requests. Updated imports with runtime markers and noqa adjustments.
Tests
backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
Updated test helpers and FakeModuleNode to call resolvers with the new root-based signature, added mocks for program and project_id resolvers, and adjusted patches for chained query behavior and bulk-loading mappings.
Frontend Type
frontend/src/types/mentorship.ts
Changed Module.experienceLevel type from ExperienceLevelEnum to string and added a JSDoc comment describing it as the API string matching runtime enum values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • arkid15r
  • kasya
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary change: migrating ModuleNode to use the strawberry-django decorator for consistency with other nodes in the codebase.
Description check ✅ Passed The description is directly related to the changeset, explaining the migration from @strawberry.type to @strawberry_django.type with supporting details about the changes made.
Linked Issues check ✅ Passed The PR addresses all requirements from issue #4226: migrates ModuleNode to @strawberry_django.type(Module, ...) with explicit fields list, updates resolvers to use root: Module pattern, and aligns with project conventions.
Out of Scope Changes check ✅ Passed All changes are within scope: ModuleNode refactoring with decorator migration, resolver updates, import adjustments, test updates, and frontend type signature change (Module.experienceLevel) align with the stated objectives.
Docstring Coverage ✅ Passed Docstring coverage is 86.21% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 12, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

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

59-62: Consider adding select_related hint for project access.

The project_name resolver accesses root.project.name, which could cause an N+1 query if the project FK isn't prefetched upstream. If this field is frequently queried, consider adding optimization hints at the query layer or using @strawberry_django.field(select_related=["project"]).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 59 - 62,
The project_name resolver on Module accesses root.project.name and can trigger
N+1 queries; update the Strawberry-Django field decorator to include a
select_related hint (e.g., use
`@strawberry_django.field`(select_related=["project"])) so the Module.project FK
is fetched with the parent query, or alternatively ensure the upstream query
that resolves Module objects calls select_related("project"); modify the
decorator on the project_name method (and any similar resolvers) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Line 230: The file ends without a trailing newline (see the declaration "tags:
list[str] = strawberry.field(default_factory=list)"); add a single newline
character at the end of the file so the last line is terminated, complying with
Ruff W292/PEP8.

---

Nitpick comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 59-62: The project_name resolver on Module accesses
root.project.name and can trigger N+1 queries; update the Strawberry-Django
field decorator to include a select_related hint (e.g., use
`@strawberry_django.field`(select_related=["project"])) so the Module.project FK
is fetched with the parent query, or alternatively ensure the upstream query
that resolves Module objects calls select_related("project"); modify the
decorator on the project_name method (and any similar resolvers) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2caffa2b-8243-4255-990a-0501e0904e99

📥 Commits

Reviewing files that changed from the base of the PR and between 0431ee7 and c05d76f.

📒 Files selected for processing (2)
  • backend/apps/mentorship/api/internal/nodes/module.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 12, 2026
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.

🧹 Nitpick comments (1)
backend/apps/mentorship/api/internal/nodes/module.py (1)

59-62: Consider whether the null check is necessary.

Based on the context snippet, project is defined as a non-nullable ForeignKey on the Module model (no null=True). The if root.project else None check may be defensive but unnecessary if the FK constraint is enforced.

This is a minor observation—the current implementation is safe and handles potential data integrity edge cases gracefully.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 59 - 62,
The null-check in the project_name resolver is unnecessary given Module.project
is a non-nullable FK; simplify the project_name method in module.py (the
strawberry_django.field resolver named project_name for Module) to directly
return root.project.name (remove the "if root.project else None" defensive
branch), or if you prefer to be explicit keep a single assert/validation that
project is present before accessing .name to avoid silent None returns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 59-62: The null-check in the project_name resolver is unnecessary
given Module.project is a non-nullable FK; simplify the project_name method in
module.py (the strawberry_django.field resolver named project_name for Module)
to directly return root.project.name (remove the "if root.project else None"
defensive branch), or if you prefer to be explicit keep a single
assert/validation that project is present before accessing .name to avoid silent
None returns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 054dd12a-06c1-47e6-b7da-6bf3701c4321

📥 Commits

Reviewing files that changed from the base of the PR and between b1b8db3 and fd9904c.

📒 Files selected for processing (2)
  • backend/apps/mentorship/api/internal/nodes/module.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 14, 2026
cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 14, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

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/nodes/module.py (1)

98-114: ⚠️ Potential issue | 🟡 Minor

Add offset validation to prevent unexpected pagination results.

normalize_limit bounds limit, but offset is used directly in queryset[offset : offset + normalized_limit]. A negative offset doesn't cause an error—it silently uses Python's negative indexing, returning unexpected results (potentially empty or partial data). This pattern is already validated in backend/apps/owasp/api/internal/queries/project_health_metrics.py.

💡 Suggested fix
         if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None:
             return []
+        if offset < 0:
+            return []

         queryset = root.issues.select_related("repository", "author").prefetch_related(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 98 - 114,
The issues resolver uses normalize_limit but leaves offset unchecked, which
allows negative offsets to trigger Python negative slicing; update the issues
method in Module (function issues) to validate the offset is non-negative before
slicing — e.g., compute a normalized_offset (or early-return []) if offset is <
0 (mirroring the pattern used in project_health_metrics.py), then use
queryset.order_by("-updated_at")[normalized_offset : normalized_offset +
normalized_limit] to avoid unexpected pagination results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 155-160: The resolver is returning duplicate users when an
issue_number maps to multiple linked issues; update the IssueUserInterest
queryset (used in the interested_users resolver) to deduplicate by user before
returning — e.g., call .distinct("user") (or .distinct() if you prefer
DB-agnostic) on the queryset
(IssueUserInterest.objects.select_related("user").filter(...).order_by("user__login").distinct("user"))
and then return [i.user for i in interests] so each user is only returned once.
- Around line 86-87: Remove the redundant boolean guard that forces a DB
evaluation when assigning issue_ids; instead, assign issue_ids using
root.issues.filter(number=issue_number).values_list("id", flat=True) without
wrapping it in an if not check and let the downstream filters that use
__in=issue_ids naturally return empty results. Apply the same change in both
resolvers where issue_ids is computed so you avoid the extra queryset
evaluation.

---

Outside diff comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 98-114: The issues resolver uses normalize_limit but leaves offset
unchecked, which allows negative offsets to trigger Python negative slicing;
update the issues method in Module (function issues) to validate the offset is
non-negative before slicing — e.g., compute a normalized_offset (or early-return
[]) if offset is < 0 (mirroring the pattern used in project_health_metrics.py),
then use queryset.order_by("-updated_at")[normalized_offset : normalized_offset
+ normalized_limit] to avoid unexpected pagination results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9ffc2e72-bd6b-4206-9d8d-b652c4c5fb86

📥 Commits

Reviewing files that changed from the base of the PR and between fd9904c and 37da258.

📒 Files selected for processing (1)
  • backend/apps/mentorship/api/internal/nodes/module.py

cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 14, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 5 files (changes from recent commits).

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.

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/nodes/module.py (1)

167-173: ⚠️ Potential issue | 🟡 Minor

Align task_deadline filtering with “latest assigned task” semantics.

At Line 167–173, the query orders by -assigned_at but does not exclude assigned_at IS NULL. That can select an unassigned task on some DBs due to null ordering.

Suggested fix
         return (
             Task.objects.filter(
                 module=root,
                 issue__number=issue_number,
                 deadline_at__isnull=False,
+                assigned_at__isnull=False,
             )
             .order_by("-assigned_at")
             .values_list("deadline_at", flat=True)
             .first()
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 167 - 173,
The Task query that fetches task_deadline currently orders by "-assigned_at" but
can return rows with assigned_at NULL; update the query that builds this
values_list (the Task.objects.filter(...) block) to explicitly exclude
unassigned tasks by adding a filter for assigned_at__isnull=False (or
.exclude(assigned_at__isnull=True)) before the .order_by("-assigned_at") so the
"latest assigned task" semantics only consider tasks with non-null assigned_at.
♻️ Duplicate comments (2)
backend/apps/mentorship/api/internal/nodes/module.py (2)

87-88: ⚠️ Potential issue | 🟡 Minor

Avoid QuerySet truthiness checks when building issue_ids.

At Line 87 and Line 153, boolean-evaluating the QuerySet triggers an extra DB hit. Assign issue_ids directly and let __in=issue_ids naturally yield empty results.

Suggested fix
-        if not (issue_ids := root.issues.filter(number=issue_number).values_list("id", flat=True)):
-            return []
+        issue_ids = root.issues.filter(number=issue_number).values_list("id", flat=True)

Apply the same change in both resolvers.

Based on learnings: In Django, boolean evaluation of a QuerySet performs a database query; prefer avoiding truthiness checks for existence-sensitive paths.

Also applies to: 153-154

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 87 - 88,
The code currently boolean-evaluates a QuerySet when building issue_ids (e.g.,
using "if not (issue_ids := root.issues.filter(...).values_list(...))"), which
causes an extra DB hit; instead assign issue_ids directly from
root.issues.filter(...).values_list("id", flat=True) and remove the truthiness
guard so subsequent queries use __in=issue_ids (letting an empty list naturally
produce no results); apply this change in both resolver locations that set
issue_ids to avoid the implicit QuerySet evaluation.

156-161: ⚠️ Potential issue | 🟡 Minor

Deduplicate interested_users to avoid repeated users.

Line 156–161 can return the same user multiple times when issue_number matches multiple linked issues. Return distinct users instead.

Suggested fix
-        interests = (
-            IssueUserInterest.objects.select_related("user")
-            .filter(module=root, issue_id__in=issue_ids)
-            .order_by("user__login")
-        )
-        return [i.user for i in interests]
+        return list(
+            User.objects.filter(
+                mentorship_interests__module=root,
+                mentorship_interests__issue_id__in=issue_ids,
+            )
+            .order_by("login")
+            .distinct()
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 156 - 161,
The current code in IssueUserInterest selection returns duplicate User objects
when a single user has interests on multiple issues; update the logic in the
function that builds `interests` (using IssueUserInterest, `root`, and
`issue_ids`) to first select distinct user IDs (e.g.
IssueUserInterest.objects.filter(module=root,
issue_id__in=issue_ids).values_list("user_id", flat=True).distinct()), then
fetch the User objects once (e.g.
User.objects.filter(id__in=distinct_ids).order_by("login")) and return that list
so users are deduplicated and still ordered by login.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 167-173: The Task query that fetches task_deadline currently
orders by "-assigned_at" but can return rows with assigned_at NULL; update the
query that builds this values_list (the Task.objects.filter(...) block) to
explicitly exclude unassigned tasks by adding a filter for
assigned_at__isnull=False (or .exclude(assigned_at__isnull=True)) before the
.order_by("-assigned_at") so the "latest assigned task" semantics only consider
tasks with non-null assigned_at.

---

Duplicate comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 87-88: The code currently boolean-evaluates a QuerySet when
building issue_ids (e.g., using "if not (issue_ids :=
root.issues.filter(...).values_list(...))"), which causes an extra DB hit;
instead assign issue_ids directly from root.issues.filter(...).values_list("id",
flat=True) and remove the truthiness guard so subsequent queries use
__in=issue_ids (letting an empty list naturally produce no results); apply this
change in both resolver locations that set issue_ids to avoid the implicit
QuerySet evaluation.
- Around line 156-161: The current code in IssueUserInterest selection returns
duplicate User objects when a single user has interests on multiple issues;
update the logic in the function that builds `interests` (using
IssueUserInterest, `root`, and `issue_ids`) to first select distinct user IDs
(e.g. IssueUserInterest.objects.filter(module=root,
issue_id__in=issue_ids).values_list("user_id", flat=True).distinct()), then
fetch the User objects once (e.g.
User.objects.filter(id__in=distinct_ids).order_by("login")) and return that list
so users are deduplicated and still ordered by login.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b99c00f7-3c14-41fa-afdd-19c898b796b9

📥 Commits

Reviewing files that changed from the base of the PR and between 37da258 and 01aed02.

⛔ Files ignored due to path filters (4)
  • frontend/src/types/__generated__/graphql.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 (1)
  • backend/apps/mentorship/api/internal/nodes/module.py

kasya
kasya previously approved these changes Mar 14, 2026
cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 14, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

@kasya kasya dismissed stale reviews from cubic-dev-ai[bot] and themself via efcf83b March 15, 2026 02:30
cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Mar 15, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 3 files (changes from recent commits).

@codecov
Copy link

codecov bot commented Mar 15, 2026

Codecov Report

❌ Patch coverage is 95.91837% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.40%. Comparing base (8821187) to head (1448dc0).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ckend/apps/mentorship/api/internal/nodes/module.py 95.91% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4256      +/-   ##
==========================================
- Coverage   99.41%   99.40%   -0.02%     
==========================================
  Files         520      520              
  Lines       16441    16430      -11     
  Branches     2292     2238      -54     
==========================================
- Hits        16345    16332      -13     
- Misses         60       62       +2     
  Partials       36       36              
Flag Coverage Δ
backend 99.81% <95.91%> (-0.02%) ⬇️
frontend 98.24% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ckend/apps/mentorship/api/internal/nodes/module.py 98.48% <95.91%> (-1.52%) ⬇️

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8821187...1448dc0. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

auto-merge was automatically disabled March 15, 2026 10:24

Head branch was pushed to by a user without write access

@sonarqubecloud
Copy link

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

@Jpeg-create
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

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

179-191: Consider database-agnostic deduplication for portability.

The distinct("user") syntax on lines 189-190 is PostgreSQL-specific (DISTINCT ON), while issue_mentees at line 93 uses the portable .distinct(). For consistency and database portability, consider querying User objects directly with standard .distinct().

Database-agnostic alternative
     `@strawberry_django.field`
     def interested_users(self, root: Module, issue_number: int) -> list[UserNode]:
         """Return users interested in this module's issue identified by its number."""
-        return [
-            issue_user_interest.user
-            for issue_user_interest in IssueUserInterest.objects.select_related("user")
-            .filter(
-                module=root,
-                issue_id__in=root.issues.filter(number=issue_number).values_list("id", flat=True),
-            )
-            .order_by("user", "user__login")
-            .distinct("user")
-        ]
+        issue_ids = root.issues.filter(number=issue_number).values_list("id", flat=True)
+        return list(
+            User.objects.filter(
+                mentorship_interests__module=root,
+                mentorship_interests__issue_id__in=issue_ids,
+            )
+            .order_by("login")
+            .distinct()
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 179 - 191,
The current interested_users resolver uses
IssueUserInterest.objects...distinct("user") which relies on PostgreSQL's
DISTINCT ON; instead query the User model directly to make the query
DB-agnostic: use User.objects.filter(issueuserinterest__module=root,
issueuserinterest__issue_id__in=root.issues.filter(number=issue_number).values_list("id",
flat=True)) with appropriate select_related/prefetch and
.order_by("login").distinct() and return those users (referencing the
interested_users resolver, IssueUserInterest, and Module/Issue relationships) so
the deduplication works across databases.

54-67: Consider non-optional return types for required FKs.

Both program and project are required ForeignKey fields in the model (no null=True), so their resolvers will always return a value. The current | None types are safe but make the GraphQL schema appear nullable when these fields are actually guaranteed.

Optional type tightening
     `@strawberry_django.field`
-    def program(self, root: Module) -> ProgramNode | None:
+    def program(self, root: Module) -> ProgramNode:
         """Get the program for this module."""
         return root.program

     `@strawberry_django.field`
-    def project_id(self, root: Module) -> strawberry.ID | None:
+    def project_id(self, root: Module) -> strawberry.ID:
         """Get the project ID for this module."""
         return root.project_id

     `@strawberry_django.field`
-    def project_name(self, root: Module) -> str | None:
+    def project_name(self, root: Module) -> str:
         """Get the project name for this module."""
-        return root.project.name if root.project else None
+        return root.project.name
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/mentorship/api/internal/nodes/module.py` around lines 54 - 67,
The GraphQL field resolvers currently mark program, project_id and project_name
as nullable but the underlying Module model has non-nullable ForeignKeys, so
update the return types: change program(self, root: Module) -> ProgramNode
(remove | None), project_id(self, root: Module) -> strawberry.ID (remove |
None), and project_name(self, root: Module) -> str (remove | None); keep
resolver bodies the same but tighten the annotated return types on the methods
program, project_id, and project_name to reflect the required FK guarantees.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 179-191: The current interested_users resolver uses
IssueUserInterest.objects...distinct("user") which relies on PostgreSQL's
DISTINCT ON; instead query the User model directly to make the query
DB-agnostic: use User.objects.filter(issueuserinterest__module=root,
issueuserinterest__issue_id__in=root.issues.filter(number=issue_number).values_list("id",
flat=True)) with appropriate select_related/prefetch and
.order_by("login").distinct() and return those users (referencing the
interested_users resolver, IssueUserInterest, and Module/Issue relationships) so
the deduplication works across databases.
- Around line 54-67: The GraphQL field resolvers currently mark program,
project_id and project_name as nullable but the underlying Module model has
non-nullable ForeignKeys, so update the return types: change program(self, root:
Module) -> ProgramNode (remove | None), project_id(self, root: Module) ->
strawberry.ID (remove | None), and project_name(self, root: Module) -> str
(remove | None); keep resolver bodies the same but tighten the annotated return
types on the methods program, project_id, and project_name to reflect the
required FK guarantees.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f797d150-6984-440c-9820-a0b95e4bdbd7

📥 Commits

Reviewing files that changed from the base of the PR and between 37da258 and c8ec731.

⛔ Files ignored due to path filters (4)
  • frontend/src/types/__generated__/graphql.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 (3)
  • backend/apps/mentorship/api/internal/nodes/module.py
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • frontend/src/types/mentorship.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/mentorship/api/internal/nodes/api_internal_module_test.py

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.

refactor: migrate ModuleNode to use strawberry-django decorator for consistency

3 participants