Skip to content

Comments

Add reusable Contribution Heatmap to member page for design consistency#3298

Merged
arkid15r merged 26 commits intoOWASP:mainfrom
mrkeshav-05:fix/member-contribution-heatmap
Jan 28, 2026
Merged

Add reusable Contribution Heatmap to member page for design consistency#3298
arkid15r merged 26 commits intoOWASP:mainfrom
mrkeshav-05:fix/member-contribution-heatmap

Conversation

@mrkeshav-05
Copy link
Contributor

@mrkeshav-05 mrkeshav-05 commented Jan 11, 2026

Proposed change

This PR refactors the member profile page to reuse the existing ContributionHeatmap component for improved design consistency and code reuse across the platform.

Resolves #2705


image image

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 11, 2026

Caution

Review failed

The head commit changed during the review from 0a83697 to 1c93f78.

Walkthrough

Adds persisted per-day contribution data to User, exposes it via GraphQL, introduces a management command to populate contribution_data, replaces inline member heatmap with the reusable ContributionHeatmap (adding a "medium" variant), updates queries/tests, and adds related migrations and Makefile adjustments.

Changes

Cohort / File(s) Summary
Backend Model & Migrations
backend/apps/github/models/user.py, backend/apps/github/migrations/0041_user_contribution_data.py, backend/apps/github/migrations/0043_alter_user_contribution_data.py, backend/apps/github/migrations/0042_merge_20260127_2218.py
Add contribution_data JSONField to User and include migrations (create, alter, and merge) to persist per-date contribution counts.
Backend GraphQL & Tests
backend/apps/github/api/internal/nodes/user.py, backend/tests/apps/github/api/internal/nodes/user_test.py
Expose contribution_data on UserNode and update tests asserting field presence.
Backend Aggregation Command & Tests
backend/apps/owasp/management/commands/owasp_aggregate_member_contributions.py, backend/apps/owasp/management/commands/owasp_aggregate_member_contributions.py.bak, backend/tests/apps/owasp/management/commands/owasp_aggregate_member_contributions_test.py, backend/tests/apps/owasp/management/commands/owasp_aggregate_entity_contributions_test.py
New management command to aggregate per-user contributions (commits/PRs/issues) into contribution_data with batching; comprehensive unit tests; test updates to reference renamed entity aggregation module.
Backend Makefile Changes
backend/Makefile, backend/apps/owasp/Makefile
Rename aggregation targets/commands from owasp-aggregate-contributionsowasp-aggregate-entity-contributions and add owasp-aggregate-member-contributions target invocations.
Frontend Page & Query Integration
frontend/src/app/members/[memberKey]/page.tsx, frontend/src/server/queries/userQueries.ts
Replace inline Heatmap with ContributionHeatmap, derive contributionData from user.contributionData, and fetch contributionData in GraphQL query.
Frontend Component & Tests
frontend/src/components/ContributionHeatmap.tsx, frontend/__tests__/unit/components/ContributionHeatmap.test.tsx
Add new variant="medium" to ContributionHeatmap API and rendering (height/width adjustments); update unit tests to cover medium variant.
Frontend Tests / Mocks
frontend/__tests__/unit/pages/UserDetails.test.tsx, frontend/__tests__/mockData/mockUserDetails.ts, frontend/__tests__/a11y/pages/UserDetails.a11y.test.tsx
Update tests to mock ContributionHeatmap (data-testid="contribution-heatmap"), add contributionData to mock user data, and adjust assertions for presence/absence of heatmap based on contribution data.
Infrastructure
docker-compose/local/compose.yaml
Rename local DB volume from db-datadb-data-pr-3298.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested reviewers

  • kasya
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes include docker-compose volume renaming (db-data-pr-3298) and a .bak backup file which are out-of-scope for the stated objectives of achieving design consistency through component reuse. Remove docker-compose volume renaming changes and the .bak backup file as they are not related to the ContributionHeatmap reuse objective.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: refactoring to reuse the ContributionHeatmap component on the member page for design consistency.
Description check ✅ Passed The description is related to the changeset, explaining the refactoring to reuse the ContributionHeatmap component and referencing issue #2705 which aligns with the changes.
Linked Issues check ✅ Passed The PR successfully addresses issue #2705 by replacing the inline Heatmap component with the reusable ContributionHeatmap, ensuring API/data alignment, and achieving design consistency across both pages.
Docstring Coverage ✅ Passed Docstring coverage is 95.71% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

🧹 Nitpick comments (2)
frontend/__tests__/unit/pages/UserDetails.test.tsx (2)

61-68: Consider accepting props in the mock for better test coverage.

The mock ContributionHeatmap component currently ignores all props. While this simplifies testing, it means tests cannot verify that correct data (contributionData, startDate, endDate, variant) is passed to the component.

💡 Optional enhancement to verify props
 jest.mock('components/ContributionHeatmap', () => {
-  const MockContributionHeatmap = () => <div data-testid="contribution-heatmap">Heatmap</div>
+  const MockContributionHeatmap = ({ contributionData, startDate, endDate, variant }: any) => (
+    <div 
+      data-testid="contribution-heatmap"
+      data-start-date={startDate}
+      data-end-date={endDate}
+      data-variant={variant}
+      data-contribution-count={Object.keys(contributionData || {}).length}
+    >
+      Heatmap
+    </div>
+  )
   MockContributionHeatmap.displayName = 'MockContributionHeatmap'
   return {
     __esModule: true,
     default: MockContributionHeatmap,
   }
 })

This would allow tests to assert on the props passed to the component, e.g., expect(heatmap).toHaveAttribute('data-variant', 'medium').


81-87: Mock data includes unused fields.

The mock data includes a years array and intensity field on contributions, but the actual page component (lines 56-69 in page.tsx) only uses the contributions array and specifically the date and count fields. The years and intensity fields appear to be unused.

