Skip to content

Commit 9d71a02

Browse files
authored
[summarize-checks] Prepare for tseslint.configs.recommendedTypeChecked (Azure#38872)
- Add/remove missing/unnecessary `async/await` - Use `zod` to validate input and create strongly-typed objects - Add type comments - Add fallbacks for undefined values
1 parent 79204bb commit 9d71a02

File tree

6 files changed

+125
-106
lines changed

6 files changed

+125
-106
lines changed

.github/workflows/src/summarize-checks/dump-trigger-metadata.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/**
22
* @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments
3-
* @returns {Promise<void>}
43
*/
5-
export default async function dumpTriggerMetadata({ context, core }) {
4+
export default function dumpTriggerMetadata({ context, core }) {
65
core.info(`Event name: ${context.eventName}`);
76
core.info(`Action: ${context.payload.action}`);
87

.github/workflows/src/summarize-checks/labelling.js

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
2. It calculates what set of labels should be added or removed from the PR.
55
*/
66

7+
import * as z from "zod";
78
import { includesEvery, includesNone } from "../../../shared/src/array.js";
89
import {
910
brchTsg,
@@ -45,19 +46,23 @@ import {
4546
* @property {Set<string>} toRemove - The set of labels to remove
4647
*/
4748

49+
export const ImpactAssessmentSchema = z.object({
50+
resourceManagerRequired: z.boolean().describe("Whether a resource manager review is required."),
51+
dataPlaneRequired: z.boolean().describe("Whether a data plane review is required."),
52+
suppressionReviewRequired: z.boolean().describe("Whether a suppression review is required."),
53+
isNewApiVersion: z.boolean().describe("Whether this PR introduces a new API version."),
54+
rpaasRpNotInPrivateRepo: z
55+
.boolean()
56+
.describe("Whether the RPaaS RP is not present in the private repo."),
57+
rpaasChange: z.boolean().describe("Whether this PR includes RPaaS changes."),
58+
newRP: z.boolean().describe("Whether this PR introduces a new resource provider."),
59+
rpaasRPMissing: z.boolean().describe("Whether the RPaaS RP label is missing."),
60+
typeSpecChanged: z.boolean().describe("Whether a TypeSpec file has changed."),
61+
isDraft: z.boolean().describe("Whether the PR is a draft."),
62+
targetBranch: z.string().describe("The name of the target branch for the PR."),
63+
});
4864
/**
49-
* @typedef {Object} ImpactAssessment
50-
* @property {boolean} resourceManagerRequired - Whether a resource manager review is required.
51-
* @property {boolean} dataPlaneRequired - Whether a data plane review is required.
52-
* @property {boolean} suppressionReviewRequired - Whether a suppression review is required.
53-
* @property {boolean} isNewApiVersion - Whether this PR introduces a new API version.
54-
* @property {boolean} rpaasRpNotInPrivateRepo - Whether the RPaaS RP is not present in the private repo.
55-
* @property {boolean} rpaasChange - Whether this PR includes RPaaS changes.
56-
* @property {boolean} newRP - Whether this PR introduces a new resource provider.
57-
* @property {boolean} rpaasRPMissing - Whether the RPaaS RP label is missing.
58-
* @property {boolean} typeSpecChanged - Whether a TypeSpec file has changed.
59-
* @property {boolean} isDraft - Whether the PR is a draft.
60-
* @property {string} targetBranch - The name of the target branch for the PR.
65+
* @typedef {import("zod").infer<typeof ImpactAssessmentSchema>} ImpactAssessment
6166
*/
6267

6368
/**
@@ -465,9 +470,9 @@ This function does the following, **among other things**:
465470
/**
466471
* @param {LabelContext} labelContext
467472
* @param {ImpactAssessment} impactAssessment
468-
* @returns {Promise<{armReviewLabelShouldBePresent: boolean}>}
473+
* @returns {{armReviewLabelShouldBePresent: boolean}}
469474
*/
470-
export async function processImpactAssessment(labelContext, impactAssessment) {
475+
export function processImpactAssessment(labelContext, impactAssessment) {
471476
console.log("ENTER definition processARMReview");
472477

473478
// By default these two should not be present. We may determine later in this function that they should be present after all.
@@ -535,7 +540,7 @@ export async function processImpactAssessment(labelContext, impactAssessment) {
535540
}
536541

537542
armReviewLabel.shouldBePresent = impactAssessment.resourceManagerRequired;
538-
await processARMReviewWorkflowLabels(
543+
processARMReviewWorkflowLabels(
539544
labelContext,
540545
armReviewLabel.shouldBePresent,
541546
impactAssessment.rpaasRPMissing,
@@ -620,9 +625,8 @@ labels are being removed or added to a PR is pipelineBotOnPRLabelEvent.ts.
620625
* @param {boolean} ciNewRPNamespaceWithoutRpaaSLabelShouldBePresent
621626
* @param {boolean} rpaasExceptionLabelShouldBePresent
622627
* @param {boolean} ciRpaasRPNotInPrivateRepoLabelShouldBePresent
623-
* @returns {Promise<void>}
624628
*/
625-
async function processARMReviewWorkflowLabels(
629+
function processARMReviewWorkflowLabels(
626630
labelContext,
627631
armReviewLabelShouldBePresent,
628632
ciNewRPNamespaceWithoutRpaaSLabelShouldBePresent,
@@ -706,7 +710,6 @@ async function processARMReviewWorkflowLabels(
706710
`blockedOnRpaas: ${blockedOnRpaas}. ` +
707711
`exactlyOneArmReviewWorkflowLabelShouldBePresent: ${exactlyOneArmReviewWorkflowLabelShouldBePresent}. `,
708712
);
709-
return;
710713
}
711714

712715
/**
@@ -1164,9 +1167,9 @@ export const requiredLabelsRules = rulesPri0dataPlane
11641167
* @param {string[]} existingLabels
11651168
* @param {string} targetBranch
11661169
*
1167-
* @returns {Promise<{presentBlockingLabels: string[], missingRequiredLabels: string[]}>}
1170+
* @returns {{presentBlockingLabels: string[], missingRequiredLabels: string[]}}
11681171
*/
1169-
export async function getPresentBlockingLabelsAndMissingRequiredLabels(
1172+
export function getPresentBlockingLabelsAndMissingRequiredLabels(
11701173
core,
11711174
repo,
11721175
owner,
@@ -1176,15 +1179,17 @@ export async function getPresentBlockingLabelsAndMissingRequiredLabels(
11761179
// this is a little bit of a strange looking branch format, but not going to touch this for now.
11771180
const repoTargetBranch = `${repo}/${targetBranch}`;
11781181

1179-
const violatedReqLabelsRules = await getViolatedRequiredLabelsRules(
1182+
const violatedReqLabelsRules = getViolatedRequiredLabelsRules(
11801183
core,
11811184
existingLabels,
11821185
repoTargetBranch,
11831186
);
11841187
const presentBlockingLabels = getPresentBlockingLabels(violatedReqLabelsRules);
11851188

1186-
const requiredLabelsFromRules = (
1187-
await getViolatedRequiredLabelsRules(core, existingLabels, repoTargetBranch)
1189+
const requiredLabelsFromRules = getViolatedRequiredLabelsRules(
1190+
core,
1191+
existingLabels,
1192+
repoTargetBranch,
11881193
)
11891194
.filter((rule) => rule.anyRequiredLabels.length > 0)
11901195
// See comment on RequiredLabelRule.anyRequiredLabels to understand why we pick [0] from rule.anyRequiredLabels here.
@@ -1201,12 +1206,12 @@ export async function getPresentBlockingLabelsAndMissingRequiredLabels(
12011206
* @param {typeof import("@actions/core")} core
12021207
* @param {string[]} labels
12031208
* @param {string} targetBranch This function uses a special format {repo/branch}, e.g. "azure-rest-api-specs/main".
1204-
* @returns {Promise<RequiredLabelRule[]>}
1209+
* @returns {RequiredLabelRule[]}
12051210
*/
1206-
export async function getViolatedRequiredLabelsRules(core, labels, targetBranch) {
1211+
export function getViolatedRequiredLabelsRules(core, labels, targetBranch) {
12071212
const violatedRules = [];
12081213
for (const rule of requiredLabelsRules) {
1209-
if (await requiredLabelRuleViolated(core, labels, targetBranch, rule)) {
1214+
if (requiredLabelRuleViolated(core, labels, targetBranch, rule)) {
12101215
violatedRules.push(rule);
12111216
}
12121217
}
@@ -1218,9 +1223,9 @@ export async function getViolatedRequiredLabelsRules(core, labels, targetBranch)
12181223
* @param {string[]} presentLabels
12191224
* @param {string} targetBranch
12201225
* @param {RequiredLabelRule} rule
1221-
* @returns {Promise<boolean>}
1226+
* @returns {boolean}
12221227
*/
1223-
export async function requiredLabelRuleViolated(core, presentLabels, targetBranch, rule) {
1228+
export function requiredLabelRuleViolated(core, presentLabels, targetBranch, rule) {
12241229
const branchIsApplicable = rule.branches === undefined || rule.branches.includes(targetBranch);
12251230

12261231
const anyPrerequisiteLabelPresent =

.github/workflows/src/summarize-checks/summarize-checks.js

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { byDate, invert } from "../../../shared/src/sort.js";
2424
import { commentOrUpdate } from "../comment.js";
2525
import { extractInputs } from "../context.js";
2626
import {
27+
ImpactAssessmentSchema,
2728
brChRevApproval,
2829
getViolatedRequiredLabelsRules,
2930
processImpactAssessment,
@@ -132,6 +133,11 @@ import path from "path";
132133
* @property {string} [target_url]
133134
*/
134135

136+
/**
137+
* @typedef {import("../github.js").CheckRuns[0]} CheckRun
138+
* @typedef {import("../github.js").CommitStatuses[0]} CommitStatus
139+
*/
140+
135141
// Placing these configuration items here until we decide another way to pull them in.
136142
const FYI_CHECK_NAMES = [
137143
"Swagger LintDiff",
@@ -294,7 +300,12 @@ export default async function summarizeChecks({ github, context, core }) {
294300
return;
295301
}
296302

297-
const targetBranch = context.payload.pull_request?.base?.ref;
303+
const targetBranch =
304+
context.eventName === "pull_request_target"
305+
? /** @type {import("@octokit/webhooks-types").PullRequestEvent} */ (context.payload)
306+
.pull_request.base.ref
307+
: undefined;
308+
298309
core.info(`PR target branch: ${targetBranch}`);
299310

300311
// Default target is this run itself
@@ -343,7 +354,7 @@ export function outputRunDetails(core, requiredCheckRuns, fyiCheckRuns) {
343354
* @param {number} issue_number
344355
* @param {string} head_sha
345356
* @param {string} event_name
346-
* @param {string} targetBranch
357+
* @param {string|undefined} targetBranch
347358
* @param {string} target_url
348359
* @returns {Promise<void>}
349360
*/
@@ -363,7 +374,7 @@ export async function summarizeChecksImpl(
363374
const prUrl = `https://github.com/${owner}/${repo}/pull/${issue_number}`;
364375
core.summary.addRaw("PR: ");
365376
core.summary.addLink(prUrl, prUrl);
366-
core.summary.write();
377+
await core.summary.write();
367378
core.setOutput("summary", process.env.GITHUB_STEP_SUMMARY);
368379

369380
let labelNames = await getExistingLabels(github, owner, repo, issue_number);
@@ -391,7 +402,7 @@ export async function summarizeChecksImpl(
391402
);
392403
}
393404

394-
let labelContext = await updateLabels(labelNames, impactAssessment);
405+
let labelContext = updateLabels(labelNames, impactAssessment);
395406

396407
core.info(
397408
`Summarize checks label actions against ${owner}/${repo}#${issue_number}: \n` +
@@ -430,7 +441,7 @@ export async function summarizeChecksImpl(
430441
}
431442
}
432443

433-
const [commentBody, automatedChecksMet] = await createNextStepsComment(
444+
const [commentBody, automatedChecksMet] = createNextStepsComment(
434445
core,
435446
repo,
436447
labelNames,
@@ -447,7 +458,7 @@ export async function summarizeChecksImpl(
447458
`Updating comment '${NEXT_STEPS_COMMENT_ID}' on ${owner}/${repo}#${issue_number} with body: ${commentBody}`,
448459
);
449460
core.summary.addRaw(`\n${commentBody}\n\n`);
450-
core.summary.write();
461+
await core.summary.write();
451462

452463
// this will remain commented until we're comfortable with the change.
453464
await commentOrUpdate(
@@ -468,7 +479,7 @@ export async function summarizeChecksImpl(
468479
);
469480
core.summary.addHeading("Automated Checks Met", 2);
470481
core.summary.addCodeBlock(JSON.stringify(automatedChecksMet, null, 2));
471-
core.summary.write();
482+
await core.summary.write();
472483
}
473484

474485
/**
@@ -646,7 +657,7 @@ export async function getCheckRunTuple(
646657
// all checks will be considered as "FYI" until we have an impact assessment, so we can
647658
// determine the target branch, and from there pull branch protect rulesets to ensure we
648659
// are marking the required checks correctly.
649-
/** @type {Array<CheckRunData & {_originalData: any, _source: string}>} */
660+
/** @type {Array<CheckRunData & {_originalData: CheckRun|CommitStatus, _source: string}>} */
650661
const allChecks = [];
651662

652663
allCheckRuns.forEach((checkRun) => {
@@ -694,14 +705,17 @@ export async function getCheckRunTuple(
694705
});
695706

696707
// Group by name and take the latest for each
708+
/** @type {Map<string, Array<CheckRunData & {_originalData: CheckRun|CommitStatus, _source: string}>>} */
697709
const checksByName = new Map();
698710

699711
allChecks.forEach((check) => {
700712
const name = check.name;
701-
if (!checksByName.has(name)) {
702-
checksByName.set(name, []);
713+
let checks = checksByName.get(name);
714+
if (checks) {
715+
checks.push(check);
716+
} else {
717+
checksByName.set(name, [check]);
703718
}
704-
checksByName.get(name).push(check);
705719
});
706720

707721
// For each group, sort by date (newest first) and take the first one
@@ -712,15 +726,12 @@ export async function getCheckRunTuple(
712726
invert(
713727
byDate((check) => {
714728
if (check._source === "checkRun") {
715-
// Check runs have started_at, completed_at, etc. Use the most recent available date
716-
return (
717-
check._originalData.completed_at ||
718-
check._originalData.started_at ||
719-
check._originalData.created_at
720-
);
729+
const originalData = /** @type {CheckRun} */ (check._originalData);
730+
// Use the most recent available date, or "1970" (oldest possible) if the data contains no dates
731+
return originalData.completed_at || originalData.started_at || "1970";
721732
} else {
722-
// Commit statuses have created_at and updated_at
723-
return check._originalData.updated_at || check._originalData.created_at;
733+
const originalData = /** @type {CommitStatus} */ (check._originalData);
734+
return originalData.updated_at;
724735
}
725736
}),
726737
),
@@ -733,18 +744,17 @@ export async function getCheckRunTuple(
733744
latestCheck.status === "completed" &&
734745
latestCheck.conclusion === "success"
735746
) {
747+
const originalData = /** @type {CheckRun} */ (latestCheck._originalData);
736748
const workflowRuns = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, {
737749
owner,
738750
repo,
739751
head_sha: head_sha,
740-
check_suite_id: latestCheck._originalData.check_suite.id,
752+
check_suite_id: originalData.check_suite?.id,
741753
per_page: PER_PAGE_MAX,
742754
});
743755

744756
if (workflowRuns.length === 0) {
745-
core.warning(
746-
`No workflow runs found for check suite ID: ${latestCheck._originalData.check_suite.id}`,
747-
);
757+
core.warning(`No workflow runs found for check suite ID: ${originalData.check_suite?.id}`);
748758
} else {
749759
// Sort by updated_at to get the most recent run
750760
const sortedRuns = workflowRuns.sort(
@@ -754,7 +764,7 @@ export async function getCheckRunTuple(
754764

755765
if (workflowRuns.length > 1) {
756766
core.info(
757-
`Found ${workflowRuns.length} workflow runs for check suite ID: ${latestCheck._originalData.check_suite.id}, using most recent: ${sortedRuns[0].id}`,
767+
`Found ${workflowRuns.length} workflow runs for check suite ID: ${originalData.check_suite?.id}, using most recent: ${sortedRuns[0].id}`,
758768
);
759769
}
760770
}
@@ -862,14 +872,14 @@ export function getCheckInfo(checkName) {
862872
* @param {typeof import("@actions/core")} core
863873
* @param {string} repo
864874
* @param {string[]} labels
865-
* @param {string} targetBranch
875+
* @param {string|undefined} targetBranch
866876
* @param {CheckRunData[]} requiredRuns
867877
* @param {CheckRunData[]} fyiRuns
868878
* @param {boolean} assessmentCompleted
869879
* @param {string} target_url
870-
* @returns {Promise<[string, CheckRunResult]>}
880+
* @returns {[string, CheckRunResult]}
871881
*/
872-
export async function createNextStepsComment(
882+
export function createNextStepsComment(
873883
core,
874884
repo,
875885
labels,
@@ -898,7 +908,7 @@ export async function createNextStepsComment(
898908
.filter((run) => checkRunIsSuccessful(run) === false)
899909
.map((run) => run.checkInfo);
900910

901-
const [commentBody, automatedChecksMet] = await buildNextStepsToMergeCommentBody(
911+
const [commentBody, automatedChecksMet] = buildNextStepsToMergeCommentBody(
902912
core,
903913
labels,
904914
`${repo}/${targetBranch}`,
@@ -921,9 +931,9 @@ export async function createNextStepsComment(
921931
* @param {CheckMetadata[]} failingFyiChecksInfo
922932
* @param {boolean} assessmentCompleted
923933
* @param {string} target_url
924-
* @returns {Promise<[string, CheckRunResult]>}
934+
* @returns {[string, CheckRunResult]}
925935
*/
926-
async function buildNextStepsToMergeCommentBody(
936+
function buildNextStepsToMergeCommentBody(
927937
core,
928938
labels,
929939
targetBranch,
@@ -936,7 +946,7 @@ async function buildNextStepsToMergeCommentBody(
936946
// Build the comment header
937947
const commentTitle = `<h2>Next Steps to Merge</h2>`;
938948

939-
const violatedReqLabelsRules = await getViolatedRequiredLabelsRules(core, labels, targetBranch);
949+
const violatedReqLabelsRules = getViolatedRequiredLabelsRules(core, labels, targetBranch);
940950

941951
// we are "blocked" if we have any violated labelling rules OR if we have any failing required checks
942952
const anyBlockerPresent = failingReqChecksInfo.length > 0 || violatedReqLabelsRules.length > 0;
@@ -1182,9 +1192,6 @@ export async function getImpactAssessment(github, core, owner, repo, runId) {
11821192

11831193
await fs.unlink(tmpZip);
11841194

1185-
/** @type {import("./labelling.js").ImpactAssessment} */
1186-
// todo: we need to zod this to ensure the structure is correct, however we do not have zod installed at time of run
1187-
const impact = JSON.parse(jsonContent);
1188-
return impact;
1195+
return ImpactAssessmentSchema.parse(JSON.parse(jsonContent));
11891196
}
11901197
// #endregion

0 commit comments

Comments
 (0)