Skip to content

Conversation

@iakgoog
Copy link

@iakgoog iakgoog commented Oct 26, 2025

Proposed change

Fix TypeScript rule S2004 violations by reducing functions with more than 4 levels of nesting:

Resolves #2487

Add the PR description here.

  • Extract test utility functions in MultiSearch.test.tsx (11 violations)
  • Simplify nested patterns in ProjectTypeDashboardCard.test.tsx (1 violation)
  • Refactor setTimeout/waitFor nesting in Home.test.tsx (1 violation)
  • Extract MockDropdownSection component in ProjectsHealthDashboardMetrics.test.tsx (1 violation)
  • Add extractBadgeTestIds utility in UserDetails.test.tsx (1 violation)
  • Create utility functions and badge components in board candidates page (6 violations)

Improves code readability, maintainability, and reduces cognitive load for developers. All functionality preserved with zero regression.

Checklist

  • I've read and followed the contributing guidelines.
  • I've run make check-test locally; all checks and tests passed.

Fix TypeScript rule S2004 violations by reducing functions with more than 4 levels of nesting:

- Extract test utility functions in MultiSearch.test.tsx (11 violations)
- Simplify nested patterns in ProjectTypeDashboardCard.test.tsx (1 violation)
- Refactor setTimeout/waitFor nesting in Home.test.tsx (1 violation)
- Extract MockDropdownSection component in ProjectsHealthDashboardMetrics.test.tsx (1 violation)
- Add extractBadgeTestIds utility in UserDetails.test.tsx (1 violation)
- Create utility functions and badge components in board candidates page (6 violations)

Improves code readability, maintainability, and reduces cognitive load for developers.
All functionality preserved with zero regression.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

Summary by CodeRabbit

  • Tests

    • Enhanced test suite with utility helpers for improved maintainability and coverage standardization across multiple components and pages.
    • Added deterministic date mocking and component mocks for isolated testing of complex UI elements.
  • Refactor

    • Reorganized BoardCandidatesPage with reusable badge components and centralized data-fetching helpers for improved code consistency.

Walkthrough

Introduces test utility helpers and component/component mocks across multiple test suites and refactors the board candidates page to extract data-fetching helpers and presentational Badge components, consolidating rendering and sorting logic.

Changes

Cohort / File(s) Summary
MultiSearch & test helpers
frontend/__tests__/unit/components/MultiSearch.test.tsx
Adds test utilities (createUserWithSetup, renderMultiSearchWithDefaults, expectSuggestionsVisible, expectListItemHighlighted, getInputElement, expectNoSuggestions, expectSuggestionsForEach) and refactors tests to use them.
Test helpers used elsewhere
frontend/__tests__/unit/pages/UserDetails.test.tsx, frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx
Adds/uses small test helpers (extractBadgeTestIds, MockDropdownSection) to replace inline mappings and section rendering in tests.
Test mocks & deterministic setup
frontend/__tests__/unit/pages/Home.test.tsx, frontend/__tests__/unit/pages/ProjectDetails.test.tsx, frontend/__tests__/unit/components/ProgramCard.test.tsx
Adds mocks for AnimatedCounter, ChapterMap, ContributionHeatmap, LineChart, BarChart; moves/mocks millify import usage; adds Date.prototype.toLocaleDateString mock in ProgramCard tests; minor formatting adjustment in ProjectTypeDashboardCard test.
Mock data
frontend/__tests__/unit/data/mockProjectDetailsData.ts
Adds createdAt: '2024-01-01T00:00:00Z' to first item in healthMetricsList.
Board candidates page refactor
frontend/src/app/board/[year]/candidates/page.tsx
Extracts fetch helpers (fetchChapterData, fetchProjectData), sorting (sortItemsByName), contribution-count and URL builders; introduces presentational components (ChapterBadge, ProjectBadge, RepositoryBadge, ChannelBadge); replaces inline fetch/render with component-driven rendering and consistent sorting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus review on:
    • frontend/src/app/board/[year]/candidates/page.tsx — verify data fetching, sorting, key handling, and that presentational components preserve previous behavior and error handling.
    • Tests that now rely on shared helpers and mocks (MultiSearch, Home, ProjectDetails) — ensure assertions remain equivalent and mocks don’t mask regressions.
    • Updated mock data (createdAt) to confirm alignment with real data expectations.

Possibly related PRs

