# # Copyright (c) 2025, RT-Thread Development Team # # SPDX-License-Identifier: Apache-2.0 # # Change Logs: # Date Author Notes # 2025-10-27 GitHub Copilot Post CI results to PR comments name: CI Results Comment # on: # workflow_run: # workflows: # - "RT-Thread BSP Static Build Check" # - "Static code analysis" # - "Check File Format and License" # - "utest_auto_run" # - "ToolsCI" # - "pkgs_test" # types: # - completed permissions: pull-requests: write issues: write actions: read checks: read jobs: comment-ci-results: runs-on: ubuntu-22.04 if: github.event.workflow_run.event == 'pull_request' && github.repository_owner == 'RT-Thread' steps: - name: Get PR number id: get-pr uses: actions/github-script@v7 with: script: | // Get PR number from workflow_run const prNumber = context.payload.workflow_run.pull_requests[0]?.number; if (!prNumber) { console.log('No PR found in workflow_run'); // Fallback: search for PR by branch const pulls = await github.rest.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}` }); if (pulls.data.length === 0) { console.log('No open PR found for this branch'); return null; } const pr = pulls.data[0]; console.log(`Found PR #${pr.number}`); return pr.number; } console.log(`Found PR #${prNumber}`); return prNumber; - name: Get workflow run details if: steps.get-pr.outputs.result != 'null' id: workflow-details uses: actions/github-script@v7 with: script: | const prNumber = ${{ steps.get-pr.outputs.result }}; if (!prNumber) { return { success: false, message: 'No PR found' }; } // Get all workflow runs for this PR const workflowRuns = await github.rest.actions.listWorkflowRunsForRepo({ owner: context.repo.owner, repo: context.repo.repo, event: 'pull_request', per_page: 100 }); // Filter runs for this specific PR const prRuns = workflowRuns.data.workflow_runs.filter(run => { return run.pull_requests.some(pr => pr.number === prNumber); }); // Get the latest run for each workflow const workflowMap = new Map(); for (const run of prRuns) { const existing = workflowMap.get(run.name); if (!existing || new Date(run.created_at) > new Date(existing.created_at)) { workflowMap.set(run.name, run); } } // Prepare results summary const results = []; for (const [name, run] of workflowMap) { let status = '🟡'; let statusText = 'In Progress'; if (run.status === 'completed') { if (run.conclusion === 'success') { status = '✅'; statusText = 'Success'; } else if (run.conclusion === 'failure') { status = '❌'; statusText = 'Failure'; } else if (run.conclusion === 'cancelled') { status = '⏭️'; statusText = 'Cancelled'; } else if (run.conclusion === 'skipped') { status = '⏭️'; statusText = 'Skipped'; } } else if (run.status === 'queued') { status = '🟠'; statusText = 'Queued'; } results.push({ name: name, status: status, statusText: statusText, url: run.html_url, conclusion: run.conclusion, runId: run.id }); } return { success: true, results: results, prNumber: prNumber }; - name: Get job details if: steps.get-pr.outputs.result != 'null' id: job-details uses: actions/github-script@v7 with: script: | const workflowDetails = ${{ steps.workflow-details.outputs.result }}; if (!workflowDetails || !workflowDetails.success) { return { jobs: [] }; } const allJobs = []; for (const result of workflowDetails.results) { try { const jobs = await github.rest.actions.listJobsForWorkflowRun({ owner: context.repo.owner, repo: context.repo.repo, run_id: result.runId, per_page: 100 }); for (const job of jobs.data.jobs) { let jobStatus = '⌛'; if (job.status === 'completed') { if (job.conclusion === 'success') { jobStatus = '✅'; } else if (job.conclusion === 'failure') { jobStatus = '❌'; } else if (job.conclusion === 'skipped') { jobStatus = '⏭️'; } } else if (job.status === 'in_progress') { jobStatus = '🔄'; } else if (job.status === 'queued') { jobStatus = '🟠'; } allJobs.push({ workflow: result.name, name: job.name, status: jobStatus, conclusion: job.conclusion || job.status, url: job.html_url }); } } catch (error) { console.log(`Error getting jobs for workflow ${result.name}: ${error.message}`); } } return { jobs: allJobs }; - name: Post or update comment if: steps.get-pr.outputs.result != 'null' uses: actions/github-script@v7 with: script: | const prNumber = ${{ steps.get-pr.outputs.result }}; const workflowDetails = ${{ steps.workflow-details.outputs.result }}; const jobDetails = ${{ steps.job-details.outputs.result }}; if (!workflowDetails || !workflowDetails.success) { console.log('No workflow details available'); return; } // Prepare comment body const now = new Date(); const timestamp = now.toISOString(); const results = workflowDetails.results; const jobs = jobDetails.jobs || []; let commentBody = '\n'; commentBody += '## 🤖 CI Test Results\n\n'; commentBody += `**Last Updated:** ${timestamp}\n\n`; commentBody += '### Test Spec & Results:\n\n'; commentBody += '✅ Success | ❌ Failure | 🟠 Queued | 🟡 Progress | ⏭️ Skipped | ⚠️ Quarantine\n\n'; // Group jobs by workflow const jobsByWorkflow = new Map(); for (const job of jobs) { if (!jobsByWorkflow.has(job.workflow)) { jobsByWorkflow.set(job.workflow, []); } jobsByWorkflow.get(job.workflow).push(job); } // Calculate overall statistics let totalSuccess = 0; let totalFailure = 0; let totalQueued = 0; let totalProgress = 0; let totalSkipped = 0; for (const result of results) { if (result.conclusion === 'success') totalSuccess++; else if (result.conclusion === 'failure') totalFailure++; else if (result.statusText === 'Queued') totalQueued++; else if (result.statusText === 'In Progress') totalProgress++; else if (result.conclusion === 'skipped' || result.conclusion === 'cancelled') totalSkipped++; } // Summary line commentBody += '#### Overall Summary\n\n'; commentBody += `- ✅ **Success:** ${totalSuccess}\n`; commentBody += `- ❌ **Failure:** ${totalFailure}\n`; commentBody += `- 🟠 **Queued:** ${totalQueued}\n`; commentBody += `- 🟡 **In Progress:** ${totalProgress}\n`; commentBody += `- ⏭️ **Skipped:** ${totalSkipped}\n\n`; commentBody += '---\n\n'; commentBody += '### Detailed Results\n\n'; // Build detailed results for (const result of results) { commentBody += `
\n`; commentBody += `${result.status} ${result.name} - ${result.statusText}\n\n`; commentBody += `**Workflow:** [${result.name}](${result.url})\n\n`; // Show jobs for this workflow const workflowJobs = jobsByWorkflow.get(result.name) || []; if (workflowJobs.length > 0) { commentBody += '**Jobs:**\n\n'; for (const job of workflowJobs) { commentBody += `- ${job.status} [${job.name}](${job.url})\n`; } } commentBody += '\n
\n\n'; } commentBody += '\n---\n'; commentBody += '*🤖 This comment is automatically generated and updated by the CI system.*\n'; // Check if comment already exists const comments = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber }); const existingComment = comments.data.find(comment => comment.user.login === 'github-actions[bot]' && comment.body.includes('') ); if (existingComment) { // Update existing comment await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existingComment.id, body: commentBody }); console.log(`Updated comment ${existingComment.id} on PR #${prNumber}`); } else { // Create new comment await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: prNumber, body: commentBody }); console.log(`Created new comment on PR #${prNumber}`); }