Skip to content

Added new column of stale issue ratio in Governance page and also add…#95

Merged
Zahnentferner merged 1 commit into
AOSSIE-Org:mainfrom
rahul-vyas-dev:feat/stale-issue-ratio
Jul 3, 2026
Merged

Added new column of stale issue ratio in Governance page and also add…#95
Zahnentferner merged 1 commit into
AOSSIE-Org:mainfrom
rahul-vyas-dev:feat/stale-issue-ratio

Conversation

@rahul-vyas-dev

@rahul-vyas-dev rahul-vyas-dev commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Screenshots/Recordings:

Before: -
Screenshot_3-7-2026_05644_localhost
Screenshot_3-7-2026_05659_localhost

After: -
https://1drv.ms/v/c/2b55451e453f4443/IQDU_RROSmoWTIQVDSiI5d1VASodqthNAW73a3yuu1QO_is?e=D9CcI3

Additional Notes:

  • Added a new Stale Issues tab.
  • Calculated stale issue ratio per repository using total issues and updated_at (90-day inactivity threshold).
  • Displayed:
    Stale issue count
    Open issue count
    Stale issue ratio
    Health status (Excellent, Healthy, Warning, Critical)
  • Sorted repositories by highest stale issue ratio.
  • Added client-side pagination (10 repositories per page).
  • Added a GitHub button for each repository to quickly navigate to its Issues page.

This helps users quickly identify repositories with inactive issues and navigate directly to GitHub for further investigation.

Checklist

  • My code follows the project's code style and conventions
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contributing Guidelines

⚠️ AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • New Features

    • Added a new governance tab for viewing stale issues by repository.
    • Introduced pagination for the stale-issues list and direct links to each repository’s issues page.
  • Bug Fixes

    • Updated governance health indicators to better reflect issue freshness and severity.
    • Reworked summary cards and tab badges to show stale-issue status more accurately.

@github-actions github-actions Bot added no-issue-linked PR has no linked issue frontend Frontend changes javascript JavaScript/TypeScript changes size/L 201-500 lines changed external-contributor External contributor labels Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a staleRepoStats computation in AppContext based on issue update ages, changes runAudit to source repos from model.totalRepos, and replaces the "Risky Repos" panel in GovernancePage with a paginated "Stale Issues Ratio" tab, stat card, and badge threshold logic.

Changes

Stale Issues Ratio

Layer / File(s) Summary
Stale stats computation and audit repo source
src/context/AppContext.jsx
Adds useMemo, computes staleRepoStats (open vs. stale counts, ratio, sorted descending) using a STALE_DAYS threshold, changes runAudit to use model.totalRepos (sliced to 15 only when no PAT), and exposes staleRepoStats via the context provider.
Stale Issues tab UI
src/pages/GovernancePage.jsx
Adds a "Stale Issues Ratio" tab with getStatus(ratio) severity mapping, pagination over staleRepoStats, updated counts/stat card/badge threshold logic, and a paginated panel replacing "Risky Repos" with repo stale/open counts, severity pills, and GitHub links.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • AOSSIE-Org/OrgExplorer#91: Related change to model.totalRepos usage aligning with total repo count computation in AppContext.

Suggested labels: Typescript Lang

Suggested reviewers: bhavik-mangla, Zahnentferner

Poem