Suggested reviewers

  • arkid15r
  • kasya

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "refactor: frontend functions to reduce deep nesting violations" directly and clearly describes the primary objective of the changeset. The title accurately summarizes the main work: extracting utility functions, simplifying nested patterns, and creating helper components to address TypeScript rule S2004 violations across test files and the board candidates page. The title is specific, concise, and immediately conveys the nature of the refactoring to someone reviewing the project history.
Linked Issues Check ✅ Passed The changes in this pull request align with the requirements from linked issue #2487. The modifications systematically address the core objective of reducing functions with more than 4 levels of nesting by extracting test utility functions (MultiSearch.test.tsx with 11 violations addressed), simplifying nested patterns (ProjectTypeDashboardCard.test.tsx), refactoring nested control flow (Home.test.tsx, ProjectsHealthDashboardMetrics.test.tsx), creating utility components (board candidates page with 6 violations), and adding helper utilities (UserDetails.test.tsx, ProgramCard.test.tsx, ProjectDetails.test.tsx). The specific techniques mentioned in the issue—breaking down complex operations into smaller functions, extracting nested logic, and creating named utilities—are all demonstrated across the changeset, with improvements to both test and application code as appropriate.
Out of Scope Changes Check ✅ Passed The vast majority of changes are directly aligned with the stated objective of addressing TypeScript rule S2004 violations by reducing deep nesting. Test files have been refactored with extracted utilities and helper functions, and the board candidates page has been refactored with helper functions and presentational components to reduce nesting in the main page logic. One minor addition (createdAt field in mockProjectDetailsData.ts) appears somewhat tangential to the nesting reduction goal, but it likely serves as supporting data for the refactored tests and does not introduce significant out-of-scope work. All changes serve the primary objective of improving code structure by reducing cognitive load through nesting reduction.
Description Check ✅ Passed The pull request description is clearly related to the changeset and provides meaningful context. It explicitly references issue #2487, lists specific files modified with violation counts, describes the general approach taken (extracting utilities, simplifying patterns, creating components), and claims that functionality is preserved with zero regression. The description goes beyond generic statements and directly connects the changes to the goal of reducing deep nesting violations, making it substantive and relevant.
✨ 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: 3

Caution

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

⚠️ Outside diff range comments (2)
frontend/__tests__/unit/pages/Home.test.tsx (1)

297-316: Fix unreliable test pattern with setTimeout inside waitFor.

The setTimeout at line 311 creates assertions that won't be properly awaited by the test framework. The assertions inside the setTimeout callback will execute after 2 seconds, but waitFor won't wait for them, potentially causing false positives or flaky tests.

If the intent is to wait for AnimatedCounter animations, consider one of these approaches:

Option 1: Remove setTimeout and verify counter values immediately

-    // Check that AnimatedCounter components are rendered (they should show some numeric values)
     await waitFor(() => {
-      // Look for any numeric content that indicates the counters are working
       const statsSection = screen.getByText('Active Projects').closest('section') || document.body
-      expect(statsSection).toBeInTheDocument();
-
-      setTimeout(() => {
-        Object.values(stats).forEach((value) =>
-          expect(screen.getByText(`${millify(value)}+`)).toBeInTheDocument()
-        )
-      }, 2000)
+      expect(statsSection).toBeInTheDocument()
+      
+      // Verify the formatted counter values are rendered
+      expect(screen.getByText('90')).toBeInTheDocument() // activeProjectsStats
+      expect(screen.getByText('250')).toBeInTheDocument() // activeChaptersStats
+      expect(screen.getByText('9.6k')).toBeInTheDocument() // contributorsStats
     })

Option 2: If animation delay is required, use nested waitFor

     await waitFor(() => {
       const statsSection = screen.getByText('Active Projects').closest('section') || document.body
-      expect(statsSection).toBeInTheDocument();
-
-      setTimeout(() => {
-        Object.values(stats).forEach((value) =>
-          expect(screen.getByText(`${millify(value)}+`)).toBeInTheDocument()
-        )
-      }, 2000)
+      expect(statsSection).toBeInTheDocument()
     })
+    
+    // Wait for animated values to appear
+    await waitFor(() => {
+      Object.values(stats).forEach((value) =>
+        expect(screen.getByText(`${millify(value)}+`)).toBeInTheDocument()
+      )
+    }, { timeout: 3000 })
frontend/src/app/board/[year]/candidates/page.tsx (1)

586-589: Copy nit: “lead by” → “led by”.

-                  No chapters or projects are lead by this candidate
+                  No chapters or projects are led by this candidate
🧹 Nitpick comments (6)
frontend/__tests__/unit/components/ProgramCard.test.tsx (1)