Consider simplifying the mock to only include fields that are actually used:

 ;(fetchHeatmapData as jest.Mock).mockResolvedValue({
-  years: [{ year: '2023' }],
   contributions: [
     { date: '2023-01-01', count: 5 },
-    { date: '2023-01-02', count: 3, intensity: '1' },
+    { date: '2023-01-02', count: 3 },
   ],
 })
📜 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 385e6e3 and 77158bc.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/pages/UserDetails.test.tsx
  • frontend/src/app/members/[memberKey]/page.tsx
  • frontend/src/components/ContributionHeatmap.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/pages/UserDetails.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/pages/UserDetails.test.tsx
📚 Learning: 2025-07-13T11:34:31.823Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/page.tsx:14-14
Timestamp: 2025-07-13T11:34:31.823Z
Learning: In the Next.js frontend mentorship application, there are two distinct types for authentication-related data: ExtendedSession for useSession hook (containing accessToken and user.login properties) and UserRolesData for useUserRoles hook (containing currentUserRoles.roles array). The correct access pattern for GitHub username is `(session as ExtendedSession)?.user?.login`.

Applied to files:

  • frontend/src/app/members/[memberKey]/page.tsx
🔇 Additional comments (7)
frontend/src/components/ContributionHeatmap.tsx (3)

252-252: LGTM!

Clean addition of the 'medium' variant to the type union. The variant remains optional with a sensible default.


276-294: LGTM!

The medium variant width calculation follows a logical progression:

  • Compact: 13.4px/week, 400px min
  • Medium: 15.5px/week, 500px min
  • Default: 19.5px/week, 600px min

The dependency array is correctly updated to use variant instead of the removed isCompact variable.


325-325: LGTM!

Height calculation correctly implements the three-tier sizing:

  • Compact: 150px
  • Medium: 172px
  • Default: 195px

The chained ternary is appropriate here for straightforward value selection.

frontend/src/app/members/[memberKey]/page.tsx (4)

6-6: LGTM!

The refactored state management is cleaner and aligns well with the ContributionHeatmap API:

  • contributionData as a date-to-count map
  • Separate dateRange for start/end dates
  • Removal of useTheme and useRef simplifies the component

Also applies to: 16-16, 21-26


56-70: Data transformation logic is sound.

The implementation correctly:

  • Validates the contributions array
  • Transforms to a date-count map
  • Derives date range from sorted dates (ISO format sort works correctly)

The conditional check on line 63 ensures the date range is only set when contributions exist, preventing edge case issues.


154-154: LGTM!

Adding overflow-x-auto enables horizontal scrolling for long usernames, multiple badges, and the heatmap on smaller screens. This improves responsive design.


179-194: LGTM!

The conditional rendering logic is thorough and correct:

  • Validates user is not a private contributor
  • Ensures contribution data exists and is non-empty
  • Verifies date range is valid before rendering

Props passed to ContributionHeatmap match the component's interface, and the "medium" variant is appropriate for this layout context.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
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/members/[memberKey]/page.tsx (1)

49-73: Remove user from dependency array to prevent double-fetching.

The user variable is not used inside this useEffect, but including it in the dependency array causes fetchData to run twice: once on mount (when user is undefined) and again when the GraphQL query completes (when user becomes defined).

🐛 Proposed fix
-  }, [memberKey, user])
+  }, [memberKey])
🧹 Nitpick comments (1)
frontend/src/components/ContributionHeatmap.tsx (1)

325-325: Consider a lookup object for variant dimensions.

The nested ternary works but a lookup object would be more maintainable as variants grow:

♻️ Optional refactor
+const VARIANT_HEIGHT = { compact: 150, medium: 172, default: 195 } as const
+
 // Then in the component:
-            height={variant === 'compact' ? 150 : variant === 'medium' ? 172 : 195}
+            height={VARIANT_HEIGHT[variant]}
📜 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 77158bc and 6bba723.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/pages/UserDetails.test.tsx
  • frontend/src/app/members/[memberKey]/page.tsx
  • frontend/src/components/ContributionHeatmap.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/tests/unit/pages/UserDetails.test.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-13T11:34:31.823Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/page.tsx:14-14
Timestamp: 2025-07-13T11:34:31.823Z
Learning: In the Next.js frontend mentorship application, there are two distinct types for authentication-related data: ExtendedSession for useSession hook (containing accessToken and user.login properties) and UserRolesData for useUserRoles hook (containing currentUserRoles.roles array). The correct access pattern for GitHub username is `(session as ExtendedSession)?.user?.login`.

Applied to files:

  • frontend/src/app/members/[memberKey]/page.tsx
🔇 Additional comments (3)
frontend/src/components/ContributionHeatmap.tsx (1)

252-252: LGTM! Clean variant extension with consistent sizing.

The medium variant is well-positioned between compact and default with proportional width/height scaling. The dependency array correctly tracks both heatmapSeries and variant.

Also applies to: 276-294

frontend/src/app/members/[memberKey]/page.tsx (2)

56-70: LGTM! Data transformation is correctly implemented.

The transformation from array to Record<string, number> is clean, and the date range extraction with ISO string sorting works correctly. The empty array case is implicitly handled by the conditional checks at render time.


179-194: LGTM! Defensive rendering with proper guards.

All edge cases are handled: private contributor check, empty data check, and date range validation. The overflow-x-auto container ensures the heatmap scrolls gracefully on smaller viewports.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
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

🧹 Nitpick comments (2)
frontend/__tests__/unit/pages/UserDetails.test.tsx (1)

82-87: Verify if the years field in the mock is necessary.

The mock returns both years and contributions, but the page component (lines 56-70) only appears to use the contributions array. The years field might be unused mock data.

Consider verifying whether years is used elsewhere, and if not, simplify the mock:

