Skip to content

Optimize GitHub app IssueNode#4609

Open
ahmedxgouda wants to merge 10 commits intoOWASP:feature/graphql-dataloadersfrom
ahmedxgouda:dataloaders/github-issue
Open

Optimize GitHub app IssueNode#4609
ahmedxgouda wants to merge 10 commits intoOWASP:feature/graphql-dataloadersfrom
ahmedxgouda:dataloaders/github-issue

Conversation

@ahmedxgouda
Copy link
Copy Markdown
Collaborator

@ahmedxgouda ahmedxgouda commented May 4, 2026

Proposed change

Resolves #4598

  • Added a custom GraphQL context to preserve dataloaders per request (so not two users use the same dataloader instance which caches the results)
  • Added a custom Asynchronous GraphQL view
  • Converted synchronous ORM operations like count to Asynchronous to avoid SynchronousOnlyOperation exception
  • Used sync_to_async with methods that may be used across other parts in the app rather than the resolver
  • Added a DataLoader for interested_users which fixed N+1 queries problem for the resolver (see the video embedded)
  • Added a utils for DataLoaders to reuse the sort function
  • Added pytest-asyncio for asynchronous tests
  • Added/updated tests

The code to check the number of db commits:

select datname, xact_commit from pg_stat_database;
GitHub.IssueNode.mp4

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 May 4, 2026

Warning

Rate limit exceeded

@ahmedxgouda has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 34 minutes and 16 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8a7b982c-42ae-4dae-a0f9-3ec389f7e325

📥 Commits

Reviewing files that changed from the base of the PR and between e2c05fa and 3cb194e.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • backend/apps/common/api/__init__.py
  • backend/apps/common/api/internal/__init__.py
  • backend/apps/mentorship/api/internal/queries/module.py
  • backend/pyproject.toml
  • backend/tests/unit/apps/common/api/__init__.py
  • backend/tests/unit/apps/common/api/internal/__init__.py
  • backend/tests/unit/apps/common/api/internal/dataloaders/__init__.py
  • backend/tests/unit/apps/common/api/internal/dataloaders/utils_test.py
  • backend/tests/unit/apps/github/api/__init__.py
  • backend/tests/unit/apps/github/api/internal/__init__.py
  • backend/tests/unit/apps/github/api/internal/dataloaders/__init__.py
  • backend/tests/unit/apps/github/api/internal/dataloaders/interested_users_test.py
  • backend/tests/unit/apps/github/api/internal/nodes/issue_test.py
  • backend/tests/unit/apps/github/api/internal/queries/organization_test.py
  • backend/tests/unit/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/unit/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/unit/apps/owasp/api/internal/queries/stats_test.py

Walkthrough

This PR introduces a Strawberry GraphQL dataloader infrastructure and converts query resolvers to async. It adds a shared dataloader utility, implements an interested_users dataloader for batch-loading GitHub issue subscribers, wires dataloaders into GraphQL context via a custom view, and converts several query resolvers (organization, mentorship modules/programs, OWASP stats) to async ORM calls.

Changes

Dataloader Infrastructure & IssueNode Optimization

