|
| 1 | +name: PR change chart |
| 2 | + |
| 3 | +# Posts (and keeps updated) a single comment on each pull request showing the |
| 4 | +# diff size per file: a Mermaid pie chart of each file's share of the churn |
| 5 | +# plus a collapsible +/- breakdown table. Uses only first-party actions and |
| 6 | +# the PR API, so there is no checkout, no build, and no third-party action. |
| 7 | + |
| 8 | +on: |
| 9 | + pull_request: |
| 10 | + types: [opened, synchronize, reopened] |
| 11 | + |
| 12 | +permissions: |
| 13 | + contents: read |
| 14 | + pull-requests: write |
| 15 | + |
| 16 | +jobs: |
| 17 | + change-chart: |
| 18 | + runs-on: ubuntu-latest |
| 19 | + steps: |
| 20 | + - name: Build and upsert the change-summary comment |
| 21 | + uses: actions/github-script@v7 |
| 22 | + with: |
| 23 | + script: | |
| 24 | + // Hidden marker so we can find and update our own comment instead |
| 25 | + // of posting a new one on every push. |
| 26 | + const MARKER = '<!-- pr-change-chart -->'; |
| 27 | + const TOP = 15; // cap rows so the chart/table stay readable |
| 28 | + const { owner, repo } = context.repo; |
| 29 | + const pr = context.payload.pull_request; |
| 30 | +
|
| 31 | + // Per-file additions/deletions come straight from the PR API. |
| 32 | + const files = await github.paginate(github.rest.pulls.listFiles, { |
| 33 | + owner, repo, pull_number: pr.number, per_page: 100, |
| 34 | + }); |
| 35 | +
|
| 36 | + let totalAdd = 0, totalDel = 0; |
| 37 | + const rows = files.map(f => { |
| 38 | + totalAdd += f.additions; |
| 39 | + totalDel += f.deletions; |
| 40 | + return { name: f.filename, add: f.additions, del: f.deletions, total: f.changes }; |
| 41 | + }).sort((a, b) => b.total - a.total); |
| 42 | +
|
| 43 | + const shown = rows.slice(0, TOP); |
| 44 | + const rest = rows.slice(TOP); |
| 45 | + const restTotal = rest.reduce((s, r) => s + r.total, 0); |
| 46 | + const churn = totalAdd + totalDel; |
| 47 | +
|
| 48 | + // Mermaid pie of churn share per file (GitHub renders Mermaid |
| 49 | + // natively). Guarded: a pie with no slices would error, so we skip |
| 50 | + // it when the diff is all-binary / zero-line. |
| 51 | + let pie = ''; |
| 52 | + if (churn > 0) { |
| 53 | + pie = '```mermaid\npie showData\n'; |
| 54 | + pie += ` title Lines changed per file (${churn} total)\n`; |
| 55 | + for (const r of shown) { |
| 56 | + if (r.total <= 0) continue; |
| 57 | + const label = r.name.replace(/"/g, "'"); |
| 58 | + pie += ` "${label}" : ${r.total}\n`; |
| 59 | + } |
| 60 | + if (restTotal > 0) pie += ` "… ${rest.length} more files" : ${restTotal}\n`; |
| 61 | + pie += '```'; |
| 62 | + } |
| 63 | +
|
| 64 | + // Collapsible per-file table with the +/- split. |
| 65 | + let table = '| File | + | − | total |\n|---|--:|--:|--:|\n'; |
| 66 | + for (const r of shown) { |
| 67 | + table += `| \`${r.name}\` | ${r.add} | ${r.del} | ${r.total} |\n`; |
| 68 | + } |
| 69 | + if (rest.length) { |
| 70 | + table += `| _… ${rest.length} more files_ | | | ${restTotal} |\n`; |
| 71 | + } |
| 72 | + table += `| **Total (${files.length} files)** | **${totalAdd}** | **${totalDel}** | **${churn}** |\n`; |
| 73 | +
|
| 74 | + const body = [ |
| 75 | + MARKER, |
| 76 | + '### 📊 Change summary', |
| 77 | + `**${files.length}** files changed · **+${totalAdd}** / **−${totalDel}** lines`, |
| 78 | + '', |
| 79 | + pie, |
| 80 | + '', |
| 81 | + '<details><summary>Per-file breakdown</summary>', |
| 82 | + '', |
| 83 | + table, |
| 84 | + '</details>', |
| 85 | + ].join('\n'); |
| 86 | +
|
| 87 | + // Upsert: update our marked comment if it exists, else create it. |
| 88 | + const comments = await github.paginate(github.rest.issues.listComments, { |
| 89 | + owner, repo, issue_number: pr.number, per_page: 100, |
| 90 | + }); |
| 91 | + const mine = comments.find(c => c.body && c.body.includes(MARKER)); |
| 92 | + if (mine) { |
| 93 | + await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body }); |
| 94 | + } else { |
| 95 | + await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body }); |
| 96 | + } |
0 commit comments