@@ -10,11 +10,19 @@ import {
1010 validateDate ,
1111 promptDependencies ,
1212 getSupportedVersions ,
13+ getReportSeverity ,
1314 pickReport ,
1415 SecurityRelease
1516} from './security-release/security-release.js' ;
1617import _ 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+
1826export 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