Layer / File(s) Summary
Shared Dataloader Utility
backend/apps/common/api/internal/dataloaders/utils.py
Generic async function results_by_keys groups QuerySet results by a dynamic key field and returns aligned lists of values for each key.
GitHub Dataloader Factory
backend/apps/github/api/internal/dataloaders/__init__.py, backend/apps/github/api/internal/dataloaders/interested_users.py
Factory make_github_dataloaders() and batch loader load_interested_users() use results_by_keys to batch-query and group IssueUserInterest records by issue ID.
GraphQL Context & View
backend/settings/graphql_context.py, backend/settings/graphql.py, backend/settings/urls.py
New NestGraphQLContext initializes github_dataloaders on each request; NestGraphQLView overrides get_context() to instantiate it; URL patterns updated to use the custom view.
IssueNode Resolver
backend/apps/github/api/internal/nodes/issue.py
interested_users resolver replaced from prefetch-based to async dataloader-based, loading users via info.context.github_dataloaders[INTERESTED_USERS_LOADER].
Query Async Conversions
backend/apps/github/api/internal/queries/organization.py, backend/apps/mentorship/api/internal/queries/module.py, backend/apps/mentorship/api/internal/queries/program.py, backend/apps/owasp/api/internal/queries/stats.py
Decorators changed from @strawberry.field to @strawberry_django.field; resolvers made async and switched from .get() to .aget() or .acount(); stats query adds explicit workspace lookup via OWASP_WORKSPACE_ID.
Tests
backend/tests/unit/apps/github/api/internal/nodes/issue_test.py, backend/tests/unit/apps/github/api/internal/queries/organization_test.py, backend/tests/unit/apps/mentorship/api/internal/queries/api_queries_module_test.py, backend/tests/unit/apps/mentorship/api/internal/queries/api_queries_program_test.py, backend/tests/unit/apps/owasp/api/internal/queries/stats_test.py
Test files updated to use asyncio.run() and AsyncMock for async resolver/query testing; dataloader tests inject mocked loaders into context; stats and program tests mock async ORM methods (acount(), aget(), afirst()).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • OWASP/Nest#1509: Complementary Strawberry GraphQL migration that establishes the async view/context foundation this PR builds upon.
  • OWASP/Nest#3788: Related optimization of IssueNode.interested_users via nested prefetch patterns; overlaps with the dataloader approach introduced here.
  • OWASP/Nest#3404: Overlapping changes to Strawberry Django field decorators and ORM prefetch patterns in similar resolver modules.

Suggested reviewers

  • arkid15r
  • kasya
🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Optimize GitHub app IssueNode' is vague and generic, using broad language that doesn't clearly convey the specific technical change (DataLoader implementation, async conversion, N+1 fix). Revise title to be more specific, such as 'Add DataLoader for IssueNode.interested_users' or 'Implement async DataLoaders and context for GitHub app' to clearly indicate the main technical change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly details the changes made, including adding a custom GraphQL context, asynchronous DataLoaders, and addressing SynchronousOnlyOperation exceptions.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Summary by CodeRabbit

  • New Features

    • Added batch data loading infrastructure for GitHub issue interested users queries to enhance performance.
  • Refactor

    • Refactored interested users field resolution on GitHub issues to use async dataloaders for improved efficiency.
    • Updated GraphQL context and view configuration to support dataloader-based data resolution.
  • Tests

    • Updated unit tests to validate async dataloader functionality for interested users queries.

Walkthrough

This PR implements a DataLoader optimization for the IssueNode.interested_users GraphQL field. A shared async utility groups batch query results by key, a GitHub-specific dataloader batches issue-to-interested-users lookups, and the GraphQL context is extended to inject dataloaders per request. The resolver switches from eager-loaded prefetch to async dataloader-based resolution.

Changes

DataLoader Implementation for Interested Users

Layer / File(s) Summary
Shared Utility
backend/apps/common/api/internal/dataloaders/utils.py
results_by_keys groups QuerySet results by a dynamic key field and returns value lists ordered to match input keys.
Dataloader Factory & Batch Logic
backend/apps/github/api/internal/dataloaders/__init__.py, backend/apps/github/api/internal/dataloaders/interested_users.py
make_github_dataloaders() constructs a dataloader dict; load_interested_users batch-fetches IssueUserInterest records, eager-loads related user profiles, and groups users by issue ID via results_by_keys.
GraphQL Context & View
backend/settings/graphql_context.py, backend/settings/graphql.py
NestGraphQLContext initializes github_dataloaders on each context instance; NestGraphQLView overrides get_context to instantiate and return the context with request/response parameters.
URL & View Wiring
backend/settings/urls.py
GraphQL endpoint switched to use NestGraphQLView instead of the default GraphQLView, preserving CSRF protection and schema.
Resolver Integration
backend/apps/github/api/internal/nodes/issue.py
IssueNode.interested_users refactored to async resolution using info.context.github_dataloaders["interested_users_loader"].load(root.pk), removing prefetch-based queryset logic and IssueUserInterest import.
Test Updates
backend/tests/unit/apps/github/api/internal/nodes/issue_test.py
test_interested_users updated to mock async dataloader, verify load is awaited with the issue PK, and assert returned users; test imports updated for asyncio and AsyncMock.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Optimize GitHub app IssueNode' is clear and directly related to the main change—optimizing the IssueNode by implementing a DataLoader for the interested_users resolver.
Description check ✅ Passed The description mentions resolving #4598 and includes a checklist, which relates to the changeset, though it contains placeholder text and incomplete items.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #4598: a DataLoader for interested_users resolver [4598] is implemented, and it loads interested users based on issue id [4598].
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the DataLoader optimization for interested_users. Supporting infrastructure (context, view, utils) is necessary for the core 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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 25.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.87%. Comparing base (82a8651) to head (9c8e4ef).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ithub/api/internal/dataloaders/interested_users.py 0.00% 9 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4609      +/-   ##
==========================================
- Coverage   98.92%   98.87%   -0.06%     
==========================================
  Files         527      528       +1     
  Lines       16956    16964       +8     
  Branches     2360     2360              