A rabbit counts the issues old,
Ninety days and growing cold. 🐇
Ratios sorted, pills of red,
Stale repos now clearly said.
Hop through pages, two by two,
Fresh insights for the whole crew! 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the main change: adding stale issue ratio information to the Governance page.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added size/L 201-500 lines changed and removed size/L 201-500 lines changed labels Jul 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/context/AppContext.jsx`:
- Around line 179-184: The Ctx.Provider value in AppProvider is rebuilt on every
render, which forces all consumers such as GovernancePage to re-render
unnecessarily. Memoize the provider value object with useMemo and include all
referenced fields and callbacks used in the value prop. Keep the memoized object
near the existing staleRepoStats memoization so the context only changes when
one of its dependencies changes.

In `@src/pages/GovernancePage.jsx`:
- Around line 75-76: The “Stale Issues Ratio” card is using the dead-issues
metric instead of the stale-issue metric, so update the calculation in
GovernancePage to derive from staleRepoStats rather than allIssues/deadIssues.
Use the existing staleRepoStats data from AppContext to compute an aggregate
stale ratio (for example, total staleCount over total openCount), and keep
getStatus(staleIssuesRatio) pointing at that corrected value so the card matches
the “Stale Issues” tab.
- Around line 216-218: The badge color logic in GovernancePage is using a single
`counts[t.key] > 40` check for all tabs, but only the new percentage-based stale
count should use that threshold. Update the span styling logic around the counts
render so `stale` keeps the `> 40` red cutoff, while raw-count tabs like `dead`,
`zombie`, and `license` preserve their prior behavior of turning red whenever
the count is greater than 0.
- Line 54: The GovernancePage component still contains a leftover debug
console.log in the stale repos pagination flow. Remove the console.log from
GovernancePage.jsx so the page no longer emits paginatedStaleRepos to the
console, keeping the render logic and pagination behavior unchanged.
- Around line 313-339: Add explicit type="button" to the pagination controls in
GovernancePage so they do not default to submit behavior. Update the two button
elements that use setStalePage for “← Previous” and “Next →” to include the
button type attribute, keeping their current disabled logic and styling
unchanged.
- Around line 46-53: The pagination state in GovernancePage.jsx is not being
clamped when staleRepoStats changes, so stalePage can stay greater than
totalPages and show an empty view. Update the GovernancePage component to reset
or clamp stalePage whenever staleRepoStats shrinks, using the stalePage state,
totalPages calculation, and the paginatedStaleRepos useMemo as the key places to
adjust the behavior so the current page always stays within bounds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6468ba52-3a67-49e9-834d-47824f334eb3

📥 Commits

Reviewing files that changed from the base of the PR and between ff17681 and d56ad29.

📒 Files selected for processing (2)
  • src/context/AppContext.jsx
  • src/pages/GovernancePage.jsx

Comment on lines 179 to 184
return (
<Ctx.Provider value={{
pat, savePat, orgs, model, issuesData,
rateLimit, loading, loadMsg, govLoading, error, totalRepo,
explore, runAudit, setError,
explore, runAudit, setError, staleRepoStats,
}}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Memoize the context value to avoid re-rendering every consumer.

The Ctx.Provider value object is reconstructed on every render of AppProvider, so every consumer (e.g. GovernancePage) re-renders even when unrelated state changes. Since useMemo is already imported for staleRepoStats, wrapping the provider value is a low-effort follow-up.

♻️ Suggested fix
+  const ctxValue = useMemo(() => ({
+    pat, savePat, orgs, model, issuesData,
+    rateLimit, loading, loadMsg, govLoading, error, totalRepo,
+    explore, runAudit, setError, staleRepoStats,
+  }), [pat, savePat, orgs, model, issuesData, rateLimit, loading, loadMsg, govLoading, error, totalRepo, explore, runAudit, staleRepoStats])
+
   return (
-    <Ctx.Provider value={{
-      pat, savePat, orgs, model, issuesData,
-      rateLimit, loading, loadMsg, govLoading, error, totalRepo,
-      explore, runAudit, setError, staleRepoStats,
-    }}>
+    <Ctx.Provider value={ctxValue}>
       {children}
     </Ctx.Provider>
   )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<Ctx.Provider value={{
pat, savePat, orgs, model, issuesData,
rateLimit, loading, loadMsg, govLoading, error, totalRepo,
explore, runAudit, setError,
explore, runAudit, setError, staleRepoStats,
}}>
const ctxValue = useMemo(() => ({
pat, savePat, orgs, model, issuesData,
rateLimit, loading, loadMsg, govLoading, error, totalRepo,
explore, runAudit, setError, staleRepoStats,
}), [pat, savePat, orgs, model, issuesData, rateLimit, loading, loadMsg, govLoading, error, totalRepo, explore, runAudit, staleRepoStats])
return (
<Ctx.Provider value={ctxValue}>
{children}
</Ctx.Provider>
)
🧰 Tools
🪛 React Doctor (0.5.8)

[warning] 180-180: Every reader of this context redraws on each render because you build its value inline.

Wrap the context value in useMemo or move it outside the component so consumers do not redraw every render.

(jsx-no-constructed-context-values)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/context/AppContext.jsx` around lines 179 - 184, The Ctx.Provider value in
AppProvider is rebuilt on every render, which forces all consumers such as
GovernancePage to re-render unnecessarily. Memoize the provider value object
with useMemo and include all referenced fields and callbacks used in the value
prop. Keep the memoized object near the existing staleRepoStats memoization so
the context only changes when one of its dependencies changes.

Source: Linters/SAST tools

Comment on lines +46 to +53
const ITEMS_PER_PAGE = 10
const [stalePage, setStalePage] = useState(1)
const totalPages = Math.ceil(staleRepoStats.length / ITEMS_PER_PAGE)

const paginatedStaleRepos = useMemo(() => {
const start = (stalePage - 1) * ITEMS_PER_PAGE
return staleRepoStats.slice(start, start + ITEMS_PER_PAGE)
}, [staleRepoStats, stalePage])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

stalePage isn't reset when staleRepoStats shrinks.

