Skip to content

Conversation

@ahmedxgouda
Copy link
Collaborator

@ahmedxgouda ahmedxgouda commented Jul 19, 2025

Resolves #1750
image

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 19, 2025

Summary by CodeRabbit

  • New Features

    • Added the ability to download detailed PDF reports of health metrics for individual projects directly from the dashboard.
    • Introduced a new PDF download button for both project-specific and overview health metrics reports.
  • Bug Fixes

    • Improved consistency and styling of generated PDF reports.
  • Chores

    • Removed obsolete management commands and related tests for PDF generation.
    • Updated backend endpoints and utilities to support the new PDF download functionality.

"""

Walkthrough

This change introduces end-to-end support for downloading detailed health metrics PDFs for individual projects. It removes the old Django management command and its test, adds a backend API endpoint and PDF generation utility, and updates the frontend to use a reusable PDF download button for both overview and project-specific reports.

Changes

Cohort / File(s) Change Summary
Frontend: PDF Download Button Integration
frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx, frontend/src/app/projects/dashboard/page.tsx, frontend/src/components/MetricsPDFButton.tsx, frontend/src/server/fetchMetricsPDF.ts
Adds a reusable MetricsPDFButton component, integrates it into project and overview dashboard pages, and introduces a utility for fetching and downloading PDFs from the backend.
Backend: Remove Old Management Command & Test
backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py, backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py
Removes the legacy Django management command and its associated test for generating project health metrics PDFs.
Backend: API Endpoint and PDF Generation
backend/apps/owasp/api/internal/views/project_health_metrics.py, backend/apps/owasp/api/internal/views/urls.py, backend/apps/owasp/utils/pdf.py
Adds a new API endpoint and supporting utility to generate and serve project-specific health metrics PDFs, including a helper for table formatting.
Backend: PDF Generation Tests
backend/tests/apps/owasp/utils/pdf_test.py
Adds and updates tests for the new PDF generation utility, covering both overview and detailed project metrics PDFs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Implement client-side interaction with the PDF details script (#1750)
Remove legacy management command and test (implicit in migration to API) (#1750)
Add backend API endpoint for detailed project health metrics PDF (#1750)
Add frontend integration for downloading project-specific PDF (#1750)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Possibly related PRs

Suggested labels

docs

Suggested reviewers

  • kasya
  • arkid15r
    """

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0c74ad and e0d5580.

📒 Files selected for processing (6)
  • backend/apps/owasp/api/internal/views/project_health_metrics.py (1 hunks)
  • backend/apps/owasp/utils/pdf.py (2 hunks)
  • backend/tests/apps/owasp/utils/pdf_test.py (2 hunks)
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx (2 hunks)
  • frontend/src/app/projects/dashboard/page.tsx (2 hunks)
  • frontend/src/server/fetchMetricsPDF.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/app/projects/dashboard/page.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx
  • backend/tests/apps/owasp/utils/pdf_test.py
  • backend/apps/owasp/api/internal/views/project_health_metrics.py
  • backend/apps/owasp/utils/pdf.py
  • frontend/src/server/fetchMetricsPDF.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
  • GitHub Check: CodeQL (python)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🔭 Outside diff range comments (2)
backend/apps/common/management/commands/purge_data.py (1)

23-23: Fix hardcoded "GitHub" string in print statement.

The print statement always displays "GitHub" regardless of which app is being processed, making logs confusing and incorrect for non-GitHub apps.

Apply this diff to fix the issue:

-                    print(f"Purged GitHub {model._meta.verbose_name_plural}")
+                    print(f"Purged {nest_app} {model._meta.verbose_name_plural}")
backend/tests/apps/common/management/commands/purge_data_test.py (1)

51-51: Fix hardcoded "GitHub" string in test assertion.

The test assertion hardcodes "GitHub" regardless of which app is being tested, making it test the incorrect behavior that exists in the main command.

Apply this diff to fix the test:

-                mock_print.assert_any_call(f"Purged GitHub {model_name}s")
+                mock_print.assert_any_call(f"Purged {app_name} {model_name}s")
🧹 Nitpick comments (23)
backend/apps/nest/graphql/nodes/api_key.py (1)

8-19: Secure field selection with good GraphQL patterns.

The ApiKeyNode implementation correctly excludes sensitive fields like hash while exposing essential information. The field selection aligns well with the frontend TypeScript interface.

Consider whether last_used_at should be included to help users track API key usage, though the current exclusion may be intentional for privacy reasons.

backend/apps/sitemap/views/member.py (1)

12-14: Consider optimizing the filtering for better performance.

The current implementation loads all non-bot users into memory before filtering by is_indexable. For better performance with large user counts, consider moving the is_indexable filter to the database level if possible.

def items(self):
    """Return list of members for sitemap generation."""
-   return [u for u in User.objects.filter(is_bot=False) if u.is_indexable]
+   # If is_indexable can be converted to a database filter:
+   # return User.objects.filter(is_bot=False, <indexable_conditions>)
+   return [u for u in User.objects.filter(is_bot=False) if u.is_indexable]

The current approach works correctly but may not scale optimally.

backend/apps/sitemap/views/committee.py (1)

12-14: Consider database-level filtering optimization.

Similar to the MemberSitemap, this implementation loads all active committees before filtering by is_indexable. Consider optimizing by moving the indexable filter to the database level if the is_indexable property can be converted to a database query condition.

def items(self):
    """Return list of committees for sitemap generation."""
-   return [c for c in Committee.objects.filter(is_active=True) if c.is_indexable]
+   # If is_indexable can be converted to a database filter:
+   # return Committee.objects.filter(is_active=True, <indexable_conditions>)
+   return [c for c in Committee.objects.filter(is_active=True) if c.is_indexable]
backend/apps/sitemap/views/chapter.py (1)

12-14: Consider database-level filtering optimization.

The current filtering approach works correctly. If is_indexable could be expressed as a database query rather than a Python property, it would improve performance for large datasets.

    def items(self):
        """Return list of chapters for sitemap generation."""
-       return [c for c in Chapter.objects.filter(is_active=True) if c.is_indexable]
+       # Consider moving is_indexable logic to database query if possible
+       return Chapter.objects.filter(is_active=True, <indexable_conditions>)

However, if is_indexable involves complex computed logic, the current approach is acceptable.

backend/apps/sitemap/views/project.py (1)

12-14: Consistent filtering pattern across sitemap classes.

The implementation matches the pattern used in other sitemap classes, ensuring consistency. The same optimization opportunity exists here as in ChapterSitemap regarding database-level filtering for is_indexable if feasible.

backend/tests/apps/sitemap/views/member_test.py (1)

7-14: Add test coverage for filtering logic.

The test correctly verifies the basic functionality but could benefit from testing the filtering behavior more thoroughly.

