@@ -18,7 +18,8 @@ import { configuration } from '../../system/configuration';
1818import { debug , log } from '../../system/decorators/log' ;
1919import { Logger } from '../../system/logger' ;
2020import { 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' ;
2223import { openUrl } from '../../system/utils' ;
2324import type { UriTypes } from '../../uris/deepLinks/deepLink' ;
2425import { DeepLinkActionType , DeepLinkType } from '../../uris/deepLinks/deepLink' ;
@@ -136,7 +137,7 @@ export type FocusPullRequest = EnrichablePullRequest & ProviderActionablePullReq
136137export type FocusItem = FocusPullRequest & {
137138 currentViewer : Account ;
138139 codeSuggestionsCount : number ;
139- codeSuggestions ?: Draft [ ] ;
140+ codeSuggestions ?: TimedResult < Draft [ ] > ;
140141 isNew : boolean ;
141142 actionableCategory : FocusActionCategory ;
142143 suggestedActions : FocusAction [ ] ;
@@ -157,8 +158,8 @@ type CachedFocusPromise<T> = {
157158const cacheExpiration = 1000 * 60 * 30 ; // 30 minutes
158159
159160type PullRequestsWithSuggestionCounts = {
160- prs : SearchedPullRequest [ ] | undefined ;
161- suggestionCounts : CodeSuggestionCounts | undefined ;
161+ prs : TimedResult < SearchedPullRequest [ ] | undefined > | undefined ;
162+ suggestionCounts : TimedResult < CodeSuggestionCounts | undefined > | undefined ;
162163} ;
163164
164165export type FocusRefreshEvent =
@@ -173,6 +174,17 @@ export type FocusRefreshEvent =
173174
174175export const supportedFocusIntegrations = [ HostingIntegrationId . GitHub ] ;
175176
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+
176188export class FocusProvider implements Disposable {
177189 private readonly _onDidChange = new EventEmitter < void > ( ) ;
178190 get onDidChange ( ) {
@@ -227,7 +239,11 @@ export class FocusProvider implements Disposable {
227239 const scope = getLogScope ( ) ;
228240
229241 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+ ) ,
231247 this . container . subscription . getSubscription ( true ) ,
232248 ] ) ;
233249
@@ -240,9 +256,13 @@ export class FocusProvider implements Disposable {
240256 const subscription = getSettledValue ( subscriptionResult ) ;
241257
242258 let suggestionCounts ;
243- if ( prs ?. length && subscription ?. account != null ) {
259+ if ( prs ?. value ?. length && subscription ?. account != null ) {
244260 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+ ) ;
246266 } catch ( ex ) {
247267 Logger . error ( ex , scope , 'Failed to get code suggestion counts' ) ;
248268 }
@@ -251,28 +271,32 @@ export class FocusProvider implements Disposable {
251271 return { prs : prs , suggestionCounts : suggestionCounts } ;
252272 }
253273
254- private _enrichedItems : CachedFocusPromise < EnrichedItem [ ] > | undefined ;
274+ private _enrichedItems : CachedFocusPromise < TimedResult < EnrichedItem [ ] > > | undefined ;
255275 @debug < FocusProvider [ 'getEnrichedItems' ] > ( { args : { 0 : o => `force=${ o ?. force } ` } } )
256276 private async getEnrichedItems ( options ?: { cancellation ?: CancellationToken ; force ?: boolean } ) {
257277 if ( options ?. force || this . _enrichedItems == null || this . _enrichedItems . expiresAt < Date . now ( ) ) {
258278 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+ ) ,
260284 expiresAt : Date . now ( ) + cacheExpiration ,
261285 } ;
262286 }
263287
264288 return this . _enrichedItems ?. promise ;
265289 }
266290
267- private _codeSuggestions : Map < string , CachedFocusPromise < Draft [ ] > > | undefined ;
291+ private _codeSuggestions : Map < string , CachedFocusPromise < TimedResult < Draft [ ] > > > | undefined ;
268292 @debug < FocusProvider [ 'getCodeSuggestions' ] > ( {
269293 args : { 0 : i => `${ i . id } (${ i . provider . name } ${ i . type } )` , 1 : o => `force=${ o ?. force } ` } ,
270294 } )
271295 private async getCodeSuggestions ( item : FocusItem , options ?: { force ?: boolean } ) {
272296 if ( item . codeSuggestionsCount < 1 ) return undefined ;
273297
274298 if ( this . _codeSuggestions == null || options ?. force ) {
275- this . _codeSuggestions = new Map < string , CachedFocusPromise < Draft [ ] > > ( ) ;
299+ this . _codeSuggestions = new Map < string , CachedFocusPromise < TimedResult < Draft [ ] > > > ( ) ;
276300 }
277301
278302 if (
@@ -281,9 +305,13 @@ export class FocusProvider implements Disposable {
281305 this . _codeSuggestions . get ( item . uuid ) ! . expiresAt < Date . now ( )
282306 ) {
283307 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+ ) ,
287315 expiresAt : Date . now ( ) + cacheExpiration ,
288316 } ) ;
289317 }
@@ -372,7 +400,7 @@ export class FocusProvider implements Disposable {
372400
373401 @log < FocusProvider [ 'openCodeSuggestion' ] > ( { args : { 0 : i => `${ i . id } (${ i . provider . name } ${ i . type } )` } } )
374402 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 ) ;
376404 if ( draft == null ) return ;
377405 this . _codeSuggestions ?. delete ( item . uuid ) ;
378406 this . _prs = undefined ;
@@ -516,7 +544,10 @@ export class FocusProvider implements Disposable {
516544 }
517545
518546 @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 > {
520551 const scope = getLogScope ( ) ;
521552
522553 const ignoredRepositories = new Set (
@@ -560,14 +591,14 @@ export class FocusProvider implements Disposable {
560591 throw failedError ;
561592 }
562593
594+ const enrichedItems = getSettledValue ( enrichedItemsResult ) ;
563595 const prsWithSuggestionCounts = getSettledValue ( prsWithCountsResult ) ;
564596 if ( prsWithSuggestionCounts != null ) {
565597 // Multiple enriched items can have the same entityId. Map by entityId to an array of enriched items.
566598 const enrichedItemsByEntityId : { [ id : string ] : EnrichedItem [ ] } = { } ;
567599
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 ) {
571602 if ( enrichedItem . entityId in enrichedItemsByEntityId ) {
572603 enrichedItemsByEntityId [ enrichedItem . entityId ] . push ( enrichedItem ) ;
573604 } else {
@@ -577,11 +608,19 @@ export class FocusProvider implements Disposable {
577608 }
578609
579610 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+ } ;
581620
582621 const filteredPrs = ! ignoredRepositories . size
583- ? prs
584- : prs . filter (
622+ ? prs . value
623+ : prs . value . filter (
585624 pr =>
586625 ! ignoredRepositories . has (
587626 `${ pr . pullRequest . repository . owner . toLowerCase ( ) } /${ pr . pullRequest . repository . repo . toLowerCase ( ) } ` ,
@@ -640,7 +679,7 @@ export class FocusProvider implements Disposable {
640679 // Map from shared category label to local actionable category, and get suggested actions
641680 categorized = ( await Promise . all (
642681 actionableItems . map ( async item => {
643- const codeSuggestionsCount = suggestionCounts ?. [ item . uuid ] ?. count ?? 0 ;
682+ const codeSuggestionsCount = suggestionCounts ?. value ?. [ item . uuid ] ?. count ?? 0 ;
644683 let actionableCategory = sharedCategoryToFocusActionCategoryMap . get (
645684 item . suggestedActionCategory ,
646685 ) ! ;
@@ -669,7 +708,14 @@ export class FocusProvider implements Disposable {
669708 ) ) satisfies FocusItem [ ] ;
670709 }
671710
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+ } ;
673719 } finally {
674720 this . updateGroupedIds ( categorized ) ;
675721 this . _onDidRefresh . fire ( failedError ? { error : failedError } : { items : categorized } ) ;
@@ -712,7 +758,10 @@ export class FocusProvider implements Disposable {
712758 @log < FocusProvider [ 'ensureFocusItemCodeSuggestions' ] > ( {
713759 args : { 0 : i => `${ i . id } (${ i . provider . name } ${ i . type } )` , 1 : o => `force=${ o ?. force } ` } ,
714760 } )
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 > {
716765 item . codeSuggestions ??= await this . getCodeSuggestions ( item , options ) ;
717766 return item . codeSuggestions ;
718767 }
@@ -836,3 +885,22 @@ function ensureRemoteUrl(url: string) {
836885export function getFocusItemIdHash ( item : FocusItem ) {
837886 return md5 ( item . uuid ) ;
838887}
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