Skip to content

Conversation

@snigdhachoudhury
Copy link
Contributor

@snigdhachoudhury snigdhachoudhury commented Dec 25, 2025

Project Leaderboard Feature

Added a comprehensive project leaderboard that displays GitHub metrics for OWASP projects.

What's included:

  • Leaderboard dashboard with tabs for different views (Dashboard, Projects, Stats, Activity)
  • Chart.js visualizations showing top 5 projects and status distribution
  • Search and filter by organization/status
  • Sort by stars, forks, issues, commits, contributors, etc.
  • Manual refresh button + auto-refresh (every 1 min)
  • REST API endpoints for filtering and sorting leaderboard data
  • GitHub API service with caching to avoid rate limits
  • Seed command with actual data for 11 OWASP projects (BLT, ZAP, Juice Shop, ModSecurity, etc.)
  • Added navigation links in sidebar, header dropdown, navbar, and home page
Home.-.Bug.Logging.Tool.and.7.more.pages.-.Personal.-.Microsoft_.Edge.2025-12-25.16-00-08.mp4

closes #3314

Summary by CodeRabbit

  • New Features

    • Project Leaderboard page with dashboard, charts, top-project highlights, filtering, sorting, search, per-project view and auto-refresh.
    • Live APIs for leaderboard data, project stats, filters, and on-demand stats refresh.
  • Chores

    • GitHub stats service added for fetching/caching repo metrics.
    • Seed commands to populate sample leaderboard/project data.
  • UI

    • Navigation and home links added for quick access to the leaderboard.

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

- Display 11 OWASP projects with stars, forks, issues, commits
- Add Chart.js visualizations and tabbed interface
- Include seed command with actual GitHub data
- Implement basic filtering and sorting

Resolves OWASP-BLT#3314
Copilot AI review requested due to automatic review settings December 25, 2025 10:57
@github-actions
Copy link
Contributor

👋 Hi @snigdhachoudhury!

This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:

  • The PR author
  • DonnieBLT
  • coderabbitai
  • copilot

Once a valid peer review is submitted, this check will pass automatically. Thank you!

@github-actions github-actions bot added needs-peer-review PR needs peer review files-changed: 15 PR changes 15 files has-conflicts PR has merge conflicts that need to be resolved has-console-statements PR contains console statements that need to be removed labels Dec 25, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Warning

Rate limit exceeded

@snigdhachoudhury has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 48 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 847af4e and e5c26bd.

📒 Files selected for processing (2)
  • website/api/leaderboard_api.py
  • website/views/project_leaderboard.py

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a Project Leaderboard feature: new template and view, REST API endpoints for leaderboard and project stats, GitHub integration service, seeding commands, frontend JS for filtering/charts/auto-refresh, and URL/navigation updates.

Changes

Cohort / File(s) Summary
URL Configuration
blt/urls.py
Register project leaderboard pages and API routes: project_leaderboard, api/project-leaderboard, api/leaderboard, api/leaderboard/filters, api/project/<id>/stats, api/project/<id>/refresh.
API & Backend Logic
website/api/leaderboard_api.py
New API: LeaderboardAPIView (cached GET, filters/sort), ProjectStatsAPIView (per-project aggregated stats), RefreshStatsAPIView (POST refresh, login-required), and leaderboard_filters endpoint.
Views & Template
website/views/project_leaderboard.py, website/templates/project_leaderboard.html
New ProjectLeaderboardView (TemplateView), JSON endpoint project_leaderboard_data, refresh_project_stats POST handler, and full leaderboard template with filters, tabs, summary cards, and charts.
GitHub Integration Service
website/services/github_service.py
GitHubService added: fetches repo data, contributors, commits, PR stats; caching (1h); helpers for batch refresh and cache invalidation.
Data Seeding
website/management/commands/seed_project_data.py, website/management/commands/seed_leaderboard_data.py
Two management commands to seed sample OWASP projects/repos — one uses ORM (seed_project_data), the other uses raw SQL upserts (seed_leaderboard_data).
Frontend: Filters & Auto-refresh
website/static/js/leaderboard_filters.js
New LeaderboardFilters class: builds/filter/sort requests to /api/leaderboard, debounce search, per-item and global refresh (CSRF-protected POST), auto-refresh, UI/loading/toasts, and exposes global instance.
Frontend: Charts & Tabs
website/static/js/project_leaderboard.js
Chart.js helpers and tab management (initializeCharts, initializeTabs, createBarChart, createPieChart), exposes window.initializeCharts.
Navigation & Home
website/templates/home.html, website/templates/includes/header.html, website/templates/includes/navbar.html
Added Project Leaderboard links/tile to home, header mega-menu, navbar, and mobile menu; mobile dropdown trigger switched to button and handler updated.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser as Browser/JS
    participant Django as Django View
    participant API as Leaderboard API
    participant Cache as Cache Layer
    participant DB as Database
    participant GH as GitHub API

    User->>Browser: Open /project_leaderboard/
    Browser->>Django: GET /project_leaderboard/
    Django->>DB: Query projects & repos
    DB-->>Django: Projects data
    Django-->>Browser: Render template with initial context

    Browser->>API: GET /api/leaderboard/?filters
    API->>Cache: Check cached response
    alt Cache Hit
        Cache-->>API: Return cached JSON
    else Cache Miss
        API->>DB: Query & aggregate repos
        DB-->>API: Repo aggregates
        API->>Cache: Store response (5m TTL)
    end
    API-->>Browser: JSON (projects, totals, filters)
    Browser->>Browser: Render leaderboard, charts, attach refresh handlers
