Skip to content
This repository was archived by the owner on Aug 26, 2025. It is now read-only.

Commit 99a939d

Browse files
committed
Update to state of PR
1 parent 992bcc9 commit 99a939d

File tree

4 files changed

+161
-147
lines changed

4 files changed

+161
-147
lines changed

.github/workflows/_reusable-verify-run-status.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ permissions:
1919

2020
jobs:
2121
check-run-status:
22-
if: ${{ (github.event_name == 'workflow_run') || (github.event_name == 'check_suite' && github.event.check_suite.app.name == 'openapi-pipeline-app') || (github.event_name == 'check_run' && github.event.check_run.name == inputs.check_run_name) }}
22+
if: |
23+
(github.event_name == 'workflow_run') ||
24+
(github.event_name == 'check_suite' && github.event.check_suite.app.name == 'openapi-pipeline-app') ||
25+
(github.event_name == 'check_run' && github.event.check_run.name == inputs.check_run_name)
2326
runs-on: ubuntu-24.04
2427

2528
steps:
Lines changed: 78 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { extractInputs } from "./context.js";
22
import { PER_PAGE_MAX } from "./github.js";
33

4+
const SUPPORTED_EVENTS = ["workflow_run", "check_run", "check_suite"];
5+
46
/**
57
* @typedef {import('@octokit/plugin-rest-endpoint-methods').RestEndpointMethodTypes} RestEndpointMethodTypes
6-
* @typedef {RestEndpointMethodTypes["checks"]["listForRef"]["response"]["data"]["check_runs"][number]} CheckRun
7-
* @typedef {RestEndpointMethodTypes["actions"]["listWorkflowRunsForRepo"]["response"]["data"]["workflow_runs"][number]} WorkflowRun
8+
* @typedef {RestEndpointMethodTypes["checks"]["listForRef"]["response"]["data"]["check_runs"]} CheckRuns
9+
* @typedef {RestEndpointMethodTypes["actions"]["listWorkflowRunsForRepo"]["response"]["data"]["workflow_runs"]} WorkflowRuns
810
*/
911