44-55: Optimize timestamp comparisons by computing once.

The mock creates new Date objects on every call to compare timestamps, which is inefficient when the mock is invoked multiple times during test execution.

Consider computing the target timestamps once outside the mock implementation:

   beforeEach(() => {
     jest.clearAllMocks()
+    const timestamp1 = new Date('2024-01-01T00:00:00Z').getTime()
+    const timestamp2 = new Date('2024-12-31T23:59:59Z').getTime()
+
     // Mock toLocaleDateString to ensure consistent formatting
     jest.spyOn(Date.prototype, 'toLocaleDateString').mockImplementation(function(this: Date, locale, options) {
-      if (this.getTime() === new Date('2024-01-01T00:00:00Z').getTime()) {
+      if (this.getTime() === timestamp1) {
         return 'Jan 1, 2024'
       }
-      if (this.getTime() === new Date('2024-12-31T23:59:59Z').getTime()) {
+      if (this.getTime() === timestamp2) {
         return 'Dec 31, 2024'
       }
       // Call the original implementation for other dates
       return new Intl.DateTimeFormat(locale, options).format(this)
     })
   })
frontend/src/app/board/[year]/candidates/page.tsx (5)

2-4: Unify Apollo imports and use type-only imports to reduce bundle size.

Import hooks from '@apollo/client' and import ApolloClient/React as types only.

-import { useQuery, useApolloClient } from '@apollo/client/react'
-import { ApolloClient } from '@apollo/client';
+import { useQuery, useApolloClient } from '@apollo/client'
+import type { ApolloClient } from '@apollo/client';
-import React from 'react'
+import type React from 'react'

Also applies to: 19-19


345-353: Parallelize chapter fetches, dedupe keys, and guard against race on unmount.

-        const chapterKeys = Object.keys(snapshot.chapterContributions)
-        const chapters: Chapter[] = []
-
-        for (const key of chapterKeys) {
-          const chapter = await fetchChapterData(client, key)
-          if (chapter) {
-            chapters.push(chapter)
-          }
-        }
-
-        setLedChapters(sortItemsByName(chapters))
+        const rawKeys = Object.keys(snapshot.chapterContributions)
+        const bareKeys = Array.from(
+          new Set(rawKeys.map(k => k.startsWith('www-chapter-') ? k.replace('www-chapter-', '') : k)),
+        )
+        let cancelled = false
+        const results = await Promise.all(bareKeys.map(k => fetchChapterData(client, k)))
+        if (cancelled) return
+        setLedChapters(sortItemsByName(results.filter(Boolean) as Chapter[]))

Also add a cleanup to the effect:

-      fetchChapters()
+      let cancelled = false
+      fetchChapters()
+      return () => { cancelled = true }

366-374: Do the same for projects: parallelize, dedupe, and cancel on unmount.

-        const projectKeys = Object.keys(snapshot.projectContributions)
-        const projects: Project[] = []
-
-        for (const key of projectKeys) {
-          const project = await fetchProjectData(client, key)
-          if (project) {
-            projects.push(project)
-          }
-        }
-
-        setLedProjects(sortItemsByName(projects))
+        const rawKeys = Object.keys(snapshot.projectContributions)
+        const bareKeys = Array.from(
+          new Set(rawKeys.map(k => k.startsWith('www-project-') ? k.replace('www-project-', '') : k)),
+        )
+        let cancelled = false
+        const results = await Promise.all(bareKeys.map(k => fetchProjectData(client, k)))
+        if (cancelled) return
+        setLedProjects(sortItemsByName(results.filter(Boolean) as Project[]))

And add the cleanup:

-      fetchProjects()
+      let cancelled = false
+      fetchProjects()
+      return () => { cancelled = true }

100-101: Remove redundant key on inner Links inside badge components.

Keys belong on the array elements where badges are mapped, not inside the badge internals.

-      <Link
-      key={chapter.id}
+      <Link
-      <Link
-      key={project.id}
+      <Link

Also applies to: 139-140


413-421: Simplify LinkedIn anchor: stop propagation only; let the anchor open.

preventDefault + window.open duplicates default behavior with target="_blank".

-                  onClick={(e) => {
-                    e.stopPropagation()
-                    e.preventDefault()
-                    window.open(
-                      `https://linkedin.com/in/${candidate.member.linkedinPageId}`,
-                      '_blank',
-                      'noopener,noreferrer'
-                    )
-                  }}
+                  onClick={(e) => e.stopPropagation()}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 294c3f4 and bf42b8c.

📒 Files selected for processing (9)
  • frontend/__tests__/unit/components/MultiSearch.test.tsx (5 hunks)
  • frontend/__tests__/unit/components/ProgramCard.test.tsx (2 hunks)
  • frontend/__tests__/unit/components/ProjectTypeDashboardCard.test.tsx (1 hunks)
  • frontend/__tests__/unit/data/mockProjectDetailsData.ts (1 hunks)
  • frontend/__tests__/unit/pages/Home.test.tsx (4 hunks)
  • frontend/__tests__/unit/pages/ProjectDetails.test.tsx (1 hunks)
  • frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx (1 hunks)
  • frontend/__tests__/unit/pages/UserDetails.test.tsx (2 hunks)
  • frontend/src/app/board/[year]/candidates/page.tsx (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/__tests__/unit/pages/Home.test.tsx (1)
frontend/__tests__/unit/data/mockHomeData.ts (1)
  • mockGraphQLData (1-158)
frontend/src/app/board/[year]/candidates/page.tsx (4)
frontend/src/types/__generated__/boardQueries.generated.ts (2)
  • GetChapterByKeyDocument (35-35)
  • GetProjectByKeyDocument (36-36)
backend/apps/owasp/api/internal/queries/snapshot.py (1)
  • snapshot (14-22)
backend/apps/owasp/models/member_snapshot.py (1)
  • MemberSnapshot (11-132)
backend/apps/owasp/api/internal/queries/chapter.py (1)
  • chapter (14-19)
🔇 Additional comments (15)
frontend/__tests__/unit/components/ProjectTypeDashboardCard.test.tsx (1)

356-368: Effective nesting reduction via extracted callback function.

The refactoring at line 364 successfully reduces nesting by extracting the inline forEach callback into a named testTypeValue function. The .not.toThrow() assertion correctly captures the intent that rendering with valid types must not throw, improving both readability and compliance with the 4-level nesting limit.

frontend/__tests__/unit/components/ProgramCard.test.tsx (3)

57-59: LGTM!

Properly restores mocks after each test to ensure test isolation.


237-237: LGTM!

Comment appropriately clarifies that the exact string match is possible due to the mocked date formatting.


44-55: Clarify how this change addresses the PR objective.

The PR objective states: "Fix TypeScript rule S2004 violations by reducing functions with more than 4 levels of nesting." However, the changes in this file introduce date mocking infrastructure rather than reducing existing deep nesting. Additionally, ProgramCard.test.tsx is not mentioned in the PR summary's list of files with violations addressed.

Could you clarify:

  1. Does this file have a deep nesting violation that this change addresses?
  2. Or is this change included as a related test infrastructure improvement?
frontend/__tests__/unit/data/mockProjectDetailsData.ts (1)

28-28: LGTM!

The addition of the createdAt field to the health metrics mock data is appropriate and supports deterministic testing of health-related UI components.

frontend/__tests__/unit/pages/ProjectDetails.test.tsx (1)

30-50: LGTM!

The addition of mocks for LineChart, BarChart, and environment configuration appropriately isolates the ProjectDetailsPage component during testing, enabling tests to verify health metrics UI without depending on real chart implementations.

frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx (1)

19-29: Well-structured extraction!

The MockDropdownSection test utility component effectively reduces nesting depth by encapsulating the section rendering logic. The component is focused, reusable, and maintains the original test behavior.

frontend/__tests__/unit/pages/UserDetails.test.tsx (1)

82-84: LGTM!

The extractBadgeTestIds utility function appropriately reduces nesting depth and improves test readability by extracting the badge test ID mapping logic.

frontend/__tests__/unit/pages/Home.test.tsx (2)

38-54: LGTM!

The AnimatedCounter mock appropriately handles edge cases (undefined/null counts) and provides simple number formatting for test scenarios. The logic is clear and suitable for test isolation.


87-93: LGTM!

The ChapterMap and ContributionHeatmap mocks appropriately isolate these components during testing with simple placeholder implementations.

frontend/__tests__/unit/components/MultiSearch.test.tsx (4)

86-123: Excellent test utility extraction!

The test helper functions effectively reduce nesting depth and significantly improve test readability:

  • createUserWithSetup, renderMultiSearchWithDefaults, and getInputElement standardize setup and element access
  • expectSuggestionsVisible, expectListItemHighlighted, expectNoSuggestions, and expectSuggestionsForEach provide focused assertion helpers

Each utility is well-named, type-safe, and serves a single purpose. This is exemplary refactoring that directly addresses the PR objectives.


346-359: LGTM!

The refactored test effectively uses the new utility functions (createUserWithSetup, renderMultiSearchWithDefaults, getInputElement, expectSuggestionsForEach) to reduce nesting depth while maintaining clear test logic.


378-437: LGTM!

The keyboard navigation tests are well-refactored using the utility functions. The use of createUserWithSetup, renderMultiSearchWithDefaults, getInputElement, expectListItemHighlighted, expectSuggestionsVisible, and expectNoSuggestions makes the tests more readable and reduces nesting depth effectively.


439-453: LGTM!

The Enter key test is cleanly refactored using the utility functions, reducing nesting while maintaining clear test intent.

frontend/src/app/board/[year]/candidates/page.tsx (1)

80-80: Verify Slack URL expects channel ID, not name.

Slack “archives/…” links typically require channel IDs (e.g., C123…), not human-readable names. If channelCommunications uses names, links may 404. Consider storing/deriving channel IDs or mapping names→IDs before rendering.

Would you confirm what snapshot.channelCommunications keys contain (ID vs name)? If names, I can propose a safe mapping strategy.

Also applies to: 223-235

Comment on lines 604 to +720
{sortedChannels.slice(1).map(([channelName, messageCount]) => (
<a
<ChannelBadge
key={channelName}
href={`https://owasp.slack.com/archives/${channelName}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-blue-700/10 ring-inset hover:bg-blue-100 dark:bg-blue-900/20 dark:text-blue-400 dark:ring-blue-400/30 dark:hover:bg-blue-900/30"
onClick={(e) => e.stopPropagation()}
>
<span>#{channelName}</span>
<span className="rounded bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold text-blue-800 dark:bg-blue-800/40 dark:text-blue-300">
{Number(messageCount)} messages
</span>
</a>
channelName={channelName}
messageCount={Number(messageCount)}
onStopPropagation={(e) => e.stopPropagation()}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Limit to 5 channels to match the section title.

-                          <ChannelBadge
+                          <ChannelBadge
                             key={channelName}
                             channelName={channelName}
                             messageCount={Number(messageCount)}
                             onStopPropagation={(e) => e.stopPropagation()}
                           />

And slice only the next 4:

-                        {sortedChannels.slice(1).map(([channelName, messageCount]) => (
+                        {sortedChannels.slice(1, 5).map(([channelName, messageCount]) => (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{sortedChannels.slice(1).map(([channelName, messageCount]) => (
<a
<ChannelBadge
key={channelName}
href={`https://owasp.slack.com/archives/${channelName}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700 ring-1 ring-blue-700/10 ring-inset hover:bg-blue-100 dark:bg-blue-900/20 dark:text-blue-400 dark:ring-blue-400/30 dark:hover:bg-blue-900/30"
onClick={(e) => e.stopPropagation()}
>
<span>#{channelName}</span>
<span className="rounded bg-blue-100 px-1.5 py-0.5 text-[10px] font-semibold text-blue-800 dark:bg-blue-800/40 dark:text-blue-300">
{Number(messageCount)} messages
</span>
</a>
channelName={channelName}
messageCount={Number(messageCount)}
onStopPropagation={(e) => e.stopPropagation()}
/>
{sortedChannels.slice(1, 5).map(([channelName, messageCount]) => (
<ChannelBadge
key={channelName}
channelName={channelName}
messageCount={Number(messageCount)}
onStopPropagation={(e) => e.stopPropagation()}
/>
🤖 Prompt for AI Agents
In frontend/src/app/board/[year]/candidates/page.tsx around lines 714 to 720,
the code maps over sortedChannels.slice(1) which renders all channels after the
first; change the slice to only take the next four channels (slice(1, 5)) so the
UI displays a total of five channels to match the section title.

@iakgoog
Copy link
Author

iakgoog commented Oct 26, 2025

@coderabbitai generate docstrings

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

Caution

Docstrings generation - FAILED

No docstrings were generated.

@sonarqubecloud
Copy link

@iakgoog
Copy link
Author

iakgoog commented Oct 26, 2025

@coderabbitai generate docstrings

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

✅ Actions performed

Initiated docstring generation; will generate only if new commits exist.

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/src/app/board/[year]/candidates/page.tsx (1)

586-589: Minor copy: “led by”, not “lead by”.

-                  No chapters or projects are lead by this candidate
+                  No chapters or projects are led by this candidate
♻️ Duplicate comments (1)
frontend/src/app/board/[year]/candidates/page.tsx (1)

704-720: Limit channels to 5 to match the header.

Same rationale as repos; cap to next 4 after the top.

-                        {sortedChannels.slice(1).map(([channelName, messageCount]) => (
+                        {sortedChannels.slice(1, 5).map(([channelName, messageCount]) => (
🧹 Nitpick comments (7)
frontend/src/app/board/[year]/candidates/page.tsx (7)

2-4: Unify Apollo imports; use type-only for types to avoid extra bundle.

Avoid mixing @apollo/client/react and @apollo/client. Import hooks from the root and import types as type-only.

-import { useQuery, useApolloClient } from '@apollo/client/react'
-import { ApolloClient } from '@apollo/client';
+import { useQuery, useApolloClient } from '@apollo/client'
+import type { ApolloClient, NormalizedCacheObject } from '@apollo/client'

Update function signatures below to use ApolloClient<NormalizedCacheObject>.


19-19: Make React a type-only import (new JSX transform).

No runtime React usage; keep types only.

-import React from 'react'
+import type React from 'react'

36-46: Tighten client typing; avoid any.

Use the cache type to improve safety.

-const fetchChapterData = async (client: ApolloClient<any>, key: string) => {
+const fetchChapterData = async (client: ApolloClient<NormalizedCacheObject>, key: string) => {

48-58: Same here: precise typing for project fetch.

-const fetchProjectData = async (client: ApolloClient<any>, key: string) => {
+const fetchProjectData = async (client: ApolloClient<NormalizedCacheObject>, key: string) => {

304-304: Specify radix for parseInt (or use Number).

Prevents accidental octal parsing.

-    variables: { year: Number.parseInt(year) },
+    variables: { year: Number.parseInt(year, 10) },

343-353: Parallelize chapter fetches and de‑dup by id (perf + correctness).

Current loop fires sequential requests and can duplicate when both bare/prefixed keys appear. Fetch in parallel and de‑dup before sorting.

-        const chapterKeys = Object.keys(snapshot.chapterContributions)
-        const chapters: Chapter[] = []
-
-        for (const key of chapterKeys) {
-          const chapter = await fetchChapterData(client, key)
-          if (chapter) {
-            chapters.push(chapter)
-          }
-        }
-
-        setLedChapters(sortItemsByName(chapters))
+        const chapterKeys = Object.keys(snapshot.chapterContributions)
+        const results = await Promise.all(
+          chapterKeys.map((key) => fetchChapterData(client, key))
+        )
+        const deduped = [
+          ...new Map(results.filter(Boolean).map((c) => [(c as Chapter).id, c as Chapter])).values(),
+        ]
+        setLedChapters(sortItemsByName(deduped))

363-374: Do the same for projects.

Parallelize and de‑dup.

-        const projectKeys = Object.keys(snapshot.projectContributions)
-        const projects: Project[] = []
-
-        for (const key of projectKeys) {
-          const project = await fetchProjectData(client, key)
-          if (project) {
-            projects.push(project)
-          }
-        }
-
-        setLedProjects(sortItemsByName(projects))
+        const projectKeys = Object.keys(snapshot.projectContributions)
+        const results = await Promise.all(
+          projectKeys.map((key) => fetchProjectData(client, key))
+        )
+        const deduped = [
+          ...new Map(results.filter(Boolean).map((p) => [(p as Project).id, p as Project])).values(),
+        ]
+        setLedProjects(sortItemsByName(deduped))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf42b8c and 3704d9a.

📒 Files selected for processing (1)
  • frontend/src/app/board/[year]/candidates/page.tsx (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/app/board/[year]/candidates/page.tsx (3)
frontend/src/types/__generated__/boardQueries.generated.ts (2)
  • GetChapterByKeyDocument (35-35)
  • GetProjectByKeyDocument (36-36)
backend/apps/owasp/models/member_snapshot.py (1)
  • MemberSnapshot (11-132)
backend/apps/owasp/api/internal/queries/chapter.py (1)
  • chapter (14-19)
🔇 Additional comments (2)
frontend/src/app/board/[year]/candidates/page.tsx (2)

64-78: Good fix: preserve 0 counts with nullish coalescing.

The ?? logic prevents truthiness bugs for zero-valued contributions. LGTM.


625-631: Repos section matches “Top 5” now.

Using slice(1, 5) aligns with the title and avoids overflow. Nice.

Also applies to: 636-644

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: Limit frontend function nesting to maximum 4 levels deep

1 participant