Skip to content

fix: optimize interested users field in IssueNode#3788

Merged
arkid15r merged 16 commits intoOWASP:mainfrom
Utkarsh-0304:fix/optimize_interestedUsers_field
Feb 16, 2026
Merged

fix: optimize interested users field in IssueNode#3788
arkid15r merged 16 commits intoOWASP:mainfrom
Utkarsh-0304:fix/optimize_interestedUsers_field

Conversation

@Utkarsh-0304
Copy link
Copy Markdown
Contributor

@Utkarsh-0304 Utkarsh-0304 commented Feb 5, 2026

Proposed change

Resolves #3659

This PR optimises the interestedUsers field in RepositoryNode by eliminating the N + 1 query problem through the use of the Prefetch object.

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
Copy Markdown
Contributor

coderabbitai Bot commented Feb 5, 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

Walkthrough

Replaces simple joins with nested Django Prefetches to load interested users and their active badges for IssueNode; introduces a module-level USER_BADGES_PREFETCH and uses a prefetched attribute (user_badges_list) as a fast-path in UserNode badge resolvers; tests adjusted for fallback behavior.

Changes

Cohort / File(s) Summary
Issue node prefetch & resolver
backend/apps/github/api/internal/nodes/issue.py
Replaced simple prefetch with a nested Prefetch on participant_interests that select_related the user and prefetches user__user_badges (active, ordered) into to_attr="user_badges_list", storing interests in to_attr="interests_users". Resolver now returns getattr(root, "interests_users", []). Added imports for IssueUserInterest and UserBadge.
User node badge prefetch & resolvers
backend/apps/github/api/internal/nodes/user.py
Added USER_BADGES_PREFETCH (module-level Prefetch) and swapped per-field prefetch specs to use it. badge_count and badges now fast-path from getattr(root, "user_badges_list", []) when present; otherwise fall back to previous queryset-based logic.
Tests (minor adjustment)
backend/tests/apps/github/api/internal/nodes/user_test.py
Removed an empty line; test ensures fallback path by explicitly clearing or not depending on user_badges_list presence. No functional test logic changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • arkid15r
  • kasya
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: optimize interested users field in IssueNode' clearly and specifically describes the main change in the PR, directly matching the code modifications to the interested_users field.
Description check ✅ Passed The description relates to the changeset by mentioning optimization of interestedUsers field through Prefetch object use, though it incorrectly states RepositoryNode instead of IssueNode.
Linked Issues check ✅ Passed The PR successfully implements the primary objective from issue #3659: eliminating N+1 queries by replacing simple optimization hints with Django's Prefetch object for nested prefetching of related user and badge data.
Out of Scope Changes check ✅ Passed All changes are in scope: the interested_users field optimization is the core requirement, and supporting improvements to user.py's badge prefetching are complementary optimizations aligned with the same objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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

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 Feb 5, 2026
Copy link
Copy Markdown
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 1 file

Confidence score: 5/5

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

coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 5, 2026
@Utkarsh-0304 Utkarsh-0304 marked this pull request as ready for review February 5, 2026 12:01
coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 7, 2026
Copy link
Copy Markdown
Collaborator

@ahmedxgouda ahmedxgouda left a comment

Choose a reason for hiding this comment

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

Thank you for working on this. You need to optimize the nested relations that can be called within the query like badgeCount.

