1
+ // This is a github action script and can be run only from github actions. To run this script locally, you need to mock the github object and context object.
2
+ module . exports = async ( { github, context, core } ) => {
3
+ const { labelsToAdd } = process . env
4
+
5
+ // Check if there are already reviewers assigned to the PR
6
+ try {
7
+ const existingReviewers = await github . rest . pulls . listRequestedReviewers ( {
8
+ owner : context . repo . owner ,
9
+ repo : context . repo . repo ,
10
+ pull_number : context . payload . pull_request . number
11
+ } )
12
+
13
+ if ( existingReviewers . data . users && existingReviewers . data . users . length > 0 ) {
14
+ core . setOutput ( 'skip' , 'true' )
15
+ core . setOutput ( 'reason' , `PR already has ${ existingReviewers . data . users . length } user reviewer(s) assigned: ${ existingReviewers . data . users . map ( u => u . login ) . join ( ', ' ) } ` )
16
+ core . info ( `Skipping reviewer assignment - users already assigned: ${ existingReviewers . data . users . map ( u => u . login ) . join ( ', ' ) } ` )
17
+ return
18
+ }
19
+ } catch ( error ) {
20
+ core . info ( `Failed to check existing reviewers: ${ error . message } ` )
21
+ core . setOutput ( 'skip' , 'true' )
22
+ core . setOutput ( 'reason' , `Failed to check existing reviewers: ${ error . message } ` )
23
+ return
24
+ }
25
+
26
+ // Check if team:external label is being added
27
+ if ( labelsToAdd && labelsToAdd . split ( ',' ) . includes ( 'team:external' ) ) {
28
+ core . setOutput ( 'reviewers' , 'joe-ayoub-segment' )
29
+ core . setOutput ( 'team' , 'external' )
30
+ core . setOutput ( 'skip' , 'false' )
31
+ core . info ( 'Assigned joe-ayoub-segment for external contributor PR' )
32
+ return
33
+ }
34
+
35
+ // Check if team:external label already exists on the PR
36
+ const existingLabels = context . payload . pull_request . labels . map ( ( label ) => label . name )
37
+ if ( existingLabels . includes ( 'team:external' ) ) {
38
+ core . setOutput ( 'reviewers' , 'joe-ayoub-segment' )
39
+ core . setOutput ( 'team' , 'external' )
40
+ core . setOutput ( 'skip' , 'false' )
41
+ core . info ( 'Assigned joe-ayoub-segment for external contributor PR (existing label)' )
42
+ return
43
+ }
44
+
45
+ // Function to get teams from existing reviewers
46
+ async function getTeamFromGitHub ( ) {
47
+ try {
48
+ // Get the list of requested reviewers (teams and individuals)
49
+ const requestedReviewers = await github . rest . pulls . listRequestedReviewers ( {
50
+ owner : context . repo . owner ,
51
+ repo : context . repo . repo ,
52
+ pull_number : context . payload . pull_request . number
53
+ } )
54
+
55
+ core . info ( `GitHub requested reviewers: ${ JSON . stringify ( requestedReviewers . data ) } ` )
56
+
57
+ // Extract teams from requested reviewers
58
+ const teams = [ ]
59
+ if ( requestedReviewers . data . teams && requestedReviewers . data . teams . length > 0 ) {
60
+ for ( const team of requestedReviewers . data . teams ) {
61
+ teams . push ( team . slug )
62
+ }
63
+ }
64
+
65
+ if ( teams . length > 0 ) {
66
+ // Return the first team
67
+ const selectedTeam = teams [ 0 ]
68
+ core . info ( `Selected team from requested reviewers: ${ selectedTeam } ` )
69
+ return selectedTeam
70
+ }
71
+
72
+ core . info ( 'No teams found in requested reviewers' )
73
+ return null
74
+
75
+ } catch ( error ) {
76
+ core . info ( `Failed to get teams from GitHub: ${ error . message } ` )
77
+ return null
78
+ }
79
+ }
80
+
81
+ // Get team assignment from GitHub's CODEOWNERS evaluation
82
+ const teamToAssign = await getTeamFromGitHub ( )
83
+
84
+ if ( ! teamToAssign ) {
85
+ core . setOutput ( 'reviewers' , 'joe-ayoub-segment' )
86
+ core . setOutput ( 'team' , 'other' )
87
+ core . setOutput ( 'skip' , 'false' )
88
+ core . info ( 'No team assigned, defaulting to joe-ayoub-segment' )
89
+ return
90
+ }
91
+
92
+ // Special handling for strategic-connections-team: only assign joe-ayoub-segment if author is not in team
93
+ if ( teamToAssign === 'strategic-connections-team' ) {
94
+ // Check if PR author is a member of strategic-connections-team
95
+ let isAuthorInTeam = false
96
+ try {
97
+ const team = await github . rest . teams . listMembersInOrg ( {
98
+ team_slug : teamToAssign ,
99
+ org : context . repo . owner
100
+ } )
101
+ const prAuthor = context . payload . pull_request . user . login
102
+ isAuthorInTeam = team . data . some ( member => member . login === prAuthor )
103
+ core . info ( `PR author ${ prAuthor } is ${ isAuthorInTeam ? 'in' : 'not in' } ${ teamToAssign } ` )
104
+ } catch ( error ) {
105
+ core . info ( `Failed to check team membership: ${ error . message } ` )
106
+ }
107
+
108
+ if ( ! isAuthorInTeam ) {
109
+ // PR targeting strategic-connections-team from non-team member -> assign to joe-ayoub-segment
110
+ core . setOutput ( 'reviewers' , 'joe-ayoub-segment' )
111
+ core . setOutput ( 'team' , 'other' )
112
+ core . setOutput ( 'skip' , 'false' )
113
+ core . info ( `PR targeting ${ teamToAssign } from non-team member, assigned to joe-ayoub-segment` )
114
+ return
115
+ }
116
+ // If author is in team, continue to team assignment logic below
117
+ }
118
+
119
+ // Get team members (assign from target team regardless of author's team membership)
120
+ try {
121
+ const team = await github . rest . teams . listMembersInOrg ( {
122
+ team_slug : teamToAssign ,
123
+ org : context . repo . owner
124
+ } )
125
+
126
+ if ( team . data . length === 0 ) {
127
+ core . setOutput ( 'skip' , 'true' )
128
+ core . setOutput ( 'reason' , `no team members found in ${ teamToAssign } ` )
129
+ return
130
+ }
131
+
132
+ // Filter out the PR author if they are in the target team (to avoid self-review)
133
+ const prAuthor = context . payload . pull_request . user . login
134
+ const eligibleMembers = team . data . filter ( member => member . login !== prAuthor )
135
+
136
+ // If no eligible members after filtering author, use all team members
137
+ const membersToSelectFrom = eligibleMembers . length > 0 ? eligibleMembers : team . data
138
+
139
+ // Get 2 consecutive members from the available team members (or all available if less than 2)
140
+ const numReviewers = Math . min ( 2 , membersToSelectFrom . length )
141
+
142
+ // Select a random starting position and take consecutive members
143
+ const startIndex = Math . floor ( Math . random ( ) * membersToSelectFrom . length )
144
+ const selectedMembers = [ ]
145
+
146
+ for ( let i = 0 ; i < numReviewers ; i ++ ) {
147
+ const index = ( startIndex + i ) % membersToSelectFrom . length
148
+ selectedMembers . push ( membersToSelectFrom [ index ] )
149
+ }
150
+
151
+ const reviewerLogins = selectedMembers . map ( member => member . login )
152
+ core . setOutput ( 'reviewers' , reviewerLogins . join ( ',' ) )
153
+ core . setOutput ( 'team' , teamToAssign )
154
+ core . setOutput ( 'skip' , 'false' )
155
+
156
+ const authorInfo = eligibleMembers . length !== team . data . length ? ` (excluded PR author: ${ prAuthor } )` : ''
157
+ core . info ( `Selected ${ reviewerLogins . join ( ', ' ) } (consecutive from index ${ startIndex } ) from ${ membersToSelectFrom . length } available members in ${ teamToAssign } ${ authorInfo } ` )
158
+
159
+ } catch ( error ) {
160
+ core . setOutput ( 'skip' , 'true' )
161
+ core . setOutput ( 'reason' , `failed to get ${ teamToAssign } members: ${ error . message } ` )
162
+ }
163
+ }
0 commit comments