Consider adding a test case that verifies objects with is_indexable=False are filtered out:

@patch("apps.sitemap.views.member.User")
def test_items_filters_non_indexable(self, mock_user):
    indexable_obj = MagicMock(is_indexable=True)
    non_indexable_obj = MagicMock(is_indexable=False)
    mock_user.objects.filter.return_value = [indexable_obj, non_indexable_obj]
    sitemap = MemberSitemap()

    result = list(sitemap.items())
    assert result == [indexable_obj]
    assert non_indexable_obj not in result
frontend/src/components/BarChart.tsx (1)

84-91: Apply optional chaining for cleaner code.

The static analysis tool correctly identified an opportunity to use optional chaining.

Apply this diff to use optional chaining:

-              if (reverseColors && reverseColors[dataPointIndex]) {
+              if (reverseColors?.[dataPointIndex]) {
frontend/__tests__/unit/data/mockApiKeysData.ts (1)

1-34: Well-structured mock data for API key testing.

The mock data structure appropriately covers both API key listing and creation scenarios. The inclusion of both active and potentially expiring keys provides good test coverage for different API key states.

Consider adding a revoked API key example to the mock data to test the full range of API key states:

    {
      uuid: '2',
      name: 'mock key 2',
      isRevoked: false,
      createdAt: '2025-07-11T07:36:44.115179+00:00',
      expiresAt: '2025-07-12T00:00:00+00:00',
    },
+   {
+     uuid: '3',
+     name: 'revoked key',
+     isRevoked: true,
+     createdAt: '2025-07-10T07:36:44.115179+00:00',
+     expiresAt: null,
+   },
backend/apps/core/api/ninja.py (1)

11-33: Well-implemented API key authentication with minor docstring fix needed.

The authentication logic is sound with proper error handling and integration with the ApiKey model. The use of the walrus operator for the authentication check is clean and efficient.

Fix the return type annotation in the docstring:

        Returns:
-           APIKey: The APIKey object if the key is valid, otherwise None.
+           ApiKey: The ApiKey object if the key is valid, otherwise None.
frontend/src/components/GeneralCompliantComponent.tsx (1)

15-16: Minor redundancy in CSS classes.

There's a redundant dark:hover:bg-red-600 class in the redClass definition - it appears twice in the same string.

-  const redClass = 'bg-red-100 dark:bg-red-700 hover:bg-red-600 dark:hover:bg-red-600'
+  const redClass = 'bg-red-100 dark:bg-red-700 hover:bg-red-200 dark:hover:bg-red-600'
backend/tests/apps/sitemap/urls_test.py (1)

21-31: Consider using more explicit URL pattern matching.

While the current approach works, accessing the private _route attribute might be fragile. Consider using Django's reverse function to test URL resolution instead.

-        found_paths = {p.pattern._route for p in sitemap_urls.urlpatterns}
-        for path in expected_paths:
-            assert path in found_paths
+        from django.urls import reverse
+        
+        # Test that each sitemap URL can be resolved
+        expected_url_names = [
+            "sitemap",
+            "sitemap-chapters", 
+            "sitemap-committees",
+            "sitemap-projects",
+            "sitemap-static",
+            "sitemap-members"
+        ]
+        for url_name in expected_url_names:
+            try:
+                reverse(f"sitemap:{url_name}")
+            except Exception as e:
+                assert False, f"URL {url_name} not properly registered: {e}"
backend/apps/sitemap/urls.py (1)

27-46: Consider refactoring repetitive URL patterns.

While the individual sitemap endpoints are correctly implemented, the repetitive pattern could be refactored for better maintainability.

Consider using a loop to generate the individual sitemap URL patterns:

+# Individual sitemap URLs
+for section, sitemap_class in sitemaps.items():
+    urlpatterns.append(
+        path(
+            f"sitemap/{section}.xml",
+            cached_sitemap_view(sitemaps={section: sitemap_class}),
+        )
+    )
-    path(
-        "sitemap/chapters.xml",
-        cached_sitemap_view(sitemaps={"chapters": ChapterSitemap}),
-    ),
-    path(
-        "sitemap/committees.xml",
-        cached_sitemap_view(sitemaps={"committees": CommitteeSitemap}),
-    ),
-    path(
-        "sitemap/members.xml",
-        cached_sitemap_view(sitemaps={"members": MemberSitemap}),
-    ),
-    path(
-        "sitemap/projects.xml",
-        cached_sitemap_view(sitemaps={"projects": ProjectSitemap}),
-    ),
-    path(
-        "sitemap/static.xml",
-        cached_sitemap_view(sitemaps={"static": StaticSitemap}),
-    ),
frontend/src/components/skeletons/ApiKeySkelton.tsx (1)

53-74: Consider adding accessibility improvements.

While the skeleton implementation is solid, consider adding accessibility enhancements for screen readers.

Add ARIA labels for better accessibility:

 <table className="w-full border-collapse">
+  <caption className="sr-only">Loading API keys data</caption>
   <thead>
     <tr className="border-b border-gray-200 dark:border-gray-700">

And consider adding aria-busy="true" to the main container:

-<div className="flex min-h-[80vh] flex-col items-center p-8">
+<div className="flex min-h-[80vh] flex-col items-center p-8" aria-busy="true" aria-label="Loading API keys">
frontend/__tests__/e2e/pages/ProjectsHealthDashboardOverview.spec.ts (1)

28-61: Consider using more specific selectors for better test reliability.

While the current text-based assertions work, using more specific selectors could improve test reliability and maintainability.

Consider using data-testid attributes or more specific selectors:

-await expect(
-  page.getByText(
-    mockProjectsDashboardOverviewData.projectHealthStats.projectsCountHealthy.toString()
-  )
-).toBeVisible()
+await expect(
+  page.getByTestId('healthy-projects-count')
+).toHaveText(
+  mockProjectsDashboardOverviewData.projectHealthStats.projectsCountHealthy.toString()
+)

This approach would require adding data-testid attributes to the dashboard components but would make the tests more robust against text changes.

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

3-26: Consider adding accessibility attributes for better screen reader support.

The component implementation is solid with good visual design and responsive behavior. However, consider adding ARIA attributes to improve accessibility.

    <div
-      className={`group relative flex h-20 w-20 flex-col items-center justify-center rounded-full border-2 shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl ${scoreStyle}`}
+      className={`group relative flex h-20 w-20 flex-col items-center justify-center rounded-full border-2 shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl ${scoreStyle}`}
+      role="img"
+      aria-label={`Health score: ${score} out of 100`}
    >
backend/tests/apps/nest/graphql/queries/api_keys_test.py (1)

8-16: Consider consolidating duplicate mock_info functions.

The mock_info function here is nearly identical to the one in backend/tests/apps/nest/graphql/mutations/api_key_test.py (lines 17-25), with only minor docstring differences. Consider extracting this to a shared test utility module to reduce code duplication.

# In a shared test utilities module (e.g., tests/utils.py)
def create_mock_graphql_info(user_pk: int = 1, is_authenticated: bool = True):
    """Return a mocked GraphQL Info object with user context."""
    info = MagicMock()
    info.context = MagicMock()
    info.context.request = MagicMock()
    info.context.request.user = MagicMock()
    info.context.request.user.is_authenticated = is_authenticated
    info.context.request.user.pk = user_pk
    return info
frontend/src/app/settings/api-keys/page.tsx (1)

93-100: Consider using a more precise regex pattern

The current regex pattern /[^a-zA-Z0-9\s-]/ uses negation to check for invalid characters. Consider using a positive assertion pattern for better clarity and maintainability:

-if (newKeyName.match(/[^a-zA-Z0-9\s-]/)) {
+if (!newKeyName.match(/^[a-zA-Z0-9\s-]+$/)) {

This ensures the entire string consists only of allowed characters and prevents edge cases with empty strings.

backend/apps/nest/models/api_key.py (1)

11-13: Consider adding documentation for these constants.

The constants lack inline documentation explaining their purpose and rationale.

-API_KEY_LENGTH = 32
-MAX_ACTIVE_KEYS = 3
-MAX_WORD_LENGTH = 100
+API_KEY_LENGTH = 32  # Length of the raw API key in bytes (256 bits of entropy)
+MAX_ACTIVE_KEYS = 3  # Maximum number of active API keys per user
+MAX_WORD_LENGTH = 100  # Maximum length for API key names
frontend/__tests__/unit/pages/ApiKeysPage.test.tsx (1)

150-176: Verify date handling in API key creation test.

The test uses addDays and format from date-fns but the assertion uses expect.any(Date). This might miss date formatting issues.

Consider making the date assertion more specific:

       expect(mockCreateMutation).toHaveBeenCalledWith({
         variables: expect.objectContaining({
           name: expectedVariables.name,
-          expiresAt: expect.any(Date),
+          expiresAt: expectedVariables.expiresAt,
         }),
       })
backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py (1)

41-42: Consider adding PDF metadata.

The PDF should include additional metadata for better document management.

         buffer = BytesIO()
         pdf = canvas.Canvas(buffer, pagesize=letter)
         pdf.setTitle(f"Health Metrics Report for {metrics.project.name}")
+        pdf.setAuthor("OWASP Nest")
+        pdf.setSubject("Project Health Metrics")
+        pdf.setCreator("OWASP Nest Management Command")
frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx (1)

59-74: Review header layout structure

The header contains three elements (title, button, and score circle) with justify-between, which may not provide ideal spacing. Consider restructuring for better alignment.

-<div className="flex items-center justify-between">
-  <h1 className="text-2xl font-bold">{metrics.projectName}</h1>
-  <Button
-    variant="solid"
-    color="primary"
-    onPress={async () => {
-      await fetchMetricsPDF(
-        `owasp/project-health-metrics/${projectKey}/pdf`,
-        `${projectKey}-health-metrics.pdf`
-      )
-    }}
-  >
-    Download PDF
-  </Button>
-  <MetricsScoreCircle score={metrics.score} />
-</div>
+<div className="flex items-center justify-between">
+  <div className="flex items-center gap-4">
+    <h1 className="text-2xl font-bold">{metrics.projectName}</h1>
+    <MetricsScoreCircle score={metrics.score} />
+  </div>
+  <Button
+    variant="solid"
+    color="primary"
+    onPress={async () => {
+      await fetchMetricsPDF(
+        `owasp/project-health-metrics/${projectKey}/pdf`,
+        `${projectKey}-health-metrics.pdf`
+      )
+    }}
+  >
+    Download PDF
+  </Button>
+</div>
backend/apps/nest/graphql/mutations/user.py (2)

18-25: Consider removing unused code field

The code field in LogoutResult is never set in the implementation. Consider removing it if not needed, or document its intended use for future implementations.


40-94: Well-implemented authentication flow with robust error handling

The enhanced GitHub authentication implementation properly validates the user type, checks for a verified primary email, and provides clear error messages for different failure scenarios. The use of Django's session-based authentication is appropriate.

Minor suggestion for the email retrieval logic at lines 47-49: Consider extracting this into a helper method for better readability:

-                gh_user_email = next(
-                    (e.email for e in gh_user.get_emails() if e.primary and e.verified), ""
-                )
+                gh_user_email = self._get_primary_verified_email(gh_user)

@ahmedxgouda ahmedxgouda force-pushed the dashboard/pdf-metrics-details-client branch from 93f8bb1 to cbefe73 Compare July 19, 2025 19:56
@github-actions github-actions bot added backend and removed docker Pull requests that update Docker code nestbot schema labels Jul 19, 2025
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

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

84-91: Simplify the condition using optional chaining.

The static analysis tool correctly identified an opportunity to use optional chaining for cleaner code.

-              if (reverseColors && reverseColors[dataPointIndex]) {
+              if (reverseColors?.[dataPointIndex]) {
frontend/src/server/queries/projectsHealthDashboardQueries.ts (1)

21-53: LGTM! Comprehensive query structure for detailed health metrics.

The new GET_PROJECT_HEALTH_METRICS_DETAILS query is well-structured and follows GraphQL best practices. The field selection is comprehensive, covering all essential health metrics including compliance flags, temporal data, counts, and scores necessary for the PDF generation feature.

The query properly:

  • Uses required parameter typing (String!)
  • Follows consistent camelCase naming
  • Retrieves latest health metrics via healthMetricsLatest
  • Includes all fields mentioned in the AI summary for backend integration

Consider grouping related fields with comments for better maintainability:

 export const GET_PROJECT_HEALTH_METRICS_DETAILS = gql`
   query Project($projectKey: String!) {
     project(key: $projectKey) {
       healthMetricsLatest {
+        # Basic project info
         ageDays
         ageDaysRequirement
         createdAt
         projectName
         score
+        
+        # Repository statistics
         contributorsCount
         forksCount
         starsCount
+        
+        # Activity metrics
         lastCommitDays
         lastCommitDaysRequirement
         lastPullRequestDays
         lastPullRequestDaysRequirement
         lastReleaseDays
         lastReleaseDaysRequirement
         owaspPageLastUpdateDays
         owaspPageLastUpdateDaysRequirement
+        
+        # Issue and PR tracking
         openIssuesCount
         openPullRequestsCount
         totalIssuesCount
         unassignedIssuesCount
         unansweredIssuesCount
+        
+        # Release information
         recentReleasesCount
         totalReleasesCount
+        
+        # Compliance flags
         isFundingRequirementsCompliant
         isLeaderRequirementsCompliant
       }
     }
   }
 `
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93f8bb1 and cbefe73.

⛔ Files ignored due to path filters (1)
  • backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • .gitignore (1 hunks)
  • backend/apps/owasp/Makefile (1 hunks)
  • backend/apps/owasp/api/v1/project_health_metrics.py (1 hunks)
  • backend/apps/owasp/api/v1/urls.py (1 hunks)
  • backend/apps/owasp/graphql/nodes/project.py (1 hunks)
  • backend/apps/owasp/graphql/nodes/project_health_metrics.py (4 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py (1 hunks)
  • backend/apps/owasp/models/project_health_metrics.py (3 hunks)
  • backend/pyproject.toml (1 hunks)
  • backend/tests/apps/owasp/graphql/nodes/project_health_metrics_test.py (2 hunks)
  • backend/tests/apps/owasp/graphql/nodes/project_test.py (1 hunks)
  • backend/tests/apps/owasp/graphql/queries/project_health_metrics_test.py (1 hunks)
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py (1 hunks)
  • cspell/custom-dict.txt (2 hunks)
  • frontend/__tests__/unit/data/mockProjectDetailsData.ts (1 hunks)
  • frontend/__tests__/unit/data/mockProjectsDashboardMetricsDetailsData.ts (1 hunks)
  • frontend/src/app/projects/[projectKey]/page.tsx (1 hunks)
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx (1 hunks)
  • frontend/src/components/BarChart.tsx (2 hunks)
  • frontend/src/components/CardDetailsPage.tsx (2 hunks)
  • frontend/src/components/DashboardCard.tsx (1 hunks)
  • frontend/src/components/GeneralCompliantComponent.tsx (1 hunks)
  • frontend/src/components/MetricsScoreCircle.tsx (1 hunks)
  • frontend/src/server/fetchMetricsPDF.ts (1 hunks)
  • frontend/src/server/queries/projectQueries.ts (1 hunks)
  • frontend/src/server/queries/projectsHealthDashboardQueries.ts (1 hunks)
  • frontend/src/types/healthMetrics.ts (1 hunks)
  • frontend/src/types/project.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • frontend/tests/unit/data/mockProjectsDashboardMetricsDetailsData.ts
  • frontend/src/components/GeneralCompliantComponent.tsx
🚧 Files skipped from review as they are similar to previous changes (20)
  • .gitignore
  • backend/pyproject.toml
  • frontend/src/app/projects/[projectKey]/page.tsx
  • cspell/custom-dict.txt
  • frontend/tests/unit/data/mockProjectDetailsData.ts
  • backend/tests/apps/owasp/graphql/queries/project_health_metrics_test.py
  • backend/apps/owasp/api/v1/urls.py
  • backend/apps/owasp/Makefile
  • backend/tests/apps/owasp/graphql/nodes/project_health_metrics_test.py
  • frontend/src/components/CardDetailsPage.tsx
  • frontend/src/components/DashboardCard.tsx
  • backend/apps/owasp/graphql/nodes/project.py
  • backend/apps/owasp/api/v1/project_health_metrics.py
  • backend/tests/apps/owasp/graphql/nodes/project_test.py
  • frontend/src/components/MetricsScoreCircle.tsx
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx
  • backend/apps/owasp/models/project_health_metrics.py
  • backend/apps/owasp/graphql/nodes/project_health_metrics.py
🧰 Additional context used
🧠 Learnings (5)
frontend/src/server/queries/projectQueries.ts (2)
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a safety check that ensures HealthMetrics component is only rendered when healthMetricsData exists and has at least one element: `healthMetricsData && healthMetricsData.length > 0`. This makes accessing data[0] safe within the HealthMetrics component.
frontend/src/types/healthMetrics.ts (4)
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a safety check that ensures HealthMetrics component is only rendered when healthMetricsData exists and has at least one element: `healthMetricsData && healthMetricsData.length > 0`. This makes accessing data[0] safe within the HealthMetrics component.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/GradientRadialChart.tsx:113-113
Timestamp: 2025-06-21T12:22:01.889Z
Learning: In the OWASP Nest project, health metrics requirements (like lastCommitDaysRequirement, lastReleaseDaysRequirement) should never be 0. A requirement value of 0 is considered invalid and should result in displaying 0 on the radial chart.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1703
File: frontend/src/components/BarChart.tsx:33-46
Timestamp: 2025-07-03T03:08:03.290Z
Learning: In the OWASP Nest project's BarChart component (frontend/src/components/BarChart.tsx), the days and requirements arrays are guaranteed to always have the same length in their use cases, so input validation for array length matching is not needed.
frontend/src/server/queries/projectsHealthDashboardQueries.ts (2)
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a safety check that ensures HealthMetrics component is only rendered when healthMetricsData exists and has at least one element: `healthMetricsData && healthMetricsData.length > 0`. This makes accessing data[0] safe within the HealthMetrics component.
frontend/src/types/project.ts (2)
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a safety check that ensures HealthMetrics component is only rendered when healthMetricsData exists and has at least one element: `healthMetricsData && healthMetricsData.length > 0`. This makes accessing data[0] safe within the HealthMetrics component.
frontend/src/components/BarChart.tsx (3)
Learnt from: ahmedxgouda
PR: OWASP/Nest#1703
File: frontend/src/components/BarChart.tsx:33-46
Timestamp: 2025-07-03T03:08:03.290Z
Learning: In the OWASP Nest project's BarChart component (frontend/src/components/BarChart.tsx), the days and requirements arrays are guaranteed to always have the same length in their use cases, so input validation for array length matching is not needed.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/GradientRadialChart.tsx:67-116
Timestamp: 2025-06-21T12:21:32.372Z
Learning: The react-apexcharts Chart component does not support ARIA attributes like aria-label and role as direct props. To add accessibility attributes to ApexCharts in React, wrap the Chart component in a container div with the appropriate ARIA attributes.
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.
🧬 Code Graph Analysis (2)
frontend/src/server/fetchMetricsPDF.ts (2)
frontend/src/utils/credentials.ts (1)
  • API_URL (1-1)
frontend/src/app/global-error.tsx (1)
  • handleAppError (66-86)
frontend/src/types/project.ts (1)
frontend/src/types/healthMetrics.ts (1)
  • HealthMetricsProps (26-52)
🪛 Biome (1.9.4)
frontend/src/components/BarChart.tsx

[error] 84-84: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (5)
frontend/src/server/fetchMetricsPDF.ts (1)

15-31: LGTM! Proper blob download implementation.

The blob handling, URL creation, download triggering, and cleanup logic follows best practices for browser file downloads. Good error handling with the global error handler integration.

frontend/src/types/project.ts (1)

22-22: LGTM! Property rename aligns with backend changes.

The rename from healthMetrics to healthMetricsList maintains type safety while aligning with the backend GraphQL schema updates mentioned in the AI summary.

frontend/src/server/queries/projectQueries.ts (1)

15-15: LGTM! GraphQL query field rename is consistent.

The rename from healthMetrics to healthMetricsList maintains query functionality while aligning with the backend schema and frontend type changes.

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

20-21: LGTM! Well-designed optional prop for color inversion.

The reverseColors prop provides flexible color logic for different metric visualization needs while maintaining backward compatibility.

frontend/src/types/healthMetrics.ts (1)

27-52: LGTM! Comprehensive type extension for detailed metrics.

The new properties are well-typed and consistently named, supporting the enhanced health metrics functionality. The additions align with the backend model extensions and provide good type safety for the new dashboard features.

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

🧹 Nitpick comments (2)
backend/apps/owasp/api/rest/v1/project_health_metrics.py (2)

30-30: Consider using context manager for file handling.

The current implementation opens the file via Path.open() within FileResponse, but using a context manager would make the resource management more explicit.

Consider this approach for better resource management:

-    return FileResponse(Path.open(pdf_path, "rb"), as_attachment=True, filename=file_name)
+    return FileResponse(pdf_path.open("rb"), as_attachment=True, filename=file_name)

Note: Django's FileResponse handles file closing automatically, so the current implementation is actually safe, but using pdf_path.open() is more idiomatic.


16-30: Consider architectural improvements for production readiness.

The current implementation has several areas that could be improved for better performance and maintainability:

  1. Asynchronous processing: PDF generation is synchronous and could be slow for large datasets
  2. File cleanup: Generated PDFs will accumulate over time with no cleanup mechanism
  3. Caching: No mechanism to avoid regenerating the same PDF repeatedly
  4. Concurrency: Multiple requests for the same project could cause race conditions

Consider implementing:

  • A background task queue (Celery) for PDF generation
  • A cleanup job to remove old PDF files
  • Caching mechanism based on metrics data freshness
  • File locking or unique temporary filenames to handle concurrent requests
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbefe73 and 04306e3.

📒 Files selected for processing (2)
  • backend/apps/owasp/api/rest/v1/project_health_metrics.py (1 hunks)
  • backend/apps/owasp/api/rest/v1/urls.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/apps/owasp/api/rest/v1/urls.py
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run backend tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (1)
backend/apps/owasp/api/rest/v1/project_health_metrics.py (1)

1-13: LGTM! Clean imports and proper setup.

The imports are well-organized and appropriate for the functionality. Using pathlib.Path is a good choice for cross-platform file handling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
backend/apps/owasp/api/rest/v1/project_health_metrics.py (1)

12-14: Add input validation for project_key parameter.

The project_key parameter should be validated to prevent potential security issues like path traversal attacks or injection vulnerabilities.

Consider adding validation like this:

+import re
+
 @router.get("/{project_key}/pdf")
 def get_project_health_metrics_pdf(request, project_key: str):
     """Generate and return a PDF report of project health metrics."""
+    # Validate project_key format (alphanumeric, hyphens, underscores only)
+    if not re.match(r'^[a-zA-Z0-9_-]+$', project_key):
+        raise HttpError(400, "Invalid project key format")
+    
🧹 Nitpick comments (2)
backend/apps/owasp/models/project_health_metrics.py (1)

184-189: Consider making PDF layout constants configurable.

The hard-coded positioning and font settings work but could be made more maintainable.

Consider extracting constants:

+# PDF Layout Constants
+PDF_TITLE_Y = 700
+PDF_SUBTITLE_Y = 680
+PDF_TITLE_X = 300
+DEFAULT_FONT = "Helvetica"
+DEFAULT_FONT_SIZE = 12

 buffer = BytesIO()
 pdf = canvas.Canvas(buffer, pagesize=letter)
 pdf.setTitle(f"Health Metrics Report for {metrics.project.name}")
-pdf.setFont("Helvetica", 12)
-pdf.drawCentredString(300, 700, f"Health Metrics Report for {metrics.project.name}")
-pdf.drawCentredString(300, 680, f"Health Score: {metrics.score:.2f}")
+pdf.setFont(DEFAULT_FONT, DEFAULT_FONT_SIZE)
+pdf.drawCentredString(PDF_TITLE_X, PDF_TITLE_Y, f"Health Metrics Report for {metrics.project.name}")
+pdf.drawCentredString(PDF_TITLE_X, PDF_SUBTITLE_Y, f"Health Score: {metrics.score:.2f}")
backend/tests/apps/owasp/models/project_health_metrics_test.py (1)

123-201: Consider adding edge case tests.

The current test covers the happy path, but consider adding tests for:

  • When no metrics are found (should return None)
  • When metrics have null values for optional fields
  • Error handling scenarios

Add these test cases:

def test_generate_detailed_pdf_no_metrics_found(self):
    """Should return None when no metrics are found."""
    with patch(
        "apps.owasp.models.project_health_metrics.ProjectHealthMetrics.get_latest_health_metrics"
    ) as mock_get_latest:
        mock_get_latest.return_value.filter.return_value.first.return_value = None
        result = ProjectHealthMetrics.generate_latest_metrics_pdf("nonexistent")
        assert result is None

def test_generate_detailed_pdf_with_null_values(self):
    """Should handle metrics with null optional fields."""
    # Test implementation for null handling
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04306e3 and 5d59233.

📒 Files selected for processing (6)
  • backend/apps/owasp/api/rest/v1/project_health_metrics.py (1 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py (0 hunks)
  • backend/apps/owasp/models/project_health_metrics.py (2 hunks)
  • backend/tests/apps/owasp/api/rest/v1/urls_test.py (2 hunks)
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py (0 hunks)
  • backend/tests/apps/owasp/models/project_health_metrics_test.py (2 hunks)
💤 Files with no reviewable changes (2)
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ahmedxgouda
PR: OWASP/Nest#1633
File: frontend/src/components/HealthMetrics.tsx:30-30
Timestamp: 2025-06-20T16:12:59.256Z
Learning: In the DetailsCard component (frontend/src/components/CardDetailsPage.tsx), there's a length check before rendering HealthMetrics: `healthMetricsData.length > 0`. This ensures that when HealthMetrics is rendered, the data array has at least one element, making accessing data[0] safe within the HealthMetrics component.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (6)
backend/tests/apps/owasp/api/rest/v1/urls_test.py (1)

7-7: LGTM! Router registration follows established pattern.

The import and router registration correctly follow the existing test structure and naming conventions.

Also applies to: 19-19

backend/apps/owasp/models/project_health_metrics.py (3)

164-183: LGTM! Method signature and data retrieval logic are well-implemented.

The method correctly queries for the latest health metrics using the established pattern and handles the case where no metrics are found.


190-218: Table data construction is comprehensive and well-structured.

The table includes all relevant metrics with clear labels and proper data formatting. The compliance status inversion logic is correct.


219-241: PDF table styling and finalization look good.

The table styling provides good visual hierarchy with headers and proper alignment. The buffer handling is correct for API consumption.

backend/tests/apps/owasp/models/project_health_metrics_test.py (2)

138-164: Mock setup is comprehensive and realistic.

The mock metrics object includes all necessary attributes with realistic values, and the query chain mocking correctly simulates the database interaction.


165-201: Test assertions thoroughly verify PDF generation.

The test properly verifies that all PDF generation components are called with correct parameters, including table data structure and canvas operations.

@ahmedxgouda ahmedxgouda force-pushed the dashboard/pdf-metrics-details-client branch from 2917f89 to 53bf2ae Compare July 28, 2025 05:47
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: 5

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

22-26: Improve accessibility for the download button.

The button lacks proper accessibility attributes and semantic meaning for screen readers.

       <FontAwesomeIcon
         icon={faFileArrowDown}
         className="ml-2 h-7 w-7 cursor-pointer text-gray-500 transition-colors duration-200 hover:text-gray-700"
+        role="button"
+        tabIndex={0}
+        aria-label={`Download ${fileName} PDF`}
+        onKeyDown={(e) => {
+          if (e.key === 'Enter' || e.key === ' ') {
+            e.preventDefault()
+            // Trigger the same onClick logic
+          }
+        }}
         onClick={async () => await fetchMetricsPDF(path, fileName)}
       />
backend/tests/apps/owasp/models/project_health_metrics_test.py (1)

163-165: Consider testing the None case for better coverage.

The test only covers the success case where metrics are found. Consider adding a test case for when no metrics are found to ensure the method returns None as expected.

Add a separate test method:

@patch(
    "apps.owasp.models.project_health_metrics.ProjectHealthMetrics.get_latest_health_metrics",
)
def test_generate_detailed_pdf_no_metrics(self, mock_get_latest_health_metrics):
    """Test PDF generation when no metrics are found."""
    mock_get_latest_health_metrics.return_value.filter.return_value.first.return_value = None
    result = ProjectHealthMetrics.generate_latest_metrics_pdf("nonexistent")
    assert result is None
backend/apps/owasp/models/project_health_metrics.py (1)

234-247: Consider extracting table styling to a reusable constant.

The table styling configuration is duplicated between the two PDF generation methods, which violates the DRY principle.

Add a class constant for reusable table styling:

 class ProjectHealthMetrics(BulkSaveModel, TimestampedModel):
     """Project health metrics model."""
+    
+    _PDF_TABLE_STYLE = [
+        ("BACKGROUND", (0, 0), (-1, 0), "lightgrey"),
+        ("TEXTCOLOR", (0, 0), (-1, 0), "black"),
+        ("ALIGN", (0, 0), (-1, -1), "CENTER"),
+        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
+        ("BOTTOMPADDING", (0, 0), (-1, 0), 5),
+        ("BACKGROUND", (0, 1), (-1, -1), "white"),
+    ]

     class Meta:

Then use it in both methods:

         table = Table(
             table_data,
             colWidths="*",
-            style=TableStyle(
-                [
-                    ("BACKGROUND", (0, 0), (-1, 0), "lightgrey"),
-                    ("TEXTCOLOR", (0, 0), (-1, 0), "black"),
-                    ("ALIGN", (0, 0), (-1, -1), "CENTER"),
-                    ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
-                    ("BOTTOMPADDING", (0, 0), (-1, 0), 5),
-                    ("BACKGROUND", (0, 1), (-1, -1), "white"),
-                ]
-            ),
+            style=TableStyle(cls._PDF_TABLE_STYLE),
         )
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2917f89 and 53bf2ae.

📒 Files selected for processing (15)
  • backend/apps/owasp/api/internal/views/project_health_metrics.py (1 hunks)
  • backend/apps/owasp/api/internal/views/urls.py (1 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_overview_pdf.py (0 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py (0 hunks)
  • backend/apps/owasp/models/project_health_metrics.py (2 hunks)
  • backend/settings/urls.py (1 hunks)
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_overview_pdf_test.py (0 hunks)
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py (0 hunks)
  • backend/tests/apps/owasp/models/project_health_metrics_test.py (2 hunks)
  • frontend/.env.example (1 hunks)
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx (2 hunks)
  • frontend/src/app/projects/dashboard/page.tsx (2 hunks)
  • frontend/src/components/MetricsPDFButton.tsx (1 hunks)
  • frontend/src/server/fetchMetricsPDF.ts (1 hunks)
  • frontend/src/utils/credentials.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_overview_pdf.py
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_overview_pdf_test.py
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py
✅ Files skipped from review due to trivial changes (5)
  • frontend/src/utils/credentials.ts
  • backend/settings/urls.py
  • frontend/.env.example
  • frontend/src/app/projects/dashboard/page.tsx
  • backend/apps/owasp/api/internal/views/urls.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx
  • frontend/src/server/fetchMetricsPDF.ts
🧰 Additional context used
🪛 GitHub Check: CodeQL
backend/apps/owasp/api/internal/views/project_health_metrics.py

[warning] 25-25: Reflected server-side cross-site scripting
Cross-site scripting vulnerability due to a user-provided value.

🪛 Ruff (0.12.2)
backend/apps/owasp/models/project_health_metrics.py

199-199: Line too long (102 > 99)

(E501)


207-207: Line too long (112 > 99)

(E501)

🪛 GitHub Actions: Run CI/CD
backend/apps/owasp/models/project_health_metrics.py

[error] 199-199: Ruff: Line too long (102 > 99) (E501)


[error] 207-207: Ruff: Line too long (112 > 99) (E501)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (6)
backend/apps/owasp/api/internal/views/project_health_metrics.py (1)

10-17: LGTM! Well-structured overview PDF generation.

The overview PDF generation view is properly implemented with appropriate decorators and response handling.

backend/tests/apps/owasp/models/project_health_metrics_test.py (2)

131-205: Comprehensive test coverage for detailed PDF generation.

The test properly mocks all ReportLab dependencies and verifies the complete PDF generation flow with appropriate assertions.


211-265: Well-structured test for overview PDF generation.

The test correctly mocks the dependencies and validates the overview PDF generation with proper table data and formatting expectations.

backend/apps/owasp/models/project_health_metrics.py (3)

164-183: LGTM! Well-implemented PDF generation with proper error handling.

The method correctly handles the case where no metrics are found by returning None, and the project key filtering logic is appropriate.


258-319: LGTM! Clean implementation of overview PDF generation.

The overview PDF generation method is well-structured with appropriate data formatting and PDF creation logic.


175-178: Project key prefix validated – no changes needed.

The www-project- prefix is used consistently across the codebase (in utility functions, model queries, API resolvers, and numerous tests). No inconsistencies were found.

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

♻️ Duplicate comments (2)
backend/apps/owasp/models/project_health_metrics.py (2)

199-203: Fix the tuple structure in table data.

The "Last Pull Request" row has an incorrect tuple structure that will cause rendering issues in the PDF table. Each table cell should be a single string value, not a tuple.

 [
     "Last Pull Request",
-    (
-        f"{metrics.last_pull_request_days}",
-        f"/{metrics.last_pull_request_days_requirement} days",
-    ),
+    f"{metrics.last_pull_request_days}/"
+    f"{metrics.last_pull_request_days_requirement} days",
 ],

209-214: Fix the tuple structure in table data.

Similar to the "Last Pull Request" row, the "OWASP Page Last Update" row has an incorrect tuple structure that will cause rendering issues.

 [
     "OWASP Page Last Update",
-    (
-        f"{metrics.owasp_page_last_update_days}",
-        f"/{metrics.owasp_page_last_update_days_requirement} days",
-    ),
+    f"{metrics.owasp_page_last_update_days}/"
+    f"{metrics.owasp_page_last_update_days_requirement} days",
 ],
🧹 Nitpick comments (2)
backend/apps/owasp/models/project_health_metrics.py (2)

242-242: Consider using explicit column widths for better table layout.

Using colWidths="*" may not provide optimal table formatting. Consider specifying explicit column widths for better control over the PDF layout.

-    colWidths="*",
+    colWidths=[200, 200],  # Adjust widths as needed

164-325: Consider extracting common PDF styling logic.

Both PDF generation methods contain similar table styling and canvas setup code. Consider extracting this into helper methods to follow DRY principles.

This would improve maintainability and reduce code duplication. You could create helper methods like _create_pdf_table_style() and _setup_pdf_canvas().

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53bf2ae and b897409.

📒 Files selected for processing (2)
  • backend/apps/owasp/models/project_health_metrics.py (2 hunks)
  • backend/tests/apps/owasp/models/project_health_metrics_test.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/apps/owasp/models/project_health_metrics_test.py
🧰 Additional context used
🧠 Learnings (1)
backend/apps/owasp/models/project_health_metrics.py (1)

Learnt from: ahmedxgouda
PR: #1633
File: frontend/src/components/GradientRadialChart.tsx:113-113
Timestamp: 2025-06-21T12:22:01.889Z
Learning: In the OWASP Nest project, health metrics requirements (like lastCommitDaysRequirement, lastReleaseDaysRequirement) should never be 0. A requirement value of 0 is considered invalid and should result in displaying 0 on the radial chart.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run frontend e2e tests
  • GitHub Check: Run frontend unit tests
  • GitHub Check: Run backend tests
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (2)
backend/apps/owasp/models/project_health_metrics.py (2)

3-11: LGTM! Clean import additions for PDF functionality.

The new imports are well-organized and necessary for the PDF generation functionality. The imports follow Python conventions with standard library imports first, followed by third-party imports.


264-325: LGTM! Well-structured overview PDF generation.

The generate_overview_pdf method is well-implemented with proper structure and error handling considerations. The logic flows clearly and uses appropriate ReportLab components.

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

♻️ Duplicate comments (3)
backend/apps/owasp/models/project_health_metrics.py (3)

164-183: Add input validation for project_key parameter.

The method lacks validation for the project_key parameter as previously suggested. Consider adding validation to ensure the parameter is valid before using it in the query.

 @staticmethod
 def generate_latest_metrics_pdf(project_key: str) -> BytesIO | None:
     """Generate a PDF report of the latest health metrics for a project.

     Args:
         project_key (str): The key of the OWASP project.

     Returns:
         BytesIO: A buffer containing the generated PDF report.

     """
+    if not project_key or not isinstance(project_key, str):
+        return None
+    
     metrics = (
         ProjectHealthMetrics.get_latest_health_metrics()
         .filter(project__key=f"www-project-{project_key}")
         .first()
     )

     if not metrics:
         return None

189-189: Handle potential None values in score formatting.

The score formatting still assumes metrics.score is not None, but the model field allows null values. This could cause a TypeError as previously noted.

-    pdf.drawCentredString(300, 680, f"Health Score: {metrics.score:.2f}")
+    score_text = f"Health Score: {metrics.score:.2f}" if metrics.score is not None else "Health Score: N/A"
+    pdf.drawCentredString(300, 680, score_text)

184-255: Add comprehensive error handling for PDF generation.

The PDF generation logic lacks error handling for potential ReportLab exceptions or memory issues. The code should be wrapped in a try-catch block as previously suggested.

     buffer = BytesIO()
-    pdf = Canvas(buffer, pagesize=letter)
-    pdf.setTitle(f"Health Metrics Report for {metrics.project.name}")
-    # ... rest of PDF generation code ...
-    pdf.showPage()
-    pdf.save()
-    buffer.seek(0)
-    return buffer
+    try:
+        pdf = Canvas(buffer, pagesize=letter)
+        pdf.setTitle(f"Health Metrics Report for {metrics.project.name}")
+        # ... rest of PDF generation code ...
+        pdf.showPage()
+        pdf.save()
+        buffer.seek(0)
+        return buffer
+    except Exception:
+        return None
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b897409 and 875356e.

📒 Files selected for processing (4)
  • backend/apps/owasp/api/internal/views/project_health_metrics.py (1 hunks)
  • backend/apps/owasp/models/project_health_metrics.py (2 hunks)
  • backend/apps/owasp/utils.py (1 hunks)
  • backend/tests/apps/owasp/models/project_health_metrics_test.py (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/apps/owasp/utils.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/apps/owasp/api/internal/views/project_health_metrics.py
  • backend/tests/apps/owasp/models/project_health_metrics_test.py
🧰 Additional context used
🧠 Learnings (1)
backend/apps/owasp/models/project_health_metrics.py (1)

Learnt from: ahmedxgouda
PR: #1633
File: frontend/src/components/GradientRadialChart.tsx:113-113
Timestamp: 2025-06-21T12:22:01.889Z
Learning: In the OWASP Nest project, health metrics requirements (like lastCommitDaysRequirement, lastReleaseDaysRequirement) should never be 0. A requirement value of 0 is considered invalid and should result in displaying 0 on the radial chart.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run frontend e2e tests
🔇 Additional comments (1)
backend/apps/owasp/models/project_health_metrics.py (1)

3-3: LGTM! Appropriate imports for PDF generation.

The imports are well-chosen for the PDF generation functionality - BytesIO for in-memory handling, ReportLab for PDF creation, and a utility function for table creation.

Also applies to: 9-10, 15-15

@ahmedxgouda ahmedxgouda marked this pull request as ready for review July 28, 2025 06:49
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.

Please apply all suggestions fixes from #1756 first

@ahmedxgouda ahmedxgouda force-pushed the dashboard/pdf-metrics-details-client branch from 7301ccc to 2845221 Compare July 29, 2025 12:31
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: 7

🧹 Nitpick comments (4)
backend/apps/owasp/utils/pdf.py (3)

13-29: Consider making styling configurable and improving parameter documentation.

The function works correctly but could benefit from:

  1. Better documentation of the col_widths parameter (what does "*" mean?)
  2. Consider extracting hardcoded styling into constants or making it configurable
+# Default table styling constants
+DEFAULT_HEADER_BG = "#f2f2f2"
+DEFAULT_CELL_BG = "#ffffff"
+DEFAULT_GRID_COLOR = "#dddddd"
+
-def create_table(data, col_widths="*"):
+def create_table(data, col_widths="*"):
-    """Create a styled table for PDF generation."""
+    """Create a styled table for PDF generation.
+    
+    Args:
+        data: Table data as list of lists
+        col_widths: Column widths ("*" for equal widths, or list of widths)
+    """
     return Table(
         data,
         colWidths=col_widths,
         style=TableStyle(
             [
-                ("BACKGROUND", (0, 0), (-1, 0), "#f2f2f2"),
+                ("BACKGROUND", (0, 0), (-1, 0), DEFAULT_HEADER_BG),
                 ("TEXTCOLOR", (0, 0), (-1, 0), "#000000"),
                 ("ALIGN", (0, 0), (-1, -1), "CENTER"),
                 ("FONTNAME", (0, 0), (-1, -1), "Helvetica"),
                 ("BOTTOMPADDING", (0, 0), (-1, -1), 12),
-                ("BACKGROUND", (0, 1), (-1, -1), "#ffffff"),
-                ("GRID", (0, 0), (-1, -1), 1, "#dddddd"),
+                ("BACKGROUND", (0, 1), (-1, -1), DEFAULT_CELL_BG),
+                ("GRID", (0, 0), (-1, -1), 1, DEFAULT_GRID_COLOR),
             ]
         ),
     )

42-84: Extract magic numbers to improve maintainability.

The function contains many hardcoded positioning and sizing values that make it difficult to maintain and understand the layout.

+# PDF layout constants
+PAGE_WIDTH = 300
+TITLE_Y_POSITION = 800
+TABLE_WIDTH = 400
+TABLE_HEIGHT = 600
+TABLE_X_POSITION = 100
+TABLE_Y_POSITION = 570
+FOOTER_Y_POSITION = 100
+
 def generate_metrics_overview_pdf() -> BytesIO:
     # ... existing code ...
     
-    canvas.drawCentredString(300, 800, "OWASP Project Health Metrics Overview")
+    canvas.drawCentredString(PAGE_WIDTH, TITLE_Y_POSITION, "OWASP Project Health Metrics Overview")
     
     # ... table data creation ...
     
     table = create_table(table_data)
-    table.wrapOn(canvas, 400, 600)
-    table.drawOn(canvas, 100, 570)
+    table.wrapOn(canvas, TABLE_WIDTH, TABLE_HEIGHT)
+    table.drawOn(canvas, TABLE_X_POSITION, TABLE_Y_POSITION)
     canvas.drawCentredString(
-        300, 100, f"Generated on: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}"
+        PAGE_WIDTH, FOOTER_Y_POSITION, f"Generated on: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}"
     )

124-129: Improve string formatting for better readability.

The current approach of using str.join() to bypass line length limits makes the code harder to read and understand.

Consider using more conventional approaches for long strings:

         [
             "Last Pull Request",
-            # To bypass ruff long line error
-            "/".join(
-                [
-                    str(metrics.last_pull_request_days),
-                    f"{metrics.last_pull_request_days_requirement} days",
-                ]
-            ),
+            (f"{metrics.last_pull_request_days}/"
+             f"{metrics.last_pull_request_days_requirement} days"),
         ],
         # ... similar change for OWASP Page Last Update ...
         [
             "OWASP Page Last Update",
-            # To bypass ruff long line error
-            "/".join(
-                [
-                    str(metrics.owasp_page_last_update_days),
-                    f"{metrics.owasp_page_last_update_days_requirement} days",
-                ]
-            ),
+            (f"{metrics.owasp_page_last_update_days}/"
+             f"{metrics.owasp_page_last_update_days_requirement} days"),
         ],

Also applies to: 138-143

backend/tests/apps/owasp/utils/pdf_test.py (1)

25-25: Update test description to reflect current functionality.

The test docstring mentions "command" but this is now testing a utility function, not a Django management command.

-        """Test that the command executes without errors."""
+        """Test that overview PDF generation executes without errors."""
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7301ccc and 2845221.

📒 Files selected for processing (11)
  • backend/apps/owasp/api/internal/views/project_health_metrics.py (1 hunks)
  • backend/apps/owasp/api/internal/views/urls.py (1 hunks)
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py (0 hunks)
  • backend/apps/owasp/utils/pdf.py (1 hunks)
  • backend/settings/urls.py (2 hunks)
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py (0 hunks)
  • backend/tests/apps/owasp/utils/pdf_test.py (1 hunks)
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx (2 hunks)
  • frontend/src/app/projects/dashboard/page.tsx (2 hunks)
  • frontend/src/components/MetricsPDFButton.tsx (1 hunks)
  • frontend/src/server/fetchMetricsPDF.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • backend/apps/owasp/management/commands/owasp_generate_project_health_metrics_pdf.py
  • backend/tests/apps/owasp/management/commands/owasp_generate_project_health_metrics_test.py
✅ Files skipped from review due to trivial changes (3)
  • frontend/src/app/projects/dashboard/page.tsx
  • frontend/src/components/MetricsPDFButton.tsx
  • backend/apps/owasp/api/internal/views/urls.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • backend/settings/urls.py
  • frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx
  • frontend/src/server/fetchMetricsPDF.ts
  • backend/apps/owasp/api/internal/views/project_health_metrics.py

@ahmedxgouda ahmedxgouda force-pushed the dashboard/pdf-metrics-details-client branch from 2845221 to ca5ba01 Compare July 30, 2025 03:02
@ahmedxgouda ahmedxgouda requested a review from arkid15r July 30, 2025 03:10
@arkid15r arkid15r enabled auto-merge July 30, 2025 19:00
@sonarqubecloud
Copy link

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.

LGTM

@arkid15r arkid15r added this pull request to the merge queue Jul 30, 2025
Merged via the queue into OWASP:main with commit 5bd70f1 Jul 30, 2025
24 checks passed
@ahmedxgouda ahmedxgouda deleted the dashboard/pdf-metrics-details-client branch August 10, 2025 06:00
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.

Implement the client side interaction with the PDF details script

2 participants