Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions .github/workflows/coverage-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Post Coverage Comment

on:
workflow_run:
workflows: ["Run Unit Tests"]
types:
- completed

permissions:
pull-requests: write
actions: read

jobs:
comment:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: Download Coverage Artifacts
uses: actions/download-artifact@v4
with:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
name: coverage-data

- name: Upload Coverage Report
id: upload-report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 14

- name: Post Comment
uses: actions/github-script@v6
env:
ARTIFACT_URL: ${{ steps.upload-report.outputs.artifact-url }}
with:
script: |
const fs = require('fs');

const loadSummary = (path) => {
try {
return JSON.parse(fs.readFileSync(path, 'utf8'));
} catch (e) {
console.log(`Could not read ${path}: ${e}`);
return null;
}
};

const mainSummary = loadSummary('./coverage-main-summary.json');
const prSummary = loadSummary('./coverage-pr-summary.json');

if (!mainSummary || !prSummary) {
console.log("Missing coverage data, skipping comment.");
return;
}

let markdown = `### 🧪 Code Coverage\n\n`;

markdown += `[⬇️ **Download Full Report**](${process.env.ARTIFACT_URL})\n\n`;
markdown += `| Metric | Main | PR | Delta |\n`;
markdown += `| :--- | :---: | :---: | :---: |\n`;

const metrics = ['lines', 'statements', 'functions', 'branches'];
const getPct = (summary, metric) => summary.total[metric].pct;

metrics.forEach(metric => {
const oldPct = getPct(mainSummary, metric);
const newPct = getPct(prSummary, metric);
const diff = (newPct - oldPct).toFixed(2);

let icon = '';
if (diff > 0) icon = '🟢';
else if (diff < 0) icon = '🔴';
else icon = '⚪️';

const diffStr = diff > 0 ? `+${diff}%` : `${diff}%`;
markdown += `| **${metric}** | ${oldPct}% | ${newPct}% | ${icon} ${diffStr} |\n`;
});

markdown += `\n_Generated by [coverage-comment.yml](https://github.com/a2aproject/a2a-js/actions/workflows/coverage-comment.yml)_`;

const prNumber = fs.readFileSync('./PR_NUMBER', 'utf8').trim();

if (!prNumber) {
console.log("No PR number found.");
return;
}

const { owner, repo } = context.repo;

const comments = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
});

const existingComment = comments.data.find(c =>
c.body.includes('Generated by [coverage-comment.yml]') &&
c.user.type === 'Bot'
);

if (existingComment) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existingComment.id,
body: markdown
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: markdown
});
}
89 changes: 14 additions & 75 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,83 +85,22 @@ jobs:
run: |
npm ci
npm run coverage
mv coverage/coverage-summary.json coverage-pr-summary.json
mv ${{ runner.temp }}/coverage-main-summary.json coverage-main-summary.json

- name: Upload Artifact (PR)
id: upload-artifact-pr
- name: Save PR number
run: |
echo ${{ github.event.number }} > ./PR_NUMBER

- name: Upload Coverage Artifacts
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request'
with:
name: coverage-report
path: coverage
retention-days: 14

- name: Post Comment
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
ARTIFACT_URL: ${{ steps.upload-artifact-pr.outputs.artifact-url }}
with:
script: |
const fs = require('fs');

const loadSummary = (path) => {
return JSON.parse(fs.readFileSync(path, 'utf8'));
};

let markdown = `### 🧪 Code Coverage\n\n`;
markdown += `[⬇️ **Download HTML Report**](${process.env.ARTIFACT_URL})\n\n`;
markdown += `| Metric | Main | PR | Delta |\n`;
markdown += `| :--- | :---: | :---: | :---: |\n`;

const metrics = ['lines', 'statements', 'functions', 'branches'];
const getPct = (summary, metric) => summary.total[metric].pct;

const mainSummary = loadSummary('${{ runner.temp }}/coverage-main-summary.json');
const prSummary = loadSummary('./coverage/coverage-summary.json');

metrics.forEach(metric => {
const oldPct = getPct(mainSummary, metric);
const newPct = getPct(prSummary, metric);
const diff = (newPct - oldPct).toFixed(2);

let icon = '';
if (diff > 0) icon = '🟢';
else if (diff < 0) icon = '🔴';
else icon = '⚪️';

const diffStr = diff > 0 ? `+${diff}%` : `${diff}%`;

markdown += `| **${metric}** | ${oldPct}% | ${newPct}% | ${icon} ${diffStr} |\n`;
});

markdown += `\n_Generated by [unit-tests.yml](https://github.com/a2aproject/a2a-js/actions/workflows/unit-tests.yml)_`;

const { owner, repo } = context.repo;
const issue_number = context.issue.number;

const comments = await github.rest.issues.listComments({
owner,
repo,
issue_number,
});

const existingComment = comments.data.find(c =>
c.body.includes('Generated by [unit-tests.yml]') &&
c.user.type === 'Bot'
);
name: coverage-data
path: |
coverage-main-summary.json
coverage-pr-summary.json
coverage/
PR_NUMBER
retention-days: 1

if (existingComment) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existingComment.id,
body: markdown
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: markdown
});
}