Loading
sequenceDiagram
    actor User
    participant Browser as Browser/JS
    participant RefreshAPI as RefreshStatsAPI
    participant Service as GitHubService
    participant GH as GitHub API
    participant DB as Database

    User->>Browser: Click "Refresh" on a project
    Browser->>RefreshAPI: POST /api/project/{id}/refresh/ (CSRF, auth)
    RefreshAPI->>DB: Validate project & list repos
    loop per repo
        RefreshAPI->>Service: refresh_repo_cache(owner,repo)
        Service->>GH: GET /repos/{owner}/{repo} and related endpoints
        GH-->>Service: Repo metrics (stars,forks,commits,contributors,prs,issues)
        Service-->>RefreshAPI: Aggregated stats
        RefreshAPI->>DB: Update repo record
    end
    DB-->>RefreshAPI: Save confirmations
    RefreshAPI-->>Browser: JSON (updated repos)
    Browser->>Browser: Hide loading, update UI, optionally reload leaderboard
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/project leaderboard' is vague and lacks specificity; it uses a generic format that does not clearly convey the main change beyond naming a feature branch. Consider a more descriptive title such as 'Add project leaderboard with GitHub metrics and filtering' to better communicate the primary change to reviewers.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR implements core requirements from issue #3314: GitHub metrics aggregation (stars, forks, commits, contributors, issues), leaderboard UI with filtering/sorting, REST API endpoints, GitHub service with caching, and seed data; however, some optional features like code quality scores, social media metrics, and WebSocket real-time updates are not included.
Out of Scope Changes check ✅ Passed All changes align with the leaderboard feature objective; navigation updates (navbar, header, home.html) are supporting enhancements directly related to exposing the new leaderboard functionality.
Docstring Coverage ✅ Passed Docstring coverage is 82.61% which is sufficient. The required threshold is 80.00%.

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.

@github-actions
Copy link
Contributor

📊 Monthly Leaderboard

Hi @snigdhachoudhury! Here's how you rank for December 2025:

Rank User PRs Reviews Comments Total
#24 @haroon0x 0 3 3 18
#25 @snigdhachoudhury 1 0 3 16
#26 @angeliaaju 1 0 0 10

Leaderboard based on contributions in December 2025. Keep up the great work! 🚀

@github-actions github-actions bot added files-changed: 14 PR changes 14 files and removed files-changed: 15 PR changes 15 files has-conflicts PR has merge conflicts that need to be resolved labels Dec 25, 2025
@github-actions
Copy link
Contributor

❌ Pre-commit checks failed

The pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them:

# Install pre-commit if you haven't already
pip install pre-commit

# Run pre-commit on all files
pre-commit run --all-files

# Or run pre-commit on staged files only
pre-commit run

After running these commands, the pre-commit hooks will automatically fix most issues.
Please review the changes, commit them, and push to your branch.

💡 Tip: You can set up pre-commit to run automatically on every commit by running:

pre-commit install
Pre-commit output
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[WARNING] repo `https://github.com/pre-commit/pre-commit-hooks` uses deprecated stage names (commit, push) which will be removed in a future version.  Hint: often `pre-commit autoupdate --repo https://github.com/pre-commit/pre-commit-hooks` will fix this.  if it does not -- consider reporting an issue to that repo.
[INFO] Initializing environment for https://github.com/pycqa/isort.
[WARNING] repo `https://github.com/pycqa/isort` uses deprecated stage names (commit, merge-commit, push) which will be removed in a future version.  Hint: often `pre-commit autoupdate --repo https://github.com/pycqa/isort` will fix this.  if it does not -- consider reporting an issue to that repo.
[INFO] Initializing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Initializing environment for https://github.com/djlint/djLint.
[INFO] Initializing environment for local.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pycqa/isort.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/djlint/djLint.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for local.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
check python ast.........................................................Passed
check builtin type constructor use.......................................Passed
check yaml...............................................................Passed
fix python encoding pragma...............................................Passed
mixed line ending........................................................Passed
isort....................................................................Failed
- hook id: isort
- files were modified by this hook

