Skip to content

refactor: extract shared ContributorsList component#3408

Merged
kasya merged 6 commits intoOWASP:mainfrom
SuyashJain17:refactor/contributors-list
Jan 21, 2026
Merged

refactor: extract shared ContributorsList component#3408
kasya merged 6 commits intoOWASP:mainfrom
SuyashJain17:refactor/contributors-list

Conversation

@SuyashJain17
Copy link
Contributor

Proposed change

Resolves #3321

This PR refactors the contributor list components by extracting the shared logic from
TopContributorsList and MenteeContributorsList into a reusable ContributorsList component.

The new component centralizes the grid layout, contributor card rendering, and show more / less
toggle behavior, while allowing different profile URL strategies via a configurable
getUrl prop. Both existing components have been converted into thin wrappers that preserve
their original public APIs and behavior.

This change reduces code duplication and improves maintainability without introducing any
visual or functional changes.

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 Jan 18, 2026

Summary by CodeRabbit

  • Refactor
    • Unified several contributor list variants into a single, consistent component used across the site, standardizing labels and display behavior.
  • New Features
    • Contributor entries now support a configurable URL resolver so profile links work correctly for members and mentees.
  • Bug Fixes
    • Avatar image URLs preserve existing query parameters to ensure correct sizing and display.

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

Walkthrough

Consolidates duplicated contributor components into a single ContributorsList component (new ContributorsListProps with getUrl), removes MenteeContributorsList, adds getMenteeUrl utility, and updates all consumers and tests to use the unified API and new URL prop.

Changes

Cohort / File(s) Summary
Core component
frontend/src/components/ContributorsList.tsx, frontend/src/components/MenteeContributorsList.tsx
Introduced ContributorsList and ContributorsListProps (adds getUrl), renamed/reworked TopContributorsListContributorsList, changed empty render to null, preserved avatar query params when appending size, removed MenteeContributorsList.tsx.
Consumers / Pages
frontend/src/app/about/page.tsx, frontend/src/app/page.tsx, frontend/src/components/CardDetailsPage.tsx, frontend/src/components/SingleModuleCard.tsx
Replaced usages of TopContributorsList/MenteeContributorsList with ContributorsList; wired getUrl prop to getMemberUrl or getMenteeUrl; added explicit label where needed.
URL utilities
frontend/src/utils/urlFormatter.ts
Added exported getMenteeUrl(programKey, moduleKey, login): string and continued use of getMemberUrl/getProjectUrl.
Tests
frontend/__tests__/a11y/components/ContributorsList.a11y.test.tsx, frontend/__tests__/unit/components/ContributorsList.test.tsx, frontend/__tests__/unit/components/CardDetailsPage.test.tsx, frontend/__tests__/unit/components/SingleModuleCard.test.tsx
Updated imports/mocks and expectations to ContributorsList, added getUrl to mock signatures, changed data-testid from top-contributors-listcontributors-list, and adjusted default label expectations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • arkid15r
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: extract shared ContributorsList component' clearly and concisely summarizes the main change: extracting shared logic from duplicate components into a reusable ContributorsList component.
Description check ✅ Passed The description is directly related to the changeset, explaining the refactoring of contributor list components by extracting shared logic and reducing duplication through a reusable ContributorsList component.
Linked Issues check ✅ Passed The PR successfully addresses issue #3321 by extracting shared logic from TopContributorsList and MenteeContributorsList into a reusable ContributorsList component with configurable getUrl prop, reducing code duplication while preserving existing behavior.
Out of Scope Changes check ✅ Passed All changes are in-scope and directly related to the refactoring objective: component extraction, updated imports, test modifications, and URL helper additions are all necessary for consolidating duplicate components into a single reusable component.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

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

🤖 Fix all issues with AI agents
In `@frontend/src/components/ContributorsList.tsx`:
- Around line 54-61: The avatar URL concatenation in ContributorsList.tsx uses
`${item?.avatarUrl}&s=60` which breaks when item?.avatarUrl has no query string;
update the Image src construction (where item?.avatarUrl is used) to append the
size parameter correctly by detecting if the URL already contains '?' and using
'?' or '&' accordingly (or use the URL/URLSearchParams APIs to add s=60) so the
final src is valid in both cases.
- Line 73: The ShowMoreButton is out of sync with the parent because it uses its
own internal isExpanded state; update the usage in ContributorsList to pass the
parent's showAllContributors to ShowMoreButton (i.e., add prop
isExpanded={showAllContributors} where ShowMoreButton is rendered alongside the
existing onToggle={toggleContributors}), and modify the ShowMoreButton component
to accept an isExpanded prop and derive its label/display from that prop instead
of local state (remove or fallback internal state so the label always reflects
the parent's showAllContributors controlled value).
🧹 Nitpick comments (1)
frontend/src/components/MenteeContributorsList.tsx (1)

22-23: Consider memoizing getMenteeUrl to prevent unnecessary re-renders.

The getMenteeUrl function is recreated on every render. While this is unlikely to cause performance issues in practice, wrapping it with useCallback would be more idiomatic and prevent ContributorsList from potentially re-rendering if it uses reference equality checks.

♻️ Optional optimization
+import { useCallback } from 'react'
 import type { IconType } from 'react-icons'
 import type { Contributor } from 'types/contributor'
 import ContributorsList from 'components/ContributorsList'

 // ... interface definition ...

 const MenteeContributorsList = ({
   contributors,
   label = 'Mentees',
   maxInitialDisplay = 12,
   icon,
   programKey,
   moduleKey,
 }: MenteeContributorsListProps) => {
-  const getMenteeUrl = (login: string) =>
-    `/my/mentorship/programs/${programKey}/modules/${moduleKey}/mentees/${login}`
+  const getMenteeUrl = useCallback(
+    (login: string) =>
+      `/my/mentorship/programs/${programKey}/modules/${moduleKey}/mentees/${login}`,
+    [programKey, moduleKey]
+  )

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 18, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 21, 2026
@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
frontend/__tests__/unit/components/ContributorsList.test.tsx (1)

503-518: The test correctly validates current behavior, but the component has a bug handling empty avatarUrl.

The component at line 58 produces src="?s=60" when avatarUrl is empty, which is exactly what the test expects. However, this is not graceful handling—?s=60 is not a valid image URL and will fail to load, resulting in a broken image rather than falling back to a placeholder. The component should either use a default avatar URL or handle empty URLs differently instead of producing invalid image URLs.

🧹 Nitpick comments (1)
frontend/src/components/CardDetailsPage.tsx (1)

334-342: Consider adding a stricter guard for URL generation.

The empty string fallbacks (programKey || '', entityKey || '') could produce malformed URLs if either key is undefined. While the current guard ensures mentees exist and contextually these keys should be present for module types, a stricter condition would be more defensive:

-        {mentees && mentees.length > 0 && (
+        {mentees && mentees.length > 0 && programKey && entityKey && (

This ensures the URL is only generated when all required parameters are available.

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

@SuyashJain17 thanks for working on this 👍🏼

I pushed a little change to just have one ContributorsList component since the wrappers would just generate urls. Moved the logic for generating url into urlFormatter for consistency - we already had other url builders there too 👌🏼

@kasya kasya added this pull request to the merge queue Jan 21, 2026
Merged via the queue into OWASP:main with commit 9c0de8b Jan 21, 2026
28 of 29 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 12, 2026
4 tasks
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.

Duplicate contributor list components in frontend

2 participants