@@ -18,7 +18,8 @@ import { configuration } from '../../system/configuration';
18
18
import { debug , log } from '../../system/decorators/log' ;
19
19
import { Logger } from '../../system/logger' ;
20
20
import { getLogScope } from '../../system/logger.scope' ;
21
- import { getSettledValue } from '../../system/promise' ;
21
+ import type { TimedResult } from '../../system/promise' ;
22
+ import { getSettledValue , timedWithSlowThreshold } from '../../system/promise' ;
22
23
import { openUrl } from '../../system/utils' ;
23
24
import type { UriTypes } from '../../uris/deepLinks/deepLink' ;
24
25
import { DeepLinkActionType , DeepLinkType } from '../../uris/deepLinks/deepLink' ;
@@ -136,7 +137,7 @@ export type FocusPullRequest = EnrichablePullRequest & ProviderActionablePullReq
136
137
export type FocusItem = FocusPullRequest & {
137
138
currentViewer : Account ;
138
139
codeSuggestionsCount : number ;
139
- codeSuggestions ?: Draft [ ] ;
140
+ codeSuggestions ?: TimedResult < Draft [ ] > ;
140
141
isNew : boolean ;
141
142
actionableCategory : FocusActionCategory ;
142
143
suggestedActions : FocusAction [ ] ;
@@ -157,8 +158,8 @@ type CachedFocusPromise<T> = {
157
158
const cacheExpiration = 1000 * 60 * 30 ; // 30 minutes
158
159
159
160
type PullRequestsWithSuggestionCounts = {
160
- prs : SearchedPullRequest [ ] | undefined ;
161
- suggestionCounts : CodeSuggestionCounts | undefined ;
161
+ prs : TimedResult < SearchedPullRequest [ ] | undefined > | undefined ;
162
+ suggestionCounts : TimedResult < CodeSuggestionCounts | undefined > | undefined ;
162
163
} ;
163
164
164
165
export type FocusRefreshEvent =
@@ -173,6 +174,17 @@ export type FocusRefreshEvent =
173
174
174
175
export const supportedFocusIntegrations = [ HostingIntegrationId . GitHub ] ;
175
176
177
+ export interface FocusItemsWithDurations {
178
+ items : FocusItem [ ] ;
179
+ timings ?: Timings ;
180
+ }
181
+
182
+ export interface Timings {
183
+ prs : number | undefined ;
184
+ codeSuggestionCounts : number | undefined ;
185
+ enrichedItems : number | undefined ;
186
+ }
187
+
176
188
export class FocusProvider implements Disposable {
177
189
private readonly _onDidChange = new EventEmitter < void > ( ) ;
178
190
get onDidChange ( ) {
@@ -227,7 +239,11 @@ export class FocusProvider implements Disposable {
227
239
const scope = getLogScope ( ) ;
228
240
229
241
const [ prsResult , subscriptionResult ] = await Promise . allSettled ( [
230
- this . container . integrations . getMyPullRequests ( [ HostingIntegrationId . GitHub ] , cancellation ) ,
242
+ withDurationAndSlowEventOnTimeout (
243
+ this . container . integrations . getMyPullRequests ( [ HostingIntegrationId . GitHub ] , cancellation ) ,
244
+ 'getMyPullRequests' ,
245
+ this . container ,
246
+ ) ,
231
247
this . container . subscription . getSubscription ( true ) ,
232
248
] ) ;
233
249
@@ -240,9 +256,13 @@ export class FocusProvider implements Disposable {
240
256
const subscription = getSettledValue ( subscriptionResult ) ;
241
257
242
258
let suggestionCounts ;
243
- if ( prs ?. length && subscription ?. account != null ) {
259
+ if ( prs ?. value ?. length && subscription ?. account != null ) {
244
260
try {
245
- suggestionCounts = await this . container . drafts . getCodeSuggestionCounts ( prs . map ( pr => pr . pullRequest ) ) ;
261
+ suggestionCounts = await withDurationAndSlowEventOnTimeout (
262
+ this . container . drafts . getCodeSuggestionCounts ( prs . value . map ( pr => pr . pullRequest ) ) ,
263
+ 'getCodeSuggestionCounts' ,
264
+ this . container ,
265
+ ) ;
246
266
} catch ( ex ) {
247
267
Logger . error ( ex , scope , 'Failed to get code suggestion counts' ) ;
248
268
}
@@ -251,28 +271,32 @@ export class FocusProvider implements Disposable {
251
271
return { prs : prs , suggestionCounts : suggestionCounts } ;
252
272
}
253
273
254
- private _enrichedItems : CachedFocusPromise < EnrichedItem [ ] > | undefined ;
274
+ private _enrichedItems : CachedFocusPromise < TimedResult < EnrichedItem [ ] > > | undefined ;
255
275
@debug < FocusProvider [ 'getEnrichedItems' ] > ( { args : { 0 : o => `force=${ o ?. force } ` } } )
256
276
private async getEnrichedItems ( options ?: { cancellation ?: CancellationToken ; force ?: boolean } ) {
257
277
if ( options ?. force || this . _enrichedItems == null || this . _enrichedItems . expiresAt < Date . now ( ) ) {
258
278
this . _enrichedItems = {
259
- promise : this . container . enrichments . get ( undefined , options ?. cancellation ) ,
279
+ promise : withDurationAndSlowEventOnTimeout (
280
+ this . container . enrichments . get ( undefined , options ?. cancellation ) ,
281
+ 'getEnrichedItems' ,
282
+ this . container ,
283
+ ) ,
260
284
expiresAt : Date . now ( ) + cacheExpiration ,
261
285
} ;
262
286
}
263
287
264
288
return this . _enrichedItems ?. promise ;
265
289
}
266
290
267
- private _codeSuggestions : Map < string , CachedFocusPromise < Draft [ ] > > | undefined ;
291
+ private _codeSuggestions : Map < string , CachedFocusPromise < TimedResult < Draft [ ] > > > | undefined ;
268
292
@debug < FocusProvider [ 'getCodeSuggestions' ] > ( {
269
293
args : { 0 : i => `${ i . id } (${ i . provider . name } ${ i . type } )` , 1 : o => `force=${ o ?. force } ` } ,
270
294
} )
271
295
private async getCodeSuggestions ( item : FocusItem , options ?: { force ?: boolean } ) {
272
296
if ( item . codeSuggestionsCount < 1 ) return undefined ;
273
297
274
298
if ( this . _codeSuggestions == null || options ?. force ) {
275
- this . _codeSuggestions = new Map < string , CachedFocusPromise < Draft [ ] > > ( ) ;
299
+ this . _codeSuggestions = new Map < string , CachedFocusPromise < TimedResult < Draft [ ] > > > ( ) ;
276
300
}
277
301
278
302
if (
@@ -281,9 +305,13 @@ export class FocusProvider implements Disposable {
281
305
this . _codeSuggestions . get ( item . uuid ) ! . expiresAt < Date . now ( )
282
306
) {
283
307
this . _codeSuggestions . set ( item . uuid , {
284
- promise : this . container . drafts . getCodeSuggestions ( item , HostingIntegrationId . GitHub , {
285
- includeArchived : false ,
286
- } ) ,
308
+ promise : withDurationAndSlowEventOnTimeout (
309
+ this . container . drafts . getCodeSuggestions ( item , HostingIntegrationId . GitHub , {
310
+ includeArchived : false ,
311
+ } ) ,
312
+ 'getCodeSuggestions' ,
313
+ this . container ,
314
+ ) ,
287
315
expiresAt : Date . now ( ) + cacheExpiration ,
288
316
} ) ;
289
317
}
@@ -372,7 +400,7 @@ export class FocusProvider implements Disposable {
372
400
373
401
@log < FocusProvider [ 'openCodeSuggestion' ] > ( { args : { 0 : i => `${ i . id } (${ i . provider . name } ${ i . type } )` } } )
374
402
openCodeSuggestion ( item : FocusItem , target : string ) {
375
- const draft = item . codeSuggestions ?. find ( d => d . id === target ) ;
403
+ const draft = item . codeSuggestions ?. value ?. find ( d => d . id === target ) ;
376
404
if ( draft == null ) return ;
377
405
this . _codeSuggestions ?. delete ( item . uuid ) ;
378
406
this . _prs = undefined ;
@@ -516,7 +544,10 @@ export class FocusProvider implements Disposable {
516
544
}
517
545
518
546
@log < FocusProvider [ 'getCategorizedItems' ] > ( { args : { 0 : o => `force=${ o ?. force } ` , 1 : false } } )
519
- async getCategorizedItems ( options ?: { force ?: boolean } , cancellation ?: CancellationToken ) : Promise < FocusItem [ ] > {
547
+ async getCategorizedItems (
548
+ options ?: { force ?: boolean } ,
549
+ cancellation ?: CancellationToken ,
550
+ ) : Promise < FocusItemsWithDurations > {
520
551
const scope = getLogScope ( ) ;
521
552
522
553
const ignoredRepositories = new Set (
@@ -560,14 +591,14 @@ export class FocusProvider implements Disposable {
560
591
throw failedError ;
561
592
}
562
593
594
+ const enrichedItems = getSettledValue ( enrichedItemsResult ) ;
563
595
const prsWithSuggestionCounts = getSettledValue ( prsWithCountsResult ) ;
564
596
if ( prsWithSuggestionCounts != null ) {
565
597
// Multiple enriched items can have the same entityId. Map by entityId to an array of enriched items.
566
598
const enrichedItemsByEntityId : { [ id : string ] : EnrichedItem [ ] } = { } ;
567
599
568
- const enrichedItems = getSettledValue ( enrichedItemsResult ) ;
569
- if ( enrichedItems != null ) {
570
- for ( const enrichedItem of enrichedItems ) {
600
+ if ( enrichedItems ?. value != null ) {
601
+ for ( const enrichedItem of enrichedItems . value ) {
571
602
if ( enrichedItem . entityId in enrichedItemsByEntityId ) {
572
603
enrichedItemsByEntityId [ enrichedItem . entityId ] . push ( enrichedItem ) ;
573
604
} else {
@@ -577,11 +608,19 @@ export class FocusProvider implements Disposable {
577
608
}
578
609
579
610
const { prs, suggestionCounts } = prsWithSuggestionCounts ;
580
- if ( prs == null ) return categorized ;
611
+ if ( prs ?. value == null )
612
+ return {
613
+ items : categorized ,
614
+ timings : {
615
+ prs : prs ?. duration ,
616
+ codeSuggestionCounts : suggestionCounts ?. duration ,
617
+ enrichedItems : enrichedItems ?. duration ,
618
+ } ,
619
+ } ;
581
620
582
621
const filteredPrs = ! ignoredRepositories . size
583
- ? prs
584
- : prs . filter (
622
+ ? prs . value
623
+ : prs . value . filter (
585
624
pr =>
586
625
! ignoredRepositories . has (
587
626
`${ pr . pullRequest . repository . owner . toLowerCase ( ) } /${ pr . pullRequest . repository . repo . toLowerCase ( ) } ` ,
@@ -640,7 +679,7 @@ export class FocusProvider implements Disposable {
640
679
// Map from shared category label to local actionable category, and get suggested actions
641
680
categorized = ( await Promise . all (
642
681
actionableItems . map ( async item => {
643
- const codeSuggestionsCount = suggestionCounts ?. [ item . uuid ] ?. count ?? 0 ;
682
+ const codeSuggestionsCount = suggestionCounts ?. value ?. [ item . uuid ] ?. count ?? 0 ;
644
683
let actionableCategory = sharedCategoryToFocusActionCategoryMap . get (
645
684
item . suggestedActionCategory ,
646
685
) ! ;
@@ -669,7 +708,14 @@ export class FocusProvider implements Disposable {
669
708
) ) satisfies FocusItem [ ] ;
670
709
}
671
710
672
- return categorized ;
711
+ return {
712
+ items : categorized ,
713
+ timings : {
714
+ prs : prsWithSuggestionCounts ?. prs ?. duration ,
715
+ codeSuggestionCounts : prsWithSuggestionCounts ?. suggestionCounts ?. duration ,
716
+ enrichedItems : enrichedItems ?. duration ,
717
+ } ,
718
+ } ;
673
719
} finally {
674
720
this . updateGroupedIds ( categorized ) ;
675
721
this . _onDidRefresh . fire ( failedError ? { error : failedError } : { items : categorized } ) ;
@@ -712,7 +758,10 @@ export class FocusProvider implements Disposable {
712
758
@log < FocusProvider [ 'ensureFocusItemCodeSuggestions' ] > ( {
713
759
args : { 0 : i => `${ i . id } (${ i . provider . name } ${ i . type } )` , 1 : o => `force=${ o ?. force } ` } ,
714
760
} )
715
- async ensureFocusItemCodeSuggestions ( item : FocusItem , options ?: { force ?: boolean } ) : Promise < Draft [ ] | undefined > {
761
+ async ensureFocusItemCodeSuggestions (
762
+ item : FocusItem ,
763
+ options ?: { force ?: boolean } ,
764
+ ) : Promise < TimedResult < Draft [ ] > | undefined > {
716
765
item . codeSuggestions ??= await this . getCodeSuggestions ( item , options ) ;
717
766
return item . codeSuggestions ;
718
767
}
@@ -836,3 +885,22 @@ function ensureRemoteUrl(url: string) {
836
885
export function getFocusItemIdHash ( item : FocusItem ) {
837
886
return md5 ( item . uuid ) ;
838
887
}
888
+
889
+ const slowEventTimeout = 1000 * 30 ; // 30 seconds
890
+
891
+ function withDurationAndSlowEventOnTimeout < T > (
892
+ promise : Promise < T > ,
893
+ name : 'getMyPullRequests' | 'getCodeSuggestionCounts' | 'getCodeSuggestions' | 'getEnrichedItems' ,
894
+ container : Container ,
895
+ ) : Promise < TimedResult < T > > {
896
+ return timedWithSlowThreshold ( promise , {
897
+ timeout : slowEventTimeout ,
898
+ onSlow : ( duration : number ) => {
899
+ container . telemetry . sendEvent ( 'launchpad/operation/slow' , {
900
+ timeout : slowEventTimeout ,
901
+ operation : name ,
902
+ duration : duration ,
903
+ } ) ;
904
+ } ,
905
+ } ) ;
906
+ }
0 commit comments