Fixing /home/runner/work/BLT/BLT/check_data.py
Fixing /home/runner/work/BLT/BLT/check_repo_columns.py
Fixing /home/runner/work/BLT/BLT/website/management/commands/seed_project_data.py


For more information, see the pre-commit documentation.

@github-actions github-actions bot added the pre-commit: failed Pre-commit checks failed label Dec 25, 2025
@github-actions github-actions bot added the tests: failed Django tests failed label Dec 25, 2025
@github-actions
Copy link
Contributor

❌ Pre-commit checks failed

The pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them:

# Install pre-commit if you haven't already
pip install pre-commit

# Run pre-commit on all files
pre-commit run --all-files

# Or run pre-commit on staged files only
pre-commit run

After running these commands, the pre-commit hooks will automatically fix most issues.
Please review the changes, commit them, and push to your branch.

💡 Tip: You can set up pre-commit to run automatically on every commit by running:

pre-commit install
Pre-commit output
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[WARNING] repo `https://github.com/pre-commit/pre-commit-hooks` uses deprecated stage names (commit, push) which will be removed in a future version.  Hint: often `pre-commit autoupdate --repo https://github.com/pre-commit/pre-commit-hooks` will fix this.  if it does not -- consider reporting an issue to that repo.
[INFO] Initializing environment for https://github.com/pycqa/isort.
[WARNING] repo `https://github.com/pycqa/isort` uses deprecated stage names (commit, merge-commit, push) which will be removed in a future version.  Hint: often `pre-commit autoupdate --repo https://github.com/pycqa/isort` will fix this.  if it does not -- consider reporting an issue to that repo.
[INFO] Initializing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Initializing environment for https://github.com/djlint/djLint.
[INFO] Initializing environment for local.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pycqa/isort.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/djlint/djLint.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for local.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
check python ast.........................................................Passed
check builtin type constructor use.......................................Passed
check yaml...............................................................Passed
fix python encoding pragma...............................................Passed
mixed line ending........................................................Passed
isort....................................................................Failed
- hook id: isort
- files were modified by this hook

Fixing /home/runner/work/BLT/BLT/check_data.py
Fixing /home/runner/work/BLT/BLT/check_repo_columns.py
Fixing /home/runner/work/BLT/BLT/website/management/commands/seed_project_data.py


For more information, see the pre-commit documentation.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds a comprehensive project leaderboard feature that displays GitHub metrics for OWASP projects. The implementation includes a full-stack solution with Django views, REST API endpoints, Chart.js visualizations, and data seeding commands.

Key changes:

  • New leaderboard dashboard with filterable/sortable project statistics (stars, forks, commits, contributors, etc.)
  • REST API endpoints for dynamic data retrieval with caching
  • GitHub service integration for fetching repository statistics
  • Management commands to seed realistic project data
  • Navigation links added across multiple templates (sidebar, navbar, header, homepage)

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
website/views/project_leaderboard.py Main view logic for leaderboard, filtering/sorting, and JSON API endpoint
website/templates/project_leaderboard.html Leaderboard UI template with tabbed interface and Chart.js visualizations
website/static/js/project_leaderboard.js JavaScript for chart rendering and tab switching functionality
website/static/js/leaderboard_filters.js Advanced filtering/sorting logic with API integration (appears unused in template)
website/services/github_service.py GitHub API integration service with caching for rate limit management
website/api/leaderboard_api.py REST API endpoints for leaderboard data, project stats, and refresh functionality
website/management/commands/seed_project_data.py Management command to seed sample OWASP project data
website/management/commands/seed_leaderboard_data.py Raw SQL-based seeding command for bypassing model validation
website/templates/includes/sidenav.html Removed inline style tag (good), added leaderboard navigation link
website/templates/includes/navbar.html Added leaderboard link to navbar and mobile dropdown menu
website/templates/includes/header.html Added leaderboard link to header dropdown menu
website/templates/home.html Added leaderboard card to tools section
blt/urls.py URL patterns for leaderboard views and API endpoints
check_repo_columns.py Utility script for checking database columns (should not be committed)
check_data.py Utility script for checking seeded data (should not be committed)

@github-actions github-actions bot added tests: passed Django tests passed and removed tests: failed Django tests failed labels Dec 25, 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: 9

♻️ Duplicate comments (3)
website/views/project_leaderboard.py (1)

195-196: Avoid exposing exception details to external users.

The exception message is returned directly in the response, which could leak stack trace information or internal details to attackers.

This was already flagged by CodeQL. Consider returning a generic error message:

     except Exception as e:
-        return JsonResponse({"status": "error", "message": f"Failed to refresh stats: {str(e)}"}, status=500)
+        logger.error(f"Failed to refresh stats for project {project_id}: {e}")
+        return JsonResponse({"status": "error", "message": "Failed to refresh stats"}, status=500)
website/api/leaderboard_api.py (2)