prefetch_related=[
Prefetch(
"participant_interests",
queryset=IssueUserInterest.objects.select_related("user").order_by("user__login"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It is optimized now for this simple query:
Image
But we still have N+1 queries for a query like this:

Image

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.77%. Comparing base (ec66d52) to head (22a70a6).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #3788   +/-   ##
=======================================
  Coverage   96.77%   96.77%           
=======================================
  Files         512      512           
  Lines       15823    15823           
  Branches     2168     2127   -41     
=======================================
  Hits        15312    15312           
  Misses        422      422           
  Partials       89       89           
Flag Coverage Δ
backend 95.99% <100.00%> (ø)
frontend 99.13% <100.00%> (ø)

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

Files with missing lines Coverage Δ
backend/apps/github/api/internal/nodes/issue.py 100.00% <100.00%> (ø)
backend/apps/github/api/internal/nodes/user.py 100.00% <100.00%> (ø)
backend/apps/github/api/internal/queries/user.py 100.00% <100.00%> (ø)
frontend/src/app/members/page.tsx 100.00% <100.00%> (ø)

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 ec66d52...22a70a6. 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.

@ahmedxgouda ahmedxgouda self-assigned this Feb 8, 2026
Copy link
Copy Markdown
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

🤖 Fix all issues with AI agents
In `@backend/apps/github/api/internal/nodes/issue.py`:
- Around line 66-85: The inner Prefetch for "user__user_badges" on the IssueNode
prefetch (to_attr="user_badges_list") doesn’t select_related the badge FK,
causing N+1 queries when UserNode.badges accesses user_badge.badge; update the
inner queryset for UserBadge (the Prefetch inside participant_interests) to
include .select_related("badge") so the badge relation is eagerly loaded (keep
the existing filter/order_by and to_attr).

In `@backend/apps/github/api/internal/nodes/user.py`:
- Around line 44-47: The fast path in the badges resolver uses getattr(root,
"user_badges_list") and then iterates user_badge.badge, which will fire N+1
queries unless the Prefetch that populates user_badges_list includes
select_related("badge"); update the Prefetch queryset where user_badges_list is
built (see the code in issue.py that creates the Prefetch) to add
.select_related("badge") so the badge relation is fetched eagerly and the
resolver (user_badges_list / badges) won’t trigger additional DB queries.
🧹 Nitpick comments (1)
backend/tests/apps/github/api/internal/nodes/user_test.py (1)

104-104: Missing test coverage for the new user_badges_list fast path.

All modified tests set user_badges_list = None, which only exercises the fallback path. There are no tests for when user_badges_list is present (the new optimization path added in user.py). Consider adding tests that supply a populated user_badges_list and verify that:

  • badge_count returns len(user_badges_list)
  • badges returns the badge objects from the list without calling .filter()

Comment thread backend/apps/github/api/internal/nodes/issue.py
Comment thread backend/apps/github/api/internal/nodes/user.py Outdated
Copy link
Copy Markdown
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/tests/apps/github/api/internal/nodes/user_test.py (1)

98-110: ⚠️ Potential issue | 🟡 Minor

Missing tests for the new user_badges_list fast path.

Setting user_badges_list = None in every badge-related test ensures the fallback path is covered, but there are no tests exercising the new early-return optimization when user_badges_list is a pre-populated list. This is the main feature of the PR and should have dedicated test coverage for both badge_count and badges resolvers.

Consider adding tests like:

def test_badge_count_field_with_prefetched_badges(self):
    mock_user = Mock()
    mock_user_badge = Mock(is_active=True)
    mock_user.user_badges_list = [mock_user_badge, mock_user_badge]

    field = self._get_field_by_name("badge_count", UserNode)
    result = field.base_resolver.wrapped_func(None, mock_user)
    assert result == 2

def test_badges_field_with_prefetched_badges(self):
    mock_badge = Mock(spec=BadgeNode)
    mock_user_badge = Mock()
    mock_user_badge.badge = mock_badge
    mock_user = Mock()
    mock_user.user_badges_list = [mock_user_badge]

    field = self._get_field_by_name("badges", UserNode)
    result = field.base_resolver.wrapped_func(None, mock_user)
    assert result == [mock_badge]

Also applies to: 112-127, 129-147, 149-212

coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 9, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 9, 2026
ahmedxgouda
ahmedxgouda previously approved these changes Feb 9, 2026
Copy link
Copy Markdown
Collaborator

@ahmedxgouda ahmedxgouda left a comment

Choose a reason for hiding this comment

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

LGTM.

Copy link
Copy Markdown
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.

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

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/apps/github/api/internal/nodes/issue.py">

<violation number="1" location="backend/apps/github/api/internal/nodes/issue.py:69">
P1: Removing the badge prefetch causes `UserNode.badges()` to silently return an empty list for interested users. The `badges` resolver reads from `getattr(root, "user_badges_list", [])`, which requires the `user__user_badges` Prefetch with `to_attr="user_badges_list"` to have been set up. Without it, any GraphQL query requesting `interestedUsers { badges { ... } }` will always get empty badges.

Consider either keeping the nested badge prefetch here, or adding `prefetch_related=[USER_BADGES_PREFETCH]` to the `badges` field decorator in `UserNode` so that strawberry-django handles it automatically.</violation>
</file>

<file name="backend/apps/github/api/internal/nodes/user.py">

<violation number="1" location="backend/apps/github/api/internal/nodes/user.py:44">
P2: Removing `prefetch_related=[USER_BADGES_PREFETCH]` from the `badges` field decorator means badges will silently return an empty list when `UserNode` is resolved from nested contexts (e.g., issue assignees/authors, mentorship mentees, snapshot users, etc.) that don't manually set up the prefetch. Previously, strawberry-django would automatically apply the prefetch when the field was resolved. Consider keeping `prefetch_related=[USER_BADGES_PREFETCH]` on this field to ensure correct behavior in all resolution contexts.</violation>
</file>

<file name="backend/tests/apps/github/api/internal/queries/user_test.py">

<violation number="1" location="backend/tests/apps/github/api/internal/queries/user_test.py:27">
P2: The assertion verifies `prefetch_related` was called but doesn't check it received `USER_BADGES_PREFETCH` as an argument. Since the whole point of this PR is to add the correct prefetch, use `assert_called_once_with(USER_BADGES_PREFETCH)` to verify the correct prefetch object is passed.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread backend/apps/github/api/internal/nodes/issue.py
Comment thread backend/apps/github/api/internal/nodes/user.py Outdated
Comment thread backend/tests/apps/github/api/internal/queries/user_test.py Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 16, 2026
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 16, 2026
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
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.

Hi @Utkarsh-0304 !

I pushed changes to this PR to address some of my concerns and issues that I found.

I believe we'll need to look more into other ways to optimize backend calls without prefetches. My main concern is that some of these cases are only "theoretical" and are only possible when you go into GraphQL explorer. For example, there's no query where we'd need to have badges on interested_users.

But since this PR addresses the issue and also has some clean up that was needed - we'd push this as is 👌🏼
Thank you!

return root.user_badges.filter(is_active=True).count()

@strawberry_django.field(prefetch_related=["user_badges__badge"])
@strawberry_django.field(prefetch_related=[USER_BADGES_PREFETCH])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I removed this resolver completely - the only place where we show badge count get's that from Algolia.

return User.objects.filter(has_public_member_page=True, login=login).first()
return (
User.objects.filter(has_public_member_page=True, login=login)
.prefetch_related(USER_BADGES_PREFETCH)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We only show badges on the single user page that is resolved here. With just adding prefetch to a @strawberry_django.field this was not working and not adding it here - this was always going into a fallback for [].

Prefetch on the field will be triggered when we fetch badges via another connection (example: Issue.author.badges if we'd ever need that).

Any optimization needs to be tested to make sure returned results are the same as before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, I realised I forgot to handle the scenario involving only users.

I will ensure to test that the returned results remain consistent in future PRs.

Thanks for your review, @kasya

@@ -1,24 +1,5 @@
import { gql } from '@apollo/client'

export const GET_LEADER_DATA = gql`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Dead code.

@arkid15r arkid15r added this pull request to the merge queue Feb 16, 2026
Merged via the queue into OWASP:main with commit 094d213 Feb 16, 2026
36 checks passed
@Utkarsh-0304
Copy link
Copy Markdown
Contributor Author

Utkarsh-0304 commented Feb 17, 2026

Hi @arkid15r, I noticed that this issue related to this PR is not assigned to the team GSOC 2026. Does this mean that this won't count towards the leaderboard?

image

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.

Optimize interestedUsers field in IssueNode

4 participants