From 55657ed3d8324e3abc0373ab4d6e31b9074ffdef Mon Sep 17 00:00:00 2001 From: jikrana Date: Tue, 1 Sep 2026 08:44:13 +0530 Subject: [PATCH 1/3] feat: add organization filter to Contributor Profile page --- src/pages/ContributorProfilePage.jsx | 77 +++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/src/pages/ContributorProfilePage.jsx b/src/pages/ContributorProfilePage.jsx index 3cafec8..4738096 100644 --- a/src/pages/ContributorProfilePage.jsx +++ b/src/pages/ContributorProfilePage.jsx @@ -109,6 +109,7 @@ export default function ContributorProfilePage() { const [rawContributions, setRawContributions] = useState([]) const [mergedPRKeys, setMergedPRKeys] = useState(new Set()) const [tab, setTab] = useState('prs') + const [selectedOrg, setSelectedOrg] = useState('all') // Date Range Filters (Defaults to Last 1 Year) const [startDate, setStartDate] = useState(() => { @@ -147,6 +148,7 @@ export default function ContributorProfilePage() { // Reset data states to prevent rendering stale profile info setRawContributions([]) setMergedPRKeys(new Set()) + setSelectedOrg('all') if (!username) { setLoading(false) @@ -244,22 +246,53 @@ export default function ContributorProfilePage() { setEndDate('') } } + - // Filter contributions by UTC date range limits const filteredContribs = useMemo(() => { return rawContributions.filter(item => { + + // Organization filter + if (selectedOrg !== 'all') { + const repoParts = item.repository_url?.split('/') || [] + const organization = repoParts[repoParts.length - 2] + + if (organization !== selectedOrg) { + return false + } + } + + // Date filter const itemTime = new Date(item.created_at).getTime() + if (startDate) { const startTime = Date.parse(startDate + 'T00:00:00.000Z') - if (isNaN(startTime) || itemTime < startTime) return false + + if (isNaN(startTime) || itemTime < startTime) { + return false + } } + if (endDate) { const endTime = Date.parse(endDate + 'T23:59:59.999Z') - if (isNaN(endTime) || itemTime > endTime) return false + + if (isNaN(endTime) || itemTime > endTime) { + return false + } } + return true }) - }, [rawContributions, startDate, endDate]) + }, [rawContributions, selectedOrg, startDate, endDate]) + + const contributorOrgs = useMemo(() => { + const orgSet = new Set() + rawContributions.forEach(item => { + const repoParts = item.repository_url?.split('/') || [] + const org = repoParts[repoParts.length - 2] + if (org) orgSet.add(org) + }) + return Array.from(orgSet) + }, [rawContributions]) // Categorize contributions (Precomputes pulls calculations outside loop) const { prs, issues } = useMemo(() => { @@ -305,18 +338,18 @@ export default function ContributorProfilePage() { // Time-series charting data (Chronological sorting by YYYY-MM) const chartData = useMemo(() => { const monthlyBuckets = {} - + filteredContribs.forEach(item => { const date = new Date(item.created_at) const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const yyyymm = `${year}-${month}` const displayName = date.toLocaleString('default', { month: 'short', year: '2-digit' }) // e.g. "May 26" - + if (!monthlyBuckets[yyyymm]) { monthlyBuckets[yyyymm] = { yyyymm, name: displayName, PRs: 0, Issues: 0 } } - + if (item.pull_request) { monthlyBuckets[yyyymm].PRs++ } else { @@ -330,7 +363,7 @@ export default function ContributorProfilePage() { // Export to Markdown Report with pipe & newline escaping const exportMarkdown = () => { const dateStr = new Date().toLocaleDateString() - const orgsStr = searchOrgs.join(', ') + const orgsStr = selectedOrg === 'all' ? searchOrgs.join(', ') : selectedOrg const dateRangeStr = (startDate || 'Beginning') + ' to ' + (endDate || 'Present') let md = `# Contribution Report: ${username}\n\n` @@ -463,6 +496,26 @@ export default function ContributorProfilePage() {
+ + {contributorOrgs.length > 1 && ( +
+ + +
+ )} +
p.isMerged).length} Merged`} accent="var(--blue)" /> i.state === 'closed').length} Closed`} accent="var(--amber)" /> - i.repository_url?.split('/').pop())).size} + i.repository_url?.split('/').pop())).size} sub="distinct repositories" accent="var(--green)" /> @@ -591,4 +644,4 @@ export default function ContributorProfilePage() {
) -} +} \ No newline at end of file From 67bdf943d3b5c1ce1285a8dd681df772d4deda0c Mon Sep 17 00:00:00 2001 From: jikrana Date: Tue, 1 Sep 2026 23:08:04 +0530 Subject: [PATCH 2/3] refactor: centralize organization URL parsing --- src/pages/ContributorProfilePage.jsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/ContributorProfilePage.jsx b/src/pages/ContributorProfilePage.jsx index 4738096..94695d2 100644 --- a/src/pages/ContributorProfilePage.jsx +++ b/src/pages/ContributorProfilePage.jsx @@ -99,6 +99,12 @@ const getFullRepoFromUrl = (url) => { return parts.slice(-2).join('/') } +const getOrgFromRepoUrl = (url) => { + if (!url) return '' + const parts = url.split('/') + return parts[parts.length - 2] || '' +} + export default function ContributorProfilePage() { const { username } = useParams() const navigate = useNavigate() @@ -253,9 +259,7 @@ export default function ContributorProfilePage() { // Organization filter if (selectedOrg !== 'all') { - const repoParts = item.repository_url?.split('/') || [] - const organization = repoParts[repoParts.length - 2] - + const organization = getOrgFromRepoUrl(item.repository_url) if (organization !== selectedOrg) { return false } @@ -287,8 +291,7 @@ export default function ContributorProfilePage() { const contributorOrgs = useMemo(() => { const orgSet = new Set() rawContributions.forEach(item => { - const repoParts = item.repository_url?.split('/') || [] - const org = repoParts[repoParts.length - 2] + const org = getOrgFromRepoUrl(item.repository_url) if (org) orgSet.add(org) }) return Array.from(orgSet) From eb876933852d34a96b3b321c3e60fe1b747325be Mon Sep 17 00:00:00 2001 From: jikrana Date: Wed, 2 Sep 2026 12:17:24 +0530 Subject: [PATCH 3/3] fix: validate repository_url shape in getOrgFromRepoUrl helper --- src/pages/ContributorProfilePage.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/ContributorProfilePage.jsx b/src/pages/ContributorProfilePage.jsx index 94695d2..721180c 100644 --- a/src/pages/ContributorProfilePage.jsx +++ b/src/pages/ContributorProfilePage.jsx @@ -100,9 +100,9 @@ const getFullRepoFromUrl = (url) => { } const getOrgFromRepoUrl = (url) => { - if (!url) return '' - const parts = url.split('/') - return parts[parts.length - 2] || '' + if (typeof url !== 'string') return '' + const match = url.match(/\/repos\/([^/]+)\/([^/]+)/) + return match ? match[1] : '' } export default function ContributorProfilePage() {