@@ -23,9 +23,24 @@ name: "showcase / eval"
2323on :
2424 issue_comment :
2525 types : [created]
26+ workflow_dispatch :
27+ inputs :
28+ pr_number :
29+ description : " PR number to evaluate"
30+ required : true
31+ type : string
32+ check_run_id :
33+ description : " Check Run ID to update with results"
34+ required : false
35+ type : string
36+ level :
37+ description : " Eval depth level"
38+ required : false
39+ default : " d5"
40+ type : string
2641
2742concurrency :
28- group : showcase-eval-${{ github.event.issue.number }}
43+ group : showcase-eval-${{ github.event.inputs.pr_number || github.event. issue.number || github.run_id }}
2944 cancel-in-progress : true
3045
3146permissions :
@@ -197,14 +212,55 @@ jobs:
197212 ].join('\n'),
198213 });
199214
215+ dispatch-gate :
216+ if : github.event_name == 'workflow_dispatch'
217+ runs-on : ubuntu-latest
218+ timeout-minutes : 2
219+ permissions :
220+ contents : read
221+ pull-requests : read
222+ outputs :
223+ pr_sha : ${{ steps.resolve.outputs.sha }}
224+ pr_number : ${{ github.event.inputs.pr_number }}
225+ level : ${{ github.event.inputs.level || 'd5' }}
226+ scope_flag : " --scope affected"
227+ scope_display : " affected"
228+ check_run_id : ${{ github.event.inputs.check_run_id }}
229+ steps :
230+ - name : Resolve PR HEAD SHA
231+ id : resolve
232+ uses : actions/github-script@v7
233+ with :
234+ script : |
235+ const pr = await github.rest.pulls.get({
236+ owner: context.repo.owner,
237+ repo: context.repo.repo,
238+ pull_number: Number(process.env.PR_NUMBER),
239+ });
240+ if (pr.data.state !== 'open') {
241+ core.setFailed(`PR #${process.env.PR_NUMBER} is not open`);
242+ return;
243+ }
244+ core.setOutput('sha', pr.data.head.sha);
245+ env :
246+ PR_NUMBER : ${{ github.event.inputs.pr_number }}
247+
200248 eval :
201- needs : gate
249+ needs : [gate, dispatch-gate]
250+ if : always() && (needs.gate.result == 'success' || needs.dispatch-gate.result == 'success')
202251 runs-on : depot-ubuntu-24.04-16
203252 timeout-minutes : 45
204253
205254 permissions :
206255 contents : read
207256
257+ env :
258+ PR_SHA : ${{ needs.gate.outputs.pr_sha || needs.dispatch-gate.outputs.pr_sha }}
259+ PR_NUMBER : ${{ needs.gate.outputs.pr_number || needs.dispatch-gate.outputs.pr_number }}
260+ EVAL_LEVEL : ${{ needs.gate.outputs.level || needs.dispatch-gate.outputs.level || 'd5' }}
261+ EVAL_SCOPE_FLAG : ${{ needs.gate.outputs.scope_flag || needs.dispatch-gate.outputs.scope_flag }}
262+ CHECK_RUN_ID : ${{ needs.dispatch-gate.outputs.check_run_id || '' }}
263+
208264 outputs :
209265 result_json : ${{ steps.run-eval.outputs.result_json }}
210266 exit_code : ${{ steps.run-eval.outputs.exit_code }}
@@ -214,7 +270,7 @@ jobs:
214270 - name : Checkout PR HEAD
215271 uses : actions/checkout@v4
216272 with :
217- ref : ${{ needs.gate.outputs.pr_sha }}
273+ ref : ${{ env.PR_SHA }}
218274 fetch-depth : 0
219275 persist-credentials : false
220276
@@ -234,9 +290,6 @@ jobs:
234290
235291 - name : Run showcase eval
236292 id : run-eval
237- env :
238- EVAL_LEVEL : ${{ needs.gate.outputs.level }}
239- EVAL_SCOPE_FLAG : ${{ needs.gate.outputs.scope_flag }}
240293 run : |
241294 set -o pipefail
242295
@@ -296,14 +349,15 @@ jobs:
296349 if-no-files-found : ignore
297350
298351 post-result :
299- needs : [gate, eval]
300- if : always() && needs.gate.result == 'success'
352+ needs : [gate, dispatch-gate, eval]
353+ if : always() && ( needs.gate.result == 'success' || needs.dispatch-gate.result == 'success')
301354 runs-on : ubuntu-latest
302355 timeout-minutes : 5
303356
304357 permissions :
305358 pull-requests : write
306359 issues : write
360+ checks : write
307361
308362 steps :
309363 - name : Post eval results to PR
@@ -313,9 +367,9 @@ jobs:
313367 RESULT_JSON : ${{ needs.eval.outputs.result_json }}
314368 STDERR_EXCERPT : ${{ needs.eval.outputs.stderr_excerpt }}
315369 EXIT_CODE : ${{ needs.eval.outputs.exit_code }}
316- LEVEL : ${{ needs.gate.outputs.level }}
317- SCOPE_DISPLAY : ${{ needs.gate.outputs.scope_display }}
318- PR_NUMBER : ${{ needs.gate.outputs.pr_number }}
370+ LEVEL : ${{ needs.gate.outputs.level || needs.dispatch-gate.outputs.level || 'd5' }}
371+ SCOPE_DISPLAY : ${{ needs.gate.outputs.scope_display || needs.dispatch-gate.outputs.scope_display || 'affected' }}
372+ PR_NUMBER : ${{ needs.gate.outputs.pr_number || needs.dispatch-gate.outputs.pr_number }}
319373 with :
320374 script : |
321375 const evalStatus = process.env.EVAL_STATUS;
@@ -492,3 +546,75 @@ jobs:
492546 issue_number: prNumber,
493547 body,
494548 });
549+
550+ - name : Generate devops-bot token
551+ id : bot-token
552+ if : needs.dispatch-gate.outputs.check_run_id != ''
553+ uses : actions/create-github-app-token@v2
554+ with :
555+ app-id : 1108748
556+ private-key : ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }}
557+
558+ - name : Update Check Run with results
559+ if : needs.dispatch-gate.outputs.check_run_id != ''
560+ uses : actions/github-script@v7
561+ with :
562+ github-token : ${{ steps.bot-token.outputs.token }}
563+ script : |
564+ const checkRunId = Number(process.env.CHECK_RUN_ID);
565+ const resultJson = process.env.RESULT_JSON || '{}';
566+ const evalStatus = '${{ needs.eval.result }}';
567+
568+ let conclusion = 'failure';
569+ let title = 'Showcase Eval — error';
570+ let summary = 'The evaluation encountered an error.';
571+
572+ try {
573+ const results = JSON.parse(resultJson);
574+ const s = results.summary || {};
575+
576+ if (evalStatus === 'success' && s.fail === 0) {
577+ conclusion = 'success';
578+ title = `${s.pass}/${s.total} passed (${(s.duration_ms / 1000).toFixed(1)}s)`;
579+ } else if (s.total === 0) {
580+ conclusion = 'neutral';
581+ title = 'No showcase integrations affected';
582+ } else {
583+ conclusion = 'failure';
584+ title = `${s.fail} failed, ${s.pass} passed`;
585+ }
586+
587+ const lines = ['## Eval Results\n'];
588+ lines.push('| Integration | Status |');
589+ lines.push('|-------------|--------|');
590+ if (results.results) {
591+ for (const [slug, tests] of Object.entries(results.results)) {
592+ const statuses = Object.values(tests);
593+ const pass = statuses.filter(t => t.status === 'pass').length;
594+ const total = statuses.length;
595+ const icon = pass === total ? '✅' : '❌';
596+ lines.push(`| ${slug} | ${icon} ${pass}/${total} |`);
597+ }
598+ }
599+ lines.push(`\n**Total:** ${s.pass} passed, ${s.fail} failed, ${s.skip} skipped (${(s.duration_ms / 1000).toFixed(1)}s)`);
600+ summary = lines.join('\n');
601+ } catch (e) {
602+ summary = `Parse error: ${e.message}`;
603+ }
604+
605+ await github.rest.checks.update({
606+ owner: context.repo.owner,
607+ repo: context.repo.repo,
608+ check_run_id: checkRunId,
609+ status: 'completed',
610+ conclusion,
611+ output: { title, summary },
612+ actions: [{
613+ label: 'Re-run Eval',
614+ description: 'Run D5 evaluation',
615+ identifier: 'run-eval',
616+ }],
617+ });
618+ env :
619+ CHECK_RUN_ID : ${{ needs.dispatch-gate.outputs.check_run_id }}
620+ RESULT_JSON : ${{ needs.eval.outputs.result_json }}
0 commit comments