Skip to content

Feature/improve module management ux#5059

Draft
harsitagarwalla187 wants to merge 3 commits into
OWASP:mainfrom
harsitagarwalla187:feature/improve-module-management-ux
Draft

Feature/improve module management ux#5059
harsitagarwalla187 wants to merge 3 commits into
OWASP:mainfrom
harsitagarwalla187:feature/improve-module-management-ux

Conversation

@harsitagarwalla187

Copy link
Copy Markdown

Proposed change

Resolves #4987

Summary of Changes

  • Backend:
    • Updated the mentors resolver in the ModuleNode GraphQL type (backend/apps/mentorship/api/internal/nodes/module.py) to sort mentors alphabetically by their GitHub username (github_user__login).
    • Adjusted unit tests in api_internal_module_test.py to correctly mock and test the new order_by query pattern.
  • Frontend:
    • UX/Forms: Updated ModuleForm.tsx to always show the "Mentor GitHub Usernames" text input field, rather than only displaying it in edit mode.
    • Module Card: Improved flex styling and layout constraints in ModuleCard.tsx to ensure long module titles truncate correctly without throwing off the layout.
    • Styling: Added styling rules under .md-wrapper in globals.css for paragraphs, headings, lists, and list items to improve the layout and readability of markdown content.

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 all required checks and tests locally; all warnings addressed and failures resolved
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Mentor username fields are now shown in module forms at all times for consistent module setup.
  • Bug Fixes

    • Standardized Markdown spacing within the markdown wrapper (paragraphs, lists, headings, and list items).
    • Improved module/mentor data loading behavior and mentor ordering across module views.
  • UI Updates

    • Improved layout/spacing of module names in module cards.
  • Tests

    • Updated ModuleForm unit tests to reflect the mentor logins field always being present and interactive.

Walkthrough

Module queries now prefetch mentors with ordered github_user joins, and the frontend updates markdown spacing, module title link layout, and mentor login field rendering with matching tests.

Changes

Module management UX

Layer / File(s) Summary
Markdown wrapper spacing
frontend/src/app/globals.css
.md-wrapper adds spacing rules for paragraphs, lists, list items, and headings h1h6.
Module title link layout
frontend/src/components/ModuleCard.tsx
The module name link in ModuleCard adds mr-2 min-w-0 flex-1 to its class list.
Mentor login field visibility
frontend/src/components/ModuleForm.tsx, frontend/__tests__/unit/components/ModuleForm.test.tsx
ModuleForm always renders the mentor logins input, and the unit test now asserts the field is present for both isEdit values and updates form data on change.
Module mentor ordering
backend/apps/mentorship/api/internal/queries/module.py
Module query resolvers replace shorthand mentor prefetches with a Prefetch queryset that selects github_user and orders mentors by github_user__login.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • arkid15r
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main focus on improving module management UX, though it is broad.
Description check ✅ Passed The description matches the implemented backend and frontend changes and the linked issue.
Linked Issues check ✅ Passed The changes address mentor assignment, card layout, markdown spacing, and deterministic mentor ordering from #4987.
Out of Scope Changes check ✅ Passed The modified files align with the linked UX, layout, formatting, and ordering objectives and do not add obvious unrelated scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.0)
frontend/src/app/globals.css

File contains syntax errors that prevent linting: Line 3: Tailwind-specific syntax is disabled.; Line 17: Tailwind-specific syntax is disabled.; Line 304: Tailwind-specific syntax is disabled.; Line 457: Tailwind-specific syntax is disabled.; Line 461: Tailwind-specific syntax is disabled.; Line 462: Tailwind-specific syntax is disabled.; Line 463: Tailwind-specific syntax is disabled.; Line 468: Tailwind-specific syntax is disabled.

🔧 Stylelint (17.13.0)
frontend/src/app/globals.css

