1- import type { Finding , ResolvedFiling , RepeatedFiling } from './types.d.js'
1+ import type { Finding , ResolvedFiling , RepeatedFiling , FindingGroupIssue , Filing } from './types.d.js'
22import process from 'node:process'
33import core from '@actions/core'
44import { Octokit } from '@octokit/core'
@@ -11,6 +11,7 @@ import {isResolvedFiling} from './isResolvedFiling.js'
1111import { openIssue } from './openIssue.js'
1212import { reopenIssue } from './reopenIssue.js'
1313import { updateFilingsWithNewFindings } from './updateFilingsWithNewFindings.js'
14+ import { OctokitResponse } from '@octokit/types'
1415const OctokitWithThrottling = Octokit . plugin ( throttling )
1516
1617export default async function ( ) {
@@ -22,10 +23,12 @@ export default async function () {
2223 const cachedFilings : ( ResolvedFiling | RepeatedFiling ) [ ] = JSON . parse (
2324 core . getInput ( 'cached_filings' , { required : false } ) || '[]' ,
2425 )
26+ const shouldOpenGroupedIssues = core . getBooleanInput ( "open_grouped_issues" )
2527 core . debug ( `Input: 'findings: ${ JSON . stringify ( findings ) } '` )
2628 core . debug ( `Input: 'repository: ${ repoWithOwner } '` )
2729 core . debug ( `Input: 'screenshot_repository: ${ screenshotRepo } '` )
2830 core . debug ( `Input: 'cached_filings: ${ JSON . stringify ( cachedFilings ) } '` )
31+ core . debug ( `Input: 'open_grouped_issues: ${ shouldOpenGroupedIssues } '` )
2932
3033 const octokit = new OctokitWithThrottling ( {
3134 auth : token ,
@@ -48,8 +51,12 @@ export default async function () {
4851 } )
4952 const filings = updateFilingsWithNewFindings ( cachedFilings , findings )
5053
54+ // Track new issues for grouping
55+ const newIssuesByProblemShort : Record < string , FindingGroupIssue [ ] > = { }
56+ const trackingIssueUrls : Record < string , string > = { }
57+
5158 for ( const filing of filings ) {
52- let response
59+ let response : OctokitResponse < any > | undefined ;
5360 try {
5461 if ( isResolvedFiling ( filing ) ) {
5562 // Close the filing’s issue (if necessary)
@@ -58,8 +65,19 @@ export default async function () {
5865 } else if ( isNewFiling ( filing ) ) {
5966 // Open a new issue for the filing
6067 response = await openIssue ( octokit , repoWithOwner , filing . findings [ 0 ] , screenshotRepo )
61- // eslint-disable-next-line @typescript-eslint/no-explicit-any
62- ; ( filing as any ) . issue = { state : 'open' } as Issue
68+ ; ( filing as Filing ) . issue = { state : 'open' } as Issue
69+
70+ // Track for grouping
71+ if ( shouldOpenGroupedIssues ) {
72+ const problemShort : string = filing . findings [ 0 ] . problemShort
73+ if ( ! newIssuesByProblemShort [ problemShort ] ) {
74+ newIssuesByProblemShort [ problemShort ] = [ ]
75+ }
76+ newIssuesByProblemShort [ problemShort ] . push ( {
77+ url : response . data . html_url ,
78+ id : response . data . number ,
79+ } )
80+ }
6381 } else if ( isRepeatedFiling ( filing ) ) {
6482 // Reopen the filing's issue (if necessary) and update the body with the latest finding
6583 response = await reopenIssue (
@@ -87,7 +105,42 @@ export default async function () {
87105 }
88106 }
89107
90- core . setOutput ( 'filings' , JSON . stringify ( filings ) )
91- core . debug ( `Output: 'filings: ${ JSON . stringify ( filings ) } '` )
92- core . info ( "Finished 'file' action" )
108+ // Open tracking issues for groups with >1 new issue and link back from each
109+ // new issue
110+ if ( shouldOpenGroupedIssues ) {
111+ for ( const [ problemShort , issues ] of Object . entries (
112+ newIssuesByProblemShort ,
113+ ) ) {
114+ if ( issues . length > 1 ) {
115+ const title : string = `${ problemShort } issues` ;
116+ const body : string =
117+ `# ${ problemShort } issues\n\n` +
118+ issues . map ( ( issue ) => `- [ ] ${ issue . url } ` ) . join ( "\n" ) ;
119+ try {
120+ const trackingResponse = await octokit . request (
121+ `POST /repos/${ repoWithOwner } /issues` ,
122+ {
123+ owner : repoWithOwner . split ( "/" ) [ 0 ] ,
124+ repo : repoWithOwner . split ( "/" ) [ 1 ] ,
125+ title,
126+ body,
127+ } ,
128+ ) ;
129+ const trackingUrl : string = trackingResponse . data . html_url ;
130+ trackingIssueUrls [ problemShort ] = trackingUrl ;
131+ core . info (
132+ `Opened tracking issue for '${ problemShort } ' with ${ issues . length } issues.` ,
133+ ) ;
134+ } catch ( error ) {
135+ core . warning (
136+ `Failed to open tracking issue for '${ problemShort } ': ${ error } ` ,
137+ ) ;
138+ }
139+ }
140+ }
141+ }
142+
143+ core . setOutput ( "filings" , JSON . stringify ( filings ) ) ;
144+ core . debug ( `Output: 'filings: ${ JSON . stringify ( filings ) } '` ) ;
145+ core . info ( "Finished 'file' action" ) ;
93146}
0 commit comments