==========================================
- Hits        16774    16773       -1     
- Misses         97      106       +9     
  Partials       85       85              
Flag Coverage Δ
backend 99.43% <25.00%> (-0.08%) ⬇️

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%> (ø)
...ithub/api/internal/dataloaders/interested_users.py 0.00% <0.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 82a8651...9c8e4ef. 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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.92%. Comparing base (45ebaf5) to head (3cb194e).

Additional details and impacted files

Impacted file tree graph

@@                     Coverage Diff                      @@
##           feature/graphql-dataloaders    #4609   +/-   ##
============================================================
  Coverage                        98.92%   98.92%           
============================================================
  Files                              527      529    +2     
  Lines                            16956    16981   +25     
  Branches                          2412     2361   -51     
============================================================
+ Hits                             16774    16799   +25     
  Misses                              97       97           
  Partials                            85       85           
Flag Coverage Δ
backend 99.50% <100.00%> (+<0.01%) ⬆️
frontend 97.30% <ø> (ø)

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

Files with missing lines Coverage Δ
...kend/apps/common/api/internal/dataloaders/utils.py 100.00% <100.00%> (ø)
...ithub/api/internal/dataloaders/interested_users.py 100.00% <100.00%> (ø)
backend/apps/github/api/internal/nodes/issue.py 100.00% <100.00%> (ø)
...d/apps/github/api/internal/queries/organization.py 100.00% <100.00%> (ø)
...end/apps/mentorship/api/internal/queries/module.py 96.87% <100.00%> (+0.20%) ⬆️
...nd/apps/mentorship/api/internal/queries/program.py 100.00% <100.00%> (ø)
backend/apps/owasp/api/internal/queries/stats.py 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 45ebaf5...3cb194e. 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.

cubic-dev-ai[bot]
cubic-dev-ai Bot previously approved these changes May 4, 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 9 files

Confidence score: 5/5

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

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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 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/github/api/internal/dataloaders/__init__.py`:
- Around line 8-10: Extract the literal loader key into a module-level constant
(e.g., INTERESTED_USERS_LOADER) in
backend/apps/github/api/internal/dataloaders/__init__.py and use it as the dict
key when returning {"INTERESTED_USERS_LOADER": make_interested_users_loader()}
(replace the quoted literal with the constant); then update any consumers
(notably the use in issue.py where the string "interested_users_loader" is
referenced) to import INTERESTED_USERS_LOADER from the dataloaders package and
use that constant instead of the bare string to avoid brittle duplicates/typos.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 821bc7cd-9a33-42f3-89e2-b7fc6e6722a9

📥 Commits

Reviewing files that changed from the base of the PR and between 87daa87 and 9c8e4ef.

📒 Files selected for processing (9)
  • backend/apps/common/api/internal/dataloaders/__init__.py
  • backend/apps/common/api/internal/dataloaders/utils.py
  • backend/apps/github/api/internal/dataloaders/__init__.py
  • backend/apps/github/api/internal/dataloaders/interested_users.py
  • backend/apps/github/api/internal/nodes/issue.py
  • backend/settings/graphql.py
  • backend/settings/graphql_context.py
  • backend/settings/urls.py
  • backend/tests/unit/apps/github/api/internal/nodes/issue_test.py

Comment thread backend/apps/github/api/internal/dataloaders/__init__.py
@ahmedxgouda ahmedxgouda changed the base branch from main to feature/graphql-dataloaders May 6, 2026 03:48
@ahmedxgouda ahmedxgouda dismissed cubic-dev-ai[bot]’s stale review May 6, 2026 03:48

The base branch was changed.

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 (3)
backend/tests/unit/apps/owasp/api/internal/queries/stats_test.py (1)

17-37: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Assert the workspace lookup uses OWASP_WORKSPACE_ID.

Both tests stub the filtered queryset, but neither verifies the resolver actually calls Workspace.objects.filter(slack_workspace_id=OWASP_WORKSPACE_ID). A regression to the wrong filter would still pass here.

Suggested test hardening
 import asyncio
 from unittest.mock import AsyncMock, MagicMock, patch

+from apps.slack.constants import OWASP_WORKSPACE_ID
 from apps.owasp.api.internal.queries.stats import StatsQuery
             query = StatsQuery()
             result = asyncio.run(query.stats_overview())

+            mock_workspace_cls.objects.filter.assert_called_once_with(
+                slack_workspace_id=OWASP_WORKSPACE_ID
+            )
             assert result.active_projects_stats == 270
             assert result.active_chapters_stats == 340
             assert result.contributors_stats == 15000
             assert result.countries_stats == 90
             assert result.slack_workspace_stats == 5000
             query = StatsQuery()
             result = asyncio.run(query.stats_overview())
+
+            mock_workspace_cls.objects.filter.assert_called_once_with(
+                slack_workspace_id=OWASP_WORKSPACE_ID
+            )
             assert result.slack_workspace_stats == 0

Also applies to: 47-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/tests/unit/apps/owasp/api/internal/queries/stats_test.py` around
lines 17 - 37, The workspace lookup in the StatsQuery tests must be asserted to
ensure Workspace.objects.filter is called with the constant OWASP_WORKSPACE_ID;
update the test(s) in stats_test.py (where StatsQuery().stats_overview() is
invoked) to assert that mock_workspace_cls.objects.filter was called with
slack_workspace_id=OWASP_WORKSPACE_ID (e.g. assert_called_once_with or
assert_called_with), and add the same assertion in the second test block
referenced (lines ~47-65) so the resolver’s filter argument is validated for
regressions.
backend/apps/mentorship/api/internal/queries/module.py (1)

28-31: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Wrap program.user_has_access with sync_to_async in async context.

user_has_access performs synchronous ORM queries (self.admins.filter(nest_user=user).exists() and self.modules.filter(...).exists()), but is called synchronously from the async get_program_modules resolver. This will raise SynchronousOnlyOperation. Use sync_to_async to safely call it from async context:

from asgiref.sync import sync_to_async

if program.status != Program.ProgramStatus.PUBLISHED and not await sync_to_async(
    program.user_has_access
)(info.context.request.user):
    return []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/apps/mentorship/api/internal/queries/module.py` around lines 28 - 31,
The async resolver get_program_modules calls the synchronous method
program.user_has_access which performs ORM queries and will raise
SynchronousOnlyOperation; wrap the call with asgiref.sync.sync_to_async and
await it instead of calling directly (import sync_to_async and replace the
direct program.user_has_access(...) call with await
sync_to_async(program.user_has_access)(info.context.request.user)), keeping the
existing conditional logic intact so the published-status check still
short-circuits.
backend/apps/mentorship/api/internal/queries/program.py (1)

36-39: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap program.user_has_access with sync_to_async in this async resolver.

This async resolver calls program.user_has_access(...), which synchronously performs ORM operations (.filter().exists()). This will raise SynchronousOnlyOperation at runtime. The same issue exists in module.py:get_program_modules (line 28).

Fix
+from asgiref.sync import sync_to_async
 
         if program.status != Program.ProgramStatus.PUBLISHED and not await sync_to_async(
             program.user_has_access
         )(info.context.request.user):
             return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/apps/mentorship/api/internal/queries/program.py` around lines 36 -
39, The async resolver in program.py calls the synchronous method
program.user_has_access(...) which performs ORM work and must be awaited via an
async wrapper; import sync_to_async (from asgiref.sync), replace direct calls to
program.user_has_access(...) with await
sync_to_async(program.user_has_access)(info.context.request.user) (and do the
same fix in module.py's get_program_modules around the call at line 28),
ensuring you await the wrapper so no SynchronousOnlyOperation is raised.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/apps/mentorship/api/internal/queries/module.py`:
- Around line 28-31: The async resolver get_program_modules calls the
synchronous method program.user_has_access which performs ORM queries and will
raise SynchronousOnlyOperation; wrap the call with asgiref.sync.sync_to_async
and await it instead of calling directly (import sync_to_async and replace the
direct program.user_has_access(...) call with await
sync_to_async(program.user_has_access)(info.context.request.user)), keeping the
existing conditional logic intact so the published-status check still
short-circuits.

In `@backend/apps/mentorship/api/internal/queries/program.py`:
- Around line 36-39: The async resolver in program.py calls the synchronous
method program.user_has_access(...) which performs ORM work and must be awaited
via an async wrapper; import sync_to_async (from asgiref.sync), replace direct
calls to program.user_has_access(...) with await
sync_to_async(program.user_has_access)(info.context.request.user) (and do the
same fix in module.py's get_program_modules around the call at line 28),
ensuring you await the wrapper so no SynchronousOnlyOperation is raised.

In `@backend/tests/unit/apps/owasp/api/internal/queries/stats_test.py`:
- Around line 17-37: The workspace lookup in the StatsQuery tests must be
asserted to ensure Workspace.objects.filter is called with the constant
OWASP_WORKSPACE_ID; update the test(s) in stats_test.py (where
StatsQuery().stats_overview() is invoked) to assert that
mock_workspace_cls.objects.filter was called with
slack_workspace_id=OWASP_WORKSPACE_ID (e.g. assert_called_once_with or
assert_called_with), and add the same assertion in the second test block
referenced (lines ~47-65) so the resolver’s filter argument is validated for
regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 67c4fc5f-36be-4207-a7e3-d5c848bbb9e3

📥 Commits

Reviewing files that changed from the base of the PR and between 9c8e4ef and e2c05fa.

📒 Files selected for processing (11)
  • backend/apps/github/api/internal/dataloaders/__init__.py
  • backend/apps/github/api/internal/nodes/issue.py
  • backend/apps/github/api/internal/queries/organization.py
  • backend/apps/mentorship/api/internal/queries/module.py
  • backend/apps/mentorship/api/internal/queries/program.py
  • backend/apps/owasp/api/internal/queries/stats.py
  • backend/tests/unit/apps/github/api/internal/nodes/issue_test.py
  • backend/tests/unit/apps/github/api/internal/queries/organization_test.py
  • backend/tests/unit/apps/mentorship/api/internal/queries/api_queries_module_test.py
  • backend/tests/unit/apps/mentorship/api/internal/queries/api_queries_program_test.py
  • backend/tests/unit/apps/owasp/api/internal/queries/stats_test.py

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 6, 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.

1 issue found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved 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/mentorship/api/internal/queries/module.py">

<violation number="1" location="backend/apps/mentorship/api/internal/queries/module.py:19">
P1: This resolver is now async, but it still calls `program.user_has_access(...)`, which performs synchronous ORM `.exists()` queries and can raise `SynchronousOnlyOperation` in async execution.</violation>
</file>

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

Comment thread backend/apps/mentorship/api/internal/queries/module.py
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 6, 2026

@ahmedxgouda ahmedxgouda marked this pull request as ready for review May 6, 2026 17:41
@ahmedxgouda ahmedxgouda requested review from arkid15r and kasya as code owners May 6, 2026 17:41
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 GitHub app IssueNode

1 participant