From c9d7f706759317b3f45e2701901ca9eb5860bca5 Mon Sep 17 00:00:00 2001 From: VarshiniGunti Date: Wed, 25 Feb 2026 20:56:58 +0530 Subject: [PATCH 1/2] feat: add PR-key filter for OpenLake leaderboard contributions (#141) --- api/leaderboard/views.py | 53 +++++++++++++++++ app/src/components/OpenlakeTable.jsx | 85 +++++++++++++++++++++++++--- 2 files changed, 129 insertions(+), 9 deletions(-) diff --git a/api/leaderboard/views.py b/api/leaderboard/views.py index 9a0f057..c128941 100644 --- a/api/leaderboard/views.py +++ b/api/leaderboard/views.py @@ -1,6 +1,8 @@ import logging +import os import re import urllib.parse +from collections import Counter from datetime import datetime import requests @@ -148,7 +150,58 @@ class GithubOrganisationAPI( queryset = openlakeContributor.objects.all() serializer_class = OL_Serializer + def _github_headers(self): + token = os.getenv("GITHUB_TOKEN", "").strip() + headers = {"Accept": "application/vnd.github+json"} + if token: + headers["Authorization"] = f"Bearer {token}" + return headers + + def _get_pr_key_contributions(self, pr_key): + normalized_key = pr_key.strip().strip("[]") + if not normalized_key: + return [] + + contributions = Counter() + page = 1 + + while True: + query = f'org:OpenLake is:pr state:all in:title "[{normalized_key}]"' + response = requests.get( + "https://api.github.com/search/issues", + params={"q": query, "per_page": 100, "page": page}, + headers=self._github_headers(), + timeout=20, + ) + + if response.status_code != 200: + break + + payload = response.json() + items = payload.get("items", []) + for item in items: + username = item.get("user", {}).get("login") + if username: + contributions[username] += 1 + + if len(items) < 100: + break + + page += 1 + + ordered_contributors = sorted( + contributions.items(), key=lambda x: x[1], reverse=True + ) + return [ + {"username": username, "contributions": contribution_count} + for username, contribution_count in ordered_contributors + ] + def get(self, request): + pr_key = request.query_params.get("pr_key", "").strip() + if pr_key: + return Response(self._get_pr_key_contributions(pr_key)) + ol_contributors = openlakeContributor.objects.all() serializer = OL_Serializer(ol_contributors, many=True) return Response(serializer.data) diff --git a/app/src/components/OpenlakeTable.jsx b/app/src/components/OpenlakeTable.jsx index 51f9609..35891f2 100644 --- a/app/src/components/OpenlakeTable.jsx +++ b/app/src/components/OpenlakeTable.jsx @@ -8,11 +8,16 @@ import { Switch } from "./ui/switch"; const BACKEND = import.meta.env.VITE_BACKEND; export function OpenLakeTable({ OLUsers }) { const [searchfield, setSearchfield] = useState(""); + const [prKeyInput, setPrKeyInput] = useState(""); + const [appliedPrKey, setAppliedPrKey] = useState(""); + const [keyFilteredUsers, setKeyFilteredUsers] = useState([]); + const [isFetchingPrKeyData, setIsFetchingPrKeyData] = useState(false); const [filteredusers, setFilteredusers] = useState([]); const [todisplayusers, setTodisplayusers] = useState([]); const [OLFriends, setOLFriends] = useState([]); const [showOLFriends, setShowOLFriends] = useState(false); const { open, isMobile } = useSidebar(); + const sourceUsers = appliedPrKey ? keyFilteredUsers : OLUsers; const columns = [ { accessorKey: "username", @@ -119,13 +124,37 @@ export function OpenLakeTable({ OLUsers }) { getccfriends(); }, []); + useEffect(() => { + if (!appliedPrKey) { + setKeyFilteredUsers([]); + return; + } + + const getPrKeyFilteredUsers = async () => { + setIsFetchingPrKeyData(true); + try { + const response = await fetch( + BACKEND + `/openlake/?pr_key=${encodeURIComponent(appliedPrKey)}`, + ); + const data = await response.json(); + setKeyFilteredUsers(Array.isArray(data) ? data : []); + } catch { + setKeyFilteredUsers([]); + } finally { + setIsFetchingPrKeyData(false); + } + }; + + getPrKeyFilteredUsers(); + }, [appliedPrKey]); + useEffect(() => { if (showOLFriends) { setTodisplayusers( - OLUsers.filter((OLUser) => OLFriends.includes(OLUser.username)), + sourceUsers.filter((OLUser) => OLFriends.includes(OLUser.username)), ); } else { - setTodisplayusers(OLUsers); + setTodisplayusers(sourceUsers); } if (searchfield === "") { setFilteredusers(todisplayusers); @@ -138,7 +167,7 @@ export function OpenLakeTable({ OLUsers }) { }), ); } - }, [showOLFriends, OLFriends, searchfield, OLUsers]); + }, [showOLFriends, OLFriends, searchfield, sourceUsers]); useEffect(() => { if (searchfield === "") { setFilteredusers(todisplayusers); @@ -163,12 +192,40 @@ export function OpenLakeTable({ OLUsers }) { }} >
- setSearchfield(val.target.value)} - type="search" - /> +
+ setSearchfield(val.target.value)} + type="search" + /> + setPrKeyInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + setAppliedPrKey(prKeyInput.trim()); + } + }} + /> + + +
Friends Only
+ {isFetchingPrKeyData ? ( +
+ Loading key-based contributions... +
+ ) : null} + {appliedPrKey ? ( +
+ Showing contributions for PR title key: [{appliedPrKey}] +
+ ) : null} ); From 947a39a98233a683ba7660f225377090750c9f5e Mon Sep 17 00:00:00 2001 From: VarshiniGunti Date: Wed, 25 Feb 2026 21:32:21 +0530 Subject: [PATCH 2/2] fix: address CodeRabbit feedback for OpenLake key filter --- api/leaderboard/views.py | 38 +++++++++++++++++++++++----- app/src/components/OpenlakeTable.jsx | 17 ++++++++++++- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/api/leaderboard/views.py b/api/leaderboard/views.py index c128941..bf3726d 100644 --- a/api/leaderboard/views.py +++ b/api/leaderboard/views.py @@ -160,7 +160,7 @@ def _github_headers(self): def _get_pr_key_contributions(self, pr_key): normalized_key = pr_key.strip().strip("[]") if not normalized_key: - return [] + return [], None, status.HTTP_200_OK contributions = Counter() page = 1 @@ -175,11 +175,28 @@ def _get_pr_key_contributions(self, pr_key): ) if response.status_code != 200: - break + error_detail = None + try: + error_detail = response.json() + except ValueError: + error_detail = response.text + return ( + [], + { + "error": "Failed to fetch data from GitHub.", + "upstream_status": response.status_code, + "details": error_detail, + }, + response.status_code, + ) payload = response.json() items = payload.get("items", []) for item in items: + title = item.get("title", "") + trimmed_title = title.lstrip() + if not trimmed_title.startswith(f"[{normalized_key}]"): + continue username = item.get("user", {}).get("login") if username: contributions[username] += 1 @@ -192,15 +209,22 @@ def _get_pr_key_contributions(self, pr_key): ordered_contributors = sorted( contributions.items(), key=lambda x: x[1], reverse=True ) - return [ - {"username": username, "contributions": contribution_count} - for username, contribution_count in ordered_contributors - ] + return ( + [ + {"username": username, "contributions": contribution_count} + for username, contribution_count in ordered_contributors + ], + None, + status.HTTP_200_OK, + ) def get(self, request): pr_key = request.query_params.get("pr_key", "").strip() if pr_key: - return Response(self._get_pr_key_contributions(pr_key)) + data, error_payload, response_status = self._get_pr_key_contributions(pr_key) + if error_payload: + return Response(error_payload, status=response_status) + return Response(data, status=response_status) ol_contributors = openlakeContributor.objects.all() serializer = OL_Serializer(ol_contributors, many=True) diff --git a/app/src/components/OpenlakeTable.jsx b/app/src/components/OpenlakeTable.jsx index 35891f2..34fcf7c 100644 --- a/app/src/components/OpenlakeTable.jsx +++ b/app/src/components/OpenlakeTable.jsx @@ -130,15 +130,26 @@ export function OpenLakeTable({ OLUsers }) { return; } + const controller = new AbortController(); + const { signal } = controller; + const getPrKeyFilteredUsers = async () => { setIsFetchingPrKeyData(true); try { const response = await fetch( BACKEND + `/openlake/?pr_key=${encodeURIComponent(appliedPrKey)}`, + { signal }, ); + if (!response.ok) { + setKeyFilteredUsers([]); + return; + } const data = await response.json(); setKeyFilteredUsers(Array.isArray(data) ? data : []); - } catch { + } catch (error) { + if (error.name === "AbortError") { + return; + } setKeyFilteredUsers([]); } finally { setIsFetchingPrKeyData(false); @@ -146,6 +157,10 @@ export function OpenLakeTable({ OLUsers }) { }; getPrKeyFilteredUsers(); + + return () => { + controller.abort(); + }; }, [appliedPrKey]); useEffect(() => {