1- import { Fragment } from 'react' ;
1+ import { Fragment , useMemo , useState } from 'react' ;
22import styled from '@emotion/styled' ;
33
44import { Alert } from '@sentry/scraps/alert' ;
55import { Checkbox } from '@sentry/scraps/checkbox' ;
6+ import { CompactSelect } from '@sentry/scraps/compactSelect' ;
67import { Flex } from '@sentry/scraps/layout' ;
8+ import { OverlayTrigger } from '@sentry/scraps/overlayTrigger' ;
79
8- import { addErrorMessage , addSuccessMessage } from 'sentry/actionCreators/indicator' ;
9- import { DropdownMenu } from 'sentry/components/dropdownMenu' ;
10+ import {
11+ addErrorMessage ,
12+ addLoadingMessage ,
13+ addSuccessMessage ,
14+ } from 'sentry/actionCreators/indicator' ;
1015import { QuestionTooltip } from 'sentry/components/questionTooltip' ;
1116import type { useBulkUpdateRepositorySettings } from 'sentry/components/repositories/useBulkUpdateRepositorySettings' ;
1217import { SimpleTable } from 'sentry/components/tables/simpleTable' ;
1318import { t , tct , tn } from 'sentry/locale' ;
19+ import type { RepositoryWithSettings } from 'sentry/types/integrations' ;
20+ import type { CodeReviewTrigger } from 'sentry/types/seer' ;
1421import { parseQueryKey } from 'sentry/utils/api/apiQueryKey' ;
1522import type { Sort } from 'sentry/utils/discover/fields' ;
1623import { useListItemCheckboxContext } from 'sentry/utils/list/useListItemCheckboxState' ;
@@ -21,8 +28,11 @@ interface Props {
2128 gridColumns : string ;
2229 isFetchingNextPage : boolean ;
2330 isPending : boolean ;
24- mutateRepositorySettings : ReturnType < typeof useBulkUpdateRepositorySettings > [ 'mutate' ] ;
31+ mutateRepositorySettings : ReturnType <
32+ typeof useBulkUpdateRepositorySettings
33+ > [ 'mutateAsync' ] ;
2534 onSortClick : ( key : Sort ) => void ;
35+ repositories : RepositoryWithSettings [ ] ;
2636 sort : Sort ;
2737}
2838
@@ -53,6 +63,7 @@ export function SeerRepoTableHeader({
5363 isPending,
5464 mutateRepositorySettings,
5565 onSortClick,
66+ repositories,
5667 sort,
5768} : Props ) {
5869 const canWrite = useCanWriteSettings ( ) ;
@@ -71,34 +82,160 @@ export function SeerRepoTableHeader({
7182 : undefined ;
7283 const queryString = queryOptions ?. query ?. query ;
7384
74- const handleBulkCodeReview = ( enabledCodeReview : boolean ) => {
85+ const selectedRepos = useMemo ( ( ) => {
86+ return repositories . filter ( repo => selectedIds . includes ( repo . id ) ) ;
87+ } , [ repositories , selectedIds ] ) ;
88+
89+ const currentCodeReviewValue = useMemo ( ( ) => {
90+ const someEnabled = selectedRepos . some ( repo => repo ?. settings ?. enabledCodeReview ) ;
91+ const someDisabled = selectedRepos . some (
92+ repo => repo ?. settings ?. enabledCodeReview === false
93+ ) ;
94+ if ( someEnabled && someDisabled ) {
95+ return undefined ;
96+ }
97+ if ( someEnabled ) {
98+ return 'enabled_code_review:enabled' ;
99+ }
100+ if ( someDisabled ) {
101+ return 'enabled_code_review:disabled' ;
102+ }
103+ return undefined ;
104+ } , [ selectedRepos ] ) ;
105+
106+ const currentTriggersValue = useMemo ( ( ) : CodeReviewTrigger [ ] => {
107+ const someOnReadyForReview = selectedRepos . every ( repo =>
108+ repo ?. settings ?. codeReviewTriggers ?. includes ( 'on_ready_for_review' )
109+ ) ;
110+ const someOnNewCommit = selectedRepos . every ( repo =>
111+ repo ?. settings ?. codeReviewTriggers ?. includes ( 'on_new_commit' )
112+ ) ;
113+ return [
114+ ...( someOnReadyForReview ? [ 'on_ready_for_review' as const ] : [ ] ) ,
115+ ...( someOnNewCommit ? [ 'on_new_commit' as const ] : [ ] ) ,
116+ ] ;
117+ } , [ selectedRepos ] ) ;
118+
119+ const [ isBulkUpdating , setIsBulkUpdating ] = useState ( false ) ;
120+
121+ const handleBulkCodeReview = async ( enabledCodeReview : boolean ) => {
75122 const repositoryIds = selectedIds === 'all' ? knownIds : selectedIds ;
76- mutateRepositorySettings (
77- {
78- enabledCodeReview,
79- repositoryIds,
80- } ,
81- {
82- onError : ( ) => {
83- addErrorMessage (
84- tn (
85- 'Failed to update code review for %s repository' ,
86- 'Failed to update code review for %s repositories' ,
87- repositoryIds . length
88- )
89- ) ;
90- } ,
91- onSuccess : ( ) => {
92- addSuccessMessage (
93- tn (
94- 'Code review updated for %s repository' ,
95- 'Code review updated for %s repositories' ,
96- repositoryIds . length
97- )
98- ) ;
99- } ,
100- }
123+ setIsBulkUpdating ( true ) ;
124+ addLoadingMessage (
125+ tn (
126+ 'Updating code review for %s repository…' ,
127+ 'Updating code review for %s repositories…' ,
128+ repositoryIds . length
129+ )
101130 ) ;
131+ try {
132+ await mutateRepositorySettings ( { enabledCodeReview, repositoryIds} ) ;
133+ addSuccessMessage (
134+ tn (
135+ 'Code review updated for %s repository' ,
136+ 'Code review updated for %s repositories' ,
137+ repositoryIds . length
138+ )
139+ ) ;
140+ } catch {
141+ addErrorMessage (
142+ tn (
143+ 'Failed to update code review for %s repository' ,
144+ 'Failed to update code review for %s repositories' ,
145+ repositoryIds . length
146+ )
147+ ) ;
148+ } finally {
149+ setIsBulkUpdating ( false ) ;
150+ }
151+ } ;
152+
153+ const handleBulkTriggers = async ( {
154+ added,
155+ removed,
156+ } : {
157+ added : CodeReviewTrigger | undefined ;
158+ removed : CodeReviewTrigger | undefined ;
159+ } ) => {
160+ const promises : Array < Promise < unknown > > = [ ] ;
161+
162+ if ( added ) {
163+ const repoIdsWithZeroTriggers : string [ ] = [ ] ;
164+ const repoIdsWithOneTrigger : string [ ] = [ ] ;
165+ for ( const repo of selectedRepos ) {
166+ if ( repo . settings ?. codeReviewTriggers ?. length === 0 ) {
167+ repoIdsWithZeroTriggers . push ( repo . id ) ;
168+ } else if ( ! repo . settings ?. codeReviewTriggers . includes ( added ) ) {
169+ repoIdsWithOneTrigger . push ( repo . id ) ;
170+ }
171+ }
172+ // Some items start with 0 triggers, they'll be saved with 1 new trigger
173+ if ( repoIdsWithZeroTriggers . length > 0 ) {
174+ promises . push (
175+ mutateRepositorySettings ( {
176+ codeReviewTriggers : [ added ] ,
177+ repositoryIds : repoIdsWithZeroTriggers ,
178+ } )
179+ ) ;
180+ }
181+ // Some items start with 1 trigger, they'll be saved with 1 new trigger for a total of 2
182+ if ( repoIdsWithOneTrigger . length > 0 ) {
183+ promises . push (
184+ mutateRepositorySettings ( {
185+ codeReviewTriggers : [ 'on_new_commit' , 'on_ready_for_review' ] ,
186+ repositoryIds : repoIdsWithOneTrigger ,
187+ } )
188+ ) ;
189+ }
190+ }
191+ if ( removed ) {
192+ const repoIdsWithOneTrigger : string [ ] = [ ] ;
193+ const repoIdsWithTwoTriggers : string [ ] = [ ] ;
194+ for ( const repo of selectedRepos ) {
195+ if ( repo . settings ?. codeReviewTriggers ?. length === 2 ) {
196+ repoIdsWithTwoTriggers . push ( repo . id ) ;
197+ } else if ( repo . settings ?. codeReviewTriggers ?. includes ( removed ) ) {
198+ repoIdsWithOneTrigger . push ( repo . id ) ;
199+ }
200+ }
201+ // Some items start with 2 triggers, we'll remove one
202+ const remainingTrigger =
203+ removed === 'on_new_commit' ? 'on_ready_for_review' : 'on_new_commit' ;
204+ if ( repoIdsWithTwoTriggers . length > 0 ) {
205+ promises . push (
206+ mutateRepositorySettings ( {
207+ codeReviewTriggers : [ remainingTrigger ] ,
208+ repositoryIds : repoIdsWithTwoTriggers ,
209+ } )
210+ ) ;
211+ }
212+ // Some items start with 1 trigger, we'll remove it
213+ if ( repoIdsWithOneTrigger . length > 0 ) {
214+ promises . push (
215+ mutateRepositorySettings ( {
216+ codeReviewTriggers : [ ] ,
217+ repositoryIds : repoIdsWithOneTrigger ,
218+ } )
219+ ) ;
220+ }
221+ }
222+
223+ if ( promises . length === 0 ) {
224+ return ;
225+ }
226+
227+ setIsBulkUpdating ( true ) ;
228+ addLoadingMessage ( t ( 'Updating triggers…' ) ) ;
229+
230+ const results = await Promise . allSettled ( promises ) ;
231+ const hasError = results . some ( r => r . status === 'rejected' ) ;
232+ setIsBulkUpdating ( false ) ;
233+
234+ if ( hasError ) {
235+ addErrorMessage ( t ( 'Failed to update triggers' ) ) ;
236+ } else {
237+ addSuccessMessage ( t ( 'Triggers updated' ) ) ;
238+ }
102239 } ;
103240
104241 return (
@@ -147,22 +284,62 @@ export function SeerRepoTableHeader({
147284 />
148285 </ TableCellFirst >
149286 < TableCellsRemainingContent align = "center" gap = "md" >
150- < DropdownMenu
151- isDisabled = { ! canWrite }
287+ < CompactSelect
288+ disabled = { ! canWrite }
289+ size = "xs"
290+ trigger = { props => (
291+ < OverlayTrigger . Button { ...props } >
292+ { t ( 'Code Review' ) }
293+ </ OverlayTrigger . Button >
294+ ) }
295+ options = { [
296+ {
297+ value : 'enabled_code_review:enabled' ,
298+ label : t ( 'Enable' ) ,
299+ disabled : isBulkUpdating ,
300+ } ,
301+ {
302+ value : 'enabled_code_review:disabled' ,
303+ label : t ( 'Disable' ) ,
304+ disabled : isBulkUpdating ,
305+ } ,
306+ ] }
307+ value = { currentCodeReviewValue }
308+ onChange = { option => {
309+ if ( option . value === 'enabled_code_review:enabled' ) {
310+ handleBulkCodeReview ( true ) ;
311+ } else {
312+ handleBulkCodeReview ( false ) ;
313+ }
314+ } }
315+ />
316+
317+ < CompactSelect < CodeReviewTrigger >
318+ disabled = { ! canWrite }
319+ multiple
152320 size = "xs"
153- items = { [
321+ trigger = { props => (
322+ < OverlayTrigger . Button { ...props } > { t ( 'Triggers' ) } </ OverlayTrigger . Button >
323+ ) }
324+ options = { [
154325 {
155- key : 'on ' ,
156- label : t ( 'On' ) ,
157- onAction : ( ) => handleBulkCodeReview ( true ) ,
326+ value : 'on_ready_for_review ' ,
327+ label : t ( 'On Ready for Review ' ) ,
328+ disabled : isBulkUpdating ,
158329 } ,
159330 {
160- key : 'off ' ,
161- label : t ( 'Off ' ) ,
162- onAction : ( ) => handleBulkCodeReview ( false ) ,
331+ value : 'on_new_commit ' ,
332+ label : t ( 'On New Commit ' ) ,
333+ disabled : isBulkUpdating ,
163334 } ,
164335 ] }
165- triggerLabel = { t ( 'Code Review' ) }
336+ value = { currentTriggersValue }
337+ onChange = { option => {
338+ const value = option . map ( v => v . value ) ;
339+ const added = value . findLast ( v => ! currentTriggersValue . includes ( v ) ) ;
340+ const removed = currentTriggersValue . findLast ( v => ! value . includes ( v ) ) ;
341+ handleBulkTriggers ( { added, removed} ) ;
342+ } }
166343 />
167344 </ TableCellsRemainingContent >
168345 </ TableHeader >
0 commit comments