ci_results_comment.yml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #
  2. # Copyright (c) 2025, RT-Thread Development Team
  3. #
  4. # SPDX-License-Identifier: Apache-2.0
  5. #
  6. # Change Logs:
  7. # Date Author Notes
  8. # 2025-10-27 GitHub Copilot Post CI results to PR comments
  9. name: CI Results Comment
  10. # on:
  11. # workflow_run:
  12. # workflows:
  13. # - "RT-Thread BSP Static Build Check"
  14. # - "Static code analysis"
  15. # - "Check File Format and License"
  16. # - "utest_auto_run"
  17. # - "ToolsCI"
  18. # - "pkgs_test"
  19. # types:
  20. # - completed
  21. permissions:
  22. pull-requests: write
  23. issues: write
  24. actions: read
  25. checks: read
  26. jobs:
  27. comment-ci-results:
  28. runs-on: ubuntu-22.04
  29. if: github.event.workflow_run.event == 'pull_request' && github.repository_owner == 'RT-Thread'
  30. steps:
  31. - name: Get PR number
  32. id: get-pr
  33. uses: actions/github-script@v7
  34. with:
  35. script: |
  36. // Get PR number from workflow_run
  37. const prNumber = context.payload.workflow_run.pull_requests[0]?.number;
  38. if (!prNumber) {
  39. console.log('No PR found in workflow_run');
  40. // Fallback: search for PR by branch
  41. const pulls = await github.rest.pulls.list({
  42. owner: context.repo.owner,
  43. repo: context.repo.repo,
  44. state: 'open',
  45. head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`
  46. });
  47. if (pulls.data.length === 0) {
  48. console.log('No open PR found for this branch');
  49. return null;
  50. }
  51. const pr = pulls.data[0];
  52. console.log(`Found PR #${pr.number}`);
  53. return pr.number;
  54. }
  55. console.log(`Found PR #${prNumber}`);
  56. return prNumber;
  57. - name: Get workflow run details
  58. if: steps.get-pr.outputs.result != 'null'
  59. id: workflow-details
  60. uses: actions/github-script@v7
  61. with:
  62. script: |
  63. const prNumber = ${{ steps.get-pr.outputs.result }};
  64. if (!prNumber) {
  65. return { success: false, message: 'No PR found' };
  66. }
  67. // Get all workflow runs for this PR
  68. const workflowRuns = await github.rest.actions.listWorkflowRunsForRepo({
  69. owner: context.repo.owner,
  70. repo: context.repo.repo,
  71. event: 'pull_request',
  72. per_page: 100
  73. });
  74. // Filter runs for this specific PR
  75. const prRuns = workflowRuns.data.workflow_runs.filter(run => {
  76. return run.pull_requests.some(pr => pr.number === prNumber);
  77. });
  78. // Get the latest run for each workflow
  79. const workflowMap = new Map();
  80. for (const run of prRuns) {
  81. const existing = workflowMap.get(run.name);
  82. if (!existing || new Date(run.created_at) > new Date(existing.created_at)) {
  83. workflowMap.set(run.name, run);
  84. }
  85. }
  86. // Prepare results summary
  87. const results = [];
  88. for (const [name, run] of workflowMap) {
  89. let status = '🟡';
  90. let statusText = 'In Progress';
  91. if (run.status === 'completed') {
  92. if (run.conclusion === 'success') {
  93. status = '✅';
  94. statusText = 'Success';
  95. } else if (run.conclusion === 'failure') {
  96. status = '❌';
  97. statusText = 'Failure';
  98. } else if (run.conclusion === 'cancelled') {
  99. status = '⏭️';
  100. statusText = 'Cancelled';
  101. } else if (run.conclusion === 'skipped') {
  102. status = '⏭️';
  103. statusText = 'Skipped';
  104. }
  105. } else if (run.status === 'queued') {
  106. status = '🟠';
  107. statusText = 'Queued';
  108. }
  109. results.push({
  110. name: name,
  111. status: status,
  112. statusText: statusText,
  113. url: run.html_url,
  114. conclusion: run.conclusion,
  115. runId: run.id
  116. });
  117. }
  118. return {
  119. success: true,
  120. results: results,
  121. prNumber: prNumber
  122. };
  123. - name: Get job details
  124. if: steps.get-pr.outputs.result != 'null'
  125. id: job-details
  126. uses: actions/github-script@v7
  127. with:
  128. script: |
  129. const workflowDetails = ${{ steps.workflow-details.outputs.result }};
  130. if (!workflowDetails || !workflowDetails.success) {
  131. return { jobs: [] };
  132. }
  133. const allJobs = [];
  134. for (const result of workflowDetails.results) {
  135. try {
  136. const jobs = await github.rest.actions.listJobsForWorkflowRun({
  137. owner: context.repo.owner,
  138. repo: context.repo.repo,
  139. run_id: result.runId,
  140. per_page: 100
  141. });
  142. for (const job of jobs.data.jobs) {
  143. let jobStatus = '⌛';
  144. if (job.status === 'completed') {
  145. if (job.conclusion === 'success') {
  146. jobStatus = '✅';
  147. } else if (job.conclusion === 'failure') {
  148. jobStatus = '❌';
  149. } else if (job.conclusion === 'skipped') {
  150. jobStatus = '⏭️';
  151. }
  152. } else if (job.status === 'in_progress') {
  153. jobStatus = '🔄';
  154. } else if (job.status === 'queued') {
  155. jobStatus = '🟠';
  156. }
  157. allJobs.push({
  158. workflow: result.name,
  159. name: job.name,
  160. status: jobStatus,
  161. conclusion: job.conclusion || job.status,
  162. url: job.html_url
  163. });
  164. }
  165. } catch (error) {
  166. console.log(`Error getting jobs for workflow ${result.name}: ${error.message}`);
  167. }
  168. }
  169. return { jobs: allJobs };
  170. - name: Post or update comment
  171. if: steps.get-pr.outputs.result != 'null'
  172. uses: actions/github-script@v7
  173. with:
  174. script: |
  175. const prNumber = ${{ steps.get-pr.outputs.result }};
  176. const workflowDetails = ${{ steps.workflow-details.outputs.result }};
  177. const jobDetails = ${{ steps.job-details.outputs.result }};
  178. if (!workflowDetails || !workflowDetails.success) {
  179. console.log('No workflow details available');
  180. return;
  181. }
  182. // Prepare comment body
  183. const now = new Date();
  184. const timestamp = now.toISOString();
  185. const results = workflowDetails.results;
  186. const jobs = jobDetails.jobs || [];
  187. let commentBody = '<!-- CI Results Comment -->\n';
  188. commentBody += '## 🤖 CI Test Results\n\n';
  189. commentBody += `**Last Updated:** ${timestamp}\n\n`;
  190. commentBody += '### Test Spec & Results:\n\n';
  191. commentBody += '✅ Success | ❌ Failure | 🟠 Queued | 🟡 Progress | ⏭️ Skipped | ⚠️ Quarantine\n\n';
  192. // Group jobs by workflow
  193. const jobsByWorkflow = new Map();
  194. for (const job of jobs) {
  195. if (!jobsByWorkflow.has(job.workflow)) {
  196. jobsByWorkflow.set(job.workflow, []);
  197. }
  198. jobsByWorkflow.get(job.workflow).push(job);
  199. }
  200. // Calculate overall statistics
  201. let totalSuccess = 0;
  202. let totalFailure = 0;
  203. let totalQueued = 0;
  204. let totalProgress = 0;
  205. let totalSkipped = 0;
  206. for (const result of results) {
  207. if (result.conclusion === 'success') totalSuccess++;
  208. else if (result.conclusion === 'failure') totalFailure++;
  209. else if (result.statusText === 'Queued') totalQueued++;
  210. else if (result.statusText === 'In Progress') totalProgress++;
  211. else if (result.conclusion === 'skipped' || result.conclusion === 'cancelled') totalSkipped++;
  212. }
  213. // Summary line
  214. commentBody += '#### Overall Summary\n\n';
  215. commentBody += `- ✅ **Success:** ${totalSuccess}\n`;
  216. commentBody += `- ❌ **Failure:** ${totalFailure}\n`;
  217. commentBody += `- 🟠 **Queued:** ${totalQueued}\n`;
  218. commentBody += `- 🟡 **In Progress:** ${totalProgress}\n`;
  219. commentBody += `- ⏭️ **Skipped:** ${totalSkipped}\n\n`;
  220. commentBody += '---\n\n';
  221. commentBody += '### Detailed Results\n\n';
  222. // Build detailed results
  223. for (const result of results) {
  224. commentBody += `<details>\n`;
  225. commentBody += `<summary>${result.status} <strong>${result.name}</strong> - ${result.statusText}</summary>\n\n`;
  226. commentBody += `**Workflow:** [${result.name}](${result.url})\n\n`;
  227. // Show jobs for this workflow
  228. const workflowJobs = jobsByWorkflow.get(result.name) || [];
  229. if (workflowJobs.length > 0) {
  230. commentBody += '**Jobs:**\n\n';
  231. for (const job of workflowJobs) {
  232. commentBody += `- ${job.status} [${job.name}](${job.url})\n`;
  233. }
  234. }
  235. commentBody += '\n</details>\n\n';
  236. }
  237. commentBody += '\n---\n';
  238. commentBody += '*🤖 This comment is automatically generated and updated by the CI system.*\n';
  239. // Check if comment already exists
  240. const comments = await github.rest.issues.listComments({
  241. owner: context.repo.owner,
  242. repo: context.repo.repo,
  243. issue_number: prNumber
  244. });
  245. const existingComment = comments.data.find(comment =>
  246. comment.user.login === 'github-actions[bot]' &&
  247. comment.body.includes('<!-- CI Results Comment -->')
  248. );
  249. if (existingComment) {
  250. // Update existing comment
  251. await github.rest.issues.updateComment({
  252. owner: context.repo.owner,
  253. repo: context.repo.repo,
  254. comment_id: existingComment.id,
  255. body: commentBody
  256. });
  257. console.log(`Updated comment ${existingComment.id} on PR #${prNumber}`);
  258. } else {
  259. // Create new comment
  260. await github.rest.issues.createComment({
  261. owner: context.repo.owner,
  262. repo: context.repo.repo,
  263. issue_number: prNumber,
  264. body: commentBody
  265. });
  266. console.log(`Created new comment on PR #${prNumber}`);
  267. }