Skip to content

Commit f7027e7

Browse files
committed
feat: check for reports without PR_URL on H1
1 parent da35751 commit f7027e7

2 files changed

Lines changed: 72 additions & 4 deletions

File tree

lib/cli.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ export default class CLI {
9494
return answer;
9595
}
9696

97+
async promptCheckbox(message, choices) {
98+
if (this.assumeYes) {
99+
return choices.filter((c) => c.checked).map((c) => c.value);
100+
}
101+
102+
return inquirer.checkbox({ message, choices });
103+
}
104+
97105
setAssumeYes() {
98106
this.assumeYes = true;
99107
}

lib/prepare_security.js

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,19 @@ import {
1010
validateDate,
1111
promptDependencies,
1212
getSupportedVersions,
13+
getReportSeverity,
1314
pickReport,
1415
SecurityRelease
1516
} from './security-release/security-release.js';
1617
import _ from 'lodash';
1718

19+
function relativeDate(date) {
20+
const days = Math.floor((Date.now() - date) / (1000 * 60 * 60 * 24));
21+
if (days < 30) return days === 1 ? '1 day ago' : `${days} days ago`;
22+
const months = Math.floor(days / 30);
23+
return months === 1 ? '1 month ago' : `${months} months ago`;
24+
}
25+
1826
export default class PrepareSecurityRelease extends SecurityRelease {
1927
title = 'Next Security Release';
2028

@@ -25,6 +33,13 @@ export default class PrepareSecurityRelease extends SecurityRelease {
2533
});
2634

2735
this.req = new Request(credentials);
36+
37+
let excludedReports = [];
38+
const showTriaged = await this.promptShowTriagedWithoutPR();
39+
if (showTriaged) {
40+
excludedReports = await this.showTriagedReportsWithoutPR();
41+
}
42+
2843
const releaseDate = await this.promptReleaseDate();
2944
if (releaseDate !== 'TBD') {
3045
validateDate(releaseDate);
@@ -34,7 +49,8 @@ export default class PrepareSecurityRelease extends SecurityRelease {
3449
let securityReleasePRUrl;
3550
const content = await this.buildDescription(releaseDate, securityReleasePRUrl);
3651
if (createVulnerabilitiesJSON) {
37-
securityReleasePRUrl = await this.startVulnerabilitiesJSONCreation(releaseDate, content);
52+
securityReleasePRUrl = await this.startVulnerabilitiesJSONCreation(
53+
releaseDate, content, excludedReports);
3854
}
3955

4056
this.cli.ok('Done!');
@@ -93,12 +109,12 @@ export default class PrepareSecurityRelease extends SecurityRelease {
93109
this.cli.ok('Done!');
94110
}
95111

96-
async startVulnerabilitiesJSONCreation(releaseDate, content) {
112+
async startVulnerabilitiesJSONCreation(releaseDate, content, excludedReports = []) {
97113
// checkout on the next-security-release branch
98114
checkoutOnSecurityReleaseBranch(this.cli, this.repository);
99115

100116
// choose the reports to include in the security release
101-
const reports = await this.chooseReports();
117+
const reports = await this.chooseReports(excludedReports);
102118
const depUpdates = await this.getDependencyUpdates();
103119
const deps = _.groupBy(depUpdates, 'name');
104120

@@ -184,17 +200,61 @@ export default class PrepareSecurityRelease extends SecurityRelease {
184200
{ defaultAnswer: true });
185201
}
186202

203+
async promptShowTriagedWithoutPR() {
204+
return this.cli.prompt(
205+
'Do you want to see which reports are triaged but have no PR URL?',
206+
{ defaultAnswer: true });
207+
}
208+
209+
async showTriagedReportsWithoutPR() {
210+
this.cli.info('Fetching triaged reports without PR URL...');
211+
const reports = await this.req.getTriagedReports();
212+
const reportsWithoutPR = reports.data.filter(
213+
(report) => !report.relationships.custom_field_values.data.length
214+
);
215+
if (!reportsWithoutPR.length) {
216+
this.cli.ok('All triaged reports have a PR URL.');
217+
return [];
218+
}
219+
const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
220+
const choices = reportsWithoutPR
221+
.sort((a, b) => {
222+
const dateA = new Date(a.attributes.created_at);
223+
const dateB = new Date(b.attributes.created_at);
224+
if (dateB - dateA !== 0) return dateB - dateA;
225+
const rankA = severityRank[getReportSeverity(a).rating] ?? 4;
226+
const rankB = severityRank[getReportSeverity(b).rating] ?? 4;
227+
return rankA - rankB;
228+
})
229+
.map((report) => {
230+
const { id, attributes: { title, created_at } } = report;
231+
const { rating } = getReportSeverity(report);
232+
const openedDate = relativeDate(new Date(created_at));
233+
const link = `https://hackerone.com/reports/${id}`;
234+
return {
235+
name: `[${openedDate}] (${rating}) ${title} - ${link}`,
236+
value: id,
237+
checked: true
238+
};
239+
});
240+
return this.cli.promptCheckbox(
241+
'Select reports to exclude from the upcoming security release:',
242+
choices
243+
);
244+
}
245+
187246
async buildDescription() {
188247
const template = await this.getSecurityIssueTemplate();
189248
return template;
190249
}
191250

192-
async chooseReports() {
251+
async chooseReports(excludedReports = []) {
193252
this.cli.info('Getting triaged H1 reports...');
194253
const reports = await this.req.getTriagedReports();
195254
const selectedReports = [];
196255

197256
for (const report of reports.data) {
257+
if (excludedReports.includes(report.id)) continue;
198258
const rep = await pickReport(report, { cli: this.cli, req: this.req });
199259
if (!rep) continue;
200260
selectedReports.push(rep);

0 commit comments

Comments
 (0)