♻️ Potential simplification if years is unused
 ;(fetchHeatmapData as jest.Mock).mockResolvedValue({
-  years: [{ year: '2023' }],
   contributions: [
     { date: '2023-01-01', count: 5, intensity: '2' },
     { date: '2023-01-02', count: 3, intensity: '1' },
   ],
 })
frontend/src/app/members/[memberKey]/page.tsx (1)

56-70: Verify browser support for Array.at() or consider a fallback.

Line 67 uses Array.at(-1) which is an ES2022 feature. While Next.js 16 and React 19 should support this, verify that your target browser matrix includes ES2022 support.

If broader browser support is needed, consider this alternative:

♻️ Alternative using traditional array indexing
 if (result.contributions.length > 0) {
   const dates = result.contributions.map((c) => c.date).sort((a, b) => a.localeCompare(b))
   setDateRange({
     startDate: dates[0],
-    endDate: dates.at(-1) ?? '',
+    endDate: dates[dates.length - 1] ?? '',
   })
 }
📜 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 6bba723 and c91f4c6.

📒 Files selected for processing (3)
  • frontend/__tests__/unit/pages/UserDetails.test.tsx
  • frontend/src/app/members/[memberKey]/page.tsx
  • frontend/src/components/ContributionHeatmap.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ContributionHeatmap.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/pages/UserDetails.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/pages/UserDetails.test.tsx
📚 Learning: 2025-07-13T11:34:31.823Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/src/app/mentorship/programs/page.tsx:14-14
Timestamp: 2025-07-13T11:34:31.823Z
Learning: In the Next.js frontend mentorship application, there are two distinct types for authentication-related data: ExtendedSession for useSession hook (containing accessToken and user.login properties) and UserRolesData for useUserRoles hook (containing currentUserRoles.roles array). The correct access pattern for GitHub username is `(session as ExtendedSession)?.user?.login`.

Applied to files:

  • frontend/src/app/members/[memberKey]/page.tsx
🔇 Additional comments (8)
frontend/__tests__/unit/pages/UserDetails.test.tsx (4)

8-8: LGTM!

The import aligns with the mock setup below and the updated page implementation.


61-68: LGTM!

The mock implementation is appropriately minimal for page-level testing. The data-testid allows tests to verify the component renders without testing its internal implementation details.


279-300: LGTM!

The test correctly verifies that the ContributionHeatmap component renders when valid data is available. The use of waitFor accounts for the asynchronous data fetching in the useEffect.


302-315: LGTM!

The test appropriately verifies that the heatmap doesn't render when fetchHeatmapData returns null. Using queryByTestId for negative assertions is the correct pattern.

frontend/src/app/members/[memberKey]/page.tsx (4)

6-6: LGTM!

All new imports are necessary for the refactored heatmap functionality and align with the PR objectives.

Also applies to: 13-13, 16-16


21-25: LGTM!

The state declarations are well-typed and appropriately initialized. Empty defaults work correctly with the conditional rendering logic below.


154-154: LGTM!

Adding overflow-x-auto improves mobile responsiveness by allowing horizontal scrolling when the heatmap is wider than the viewport. This prevents layout breaking on smaller screens.


179-194: Verify the variant="medium" prop value.

The code passes variant="medium" to ContributionHeatmap. According to the AI summary, the component now supports 'default' | 'medium' | 'compact' variants, so this should be valid. However, since the ContributionHeatmap component file isn't included in this review, please verify that "medium" is correctly implemented and produces the desired visual result.

The conditional rendering logic is excellent—multiple checks ensure the component only renders with complete, valid data.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 11, 2026
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

It looks like it still uses the 3rd party data fetching instead of our local data. The idea is to get rid of both external dependency and components code related to that. We want it to fetch our DB for user contributions data. It means it also needs to be update to day on a daily basis.

@arkid15r arkid15r marked this pull request as draft January 11, 2026 17:42
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

🤖 Fix all issues with AI agents
In @backend/apps/github/management/commands/github_aggregate_contributions.py:
- Around line 34-45: The default for batch size is inconsistent: the
parser.add_argument("--batch-size") sets default=100 but in handle(...) the code
reads options.get("batch_size", 1000); change one to match the other. Either
update parser.add_argument to default=1000 or change options.get("batch_size",
100) in the github_aggregate_contributions.Command.handle method so that the
parser default and the handle fallback use the same value, and ensure the
variable name batch_size remains used consistently.
- Around line 102-113: The current Commit queryset groups by the full datetime
because it uses .values("created_at").annotate(count=Count("id")), so change it
to truncate created_at to date at DB level (use TruncDate on Commit.created_at
in the queryset and .values("date")/.annotate(count=Count("id"))), then use that
truncated date when building date_str; apply the same TruncDate pattern to the
prs and issues queries so all three aggregate by date instead of full datetime
for correct and efficient grouping.

In
@backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py:
- Around line 286-293: The loop verifying YYYY-MM-DD treats the datetime as
timezone-aware by calling datetime.strptime(date_str,
"%Y-%m-%d").astimezone(UTC), which forces a local-TZ assumption on a naive
datetime; simply remove the .astimezone(UTC) call and validate the format with
datetime.strptime(date_str, "%Y-%m-%d") (or use
datetime.fromisoformat(date_str)) and keep the subsequent length and
dash-position assertions on result.
🧹 Nitpick comments (4)
backend/tests/apps/github/api/internal/nodes/user_test.py (1)

313-338: Tests verify mock behavior rather than actual field resolution.

These tests only verify that mock_user.contribution_data returns what was assigned to it, which tests the mock itself rather than the UserNode field resolution. Compare with other tests in this file (e.g., test_created_at_field, test_issues_count_field) that call UserNode.field_name(mock_user) to test actual resolver behavior.

Since contribution_data is a passthrough field (no custom resolver), this is acceptable but provides limited value. The real validation happens in the test_meta_configuration test above.

frontend/src/components/ContributionHeatmap.tsx (2)

273-294: LGTM! Consistent width calculation pattern.

The medium variant follows the established pattern with appropriate pixel-per-week (16) and padding (45) values that fall between compact and default. The minimum width of 500px is sensible.

One minor observation: consider extracting the magic numbers (pixelPerWeek, padding, minWidth) into a configuration object for each variant to improve maintainability.

♻️ Optional: Extract variant configuration
const VARIANT_CONFIG = {
  compact: { pixelPerWeek: 13.4, padding: 40, minWidth: 400 },
  medium: { pixelPerWeek: 16, padding: 45, minWidth: 500 },
  default: { pixelPerWeek: 19.5, padding: 50, minWidth: 600 },
} as const

// Then in useMemo:
const config = VARIANT_CONFIG[variant]
const calculatedWidth = weeksCount * config.pixelPerWeek + config.padding
return Math.max(config.minWidth, calculatedWidth)

298-302: Consider memoizing getChartHeight.

getChartHeight is defined as a function inside the component and will be recreated on every render. Since it only depends on variant, it could be memoized or converted to a simple lookup.

♻️ Optional: Convert to memoized value or constant lookup
-  const getChartHeight = () => {
-    if (variant === 'compact') return 150
-    if (variant === 'medium') return 172
-    return 195
-  }
+  const chartHeight = useMemo(() => {
+    const heights = { compact: 150, medium: 172, default: 195 }
+    return heights[variant]
+  }, [variant])

Then use chartHeight directly instead of getChartHeight() on line 331.

backend/apps/github/management/commands/github_aggregate_contributions.py (1)

66-76: Consider using bulk_update instead of individual saves for better performance.

Currently, each user is saved individually inside the loop, resulting in one DB write per user. For large datasets, using bulk_update in batches would significantly improve performance.

♻️ Proposed refactor using bulk_update
processed = 0
users_to_update = []

for user in users.iterator(chunk_size=batch_size):
    contribution_data = self._aggregate_user_contributions(user, start_date)

    if contribution_data:
        user.contribution_data = contribution_data
        users_to_update.append(user)
        processed += 1

        if len(users_to_update) >= batch_size:
            User.objects.bulk_update(users_to_update, ["contribution_data"])
            users_to_update = []
            self.stdout.write(f"Processed {processed}/{total_users} users...")

# Save remaining users
if users_to_update:
    User.objects.bulk_update(users_to_update, ["contribution_data"])
📜 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 c91f4c6 and 7953acb.

⛔ Files ignored due to path filters (2)
  • frontend/src/types/__generated__/graphql.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/userQueries.generated.ts is excluded by !**/__generated__/**
📒 Files selected for processing (15)
  • backend/Makefile
  • backend/apps/github/Makefile
  • backend/apps/github/api/internal/nodes/user.py
  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/apps/github/migrations/0041_user_contribution_data.py
  • backend/apps/github/models/user.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
  • backend/tests/apps/github/models/user_test.py
  • frontend/__tests__/mockData/mockUserDetails.ts
  • frontend/__tests__/unit/components/ContributionHeatmap.test.tsx
  • frontend/__tests__/unit/pages/UserDetails.test.tsx
  • frontend/src/app/members/[memberKey]/page.tsx
  • frontend/src/components/ContributionHeatmap.tsx
  • frontend/src/server/queries/userQueries.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/app/members/[memberKey]/page.tsx
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/github/migrations/0041_user_contribution_data.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
  • backend/tests/apps/github/models/user_test.py
  • backend/apps/github/api/internal/nodes/user.py
  • backend/apps/github/models/user.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
  • backend/apps/github/management/commands/github_aggregate_contributions.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/github/migrations/0041_user_contribution_data.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
  • backend/tests/apps/github/models/user_test.py
  • backend/apps/github/api/internal/nodes/user.py
  • backend/apps/github/models/user.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
  • backend/apps/github/management/commands/github_aggregate_contributions.py
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/pages/UserDetails.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/pages/UserDetails.test.tsx
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
  • backend/apps/github/management/commands/github_aggregate_contributions.py
🧬 Code graph analysis (3)
backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py (1)
backend/apps/github/management/commands/github_aggregate_contributions.py (2)
  • _aggregate_user_contributions (82-141)
  • handle (41-80)
backend/tests/apps/github/models/user_test.py (1)
backend/apps/github/models/user.py (1)
  • User (28-223)
backend/apps/github/management/commands/github_aggregate_contributions.py (1)
backend/apps/github/models/commit.py (1)
  • Commit (11-110)
🔇 Additional comments (24)
frontend/src/server/queries/userQueries.ts (1)

88-88: LGTM!

The contributionData field is correctly added to the GraphQL query, enabling the frontend to fetch per-day contribution counts for the heatmap component.

backend/Makefile (1)

159-159: LGTM!

The github-aggregate-contributions target is correctly added after github-update-users, ensuring users exist before their contributions are aggregated.

backend/apps/github/Makefile (1)

24-27: LGTM!

The new Makefile target follows the established pattern consistently with other targets in this file.

frontend/__tests__/mockData/mockUserDetails.ts (1)

16-22: LGTM!

The mock contributionData provides good test coverage with varied values including a zero-contribution day, which helps validate edge case handling in the heatmap component.

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

629-634: LGTM!

Good test coverage for the new medium variant, verifying the expected height of 172px which fits logically between default (195px) and compact (150px).


661-668: LGTM!

Consistent test coverage for the medium variant's container styling, following the same pattern as existing variant tests.


677-691: Test now uses medium variant for rerender scenario.

The test verifies that title styling remains consistent across variants. Switching from compact to medium in the rerender case still validates the intended behavior (title classes unchanged between variants).

backend/tests/apps/github/api/internal/nodes/user_test.py (1)

26-26: LGTM!

The contribution_data field is correctly added to the expected field names set in alphabetical order, consistent with the existing test pattern.

backend/apps/github/models/user.py (1)

57-62: LGTM!

The contribution_data JSONField is correctly defined:

  • Uses default=dict (callable) to avoid the shared mutable default anti-pattern
  • blank=True without null=True is the correct pattern for JSONField—empty dict is preferable to NULL
  • Clear help text documents the expected date -> count mapping structure
backend/apps/github/migrations/0041_user_contribution_data.py (1)

1-22: LGTM!

The migration correctly adds the contribution_data JSONField with options matching the model definition. This is an additive, backward-compatible change that won't affect existing data.

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

8-8: LGTM!

Good cleanup—only importing fetchHeatmapData since drawContributions is no longer used directly in tests.


61-68: LGTM!

The mock correctly provides a data-testid for reliable querying in tests, following the pattern used by other mocked components in this file.


81-87: LGTM!

The mock return value now includes the contributions array with the expected shape (date, count, intensity), aligning with the heatmap data structure.


297-299: LGTM!

Switching from alt text querying to data-testid is more reliable and decouples tests from accessibility attributes that might change.


303-321: LGTM!

The test correctly verifies that when contributionData is null, the heatmap component is not rendered. This properly tests the conditional rendering logic in the page component.


827-886: Good test coverage for contribution data edge cases.

The new test suite covers the key scenarios:

  • Heatmap renders when data exists
  • Heatmap doesn't render with empty object {}
  • Heatmap doesn't render with null

This ensures the page handles various backend states gracefully.


843-863: No changes needed. The test correctly verifies conditional rendering behavior.

The conditional rendering check (hasContributionData && ...) is correctly implemented in the page component at line 166. When contributionData is {}, hasContributionData evaluates to false (since Object.keys({}).length > 0 is false), preventing the entire <ContributionHeatmap> component from rendering. The mock is never invoked, so the test correctly passes when the heatmap is not in the DOM.

backend/apps/github/api/internal/nodes/user.py (1)

16-16: JSONField requires explicit GraphQL type mapping in strawberry-django v0.73.0

Adding contribution_data to the fields list without an explicit scalar type will cause GraphQL serialization failures. Strawberry-django v0.73.0 does not automatically handle Django's JSONField—that support was added later in Strawberry core v0.99.0+.

To fix this, you need to either:

  • Import and use Strawberry's JSON scalar (from strawberry.scalars import JSON) and define the field explicitly, or
  • Create a custom resolver that returns the JSONField as a Python dict/list

Likely an incorrect or invalid review comment.

frontend/src/components/ContributionHeatmap.tsx (1)

252-252: LGTM! Clean variant extension.

The new 'medium' variant is appropriately added to the union type, maintaining type safety and allowing consumers to use the new size option.

backend/tests/apps/github/models/user_test.py (1)

219-248: LGTM! Good coverage of the new contribution_data field.

The tests appropriately verify:

  1. Default value is an empty dict
  2. JSON data can be stored and retrieved
  3. The field can be updated

These in-memory tests are suitable for unit testing the field definition. If integration tests exist elsewhere that verify actual database persistence, this coverage is complete.

backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py (4)

9-42: LGTM! Useful MockQuerySet utility.

The MockQuerySet class provides a clean way to mock Django QuerySet behavior without database access. It supports the necessary methods (filter, exists, count, iterator, values, annotate) used by the command.


47-86: LGTM! Good test for empty contributions scenario.

The test properly mocks timezone.now() and verifies that 30 days of zero-filled data is returned when no contributions exist.


87-135: LGTM! Comprehensive test with contribution data.

The test verifies correct aggregation across commits, PRs, and issues on different dates. The assertions clearly document expected behavior (e.g., Jan 1st = 2 commits + 1 PR = 3).


166-183: LGTM! Good negative test for user not found.

The test properly verifies the error message is written when a specified user doesn't 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: 1

🤖 Fix all issues with AI agents
In @backend/apps/github/management/commands/github_aggregate_contributions.py:
- Around line 117-141: The PR and Issue queries are grouping by the full
datetime; change both queries to use TruncDate("created_at") so aggregation
happens in the DB: in the PullRequest and Issue query blocks replace
.values("created_at").annotate(count=Count("id")) with
.annotate(date=TruncDate("created_at")).values("date").annotate(count=Count("id"))
(and update the loops to read pr["date"] / issue["date"] instead of
["created_at"]); also ensure TruncDate is imported from
django.db.models.functions if not already.
🧹 Nitpick comments (2)
backend/tests/apps/github/api/internal/nodes/user_test.py (1)

313-337: Tests don't exercise UserNode logic.

These tests only verify that a Mock object returns the value assigned to it, which is tautological. Unlike other tests in this file (e.g., test_url_field calls UserNode.url(mock_user)), these don't invoke any UserNode method.

If contribution_data is a simple passthrough field (auto-resolved by Strawberry from the model), explicit tests may be unnecessary. If there's a custom resolver, test it like other fields:

result = UserNode.contribution_data(mock_user)

Otherwise, consider removing these tests or clarifying their intent.

backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py (1)

31-41: Remove unused noqa directives.

Per static analysis, the ARG002 rule is not enabled, making these noqa comments unnecessary.

🔧 Proposed fix
-    def iterator(self, chunk_size=None):  # noqa: ARG002
+    def iterator(self, chunk_size=None):
         """Mock iterator method."""
         return iter(self._items)

-    def values(self, *_args):  # noqa: ARG002
+    def values(self, *_args):
         """Mock values method."""
         return self
📜 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 7953acb and 3e6520d.

📒 Files selected for processing (3)
  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
📚 Learning: 2025-09-10T15:32:12.688Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2211
File: backend/apps/nest/controllers/calendar_events.py:0-0
Timestamp: 2025-09-10T15:32:12.688Z
Learning: In the backend/apps/nest/controllers/calendar_events.py file, the scheduled_time parameter in the schedule_reminder function is guaranteed to be timezone-aware, so explicit timezone awareness validation is not needed.

Applied to files:

  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
🧬 Code graph analysis (2)
backend/apps/github/management/commands/github_aggregate_contributions.py (2)
backend/apps/github/models/commit.py (1)
  • Commit (11-110)
backend/apps/github/models/user.py (2)
  • User (28-223)
  • issues (74-81)
backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py (1)
backend/apps/github/management/commands/github_aggregate_contributions.py (2)
  • _aggregate_user_contributions (83-143)
  • handle (42-81)
🪛 Ruff (0.14.11)
backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py

31-31: Unused noqa directive (non-enabled: ARG002)

Remove unused noqa directive

(RUF100)


35-35: Unused noqa directive (non-enabled: ARG002)

Remove unused noqa directive

(RUF100)

🔇 Additional comments (11)
backend/tests/apps/github/api/internal/nodes/user_test.py (1)

26-26: LGTM!

The expected field set correctly includes the new contribution_data field, aligning with the model and GraphQL schema updates.

backend/apps/github/management/commands/github_aggregate_contributions.py (3)

1-21: LGTM!

Imports are appropriate. Using django.utils.timezone for timezone-aware datetime handling is correct, and TruncDate/Count are suitable for the aggregation queries.


22-40: LGTM!

Command arguments are well-structured with sensible defaults and clear help text.


42-81: LGTM!

Good use of users.exists() for existence checks and users.iterator(chunk_size=batch_size) for memory-efficient processing. The update_fields parameter in save() is a good practice for performance.

One minor note: Line 71's if contribution_data: will be True as long as the dict has keys (even if all counts are 0). This should work correctly since _aggregate_user_contributions initializes all dates in the range.

backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py (7)

47-81: LGTM!

Test correctly validates the empty contribution case with proper mocking of timezone and model queries.


83-127: Test mirrors current implementation inconsistency.

The mock data for PRs and issues uses created_at (datetime) while commits use date. If you apply the suggested fix to use TruncDate for PRs and issues in the command, update these mocks accordingly:

             mock_pr.objects.filter.return_value = MockQuerySet(
-                [{"created_at": datetime(2024, 1, 1, 11, 0, tzinfo=UTC), "count": 1}]
+                [{"date": datetime(2024, 1, 1, tzinfo=UTC).date(), "count": 1}]
             )
             mock_issue.objects.filter.return_value = MockQuerySet(
-                [{"created_at": datetime(2024, 1, 3, 12, 0, tzinfo=UTC), "count": 3}]
+                [{"date": datetime(2024, 1, 3, tzinfo=UTC).date(), "count": 3}]
             )

129-156: LGTM!

Test properly validates the handle flow for a specific user, including verification that save() is called with the correct update_fields.


158-174: LGTM!

Good negative test case that verifies proper error messaging when a user is not found.


176-209: LGTM!

Test validates batch processing of multiple users correctly.


211-243: LGTM!

Test properly validates the custom days parameter affects the start date calculation.


245-277: LGTM!

Test validates the date format contract for contribution data keys.

@mrkeshav-05 mrkeshav-05 force-pushed the fix/member-contribution-heatmap branch 2 times, most recently from f4302b9 to cc0637b Compare January 14, 2026 09:33
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: 1

🤖 Fix all issues with AI agents
In `@backend/tests/apps/github/api/internal/nodes/user_test.py`:
- Around line 313-337: The two tests (test_contribution_data_field and
test_contribution_data_field_empty) only set and read attributes on a Mock and
do not call any UserNode logic; either update them to exercise the actual
resolver (e.g., call UserNode.contribution_data(user) or the appropriate
resolver method on UserNode) and assert its returned value based on a mock User
instance, or remove these tests if contribution_data is just a direct field and
already covered by test_meta_configuration; locate the tests by their names and
the UserNode class/resolver to implement the change.
🧹 Nitpick comments (1)
backend/apps/github/management/commands/github_aggregate_contributions.py (1)

83-145: LGTM with optional DRY suggestion.

The aggregation logic is correct. All three queries (Commits, PRs, Issues) properly use TruncDate for database-level date grouping.

♻️ Optional: Extract repeated query pattern into helper

The three query blocks share identical structure. If you want to reduce duplication:

def _aggregate_user_contributions(self, user: User, start_date: datetime) -> dict[str, int]:
    contribution_data = {}
    current_date = start_date.date()
    end_date = timezone.now().date()

    while current_date <= end_date:
        date_str = current_date.strftime("%Y-%m-%d")
        contribution_data[date_str] = 0
        current_date += timedelta(days=1)

    models = [Commit, PullRequest, Issue]
    for model in models:
        queryset = (
            model.objects.filter(author=user, created_at__gte=start_date)
            .annotate(date=TruncDate("created_at"))
            .values("date")
            .annotate(count=Count("id"))
        )
        for entry in queryset:
            date_str = entry["date"].strftime("%Y-%m-%d")
            contribution_data[date_str] = contribution_data.get(date_str, 0) + entry["count"]

    return contribution_data
📜 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 f4302b9 and cc0637b.

📒 Files selected for processing (3)
  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/api/internal/nodes/user_test.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-31T05:17:39.659Z
Learnt from: kart-u
Repo: OWASP/Nest PR: 3101
File: backend/apps/common/extensions.py:92-98
Timestamp: 2025-12-31T05:17:39.659Z
Learning: In this codebase, import OperationType for GraphQL operations from the graphql-core package rather than from strawberry. Use 'from graphql import OperationType'. Strawberry re-exports via graphql-core internally, so relying on strawberry's API may be brittle. Apply this rule to all Python files that deal with GraphQL operation types; ensure imports come from graphql (graphql-core) and not from strawberry packages. This improves compatibility and avoids coupling to strawberry's internals.

Applied to files:

  • backend/tests/apps/github/api/internal/nodes/user_test.py
  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
📚 Learning: 2026-01-01T17:48:23.963Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:41-47
Timestamp: 2026-01-01T17:48:23.963Z
Learning: In Django code, be aware that a QuerySet's boolean evaluation (e.g., if not queryset) runs a database query to determine emptiness. While it is technically valid to use the queryset in a boolean context, use queryset.exists() for existence checks to avoid unnecessary queries and improve performance. Applicable broadly to Python/Django files rather than just this specific path.

Applied to files:

  • backend/tests/apps/github/api/internal/nodes/user_test.py
  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
📚 Learning: 2025-12-18T05:39:42.678Z
Learnt from: rudransh-shrivastava
Repo: OWASP/Nest PR: 2948
File: backend/apps/owasp/management/commands/owasp_generate_community_snapshot_video.py:40-40
Timestamp: 2025-12-18T05:39:42.678Z
Learning: In Django management commands, prefer using self.stdout.write(...) over print(...) for user-facing stdout output. This aligns with Django conventions and improves testability. When emitting messages, consider using self.stdout.write and, for styled messages, use self.style.SUCCESS/ERROR as appropriate to maintain consistent command output formatting. Apply this guideline to all Python files within any project's management/commands directory.

Applied to files:

  • backend/apps/github/management/commands/github_aggregate_contributions.py
  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
📚 Learning: 2025-09-10T15:32:12.688Z
Learnt from: ahmedxgouda
Repo: OWASP/Nest PR: 2211
File: backend/apps/nest/controllers/calendar_events.py:0-0
Timestamp: 2025-09-10T15:32:12.688Z
Learning: In the backend/apps/nest/controllers/calendar_events.py file, the scheduled_time parameter in the schedule_reminder function is guaranteed to be timezone-aware, so explicit timezone awareness validation is not needed.

Applied to files:

  • backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py
🧬 Code graph analysis (2)
backend/apps/github/management/commands/github_aggregate_contributions.py (2)
backend/apps/github/models/commit.py (1)
  • Commit (11-110)
backend/apps/github/models/user.py (2)
  • User (28-223)
  • issues (74-81)
backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py (1)
backend/apps/github/management/commands/github_aggregate_contributions.py (2)
  • _aggregate_user_contributions (83-145)
  • handle (42-81)
🔇 Additional comments (13)
backend/tests/apps/github/api/internal/nodes/user_test.py (1)

26-26: LGTM!

The addition of contribution_data to the expected field names correctly validates the new GraphQL field exposure in UserNode.

backend/apps/github/management/commands/github_aggregate_contributions.py (3)

1-15: LGTM!

Imports are well-organized and appropriate. Using TruncDate from django.db.models.functions aligns with best practices for date aggregation at the database level.


22-40: LGTM!

Command arguments are well-defined with appropriate defaults and help text.


42-81: LGTM!

The handle method follows Django conventions well:

  • Uses self.stdout.write with styled output
  • Uses users.exists() for existence check (efficient)
  • Memory-efficient iteration with iterator(chunk_size=batch_size)
  • Targeted save with update_fields=["contribution_data"]
backend/tests/apps/github/management/commands/github_aggregate_contributions_test.py (9)

1-7: LGTM!

Imports are appropriate for the test module.


9-42: LGTM!

The MockQuerySet is a clean utility for testing without database access. It properly supports the chaining pattern used by the command.


47-81: LGTM!

Test correctly validates zero-filled date range when there are no contributions.


83-127: LGTM!

Test correctly validates aggregation logic across commits, PRs, and issues with proper date summing.


129-156: LGTM!

Test correctly validates the handle method with a found user, including verification of save behavior.


158-174: LGTM!

Test correctly validates the error path when a specified user is not found.


176-209: LGTM!

Test correctly validates bulk processing of multiple users.


211-243: LGTM!

Test correctly validates custom days parameter handling with appropriate datetime comparison tolerance.


245-277: LGTM!

Test correctly validates the YYYY-MM-DD date format for contribution data keys.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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: 1

🤖 Fix all issues with AI agents
In `@docker-compose/local/compose.yaml`:
- Line 70: Revert the PR-specific Docker volume name introduced in compose.yaml:
replace any occurrences of the volume identifier "db-data-pr-3298" back to the
canonical "db-data" used on main (this appears in the service bind for Postgres
and the volumes section); ensure both the service volume mount and the volumes
declaration (symbols: the Postgres service volume mapping and the volumes entry
that defines db-data-pr-3298) are updated so the repo uses "db-data" again
before merging.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 26, 2026
Copy link
Collaborator

@arkid15r arkid15r left a comment

Choose a reason for hiding this comment

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

It doesn't seem you ran the tests locally 🤷‍♂️

e2e-nest-data-loader  | Loading data from dump...
e2e-nest-db           | 2026-01-28 01:22:12.548 UTC [116] ERROR:  null value in column "contribution_data" of relation "github_users" violates not-null constraint
e2e-nest-db           | 2026-01-28 01:22:12.548 UTC [116] DETAIL:  Failing row contains (1908, 2024-09-12 00:30:14.55+00, 2025-03-20 12:58:16.628+00, , Dhanxy, , https://avatars.githubusercontent.com/u/107783562?v=4, , , 0, 1, 0, 0, 0, , f, , U_kgDOBmylig, 2022-06-19 04:40:23+00, 2023-11-29 20:07:09+00, f, 0, f, t, null).
e2e-nest-db           | 2026-01-28 01:22:12.548 UTC [116] CONTEXT:  COPY github_users, line 1: "1908    2024-09-12 00:30:14.55+00       2025-03-20 12:58:16.628+00              Dhanxy          https://avatars.githubusercontent..."
e2e-nest-db           | 2026-01-28 01:22:12.548 UTC [116] STATEMENT:  COPY public.github_users (id, nest_created_at, nest_updated_at, name, login, email, avatar_url, company, location, collaborators_count, following_count, followers_count, public_gists_count, public_repositories_count, bio, is_hireable, twitter_username, node_id, created_at, updated_at, is_bot, contributions_count, is_owasp_staff, has_public_member_page) FROM stdin;

I'm merging this PR after applying multiple fixes.


if contribution_data:
user.contribution_data = contribution_data
user.save(update_fields=["contribution_data"])
Copy link
Collaborator

Choose a reason for hiding this comment

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

I indicated this case for you in previous similar PR review but it doesn't seem you learned from it. In general it's not a good sign from mentor's perspective.

Aggregating OWASP community member contributions
Aggregating contributions since 2025-01-28 (365 days back)
Processing 6965 users...
Processed 100/6965 users...
^Cmake: *** [owasp-aggregate-member-contributions] Interrupt: 2

make owasp-aggregate-member-contributions  0.02s user 0.03s system 0% cpu 4:43.55 total

Aggregating OWASP community member contributions
Aggregating contributions since 2025-01-28 (365 days back)
Processing 6965 users...
Successfully aggregated contributions for 6965 members
make owasp-aggregate-member-contributions  0.03s user 0.04s system 0% cpu 30.167 total

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for missing this, I understand the concern and will make sure to be more careful about this in future PRs.

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__/a11y/pages/UserDetails.a11y.test.tsx (1)

42-45: Remove the unused githubHeatmap mock.

The fetchHeatmapData and drawContributions functions are not used anywhere in the codebase. Since the ContributionHeatmap component is already mocked, these utility mocks serve no purpose. Remove lines 42-45.

🧹 Nitpick comments (4)
backend/apps/github/migrations/0043_alter_user_contribution_data.py (1)

15-21: Consider the semantics of null=True with default=dict.

Having both null=True and default=dict allows the field to be either None or {}. If this is intentional (e.g., None = "not yet computed", {} = "computed but no contributions"), this is fine. Otherwise, you could simplify by removing null=True to ensure the field always contains a dict.

backend/tests/apps/owasp/management/commands/owasp_aggregate_entity_contributions_test.py (1)

53-56: Consider renaming the test class for clarity.

The test class TestOwaspAggregateContributions could be renamed to TestOwaspAggregateEntityContributions to better reflect the command being tested and distinguish it from the member contributions tests.

Suggested change
-class TestOwaspAggregateContributions:
+class TestOwaspAggregateEntityContributions:
frontend/__tests__/unit/pages/UserDetails.test.tsx (1)

281-301: Remove the redundant null-contribution test.
This test now overlaps with the later “Contribution Heatmap” block that also covers contributionData: null. Keeping one will reduce test duplication.

🧹 Suggested cleanup
-test('handles contribution heatmap loading error correctly', async () => {
-  const dataWithoutContributions = {
-    ...mockUserDetailsData,
-    user: {
-      ...mockUserDetailsData.user,
-      contributionData: null,
-    },
-  }
-
-  ;(useQuery as unknown as jest.Mock).mockReturnValue({
-    data: dataWithoutContributions,
-    error: null,
-    loading: false,
-  })
-
-  render(<UserDetailsPage />)
-
-  await waitFor(() => {
-    const heatmap = screen.queryByTestId('contribution-heatmap')
-    expect(heatmap).not.toBeInTheDocument()
-  })
-})
backend/apps/owasp/management/commands/owasp_aggregate_member_contributions.py.bak (1)

1-142: Consider removing the .bak command file.
It duplicates the active command and can easily drift or confuse future edits. Keeping only the real command file is cleaner.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 28, 2026
@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 95.12195% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.55%. Comparing base (fac8fb3) to head (1c93f78).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...t/commands/owasp_aggregate_member_contributions.py 94.73% 3 Missing ⚠️
frontend/src/app/members/[memberKey]/page.tsx 92.85% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3298      +/-   ##
==========================================
+ Coverage   85.49%   85.55%   +0.06%     
==========================================
  Files         462      463       +1     
  Lines       14260    14306      +46     
  Branches     1900     1905       +5     
==========================================
+ Hits        12191    12240      +49     
+ Misses       1689     1687       -2     
+ Partials      380      379       -1     
Flag Coverage Δ
backend 84.53% <94.82%> (+0.05%) ⬆️
frontend 88.40% <95.83%> (+0.12%) ⬆️

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/user.py 100.00% <ø> (ø)
backend/apps/github/models/user.py 98.70% <100.00%> (+0.01%) ⬆️
...t/commands/owasp_aggregate_entity_contributions.py 86.36% <ø> (ø)
frontend/src/components/ContributionHeatmap.tsx 81.81% <100.00%> (+1.38%) ⬆️
frontend/src/app/members/[memberKey]/page.tsx 88.05% <92.85%> (+4.33%) ⬆️
...t/commands/owasp_aggregate_member_contributions.py 94.73% <94.73%> (ø)

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 fac8fb3...1c93f78. 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.

@arkid15r arkid15r enabled auto-merge January 28, 2026 02:28
arkid15r
arkid15r previously approved these changes Jan 28, 2026
@arkid15r arkid15r added this pull request to the merge queue Jan 28, 2026
@arkid15r arkid15r removed this pull request from the merge queue due to a manual request Jan 28, 2026
@arkid15r arkid15r dismissed stale reviews from coderabbitai[bot] and themself via 0a83697 January 28, 2026 02:41
@arkid15r arkid15r enabled auto-merge January 28, 2026 02:43
@sonarqubecloud
Copy link

@arkid15r arkid15r added this pull request to the merge queue Jan 28, 2026
Merged via the queue into OWASP:main with commit 04aaecc Jan 28, 2026
35 checks passed
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.

Add reusable Contribution Heatmap to member page for design consistency

2 participants