182-185: Incomplete URL substring sanitization for GitHub URL parsing.

Checking "github.com" in repo.repo_url is insufficient as a malicious URL like https://evil.com/github.com/owner/repo would pass the check.

This was already flagged by CodeQL. Consider using proper URL parsing:

from urllib.parse import urlparse

parsed = urlparse(repo.repo_url)
if parsed.netloc == "github.com" or parsed.netloc == "www.github.com":
    parts = parsed.path.strip("/").split("/")
    if len(parts) >= 2:
        owner, repo_name = parts[0], parts[1]

214-216: Avoid exposing exception details to external users.

This was already flagged by CodeQL. The exception message should not be returned directly:

         except Exception as e:
             logger.error(f"Error refreshing stats: {e}")
-            return JsonResponse({"success": False, "error": str(e)}, status=500)
+            return JsonResponse({"success": False, "error": "An internal error occurred"}, status=500)
🧹 Nitpick comments (13)
check_data.py (1)

1-9: Consider adding a __main__ guard and optional Django setup

Right now the ORM queries run at import and assume Django is already configured. If you ever want to run this like python check_data.py, consider:

  • Moving the print/loop code into a main() function guarded by if __name__ == "__main__":.
  • Optionally mirroring check_repo_columns.py by setting DJANGO_SETTINGS_MODULE and calling django.setup() here as well.

This keeps it clearly in “diagnostic script” territory and avoids surprises if imported elsewhere.

check_repo_columns.py (1)

1-21: Use a context manager and __main__ guard for the diagnostic script

For a small robustness win:

  • Wrap the DB work in with connection.cursor() as cursor: so the cursor is always closed.
  • Put the query/print logic under if __name__ == "__main__": to avoid executing it on import.

Example:

Proposed structure
-import os
-import django
-
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blt.settings')
-django.setup()
-
-from django.db import connection
-
-cursor = connection.cursor()
-cursor.execute("""
+import os
+import django
+from django.db import connection
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "blt.settings")
+django.setup()
+
+
+def main():
+    with connection.cursor() as cursor:
+        cursor.execute("""
     SELECT column_name, column_default, is_nullable, data_type 
     FROM information_schema.columns 
     WHERE table_name = 'website_repo' AND is_nullable = 'NO' 
     ORDER BY ordinal_position
-""")
-
-print("NOT NULL columns in website_repo:")
-print("-" * 80)
-for row in cursor.fetchall():
-    default = str(row[1]) if row[1] else "None"
-    print(f"{row[0]:30} | Default: {default:20} | Type: {row[3]}")
+        """)
+
+        print("NOT NULL columns in website_repo:")
+        print("-" * 80)
+        for row in cursor.fetchall():
+            default = str(row[1]) if row[1] else "None"
+            print(f"{row[0]:30} | Default: {default:20} | Type: {row[3]}")
+
+
+if __name__ == "__main__":
+    main()
website/templates/includes/header.html (1)

244-247: Align Project Leaderboard menu item styling with other entries

The new “Project Leaderboard” link works, but its classes are simpler than neighboring items (no active-state highlighting, no dark-mode text/background variants, no transition classes). For consistency and dark-mode support, consider mirroring the pattern used for project_list/repo_list (conditional classes based on request.resolver_match.url_name, dark: variants, hover:bg etc.).

website/templates/home.html (1)

1105-1111: Add dark‑mode classes to the new Project Leaderboard tile

The new tile uses bg-gray-50 and light-mode text classes only, while neighboring tiles use bg-gray-50 dark:bg-gray-900 and dark:text-gray-400. To keep the “Tools for Projects” grid consistent in dark mode, consider:

  • Adding dark:bg-gray-900 to the wrapper.
  • Adding dark:text-gray-400 (or similar) to the description text.

You can largely copy the class set from the adjacent cards.

website/templates/includes/navbar.html (1)

27-31: Navbar additions for Project Leaderboard and mobile toggle look good; consider minor accessibility polish

  • Switching the mobile menu trigger to a <button> and adding e.preventDefault() / e.stopPropagation() is a nice behavioral cleanup.
  • New Project Leaderboard links (mobile dropdown and desktop button) follow the existing visual pattern and correct URL name.

If you want to go a bit further for accessibility, you could add aria-expanded and aria-controls to #dropdown-toggle-mobile and toggle aria-expanded together with the hidden class on #dropdown-mobile.

Also applies to: 45-50, 59-64, 120-123

blt/urls.py (1)

22-27: Leaderboard URLs are wired correctly; consider versioning and guarding refresh endpoints

The new imports and URL patterns:

  • project_leaderboard/ProjectLeaderboardView
  • /api/project-leaderboard/, /api/leaderboard/, /api/leaderboard/filters/
  • /api/project/<int:project_id>/stats/, /api/project/<int:project_id>/refresh/