If a re-run audit yields fewer repos, stalePage can remain past totalPages, showing an empty page with "Page X of Y" where X > Y (Next stays enabled since stalePage === totalPages never holds).

🐛 Suggested fix
+  useEffect(() => {
+    setStalePage(1)
+  }, [staleRepoStats])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ITEMS_PER_PAGE = 10
const [stalePage, setStalePage] = useState(1)
const totalPages = Math.ceil(staleRepoStats.length / ITEMS_PER_PAGE)
const paginatedStaleRepos = useMemo(() => {
const start = (stalePage - 1) * ITEMS_PER_PAGE
return staleRepoStats.slice(start, start + ITEMS_PER_PAGE)
}, [staleRepoStats, stalePage])
const ITEMS_PER_PAGE = 10
const [stalePage, setStalePage] = useState(1)
const totalPages = Math.ceil(staleRepoStats.length / ITEMS_PER_PAGE)
useEffect(() => {
setStalePage(1)
}, [staleRepoStats])
const paginatedStaleRepos = useMemo(() => {
const start = (stalePage - 1) * ITEMS_PER_PAGE
return staleRepoStats.slice(start, start + ITEMS_PER_PAGE)
}, [staleRepoStats, stalePage])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/GovernancePage.jsx` around lines 46 - 53, The pagination state in
GovernancePage.jsx is not being clamped when staleRepoStats changes, so
stalePage can stay greater than totalPages and show an empty view. Update the
GovernancePage component to reset or clamp stalePage whenever staleRepoStats
shrinks, using the stalePage state, totalPages calculation, and the
paginatedStaleRepos useMemo as the key places to adjust the behavior so the
current page always stays within bounds.

const start = (stalePage - 1) * ITEMS_PER_PAGE
return staleRepoStats.slice(start, start + ITEMS_PER_PAGE)
}, [staleRepoStats, stalePage])
console.log(paginatedStaleRepos);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove leftover debug console.log.

🧹 Suggested fix
-  console.log(paginatedStaleRepos);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(paginatedStaleRepos);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/GovernancePage.jsx` at line 54, The GovernancePage component still
contains a leftover debug console.log in the stale repos pagination flow. Remove
the console.log from GovernancePage.jsx so the page no longer emits
paginatedStaleRepos to the console, keeping the render logic and pagination
behavior unchanged.

Comment on lines +75 to +76
// Health check 2 — Percentage of dead issues relative to all issues
const staleIssuesRatio = allIssues.length ? (deadIssues.length / allIssues.length) * 100 : 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

"Stale Issues Ratio" stat card shows the wrong metric.

staleIssuesRatio is computed as dead issues (created_at ≥ 90 days) divided by all issues — the same definition used for the "Dead Issues" health check, not the new stale-issue metric (updated_at inactivity) computed as staleRepoStats in AppContext. The stat card labeled "Stale Issues Ratio" and its severity color (getStatus(staleIssuesRatio)) will therefore show numbers inconsistent with the "Stale Issues" tab built from staleRepoStats, which is confusing since both are presented under the same "stale" concept on this page.

Consider deriving the stat card from staleRepoStats instead, e.g. an aggregate ratio across all repos:

const totalStale = staleRepoStats.reduce((s, r) => s + r.staleCount, 0)
const totalOpen  = staleRepoStats.reduce((s, r) => s + r.openCount, 0)
const staleIssuesRatio = totalOpen ? (totalStale / totalOpen) * 100 : 0

Also applies to: 158-158

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/GovernancePage.jsx` around lines 75 - 76, The “Stale Issues Ratio”
card is using the dead-issues metric instead of the stale-issue metric, so
update the calculation in GovernancePage to derive from staleRepoStats rather
than allIssues/deadIssues. Use the existing staleRepoStats data from AppContext
to compute an aggregate stale ratio (for example, total staleCount over total
openCount), and keep getStatus(staleIssuesRatio) pointing at that corrected
value so the card matches the “Stale Issues” tab.

Comment on lines +216 to 218
<span style={{ color: counts[t.key] > 40 ? 'var(--red)' : 'var(--green)', marginLeft: 4 }}>
{counts[t.key]}
</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Badge threshold > 40 is misapplied to raw-count tabs.

This condition now drives the badge color for dead, zombie, and license tabs too, but those counts values are raw counts (e.g. deadIssues.length, which per the PR screenshots can be in the thousands), not percentages. A threshold of 40 makes sense for the new percentage-based stale count but silently changes when dead/zombie/license badges turn red (previously any count > 0).

🐛 Suggested fix
-              <span style={{ color: counts[t.key] > 40 ? 'var(--red)' : 'var(--green)', marginLeft: 4 }}>
+              <span style={{ color: (t.key === 'stale' ? counts[t.key] > 40 : counts[t.key] > 0) ? 'var(--red)' : 'var(--green)', marginLeft: 4 }}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span style={{ color: counts[t.key] > 40 ? 'var(--red)' : 'var(--green)', marginLeft: 4 }}>
{counts[t.key]}
</span>
<span style={{ color: (t.key === 'stale' ? counts[t.key] > 40 : counts[t.key] > 0) ? 'var(--red)' : 'var(--green)', marginLeft: 4 }}>
{counts[t.key]}
</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/GovernancePage.jsx` around lines 216 - 218, The badge color logic
in GovernancePage is using a single `counts[t.key] > 40` check for all tabs, but
only the new percentage-based stale count should use that threshold. Update the
span styling logic around the counts render so `stale` keeps the `> 40` red
cutoff, while raw-count tabs like `dead`, `zombie`, and `license` preserve their
prior behavior of turning red whenever the count is greater than 0.