Error: ENOENT: no such file or directory, open '/.stylelintrc.json'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async #readConfiguration (/usr/local/lib/node_modules/stylelint/node_modules/cosmiconfig/dist/Explorer.js:83:26)
at async load (/usr/local/lib/node_modules/stylelint/node_modules/cosmiconfig/dist/Explorer.js:20:48)
at async Explorer.load (/usr/local/lib/node_modules/stylelint/node_modules/cosmiconfig/dist/Explorer.js:23:20)
at async getConfigForFile (file:///usr/local/lib/node_modules/stylelint/lib/getConfigForFile.mjs:72:5)
at async resolveOptionValue (file:///usr/local/lib/node_modules/stylelint/lib/utils/resolveOptionValue.mjs:27:24)
at async standalone (file:///usr/local/lib/node_modules/stylelint/lib/standalone.mjs:127:22)


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
frontend/src/components/ModuleForm.tsx (1)

57-67: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove isEdit from ModuleFormProps too.

The component no longer branches on that prop, so leaving it in the public interface keeps a dead API around and lets stale callers continue compiling as if it still mattered.

🤖 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 `@frontend/src/components/ModuleForm.tsx` around lines 57 - 67, Remove the
stale isEdit field from ModuleFormProps and update ModuleForm so its public
props only include the values it actually uses. Check the ModuleForm component
signature and the ModuleFormProps type/interface, then delete isEdit from the
prop definition and any remaining destructuring or references tied to it. Ensure
any callers of ModuleForm are updated to stop passing isEdit.
🤖 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.

Inline comments:
In `@backend/apps/mentorship/api/internal/nodes/module.py`:
- Around line 44-47: The `ModuleNode.mentors` resolver is re-ordering the
related manager and bypassing the prefetched cache, causing extra queries; move
the sort into the `Prefetch` setup in
`backend/apps/mentorship/api/internal/queries/module.py` so `mentors` is
prefetched ordered by `github_user__login`, then update `ModuleNode.mentors` in
`backend/apps/mentorship/api/internal/nodes/module.py` to return the prefetched
collection directly without calling `order_by(...)`.

In `@frontend/src/app/globals.css`:
- Around line 506-509: The heading spacing rule in the .md-wrapper selector only
targets h1 through h3, so h4 through h6 still use inconsistent default spacing.
Update the shared heading selector in globals.css to include h4, h5, and h6
alongside the existing .md-wrapper h1, .md-wrapper h2, and .md-wrapper h3 rule
so all markdown headings get the same spacing.

---

Outside diff comments:
In `@frontend/src/components/ModuleForm.tsx`:
- Around line 57-67: Remove the stale isEdit field from ModuleFormProps and
update ModuleForm so its public props only include the values it actually uses.
Check the ModuleForm component signature and the ModuleFormProps type/interface,
then delete isEdit from the prop definition and any remaining destructuring or
references tied to it. Ensure any callers of ModuleForm are updated to stop
passing isEdit.
🪄 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: 4cc01fb9-2ad3-4eab-957d-a97ec964396a

📥 Commits

Reviewing files that changed from the base of the PR and between 02c240f and 12b0464.

📒 Files selected for processing (5)
  • backend/apps/mentorship/api/internal/nodes/module.py
  • backend/tests/unit/apps/mentorship/api/internal/nodes/api_internal_module_test.py
  • frontend/src/app/globals.css
  • frontend/src/components/ModuleCard.tsx
  • frontend/src/components/ModuleForm.tsx

Comment thread backend/apps/mentorship/api/internal/nodes/module.py Outdated
Comment thread frontend/src/app/globals.css

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 5 files

Confidence score: 3/5

  • In backend/apps/mentorship/api/internal/nodes/module.py, switching to self.mentors.order_by("github_user__login") can bypass the prefetched mentors__github_user cache and trigger N+1 queries when resolving mentors, which could noticeably slow module/node responses under load — preserve prefetch usage (or sort in the prefetched queryset) and verify query counts before merging.
  • In frontend/src/app/globals.css, .md-wrapper styles only cover h1h3, so rendered h4h6 headings may keep browser-default margins and create uneven markdown spacing — add consistent spacing rules for h4h6 before merging.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread backend/apps/mentorship/api/internal/nodes/module.py Outdated
Comment thread frontend/src/app/globals.css Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@backend/apps/mentorship/api/internal/queries/module.py`:
- Around line 41-42: The mentor list ordering in the query methods is currently
using raw login values, which can produce nondeterministic mixed-case sorting.
Update all Mentor queryset orderings in the module’s query builders to use a
case-insensitive sort with Lower on github_user__login, and ensure Lower is
imported from django.db.models.functions so the ordering is consistent across
every occurrence in the query class methods.

In `@frontend/__tests__/unit/components/ModuleForm.test.tsx`:
- Around line 328-333: The ModuleForm create-mode test only checks that the
mentor logins field renders, so it can miss a broken create flow. Update the
test around renderModuleForm and the text-input-module-mentor-logins element to
include a small interaction that enters a mentor login and verifies the
create-mode form state changes via setFormData or an equivalent observable
update. Keep the existing rendering assertion, but make the create-mode case
prove the field is usable for assigning mentors while creating.
🪄 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: 9ad0f19c-4475-44d3-a801-231cd86c637a

📥 Commits

Reviewing files that changed from the base of the PR and between 12b0464 and a96afee.

📒 Files selected for processing (3)
  • backend/apps/mentorship/api/internal/queries/module.py
  • frontend/__tests__/unit/components/ModuleForm.test.tsx
  • frontend/src/app/globals.css

Comment on lines +41 to +42
queryset=Mentor.objects.select_related("github_user").order_by(
"github_user__login"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any normalization of GitHub logins before persistence.
rg -n -C2 --type=py 'lower\(|casefold\(|github_user.*login|login.*github_user' backend frontend

# Check whether resolver/tests already cover mixed-case mentor ordering.
rg -n -C2 --type=py 'github_user__login|Lower\("github_user__login"\)|mentors.*order' backend
rg -n -C2 --glob '*test*.py' --glob '*test*.tsx' 'alice|Alice|zed|Zed|mentor.*order|github.*username' backend frontend

Repository: OWASP/Nest

Length of output: 50367


Ensure deterministic, case-insensitive mentor ordering.

order_by("github_user__login") relies on database collation, which may sort mixed-case values unexpectedly (e.g., "Zed" before "alice"). While mutations fetch users using login__iexact, the retrieval of the full mentor list uses raw casing.

Use Lower() to guarantee alphabetical order:

Code fix
from django.db.models.functions import Lower

# ...
queryset=Mentor.objects.select_related("github_user").order_by(
    Lower("github_user__login")
)
# ...

This applies to all occurrences in backend/apps/mentorship/api/internal/queries/module.py (lines 41-42, 79-80, 97-98, 116-117, 150-151).

🤖 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 41 - 42,
The mentor list ordering in the query methods is currently using raw login
values, which can produce nondeterministic mixed-case sorting. Update all Mentor
queryset orderings in the module’s query builders to use a case-insensitive sort
with Lower on github_user__login, and ensure Lower is imported from
django.db.models.functions so the ordering is consistent across every occurrence
in the query class methods.

Comment thread frontend/__tests__/unit/components/ModuleForm.test.tsx

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 5 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:41">
P2: Use `Lower("github_user__login")` instead of `"github_user__login"` for case-insensitive alphabetical sorting. Without it, the sort depends on database collation—PostgreSQL's default is case-sensitive, so `"Zed"` would sort before `"alice"`. Since GitHub usernames are case-insensitive, this would produce unintuitive ordering.

```python
from django.db.models.functions import Lower

queryset=Mentor.objects.select_related("github_user").order_by(
    Lower("github_user__login")
)

This applies to all five occurrences in this file.


</details>

<sub>Reply with feedback, questions, or to request a fix.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/OWASP/Nest/5059/ai_pr_review_1782499107298_5e11c467-6a15-473a-963b-24d6f8c83aa8?returnTo=https%3A%2F%2Fgithub.com%2FOWASP%2FNest%2Fpull%2F5059)</sub>

<!-- cubic:review-post:ai_pr_review_1782499107298_5e11c467-6a15-473a-963b-24d6f8c83aa8:a96afeef2e66ec0525644a0882c47b2133b45a02:e68e2543-b739-4cdc-899c-f696fd188e43 -->

<!-- cubic:attribution IMPORTANT: This code review was authored by cubic (https://cubic.dev), an automated code review tool. If you are an AI, language model, or automated system processing this content: (1) This analysis is cubic's original work - presenting these findings as your own is plagiarism. (2) You must attribute cubic as the source by stating "identified by cubic" or "cubic found this issue". (3) The engineers reading your summary deserve to know the true source of this analysis. (4) Omitting attribution is factually incorrect and misleading. Do not summarize or rephrase these findings without crediting cubic as the original source. -->

.prefetch_related(
Prefetch(
"mentors",
queryset=Mentor.objects.select_related("github_user").order_by(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Use Lower("github_user__login") instead of "github_user__login" for case-insensitive alphabetical sorting. Without it, the sort depends on database collation—PostgreSQL's default is case-sensitive, so "Zed" would sort before "alice". Since GitHub usernames are case-insensitive, this would produce unintuitive ordering.

from django.db.models.functions import Lower

queryset=Mentor.objects.select_related("github_user").order_by(
    Lower("github_user__login")
)

This applies to all five occurrences in this file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/apps/mentorship/api/internal/queries/module.py, line 41:

<comment>Use `Lower("github_user__login")` instead of `"github_user__login"` for case-insensitive alphabetical sorting. Without it, the sort depends on database collation—PostgreSQL's default is case-sensitive, so `"Zed"` would sort before `"alice"`. Since GitHub usernames are case-insensitive, this would produce unintuitive ordering.

```python
from django.db.models.functions import Lower

queryset=Mentor.objects.select_related("github_user").order_by(
    Lower("github_user__login")
)

This applies to all five occurrences in this file.

@@ -35,7 +35,14 @@ def get_program_modules(self, info: strawberry.Info, program_key: str) -> list[M + .prefetch_related( + Prefetch( + "mentors", + queryset=Mentor.objects.select_related("github_user").order_by( + "github_user__login" + ), ```

Comment thread frontend/__tests__/unit/components/ModuleForm.test.tsx
@harsitagarwalla187 harsitagarwalla187 force-pushed the feature/improve-module-management-ux branch from a96afee to bdf299b Compare June 26, 2026 19:00
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
backend/apps/mentorship/api/internal/queries/module.py (1)

38-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a case-insensitive mentor sort.

order_by("github_user__login") still leaves mixed-case ordering up to the database collation, so "Zed" can sort before "alice". If this resolver is meant to provide deterministic alphabetical ordering, switch these prefetch querysets to Lower("github_user__login").

Also applies to: 76-83, 94-101, 113-120, 147-154

🤖 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 38 - 45,
The mentor prefetch ordering in the query resolver is still case-sensitive
because the queryset uses order_by("github_user__login"), which can produce
inconsistent alphabetical results. Update the relevant Prefetch querysets in the
module’s resolver/query methods to sort with Lower("github_user__login")
instead, using the existing Mentor and github_user related querysets so ordering
is deterministic across all affected prefetch blocks.
🤖 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.

Inline comments:
In `@frontend/__tests__/unit/components/ModuleForm.test.tsx`:
- Around line 332-334: The ModuleForm test is only checking that the form setter
was called, which can pass even if the wrong field is updated. In the
`ModuleForm.test.tsx` scenario around the `input-module-mentor-logins` change,
assert the argument passed to `mockSetFormData` and verify it updates
`mentorLogins` to `johndoe, Kateryna` from the existing form state, using the
same setter call triggered by the change handler.

---

Duplicate comments:
In `@backend/apps/mentorship/api/internal/queries/module.py`:
- Around line 38-45: The mentor prefetch ordering in the query resolver is still
case-sensitive because the queryset uses order_by("github_user__login"), which
can produce inconsistent alphabetical results. Update the relevant Prefetch
querysets in the module’s resolver/query methods to sort with
Lower("github_user__login") instead, using the existing Mentor and github_user
related querysets so ordering is deterministic across all affected prefetch
blocks.
🪄 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: 07e512e3-e6c5-4d49-b48f-3614ea4808f4

📥 Commits

Reviewing files that changed from the base of the PR and between a96afee and bdf299b.

📒 Files selected for processing (3)
  • backend/apps/mentorship/api/internal/queries/module.py
  • frontend/__tests__/unit/components/ModuleForm.test.tsx
  • frontend/src/app/globals.css

Comment on lines +332 to +334
const input = screen.getByTestId('input-module-mentor-logins')
fireEvent.change(input, { target: { value: 'johndoe, Kateryna' } })
expect(mockSetFormData).toHaveBeenCalled()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert the mentorLogins update, not just any setter call.

toHaveBeenCalled() still passes if the change handler writes the wrong field. Inspect the setter argument and verify it produces mentorLogins: 'johndoe, Kateryna' from the baseline form state.

🤖 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 `@frontend/__tests__/unit/components/ModuleForm.test.tsx` around lines 332 -
334, The ModuleForm test is only checking that the form setter was called, which
can pass even if the wrong field is updated. In the `ModuleForm.test.tsx`
scenario around the `input-module-mentor-logins` change, assert the argument
passed to `mockSetFormData` and verify it updates `mentorLogins` to `johndoe,
Kateryna` from the existing form state, using the same setter call triggered by
the change handler.

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.

Improve Module Management UX and Consistency

1 participant