12+
/* v8 ignore start */
1013
/**
1114
* Given the name of a completed check run name and a completed workflow, verify
1215
* that both have the same conclusion. If conclusions are different, fail the
@@ -24,41 +27,77 @@ export async function verifyRunStatus({ github, context, core }) {
2427
throw new Error("WORKFLOW_NAME is not set");
2528
}
2629

27-
// Exit early when context is a check_run event and the check run does not
28-
// match the checkRunName.
30+
if (!SUPPORTED_EVENTS.some((e) => e === context.eventName)) {
31+
throw new Error(
32+
`Unsupported event: ${context.eventName}. Supported events: ${SUPPORTED_EVENTS.join(", ")}`,
33+
);
34+
}
35+
36+
if (context.eventName === "check_suite" && context.payload.check_suite.status !== "completed") {
37+
core.setFailed(`Check suite ${context.payload.check_suite.app.name} is not completed. Cannot evaluate incomplete check suite.`);
38+
return;
39+
}
40+
41+
return await verifyRunStatusImpl({ github, context, core , checkRunName, workflowName});
42+
}
43+
/* v8 ignore stop */
44+
45+
/**
46+
* @param {Object} params
47+
* @param {import('github-script').AsyncFunctionArguments["github"]} params.github
48+
* @param {import('github-script').AsyncFunctionArguments["context"]} params.context
49+
* @param {import('github-script').AsyncFunctionArguments["core"]} params.core
50+
* @param {string} params.checkRunName
51+
* @param {string} params.workflowName
52+
*/
53+
export async function verifyRunStatusImpl({github, context, core, checkRunName, workflowName}) {
2954
if (context.eventName == "check_run") {
3055
const contextRunName = context.payload.check_run.name;
3156
if (contextRunName !== checkRunName) {
32-
core.notice(`Check run name (${contextRunName}) does not match input: ${checkRunName}`);
57+
core.setFailed(`Check run name (${contextRunName}) does not match input: ${checkRunName}. Ensure job is filtering by github.event.check_run.name.`);
3358
return;
3459
}
3560
}
3661

3762
const { head_sha } = await extractInputs(github, context, core);
3863

39-
const checkRun =
40-
context.eventName == "check_run"
41-
? context.payload.check_run
42-
: await getCheckRun(github, context, core, checkRunName, head_sha);
64+
let checkRun;
65+
if (context.eventName == "check_run") {
66+
checkRun = context.payload.check_run;
67+
} else {
68+
const checkRuns = await getCheckRuns(github, context, checkRunName, head_sha);
69+
if (checkRuns.length === 0) {
70+
if (context.eventName === "check_suite") {
71+
const message = `Could not locate check run ${checkRunName} in check suite ${context.payload.check_suite.app.name}. Ensure job is filtering by github.event.check_suite.app.name.`;
72+
core.setFailed(message);
73+
return;
74+
}
75+
76+
core.notice(`No completed check run with name: ${checkRunName}. Not enough information to judge success or failure. Ending with success status.`);
77+
return;
78+
}
4379

44-
if (!checkRun) {
45-
core.notice(`No completed check run with name: ${checkRunName}`);
46-
return;
80+
// Use the most recent check run
81+
checkRun = checkRuns[0];
4782
}
4883

4984
core.info(
5085
`Check run name: ${checkRun.name}, conclusion: ${checkRun.conclusion}, URL: ${checkRun.html_url}`,
5186
);
5287
core.debug(`Check run: ${JSON.stringify(checkRun)}`);
5388

54-
const workflowRun =
55-
context.eventName == "workflow_run"
56-
? context.payload.workflow_run
57-
: await getWorkflowRun(github, context, core, workflowName, head_sha);
89+
let workflowRun;
90+
if (context.eventName == "workflow_run") {
91+
workflowRun = context.payload.workflow_run;
92+
} else {
93+
const workflowRuns = await getWorkflowRuns(github, context, workflowName, head_sha);
94+
if (workflowRuns.length === 0) {
95+
core.notice(`No completed workflow run with name: ${workflowName}. Not enough information to judge success or failure. Ending with success status.`);
96+
return;
97+
}
5898

59-
if (!workflowRun) {
60-
core.notice(`No completed workflow run with name: ${workflowName}`);
61-
return;
99+
// Use the most recent workflow run
100+
workflowRun = workflowRuns[0];
62101
}
63102

64103
core.info(
@@ -80,61 +119,44 @@ export async function verifyRunStatus({ github, context, core }) {
80119
* Returns the check with the given checkRunName for the given ref.
81120
* @param {import('github-script').AsyncFunctionArguments['github']} github
82121
* @param {import('github-script').AsyncFunctionArguments['context']} context
83-
* @param {import('github-script').AsyncFunctionArguments['core']} core
84122
* @param {string} checkRunName
85123
* @param {string} ref
86-
* @returns {Promise<CheckRun | null>}
124+
* @returns {Promise<CheckRuns>}
87125
*/
88-
export async function getCheckRun(
126+
export async function getCheckRuns(
89127
github,
90128
context,
91-
core,
92129
checkRunName,
93130
ref,
94131
) {
95-
const checkRuns = await github.paginate(github.rest.checks.listForRef, {
132+
const result = await github.paginate(github.rest.checks.listForRef, {
96133
...context.repo,
97134
ref: ref,
98135
check_name: checkRunName,
99136
status: "completed",
100137
per_page: PER_PAGE_MAX,
101138
});
102139

103-
if (checkRuns.length === 0) {
104-
return null;
105-
}
106-
107-
if (checkRuns.length > 1) {
108-
core.info(`Multiple check runs:`);
109-
checkRuns.forEach((cr) => {
110-
core.info(`- ${cr.name}: ${cr.conclusion}`);
111-
});
112-
113-
const message = `Multiple completed check runs with name: ${checkRunName}`;
114-
core.setFailed(message);
115-
throw new Error(message);
116-
}
117-
118-
return checkRuns[0];
140+
// a and b will never be null because status is "completed"
141+
/* v8 ignore next */
142+
return result.sort((a, b) => compareDatesDescending(a.completed_at || '', b.completed_at || ''));
119143
}
120144

121145
/**
122146
* Returns the workflow run with the given workflowName for the given ref.
123147
* @param {import('github-script').AsyncFunctionArguments['github']} github
124148
* @param {import('github-script').AsyncFunctionArguments['context']} context
125-
* @param {import('github-script').AsyncFunctionArguments['core']} core
126149
* @param {string} workflowName
127150
* @param {string} ref
128-
* @returns {Promise<WorkflowRun | null>}
151+
* @returns {Promise<WorkflowRuns>}
129152
*/
130-
export async function getWorkflowRun(
153+
export async function getWorkflowRuns(
131154
github,
132155
context,
133-
core,
134156
workflowName,
135157
ref,
136158
) {
137-
const workflowRuns = await github.paginate(
159+
const result = await github.paginate(
138160
github.rest.actions.listWorkflowRunsForRepo,
139161
{
140162
...context.repo,
@@ -144,31 +166,15 @@ export async function getWorkflowRun(
144166
},
145167
);
146168

147-
if (workflowRuns.length === 0) {
148-
core.info(`No completed workflow runs`);
149-
return null;
150-
}
151-
152-
const matches = workflowRuns.filter((run) => run.name === workflowName);
153-
154-
if (matches.length === 0) {
155-
return null;
156-
}
157-
158-
if (matches.length > 1) {
159-
core.warning(
160-
`Multiple matching workflow runs, selecting the most recent run`,
161-
);
162-
matches.forEach((wf) =>
163-
core.info(`- ${wf.name}: ${wf.conclusion} ${wf.html_url}`),
164-
);
165-
166-
// Sort by "updated_at" descending, so most recent run is at index 0
167-
matches.sort(
168-
(a, b) =>
169-
new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(),
170-
);
171-
}
172-
173-
return matches[0];
169+
return result.filter((run) => run.name === workflowName).sort((a, b) => compareDatesDescending(a.updated_at, b.updated_at));
174170
}
171+
172+
/**
173+
* Compares two date strings in descending order.
174+
* @param {string} a date string of the form "YYYY-MM-DDTHH:mm:ssZ"
175+
* @param {string} b date string of the form "YYYY-MM-DDTHH:mm:ssZ"
176+
* @returns
177+
*/
178+
export function compareDatesDescending(a, b) {
179+
return new Date(b).getTime() - new Date(a).getTime();
180+
}

0 commit comments

Comments
 (0)