Comment on lines +313 to +339
<button
onClick={() => setStalePage(p => p - 1)}
disabled={stalePage === 1}
style={{
padding: '8px 14px',
cursor: stalePage === 1 ? 'not-allowed' : 'pointer',
opacity: stalePage === 1 ? 0.5 : 1
}}
>
← Previous
</button>

<span style={{ fontSize: 13, color: 'var(--text2)' }}>
Page {stalePage} of {totalPages}
</span>

<button
onClick={() => setStalePage(p => p + 1)}
disabled={stalePage === totalPages}
style={{
padding: '8px 14px',
cursor: stalePage === totalPages ? 'not-allowed' : 'pointer',
opacity: stalePage === totalPages ? 0.5 : 1
}}
>
Next →
</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add explicit type="button" to pagination controls.

Without an explicit type, these buttons default to type="submit" and could trigger accidental form submission if this markup is ever nested in a <form>.

🔧 Suggested fix
   <button
+    type="button"
     onClick={() => setStalePage(p => p - 1)}
     disabled={stalePage === 1}
   <button
+    type="button"
     onClick={() => setStalePage(p => p + 1)}
     disabled={stalePage === totalPages}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
onClick={() => setStalePage(p => p - 1)}
disabled={stalePage === 1}
style={{
padding: '8px 14px',
cursor: stalePage === 1 ? 'not-allowed' : 'pointer',
opacity: stalePage === 1 ? 0.5 : 1
}}
>
Previous
</button>
<span style={{ fontSize: 13, color: 'var(--text2)' }}>
Page {stalePage} of {totalPages}
</span>
<button
onClick={() => setStalePage(p => p + 1)}
disabled={stalePage === totalPages}
style={{
padding: '8px 14px',
cursor: stalePage === totalPages ? 'not-allowed' : 'pointer',
opacity: stalePage === totalPages ? 0.5 : 1
}}
>
Next
</button>
<button
type="button"
onClick={() => setStalePage(p => p - 1)}
disabled={stalePage === 1}
style={{
padding: '8px 14px',
cursor: stalePage === 1 ? 'not-allowed' : 'pointer',
opacity: stalePage === 1 ? 0.5 : 1
}}
>
Previous
</button>
<span style={{ fontSize: 13, color: 'var(--text2)' }}>
Page {stalePage} of {totalPages}
</span>
<button
type="button"
onClick={() => setStalePage(p => p + 1)}
disabled={stalePage === totalPages}
style={{
padding: '8px 14px',
cursor: stalePage === totalPages ? 'not-allowed' : 'pointer',
opacity: stalePage === totalPages ? 0.5 : 1
}}
>
Next
</button>
🧰 Tools
🪛 React Doctor (0.5.8)

[warning] 313-313: Your users can submit the form by accident because a <button> with no type defaults to submit.

Set an explicit button type so plain buttons do not submit forms by accident: type="button", "submit", or "reset".

(button-has-type)


[warning] 329-329: Your users can submit the form by accident because a <button> with no type defaults to submit.

Set an explicit button type so plain buttons do not submit forms by accident: type="button", "submit", or "reset".

(button-has-type)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/GovernancePage.jsx` around lines 313 - 339, Add explicit
type="button" to the pagination controls in GovernancePage so they do not
default to submit behavior. Update the two button elements that use setStalePage
for “← Previous” and “Next →” to include the button type attribute, keeping
their current disabled logic and styling unchanged.

Source: Linters/SAST tools

@Zahnentferner Zahnentferner merged commit a91f8a4 into AOSSIE-Org:main Jul 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor External contributor frontend Frontend changes javascript JavaScript/TypeScript changes no-issue-linked PR has no linked issue size/L 201-500 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants