Skip to content

Commit 5c058f0

Browse files
authored
Added more details in the rule output (#105)
Added detailed explanation in the rules output, informing how each rule works and a list of approvals that counts towards incomplete rules (so we can see who's review count where). Resolves #103
1 parent f4e4c61 commit 5c058f0

File tree

2 files changed

+73
-12
lines changed

2 files changed

+73
-12
lines changed

src/runner.ts

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ type ReviewReport = {
2020
usersToRequest?: string[];
2121
/** If applicable, the missing minimum fellows rank required to review */
2222
missingRank?: number;
23+
/** If applicable, reviews that count towards this rule */
24+
countingReviews: string[];
2325
};
2426

25-
export type RuleReport = { name: string } & ReviewReport;
27+
export type RuleReport = { name: string; type: RuleTypes } & ReviewReport;
2628

2729
type ReviewState = [true] | [false, ReviewReport];
2830

@@ -96,7 +98,7 @@ export class ActionRunner {
9698
const [result, missingData] = await this.evaluateCondition(rule, rule.countAuthor);
9799
if (!result) {
98100
this.logger.error(`Missing the reviews from ${JSON.stringify(missingData.missingUsers)}`);
99-
errorReports.push({ ...missingData, name: rule.name });
101+
errorReports.push({ ...missingData, name: rule.name, type: rule.type });
100102
}
101103

102104
break;
@@ -112,7 +114,7 @@ export class ActionRunner {
112114
}
113115
}
114116
if (reports.length > 0) {
115-
const finalReport = unifyReport(reports, rule.name);
117+
const finalReport = unifyReport(reports, rule.name, rule.type);
116118
this.logger.error(`Missing the reviews from ${JSON.stringify(finalReport.missingUsers)}`);
117119
errorReports.push(finalReport);
118120
}
@@ -139,7 +141,7 @@ export class ActionRunner {
139141
.reduce((a, b) => (a < b ? a : b), 999);
140142
// We get the lowest rank required
141143
// We unify the reports
142-
const finalReport = unifyReport(reports, rule.name);
144+
const finalReport = unifyReport(reports, rule.name, rule.type);
143145
// We set the value to the minimum neccesary
144146
finalReport.missingReviews = lowerAmountOfReviewsNeeded;
145147
this.logger.error(`Missing the reviews from ${JSON.stringify(finalReport.missingUsers)}`);
@@ -152,15 +154,15 @@ export class ActionRunner {
152154
const [result, missingData] = await this.andDistinctEvaluation(rule);
153155
if (!result) {
154156
this.logger.error(`Missing the reviews from ${JSON.stringify(missingData.missingUsers)}`);
155-
errorReports.push({ ...missingData, name: rule.name });
157+
errorReports.push({ ...missingData, name: rule.name, type: rule.type });
156158
}
157159
break;
158160
}
159161
case RuleTypes.Fellows: {
160162
const [result, missingData] = await this.fellowsEvaluation(rule);
161163
if (!result) {
162164
this.logger.error(`Missing the reviews from ${JSON.stringify(missingData.missingUsers)}`);
163-
errorReports.push({ ...missingData, name: rule.name });
165+
errorReports.push({ ...missingData, name: rule.name, type: rule.type });
164166
}
165167
break;
166168
}
@@ -177,21 +179,27 @@ export class ActionRunner {
177179
return { files: modifiedFiles, reports: errorReports };
178180
}
179181

180-
/** WIP - Class that will assign the requests for review */
181182
async requestReviewers(
182183
reports: RuleReport[],
183184
preventReviewRequests: ConfigurationFile["preventReviewRequests"],
184185
): Promise<void> {
185186
if (reports.length === 0) {
186187
return;
187188
}
188-
const finalReport: ReviewReport = { missingReviews: 0, missingUsers: [], teamsToRequest: [], usersToRequest: [] };
189+
const finalReport: ReviewReport = {
190+
missingReviews: 0,
191+
missingUsers: [],
192+
teamsToRequest: [],
193+
usersToRequest: [],
194+
countingReviews: [],
195+
};
189196

190197
for (const report of reports) {
191198
finalReport.missingReviews += report.missingReviews;
192199
finalReport.missingUsers = concatArraysUniquely(finalReport.missingUsers, report.missingUsers);
193200
finalReport.teamsToRequest = concatArraysUniquely(finalReport.teamsToRequest, report.teamsToRequest);
194201
finalReport.usersToRequest = concatArraysUniquely(finalReport.usersToRequest, report.usersToRequest);
202+
finalReport.countingReviews = concatArraysUniquely(finalReport.countingReviews, report.countingReviews);
195203
}
196204

197205
this.logger.debug(`Request data: ${JSON.stringify(finalReport)}`);
@@ -243,11 +251,38 @@ export class ActionRunner {
243251
}
244252

245253
for (const report of reports) {
254+
const ruleExplanation = (type: RuleTypes) => {
255+
switch (type) {
256+
case RuleTypes.Basic:
257+
return "Rule 'Basic' requires a given amount of reviews from users/teams";
258+
case RuleTypes.And:
259+
return "Rule 'And' has many required reviewers/teams and requires all of them to be fulfilled.";
260+
case RuleTypes.Or:
261+
return "Rule 'Or' has many required reviewers/teams and requires at least one of them to be fulfilled.";
262+
case RuleTypes.AndDistinct:
263+
return (
264+
"Rule 'And Distinct' has many required reviewers/teams and requires all of them to be fulfilled **by different users**.\n\n" +
265+
"The approval of one user that belongs to _two teams_ will count only towards one team."
266+
);
267+
case RuleTypes.Fellows:
268+
return "Rule 'Fellows' requires a given amount of reviews from users whose Fellowship ranking is the required rank or great.";
269+
default:
270+
console.error("Out of range for rule type", type);
271+
return "Unhandled rule";
272+
}
273+
};
274+
246275
check.output.summary += `- **${report.name}**\n`;
247276
let text = summary
248277
.emptyBuffer()
249278
.addHeading(report.name, 2)
250-
.addHeading(`Missing ${report.missingReviews} review${report.missingReviews > 1 ? "s" : ""}`, 4);
279+
.addHeading(`Missing ${report.missingReviews} review${report.missingReviews > 1 ? "s" : ""}`, 4)
280+
.addDetails(
281+
"Rule explanation",
282+
`${ruleExplanation(
283+
report.type,
284+
)}\n\nFor more info found out how the rules work in [Review-bot types](https://github.com/paritytech/review-bot#types)`,
285+
);
251286
if (report.usersToRequest && report.usersToRequest.length > 0) {
252287
text = text
253288
.addHeading("Missing users", 3)
@@ -263,6 +298,13 @@ export class ActionRunner {
263298
.addRaw(`Missing reviews from rank \`${report.missingRank}\` or above`)
264299
.addEOL();
265300
}
301+
if (report.countingReviews.length > 0) {
302+
text = text
303+
.addHeading("Users approvals that counted towards this rule", 3)
304+
.addEOL()
305+
.addList(report.countingReviews)
306+
.addEOL();
307+
}
266308

267309
check.output.text += text.stringify() + "\n";
268310
}
@@ -289,6 +331,8 @@ export class ActionRunner {
289331
// We get the list of users that approved the PR
290332
const approvals = await this.prApi.listApprovedReviewsAuthors(rule.countAuthor ?? false);
291333

334+
let countingReviews: string[] = [];
335+
292336
// Utility method used to generate error
293337
const generateErrorReport = (): ReviewReport => {
294338
const filterMissingUsers = (reviewData: { users?: string[] }[]): string[] =>
@@ -301,6 +345,7 @@ export class ActionRunner {
301345
missingUsers: filterMissingUsers(requirements),
302346
teamsToRequest: rule.reviewers.flatMap((r) => r.teams ?? []),
303347
usersToRequest: filterMissingUsers(rule.reviewers),
348+
countingReviews,
304349
};
305350
};
306351

@@ -327,6 +372,8 @@ export class ActionRunner {
327372
}
328373
this.logger.debug(`Matching approvals: ${JSON.stringify(conditionApprovals)}`);
329374

375+
countingReviews = [...new Set(conditionApprovals.flatMap(({ matchingUsers }) => matchingUsers))];
376+
330377
// If one of the rules doesn't have the required approval we fail the evaluation
331378
if (conditionApprovals.some((cond) => cond.matchingUsers.length === 0)) {
332379
this.logger.warn("One of the groups does not have any approvals");
@@ -421,12 +468,16 @@ export class ActionRunner {
421468
const approvals = await this.prApi.listApprovedReviewsAuthors(countAuthor ?? false);
422469
this.logger.info(`Found ${approvals.length} approvals.`);
423470

471+
// List of user reviews which fulfill this rule
472+
const countingReviews: string[] = [];
473+
424474
// This is the amount of reviews required. To succeed this should be 0 or lower
425475
let missingReviews = rule.minApprovals;
426476
for (const requiredUser of requiredUsers) {
427477
// We check for the approvals, if it is a required reviewer we lower the amount of missing reviews
428478
if (approvals.indexOf(requiredUser) > -1) {
429479
missingReviews--;
480+
countingReviews.push(requiredUser);
430481
}
431482
}
432483

@@ -444,6 +495,7 @@ export class ActionRunner {
444495
missingUsers: requiredUsers.filter((u) => approvals.indexOf(u) < 0).filter((u) => u !== author),
445496
teamsToRequest: rule.teams ? rule.teams : undefined,
446497
usersToRequest: rule.users ? rule.users.filter((u) => approvals.indexOf(u)) : undefined,
498+
countingReviews,
447499
},
448500
];
449501
} else {
@@ -472,12 +524,16 @@ export class ActionRunner {
472524
const approvals = await this.prApi.listApprovedReviewsAuthors(rule.countAuthor ?? false);
473525
this.logger.info(`Found ${approvals.length} approvals.`);
474526

527+
// List of user reviews which fulfill this rule
528+
const countingReviews: string[] = [];
529+
475530
// This is the amount of reviews required. To succeed this should be 0 or lower
476531
let missingReviews = rule.minApprovals;
477532
for (const requiredUser of requiredUsers) {
478533
// We check for the approvals, if it is a required reviewer we lower the amount of missing reviews
479534
if (approvals.indexOf(requiredUser) > -1) {
480535
missingReviews--;
536+
countingReviews.push(requiredUser);
481537
}
482538
}
483539

@@ -494,6 +550,7 @@ export class ActionRunner {
494550
// Remove all the users who approved the PR + the author (if he belongs to the group)
495551
missingUsers: requiredUsers.filter((u) => approvals.indexOf(u) < 0).filter((u) => u !== author),
496552
missingRank: rule.minRank,
553+
countingReviews,
497554
},
498555
];
499556
} else {
@@ -589,7 +646,7 @@ const getRequiredRanks = (reports: Pick<ReviewReport, "missingRank">[]): number[
589646
}
590647
};
591648

592-
const unifyReport = (reports: ReviewReport[], name: string): RuleReport => {
649+
const unifyReport = (reports: ReviewReport[], name: string, type: RuleTypes): RuleReport => {
593650
const ranks = getRequiredRanks(reports);
594651
const missingRank = ranks ? Math.max(...(ranks as number[])) : undefined;
595652

@@ -599,6 +656,8 @@ const unifyReport = (reports: ReviewReport[], name: string): RuleReport => {
599656
teamsToRequest: [...new Set(reports.flatMap((r) => r.teamsToRequest ?? []))],
600657
usersToRequest: [...new Set(reports.flatMap((r) => r.usersToRequest ?? []))],
601658
name,
659+
type,
602660
missingRank,
661+
countingReviews: [...new Set(reports.flatMap((r) => r.countingReviews))],
603662
};
604663
};

src/test/runner/runner.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { GitHubChecksApi } from "../../github/check";
55
import { PullRequestApi } from "../../github/pullRequest";
66
import { ActionLogger, TeamApi } from "../../github/types";
77
import { ConfigurationFile, Rule, RuleTypes } from "../../rules/types";
8-
import { ActionRunner } from "../../runner";
8+
import { ActionRunner, RuleReport } from "../../runner";
99

1010
describe("Shared validations", () => {
1111
let api: MockProxy<PullRequestApi>;
@@ -94,12 +94,14 @@ describe("Shared validations", () => {
9494
});
9595

9696
describe("Validation in requestReviewers", () => {
97-
const exampleReport = {
97+
const exampleReport: RuleReport = {
9898
name: "Example",
9999
missingUsers: ["user-1", "user-2", "user-3"],
100100
missingReviews: 2,
101101
teamsToRequest: ["team-1"],
102102
usersToRequest: ["user-1"],
103+
type: RuleTypes.Basic,
104+
countingReviews: [],
103105
};
104106

105107
test("should request reviewers if object is not defined", async () => {

0 commit comments

Comments
 (0)