]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Try manual Codecov comments
authorshamoon <4887959+shamoon@users.noreply.github.com>
Wed, 24 Sep 2025 21:48:06 +0000 (14:48 -0700)
committershamoon <4887959+shamoon@users.noreply.github.com>
Thu, 25 Sep 2025 07:46:00 +0000 (00:46 -0700)
.github/workflows/ci.yml

index 363b2a5121650546be24c7c534e6899cfda1d44b..f0261605e20a764040f0df7f08adc24790626327 100644 (file)
@@ -322,6 +322,220 @@ jobs:
         run: cd src-ui && pnpm exec playwright install
       - name: Run Playwright e2e tests
         run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
+  codecov-comment:
+    name: "Codecov PR Comment"
+    runs-on: ubuntu-24.04
+    needs:
+      - tests-backend
+      - tests-frontend
+      - tests-frontend-e2e
+    if: github.event_name == 'pull_request'
+    permissions:
+      contents: read
+      pull-requests: write
+    steps:
+      - name: Gather pull request context
+        id: pr
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const pr = context.payload.pull_request;
+            if (!pr) {
+              core.info('No associated pull request. Skipping.');
+              core.setOutput('shouldRun', 'false');
+              return;
+            }
+
+            core.setOutput('shouldRun', 'true');
+            core.setOutput('prNumber', pr.number.toString());
+            core.setOutput('headSha', pr.head.sha);
+      - name: Fetch Codecov coverage
+        id: coverage
+        if: steps.pr.outputs.shouldRun == 'true'
+        uses: actions/github-script@v7
+        env:
+          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+          COMMIT_SHA: ${{ steps.pr.outputs.headSha }}
+        with:
+          script: |
+            const token = process.env.CODECOV_TOKEN;
+            if (!token) {
+              core.warning('CODECOV_TOKEN secret is not available; skipping comment.');
+              core.setOutput('shouldComment', 'false');
+              return;
+            }
+
+            const commitSha = process.env.COMMIT_SHA;
+            const owner = context.repo.owner;
+            const repo = context.repo.repo;
+            const url = `https://codecov.io/api/v2/github/${owner}/repos/${repo}/commits/${commitSha}/report`;
+            const maxAttempts = 10;
+            const waitMs = 15000;
+            const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+
+            let data;
+            for (let attempt = 1; attempt <= maxAttempts; attempt++) {
+              core.info(`Fetching Codecov report (attempt ${attempt}/${maxAttempts})`);
+              const response = await fetch(url, {
+                headers: {
+                  Authorization: `Bearer ${token}`,
+                  'Content-Type': 'application/json',
+                  Accept: 'application/json',
+                },
+              });
+
+              if (response.status === 404) {
+                core.info('Report not ready yet (404). Waiting before retrying.');
+                await sleep(waitMs);
+                continue;
+              }
+
+              if (!response.ok) {
+                const text = await response.text();
+                throw new Error(`Codecov API returned ${response.status}: ${text}`);
+              }
+
+              data = await response.json();
+              if (data && Object.keys(data).length > 0) {
+                break;
+              }
+
+              core.info('Report payload empty. Waiting before retrying.');
+              await sleep(waitMs);
+            }
+
+            if (!data) {
+              core.warning('Unable to retrieve Codecov report after multiple attempts.');
+              core.setOutput('shouldComment', 'false');
+              return;
+            }
+
+            const totals = data.report?.totals ?? data.commit?.totals ?? data.totals;
+            if (!totals) {
+              core.warning('Codecov response does not contain coverage totals.');
+              core.setOutput('shouldComment', 'false');
+              return;
+            }
+
+            const compareTotals = data.report?.compare?.totals ?? data.compare?.totals;
+            const flagsRaw = data.report?.totals_by_flag ?? data.report?.components ?? [];
+
+            const toNumber = (value) => {
+              if (value === null || value === undefined || value === '') {
+                return undefined;
+              }
+              const num = Number(value);
+              return Number.isFinite(num) ? num : undefined;
+            };
+
+            const coverage = toNumber(totals.coverage);
+            const baseCoverage = toNumber(compareTotals?.base_coverage ?? compareTotals?.base);
+            const delta = toNumber(
+              compareTotals?.coverage_change ??
+              compareTotals?.coverage_diff ??
+              totals.delta ??
+              totals.diff ??
+              totals.change,
+            );
+
+            const formatPercent = (value) => {
+              if (value === undefined) return '—';
+              return `${value.toFixed(2)}%`;
+            };
+
+            const formatDelta = (value) => {
+              if (value === undefined) return '—';
+              const sign = value >= 0 ? '+' : '';
+              return `${sign}${value.toFixed(2)}%`;
+            };
+
+            const shortSha = commitSha.slice(0, 7);
+            const lines = [
+              '<!-- codecov-coverage-comment -->',
+              '**Codecov Coverage**',
+              '',
+              `- Head \`${shortSha}\`: ${formatPercent(coverage)}`,
+            ];
+
+            if (baseCoverage !== undefined) {
+              lines.push(`- Base: ${formatPercent(baseCoverage)}`);
+            }
+
+            if (delta !== undefined) {
+              lines.push(`- Change: ${formatDelta(delta)}`);
+            }
+
+            const flagEntries = Array.isArray(flagsRaw)
+              ? flagsRaw
+              : Object.entries(flagsRaw).map(([name, totals]) => ({ name, totals }));
+
+            const flagRows = [];
+            for (const entry of flagEntries) {
+              const label = entry.flag ?? entry.name ?? entry.component ?? entry.id;
+              const entryTotals = entry.totals ?? entry;
+              const entryCoverage = toNumber(entryTotals?.coverage);
+              const entryDelta = toNumber(
+                entryTotals?.coverage_change ??
+                entryTotals?.coverage_diff ??
+                entryTotals?.delta ??
+                entryTotals?.diff,
+              );
+              if (!label || entryCoverage === undefined) {
+                continue;
+              }
+              flagRows.push(`| ${label} | ${formatPercent(entryCoverage)} | ${formatDelta(entryDelta)} |`);
+            }
+
+            if (flagRows.length) {
+              lines.push('');
+              lines.push('| Flag | Coverage | Change |');
+              lines.push('| --- | --- | --- |');
+              lines.push(...flagRows);
+            }
+
+            const commentBody = lines.join('\n');
+            const shouldComment = coverage !== undefined;
+            core.setOutput('shouldComment', shouldComment ? 'true' : 'false');
+            if (shouldComment) {
+              core.setOutput('commentBody', commentBody);
+            }
+      - name: Upsert coverage comment
+        if: steps.pr.outputs.shouldRun == 'true' && steps.coverage.outputs.shouldComment == 'true'
+        uses: actions/github-script@v7
+        env:
+          PR_NUMBER: ${{ steps.pr.outputs.prNumber }}
+          COMMENT_BODY: ${{ steps.coverage.outputs.commentBody }}
+        with:
+          script: |
+            const prNumber = Number(process.env.PR_NUMBER);
+            const body = process.env.COMMENT_BODY;
+            const marker = '<!-- codecov-coverage-comment -->';
+
+            const { data: comments } = await github.rest.issues.listComments({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: prNumber,
+              per_page: 100,
+            });
+
+            const existing = comments.find((comment) => comment.body?.includes(marker));
+            if (existing) {
+              core.info(`Updating existing coverage comment (id: ${existing.id}).`);
+              await github.rest.issues.updateComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                comment_id: existing.id,
+                body,
+              });
+            } else {
+              core.info('Creating new coverage comment.');
+              await github.rest.issues.createComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: prNumber,
+                body,
+              });
+            }
   frontend-bundle-analysis:
     name: "Frontend Bundle Analysis"
     runs-on: ubuntu-24.04