are consistent with the rest of the URL config and the templates that link to project_leaderboard.

Two follow‑ups to consider:

  1. API versioning: Most existing REST endpoints live under /api/v1/.... Aligning these new endpoints to the same versioned prefix (or at least planning for it) will make future evolution easier.
  2. Protection of refresh endpoint: The RefreshStatsAPIView path likely triggers outbound GitHub calls/cache updates. Ensure its DRF permissions, throttling, and any internal rate limiting are configured so anonymous or abusive traffic cannot exhaust your GitHub rate limits or overload the job.

Also applies to: 315-315, 1136-1141

website/templates/project_leaderboard.html (2)

483-483: Consider adding Subresource Integrity (SRI) for CDN-loaded script.

Loading Chart.js from a CDN without an integrity hash exposes the page to supply-chain attacks if the CDN is compromised.

🔎 Proposed fix
-    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" integrity="sha384-..." crossorigin="anonymous"></script>

You can generate the correct hash using:

curl -s https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js | openssl dgst -sha384 -binary | openssl base64 -A

530-546: Auto-refresh interval is not cleared on page unload, causing potential memory leak.

When navigating away from the page, the setInterval continues running. While modern browsers handle this, it's good practice to clean up.

🔎 Proposed fix
         if (autoRefreshCheck) {
             autoRefreshCheck.addEventListener('change', function() {
                 if (this.checked) {
                     autoRefreshInterval = setInterval(function() {
                         location.reload();
                     }, 60000); // 1 minute
                 } else {
                     if (autoRefreshInterval) {
                         clearInterval(autoRefreshInterval);
                         autoRefreshInterval = null;
                     }
                 }
             });
+            
+            // Cleanup on page unload
+            window.addEventListener('beforeunload', function() {
+                if (autoRefreshInterval) {
+                    clearInterval(autoRefreshInterval);
+                }
+            });
         }
     }
website/views/project_leaderboard.py (2)

20-57: Consider using database aggregation instead of Python iteration.

The current implementation fetches all repos and aggregates in Python. For larger datasets, this could be optimized using Django's annotate() with Sum().

🔎 Suggested approach
from django.db.models import Sum, Count

projects = Project.objects.select_related("organization").annotate(
    stars=Sum("repos__stars"),
    forks=Sum("repos__forks"),
    issues=Sum("repos__open_issues"),
    watchers=Sum("repos__watchers"),
    contributors=Sum("repos__contributor_count"),
    commits=Sum("repos__commit_count"),
    repos_count=Count("repos"),
)

This moves aggregation to the database layer, reducing memory usage and improving performance.


156-178: Manually instantiating the view works but is unconventional.

The approach of creating a ProjectLeaderboardView instance and manually setting request works, but consider extracting the shared logic into a service class or mixin for cleaner separation.

website/services/github_service.py (2)

107-109: Consider using more specific exception types.

Catching broad Exception can mask unexpected errors. For HTTP operations, requests.RequestException (already used in get_repo_stats) is more appropriate.

🔎 Example for _get_contributors_count
-        except Exception as e:
+        except requests.RequestException as e:
             logger.warning(f"Could not fetch contributors count: {e}")
             return 0

Also applies to: 128-130, 155-157


175-189: Sequential API calls in get_multiple_repos may be slow for many repositories.

For projects with many repos, consider using concurrent.futures.ThreadPoolExecutor or async requests to parallelize API calls while respecting rate limits.

website/static/js/leaderboard_filters.js (1)

218-226: Hardcoded max values for percentage calculation are inflexible.

The maxValues object uses static values that may become outdated as projects grow. Consider calculating the max dynamically from the current dataset.

🔎 Proposed approach

Store max values when data is loaded:

renderLeaderboard(projects) {
    // Calculate max values dynamically
    this.maxValues = {
        stars: Math.max(...projects.map(p => p.stats.stars), 1),
        forks: Math.max(...projects.map(p => p.stats.forks), 1),
        commits: Math.max(...projects.map(p => p.stats.commits), 1)
    };
    // ... rest of render logic
}

