diff --git a/src/context/AppContext.jsx b/src/context/AppContext.jsx index 0d6279c..74a4a52 100644 --- a/src/context/AppContext.jsx +++ b/src/context/AppContext.jsx @@ -33,7 +33,8 @@ export function AppProvider({ children }) { const [loading, setLoading] = useState(false) const [loadMsg, setLoadMsg] = useState('') const [govLoading, setGovLoading] = useState(false) - const [error, setError] = useState('') + const [exploreError, setExploreError] = useState('') + const [auditError, setAuditError] = useState('') const [totalRepo, setTotalRepo] = useState(0) useEffect(() => { @@ -75,7 +76,7 @@ export function AppProvider({ children }) { // Multi-org explore — core of Section 3.2.0 const explore = useCallback(async orgNames => { - setLoading(true); setError(''); setModel(null); setOrgs([]); setIssuesData({}) + setLoading(true); setExploreError(''); setModel(null); setOrgs([]); setIssuesData({}); setAuditError('') try { setLoadMsg('Fetching organization metadata...') const orgRes = await Promise.allSettled(orgNames.map(n => fetchOrg(n, pat))) @@ -124,7 +125,7 @@ export function AppProvider({ children }) { localStorage.setItem('oe_recent', JSON.stringify([...new Set([entry, ...prev])].slice(0, 6))) return true } catch (err) { - setError(err.message === 'RATE_LIMIT' + setExploreError(err.message === 'RATE_LIMIT' ? 'GitHub API rate limit reached. Add a PAT in Settings for 5,000 req/hr.' : err.message) return false @@ -137,18 +138,46 @@ export function AppProvider({ children }) { const runAudit = useCallback(async () => { if (!model || govLoading) return setGovLoading(true) + setAuditError('') const map = {} - const repos = pat? model.totalRepos : model.totalRepos.slice(0, 15) + const repos = pat ? model.totalRepos : model.totalRepos.slice(0, 15) + let rateLimitHit = false + const otherFailures = [] // Batches of 5 using Promise.allSettled for (let i = 0; i < repos.length; i += 5) { const batch = repos.slice(i, i + 5) - await Promise.allSettled(batch.map(async repo => { - map[`${repo.orgLogin}/${repo.name}`] = await fetchIssues(repo.orgLogin, repo.name, pat) + const results = await Promise.allSettled(batch.map(async repo => { + const issues = await fetchIssues(repo.orgLogin, repo.name, pat) + map[`${repo.orgLogin}/${repo.name}`] = issues })) + + for (let j = 0; j < results.length; j++) { + const result = results[j] + if (result.status === 'rejected') { + const msg = result.reason?.message + if (msg === 'RATE_LIMIT') { + rateLimitHit = true + } else { + // NOT_FOUND, HTTP_4xx/5xx, network failures, etc. + otherFailures.push(batch[j] ? `${batch[j].orgLogin}/${batch[j].name}` : 'unknown repo') + } + } + } + + if (rateLimitHit) { + break // no point continuing — remaining fetches will also fail + } } + setIssuesData(map) setGovLoading(false) + + if (rateLimitHit) { + setAuditError('GitHub API rate limit reached during audit. Results shown may be incomplete. Add a PAT in Settings for 5,000 req/hr.') + } else if (otherFailures.length) { + setAuditError(`Audit completed with errors. Failed to fetch issues for: ${otherFailures.join(', ')}. Results may be incomplete.`) + } }, [model, pat, govLoading]) const STALE_DAYS = 90 @@ -188,8 +217,10 @@ export function AppProvider({ children }) { return ( {children} diff --git a/src/pages/GovernancePage.jsx b/src/pages/GovernancePage.jsx index 76a290c..f70f831 100644 --- a/src/pages/GovernancePage.jsx +++ b/src/pages/GovernancePage.jsx @@ -40,7 +40,7 @@ const getStatus = ratio => { } export default function GovernancePage() { - const { model, issuesData, runAudit, govLoading, staleRepoStats } = useApp() + const { model, issuesData, runAudit, govLoading, staleRepoStats, auditError, setAuditError } = useApp() const [tab, setTab] = useState('dead') const ITEMS_PER_PAGE = 10 @@ -152,6 +152,29 @@ export default function GovernancePage() { } /> + {/* Audit error banner */} + {auditError && ( +
+ {auditError} + +
+ )} + {/* Summary stat cards */}
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index cffcbd3..015a037 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -7,7 +7,7 @@ import { C, Spinner } from '../components/UI' const QUICK = ['AOSSIE-Org', 'DjedAlliance', 'StabilityNexus'] export default function HomePage() { - const { explore, loading, loadMsg, error } = useApp() + const { explore, loading, loadMsg, exploreError } = useApp() const navigate = useNavigate() const [input, setInput] = useState('') const [chips, setChips] = useState([]) @@ -89,7 +89,7 @@ export default function HomePage() {

Type an org name and press Enter or comma to add. Add multiple orgs to analyze as a unified portfolio.

- {error &&

{error}

} + {exploreError &&

{exploreError}

}
{/* Loading */}