calculatePercentage(value, metric) {
    return Math.min((value / this.maxValues[metric]) * 100, 100);
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 679ce84 and 42ace48.

📒 Files selected for processing (14)
  • blt/urls.py
  • check_data.py
  • check_repo_columns.py
  • website/api/leaderboard_api.py
  • website/management/commands/seed_leaderboard_data.py
  • website/management/commands/seed_project_data.py
  • website/services/github_service.py
  • website/static/js/leaderboard_filters.js
  • website/static/js/project_leaderboard.js
  • website/templates/home.html
  • website/templates/includes/header.html
  • website/templates/includes/navbar.html
  • website/templates/project_leaderboard.html
  • website/views/project_leaderboard.py
🧰 Additional context used
🪛 GitHub Actions: Check Console Statements
website/static/js/leaderboard_filters.js

[error] 113-113: Console statement detected: console.error('Error loading leaderboard:', error);. Detected by step 'Run actions/github-script@v7'.


[error] 259-259: Console statement detected: console.error('Error refreshing stats:', error);. Detected by step 'Run actions/github-script@v7'.

🪛 GitHub Check: CodeQL
website/api/leaderboard_api.py

[failure] 183-183: Incomplete URL substring sanitization
The string github.com may be at an arbitrary position in the sanitized URL.


[warning] 216-216: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

website/views/project_leaderboard.py

[warning] 196-196: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

🔇 Additional comments (13)
website/management/commands/seed_project_data.py (1)

12-140: Seed command logic looks sound and idempotent

Using Project.objects.get_or_create(slug=...) and only creating repos when created keeps this command safe to re-run. Field mappings for stars/forks/issues/commits/contributors on Repo also look consistent with the intended sample data.

website/static/js/project_leaderboard.js (5)

7-23: LGTM!

The color palette is well-defined with a clear naming convention. The shades array provides good gradient options for charts with multiple data points.


28-57: LGTM!

Tab switching logic is clean with proper state management for button classes and content visibility. The use of data-tab attributes provides a flexible, data-driven approach.


62-119: LGTM!

Chart helper functions are well-configured with responsive behavior, consistent styling, and sensible defaults. The separation between bar and pie chart creation improves reusability.

Also applies to: 124-146


151-212: LGTM!

The initializeCharts function handles multiple chart types with proper null checks for both canvas elements and data. The spread operator for sorting preserves the original array, which is good practice.


214-222: LGTM!

Proper handling of DOM ready state with fallback for already-loaded documents. Exporting to window object allows template integration.

website/templates/project_leaderboard.html (1)

14-91: LGTM!

The filter form, tab navigation, dashboard summary cards, project cards grid, and chart containers are well-structured with proper accessibility attributes and responsive design using Tailwind CSS.

Also applies to: 92-117, 121-246, 393-476

website/views/project_leaderboard.py (1)

78-98: LGTM!

The sorting logic handles both numeric and string fields correctly with the isinstance check. The context data assembly is comprehensive and well-organized.

Also applies to: 100-153

website/services/github_service.py (2)

29-86: LGTM!

The caching strategy with 1-hour timeout is sensible for GitHub data. Link header parsing for pagination is the correct approach for getting total counts. Error handling gracefully falls back to safe defaults.

Also applies to: 88-98, 111-131, 132-157, 159-173


20-27: No action needed. GitHub API accepts both token and Bearer authentication formats for personal access tokens, with no preference stated by GitHub. The current implementation using token {github_token} is fully compliant with GitHub's documentation and recommendations.

Likely an incorrect or invalid review comment.

website/api/leaderboard_api.py (2)

19-52: LGTM!

The caching decorator, query building, sorting logic, and JSON response formatting are well-implemented. The ProjectStatsAPIView properly handles the DoesNotExist case with a 404 response.

Also applies to: 59-117, 120-168


53-54: Language filter logic appears incorrect.

Filtering by repo_url__icontains=language searches for the language string in the URL, which won't work as intended. GitHub URLs don't contain language information.

If the Repo model has a language field, use that instead:

         if language:
-            repos = repos.filter(repo_url__icontains=language)
+            repos = repos.filter(language__iexact=language)

If not, consider adding a language field to the Repo model and populating it from the GitHub API response.

website/static/js/leaderboard_filters.js (1)

5-16: LGTM!

The class structure is well-organized with clear separation of concerns. Event listener attachment with debouncing for search, CSRF token handling, toast notifications, and animation logic are all properly implemented.

Also applies to: 18-91, 93-118, 228-234, 236-267, 269-299, 301-377, 380-383

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Dec 25, 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

♻️ Duplicate comments (1)
website/api/leaderboard_api.py (1)

193-200: URL validation remains vulnerable to SSRF attacks.

Despite the regex improvement, the validation at lines 193 and 196 can still be bypassed. The substring check and regex pattern will match github.com anywhere in the URL, not specifically as the domain.

Attack vectors:

  • https://evil.com/github.com/owner/repo ✓ passes
  • https://github.com.evil.com/owner/repo ✓ passes
  • https://evil.com?redirect=github.com/owner/repo ✓ passes
🔎 Proposed fix using proper URL parsing
+            from urllib.parse import urlparse
+
             for repo in project.repos.all():
                 # Parse owner/repo from URL with strict validation
                 try:
-                    if "github.com" in repo.repo_url:
-                        # Extract owner/repo from GitHub URL
-                        # Expected format: https://github.com/owner/repo or github.com/owner/repo
-                        match = re.search(r"github\.com[:/]([^/]+)/([^/]+)", repo.repo_url)
-                        if match:
-                            owner, repo_name = match.group(1), match.group(2)
-                            # Remove .git suffix if present
-                            repo_name = repo_name.rstrip("/").replace(".git", "")
-
-                            # Fetch fresh data
-                            stats = github_service.refresh_repo_cache(owner, repo_name)
+                    parsed = urlparse(repo.repo_url)
+                    # Validate github.com is the actual domain
+                    if parsed.netloc in ("github.com", "www.github.com"):
+                        # Extract owner/repo from path
+                        path_parts = parsed.path.strip("/").split("/")
+                        if len(path_parts) >= 2:
+                            owner = path_parts[0]
+                            repo_name = path_parts[1].replace(".git", "")
+
+                            # Fetch fresh data
+                            stats = github_service.refresh_repo_cache(owner, repo_name)
+                        else:
+                            logger.warning(f"Invalid GitHub URL path for repo {repo.id}: {repo.repo_url}")
+                            continue
+                    else:
+                        continue
+
                             if stats:
                                 repo.stars = stats.get("stars", repo.stars)
🧹 Nitpick comments (2)
website/views/project_leaderboard.py (2)

45-51: Add None-safety for repo metric aggregations.

The code sums numeric fields from repos without checking for None values. If any repo has None for stars, forks, etc., the sum will fail with a TypeError.

🔎 Proposed fix using generator with default values
             # GitHub Metrics
-            "stars": sum(r.stars for r in repos),
-            "forks": sum(r.forks for r in repos),
-            "issues": sum(r.open_issues for r in repos),
-            "watchers": sum(r.watchers for r in repos),
-            "contributors": sum(r.contributor_count for r in repos),
-            "commits": sum(r.commit_count for r in repos),
+            "stars": sum(r.stars or 0 for r in repos),
+            "forks": sum(r.forks or 0 for r in repos),
+            "issues": sum(r.open_issues or 0 for r in repos),
+            "watchers": sum(r.watchers or 0 for r in repos),
+            "contributors": sum(r.contributor_count or 0 for r in repos),
+            "commits": sum(r.commit_count or 0 for r in repos),

165-170: Consider refactoring to avoid view instantiation pattern.

Instantiating ProjectLeaderboardView to reuse get_context_data is unconventional and tightly couples the JSON endpoint to the view class. Consider extracting shared logic into a separate service or helper function.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 3572d45 and 847af4e.

📒 Files selected for processing (4)
  • website/api/leaderboard_api.py
  • website/management/commands/seed_project_data.py
  • website/static/js/leaderboard_filters.js
  • website/views/project_leaderboard.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • website/management/commands/seed_project_data.py
  • website/static/js/leaderboard_filters.js
🧰 Additional context used
🪛 GitHub Check: CodeQL
website/api/leaderboard_api.py

[failure] 193-193: Incomplete URL substring sanitization
The string github.com may be at an arbitrary position in the sanitized URL.

⏰ 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: Agent
🔇 Additional comments (6)
website/views/project_leaderboard.py (1)

190-206: LGTM! Error handling follows best practices.

The error handling correctly logs detailed exception information for debugging (line 205) while returning a generic error message to the user (line 206). This is the recommended approach for security.

website/api/leaderboard_api.py (5)

44-52: LGTM! Input validation properly implemented.

The safe integer conversions with try-except blocks correctly handle invalid user input, addressing the concerns from previous reviews.


61-62: LGTM! Language filter now uses the correct field.

Using primary_language__iexact properly filters by the programming language field instead of searching in URLs.


182-182: LGTM! Authentication properly enforced.

The @method_decorator(login_required) decorator prevents unauthenticated users from triggering potentially expensive GitHub API calls.


216-218: LGTM! Error logging is appropriate.

Logging exception details to the server (line 217) is standard practice for debugging and does not expose sensitive information to users. The continue statement ensures processing continues for other repos.


230-232: LGTM! Error handling follows security best practices.

The exception is logged for debugging (line 231) while returning a generic error message to the client (line 232), which is the correct approach to avoid information disclosure.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 18 comments.

@github-actions github-actions bot added tests: failed Django tests failed tests: passed Django tests passed last-active: 0d PR last updated 0 days ago last-active: 2d PR last updated 2 days ago last-active: 1d PR last updated 1 day ago and removed tests: passed Django tests passed tests: failed Django tests failed last-active: 0d PR last updated 0 days ago last-active: 2d PR last updated 2 days ago labels Dec 25, 2025
@github-actions github-actions bot added last-active: 2d PR last updated 2 days ago last-active: 1d PR last updated 1 day ago last-active: 0d PR last updated 0 days ago and removed last-active: 1d PR last updated 1 day ago last-active: 2d PR last updated 2 days ago last-active: 0d PR last updated 0 days ago labels Jan 3, 2026
@snigdhachoudhury
Copy link
Contributor Author

@DonnieBLT hello, can you please review this

@Shhazzz
Copy link
Contributor

Shhazzz commented Jan 6, 2026

Looks good overall — solid feature and clean UI. Nice work!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the relevant files to verify the reported issues

echo "=== website/api/leaderboard_api.py ==="
cat website/api/leaderboard_api.py

echo -e "\n\n=== website/views/project_leaderboard.py ==="
cat website/views/project_leaderboard.py

echo -e "\n\n=== website/static/js/leaderboard_filters.js ==="
cat website/static/js/leaderboard_filters.js

echo -e "\n\n=== website/services/github_service.py ==="
cat website/services/github_service.py

Length of output: 41001


@Jayant2908: I've analyzed all 7 issues against the actual code. Here's my assessment with evidence:

✅ Not Actually Issues (Code Already Handles Correctly)

2. Exception Exposure - FALSE POSITIVE ❌

Status: Already handled correctly in the code.

Looking at the actual code:

  • leaderboard_api.py line 205: Returns generic "Failed to refresh stats"
  • project_leaderboard.py line 157: Returns generic "Failed to refresh stats"
  • Exceptions are logged server-side but NOT exposed to clients

The code already follows best practices here.

4. Hardcoded Max Values - FALSE POSITIVE ❌

Status: Dynamically calculated, no hardcoding.

In leaderboard_filters.js lines 154-171, the calculatePercentage() method:

const maxValue = Math.max(
    ...this.projects.map(p => p.stats[dataKey] || 0)
);

Maxima are computed dynamically from the current dataset, not hardcoded.


🔴 Should Fix in This PR (Security & Stability)

3. Rate Limiting on Refresh Endpoint - VALID ⚠️

Severity: Medium-High
Why fix now: Security issue; endpoint makes external API calls and is protected only by login_required.

Recommendation: Implement Django throttling or require admin permissions:

from rest_framework.throttling import UserRateThrottle

class RefreshStatsAPIView(View):
    throttle_classes = [UserRateThrottle]  # or use django-ratelimit

5. Auto-Refresh Memory Leak - VALID ⚠️

Severity: Low-Medium
Why fix now: Can cause browser performance degradation over time.

Recommendation: Add cleanup in leaderboard_filters.js:

// Add destructor or page unload handler
window.addEventListener('beforeunload', () => {
    if (window.leaderboardFilters) {
        window.leaderboardFilters.stopAutoRefresh();
    }
});

🟡 Can Defer to Follow-Up (Performance Optimizations)

6. Database Aggregation - VALID but Non-Critical 📊

Severity: Medium (performance impact grows with data)
Why defer: Works correctly, optimization doesn't block feature launch.

Current code (lines 40-50 in project_leaderboard.py):

"stars": sum(r.stars for r in repos),

Better approach for follow-up:

from django.db.models import Sum
projects = Project.objects.annotate(
    total_stars=Sum('repos__stars'),
    total_forks=Sum('repos__forks'),
    # etc.
)

7. Sequential GitHub API Calls - VALID but Non-Critical 📊

Severity: Medium (performance issue)
Why defer: Works correctly, parallelization can be complex with rate limits.

For follow-up, consider ThreadPoolExecutor with rate limit awareness.


🟠 Medium Priority (Validation Improvement)

1. URL Validation - VALID but Lower Risk 🔍

Severity: Low-Medium
Context: Extracts from database repo.repo_url, not direct user input.

Current regex (line 183):

match = re.search(r"github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?(?:/|$)", repo.repo_url)

Could be improved with strict domain validation, but since it's reading from the database (not user input in the endpoint), the SSRF risk is lower.


📋 Summary & Recommendations

Must fix in this PR (2 issues):

  1. ✅ Add rate limiting to refresh endpoint
  2. ✅ Fix auto-refresh memory leak

Can defer to follow-up issue (3 issues):

  1. Database aggregation optimization
  2. Parallel GitHub API calls
  3. Stricter URL validation

Not issues (2 claims):

  1. Exception exposure - already handled
  2. Hardcoded max values - dynamically calculated

My recommendation: Fix the 2 critical issues (#3 and #5) in this PR since they're security/stability concerns with straightforward fixes. The performance optimizations can be tracked in a follow-up issue as they don't block the feature and require more careful implementation.

Would you like me to create a follow-up issue for the deferred items?

Copy link
Contributor

@Jayant2908 Jayant2908 left a comment

Choose a reason for hiding this comment

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

Great work!! Do look at the two fixes mentioned by coderabbitai and you should be good to go!

@github-actions github-actions bot added the last-active: 0d PR last updated 0 days ago label Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-requested PR has requested changes from a reviewer files-changed: 12 PR changes 12 files last-active: 0d PR last updated 0 days ago needs-peer-review PR needs peer review pre-commit: passed Pre-commit checks passed quality: high tests: passed Django tests passed

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

🏆 Feature Request: Engaging Project Leaderboard with Comprehensive Stats

3 participants