44 *--------------------------------------------------------------------------------------------*/
55
66import * as vscode from 'vscode' ;
7- import { PRType } from './interface' ;
87import { PullRequestModel } from './pullRequestModel' ;
98import { PullRequestOverviewPanel } from './pullRequestOverview' ;
109import { RepositoriesManager } from './repositoriesManager' ;
1110import { debounce } from '../common/async' ;
1211import { COPILOT_ACCOUNTS } from '../common/comment' ;
1312import { COPILOT_LOGINS , copilotEventToStatus , CopilotPRStatus } from '../common/copilot' ;
1413import { Disposable } from '../common/lifecycle' ;
15- import Logger from '../common/logger' ;
1614import { PR_SETTINGS_NAMESPACE , QUERIES } from '../common/settingKeys' ;
15+ import { PrsTreeModel } from '../view/prsTreeModel' ;
1716
1817export function isCopilotQuery ( query : string ) : boolean {
1918 const lowerQuery = query . toLowerCase ( ) ;
2019 return COPILOT_LOGINS . some ( login => lowerQuery . includes ( `author:${ login . toLowerCase ( ) } ` ) ) ;
2120}
2221
22+ export function getCopilotQuery ( ) : string | undefined {
23+ const queries = vscode . workspace . getConfiguration ( PR_SETTINGS_NAMESPACE ) . get < { label : string ; query : string } [ ] > ( QUERIES , [ ] ) ;
24+ return queries . find ( query => isCopilotQuery ( query . query ) ) ?. query ;
25+ }
26+
2327export interface CodingAgentPRAndStatus {
2428 item : PullRequestModel ;
2529 status : CopilotPRStatus ;
@@ -31,15 +35,9 @@ export class CopilotStateModel extends Disposable {
3135 private readonly _states : Map < string , CodingAgentPRAndStatus > = new Map ( ) ;
3236 private readonly _showNotification : Set < string > = new Set ( ) ;
3337 private readonly _onDidChangeStates = this . _register ( new vscode . EventEmitter < void > ( ) ) ;
34- readonly onDidChangeStates = this . _onDidChangeStates . event ;
38+ readonly onDidChangeCopilotStates = this . _onDidChangeStates . event ;
3539 private readonly _onDidChangeNotifications = this . _register ( new vscode . EventEmitter < PullRequestModel [ ] > ( ) ) ;
36- readonly onDidChangeNotifications = this . _onDidChangeNotifications . event ;
37- private readonly _onRefresh = this . _register ( new vscode . EventEmitter < void > ( ) ) ;
38- readonly onRefresh = this . _onRefresh . event ;
39-
40- clear ( ) : void {
41- this . _onRefresh . fire ( ) ;
42- }
40+ readonly onDidChangeCopilotNotifications = this . _onDidChangeNotifications . event ;
4341
4442 makeKey ( owner : string , repo : string , prNumber ?: number ) : string {
4543 if ( prNumber === undefined ) {
@@ -60,17 +58,17 @@ export class CopilotStateModel extends Disposable {
6058 }
6159 }
6260
63- set ( statuses : { pullRequestModel : PullRequestModel , status : CopilotPRStatus } [ ] ) : void {
61+ set ( statuses : CodingAgentPRAndStatus [ ] ) : void {
6462 const changedModels : PullRequestModel [ ] = [ ] ;
6563 const changedKeys : string [ ] = [ ] ;
66- for ( const { pullRequestModel , status } of statuses ) {
67- const key = this . makeKey ( pullRequestModel . remote . owner , pullRequestModel . remote . repositoryName , pullRequestModel . number ) ;
64+ for ( const { item , status } of statuses ) {
65+ const key = this . makeKey ( item . remote . owner , item . remote . repositoryName , item . number ) ;
6866 const currentStatus = this . _states . get ( key ) ;
6967 if ( currentStatus ?. status === status ) {
7068 continue ;
7169 }
72- this . _states . set ( key , { item : pullRequestModel , status } ) ;
73- changedModels . push ( pullRequestModel ) ;
70+ this . _states . set ( key , { item, status } ) ;
71+ changedModels . push ( item ) ;
7472 changedKeys . push ( key ) ;
7573 }
7674 if ( changedModels . length > 0 ) {
@@ -188,9 +186,11 @@ export class CopilotStateModel extends Disposable {
188186}
189187
190188export class CopilotPRWatcher extends Disposable {
189+ private readonly _model : CopilotStateModel ;
191190
192- constructor ( private readonly _reposManager : RepositoriesManager , private readonly _model : CopilotStateModel ) {
191+ constructor ( private readonly _reposManager : RepositoriesManager , private readonly _prsTreeModel : PrsTreeModel ) {
193192 super ( ) ;
193+ this . _model = _prsTreeModel . copilotStateModel ;
194194 if ( this . _reposManager . folderManagers . length === 0 ) {
195195 const initDisposable = this . _reposManager . onDidChangeAnyGitHubRepository ( ( ) => {
196196 initDisposable . dispose ( ) ;
@@ -199,16 +199,18 @@ export class CopilotPRWatcher extends Disposable {
199199 } else {
200200 this . _initialize ( ) ;
201201 }
202- this . _register ( this . _model . onRefresh ( ( ) => this . _getStateChanges ( ) ) ) ;
203202 }
204203
205204 private _initialize ( ) {
206- this . _getStateChanges ( ) ;
205+ this . _prsTreeModel . refreshCopilotStateChanges ( true ) ;
207206 this . _pollForChanges ( ) ;
208- const updateFullState = debounce ( ( ) => this . _getStateChanges ( ) , 50 ) ;
207+ const updateFullState = debounce ( ( ) => this . _prsTreeModel . refreshCopilotStateChanges ( true ) , 50 ) ;
209208 this . _register ( this . _reposManager . onDidChangeAnyPullRequests ( e => {
210209 if ( e . some ( pr => COPILOT_ACCOUNTS [ pr . model . author . login ] ) ) {
211- if ( this . _model . isInitialized && e . some ( pr => this . _model . get ( pr . model . remote . owner , pr . model . remote . repositoryName , pr . model . number ) === CopilotPRStatus . None ) ) {
210+ if ( ! this . _model . isInitialized ) {
211+ return ;
212+ }
213+ if ( e . some ( pr => this . _model . get ( pr . model . remote . owner , pr . model . remote . repositoryName , pr . model . number ) === CopilotPRStatus . None ) ) {
212214 // A PR we don't know about was updated
213215 updateFullState ( ) ;
214216 } else {
@@ -238,11 +240,6 @@ export class CopilotPRWatcher extends Disposable {
238240 this . _register ( { dispose : ( ) => this . _pollTimeout && clearTimeout ( this . _pollTimeout ) } ) ;
239241 }
240242
241- private _queriesIncludeCopilot ( ) : string | undefined {
242- const queries = vscode . workspace . getConfiguration ( PR_SETTINGS_NAMESPACE ) . get < { label : string ; query : string } [ ] > ( QUERIES , [ ] ) ;
243- return queries . find ( query => isCopilotQuery ( query . query ) ) ?. query ;
244- }
245-
246243 private get _pollInterval ( ) : number {
247244 if ( vscode . window . state . active || vscode . window . state . focused ) {
248245 return 60 * 1000 * 2 ; // Poll every 2 minutes
@@ -258,7 +255,7 @@ export class CopilotPRWatcher extends Disposable {
258255 this . _pollTimeout = undefined ;
259256 }
260257 this . _lastPollTime = Date . now ( ) ;
261- const shouldContinue = await this . _getStateChanges ( ) ;
258+ const shouldContinue = await this . _prsTreeModel . refreshCopilotStateChanges ( true ) ;
262259
263260 if ( shouldContinue ) {
264261 this . _pollTimeout = setTimeout ( ( ) => {
@@ -268,7 +265,7 @@ export class CopilotPRWatcher extends Disposable {
268265 }
269266
270267 private async _updateSingleState ( pr : PullRequestModel ) : Promise < void > {
271- const changes : { pullRequestModel : PullRequestModel , status : CopilotPRStatus } [ ] = [ ] ;
268+ const changes : CodingAgentPRAndStatus [ ] = [ ] ;
272269
273270 const copilotEvents = await pr . getCopilotTimelineEvents ( pr , false , ! this . _model . isInitialized ) ;
274271 let latestEvent = copilotEventToStatus ( copilotEvents [ copilotEvents . length - 1 ] ) ;
@@ -280,74 +277,10 @@ export class CopilotPRWatcher extends Disposable {
280277 }
281278 const lastStatus = this . _model . get ( pr . remote . owner , pr . remote . repositoryName , pr . number ) ?? CopilotPRStatus . None ;
282279 if ( latestEvent !== lastStatus ) {
283- changes . push ( { pullRequestModel : pr , status : latestEvent } ) ;
280+ changes . push ( { item : pr , status : latestEvent } ) ;
284281 }
285282 this . _model . set ( changes ) ;
286283 }
287284
288- private _getStateChangesPromise : Promise < boolean > | undefined ;
289- private async _getStateChanges ( ) : Promise < boolean > {
290- // Return the existing in-flight promise if one exists
291- if ( this . _getStateChangesPromise ) {
292- return this . _getStateChangesPromise ;
293- }
294-
295- // Create and store the in-flight promise, and ensure it's cleared when done
296- this . _getStateChangesPromise = ( async ( ) => {
297- try {
298- const query = this . _queriesIncludeCopilot ( ) ;
299- if ( ! query ) {
300- return false ;
301- }
302- const unseenKeys : Set < string > = new Set ( this . _model . keys ( ) ) ;
303- let initialized = 0 ;
304-
305- const changes : { pullRequestModel : PullRequestModel , status : CopilotPRStatus } [ ] = [ ] ;
306- for ( const folderManager of this . _reposManager . folderManagers ) {
307- initialized ++ ;
308- const items : PullRequestModel [ ] = [ ] ;
309- let hasMore = true ;
310- do {
311- const prs = await folderManager . getPullRequests ( PRType . Query , { fetchOnePagePerRepo : true , fetchNextPage : ! this . _model . isInitialized } , query ) ;
312- items . push ( ...prs . items ) ;
313- hasMore = prs . hasMorePages ;
314- } while ( hasMore ) ;
315-
316- for ( const pr of items ) {
317- unseenKeys . delete ( this . _model . makeKey ( pr . remote . owner , pr . remote . repositoryName , pr . number ) ) ;
318- const copilotEvents = await pr . getCopilotTimelineEvents ( pr , false , ! this . _model . isInitialized ) ;
319- let latestEvent = copilotEventToStatus ( copilotEvents [ copilotEvents . length - 1 ] ) ;
320- if ( latestEvent === CopilotPRStatus . None ) {
321- if ( ! COPILOT_ACCOUNTS [ pr . author . login ] ) {
322- continue ;
323- }
324- latestEvent = CopilotPRStatus . Started ;
325- }
326- const lastStatus = this . _model . get ( pr . remote . owner , pr . remote . repositoryName , pr . number ) ?? CopilotPRStatus . None ;
327- if ( latestEvent !== lastStatus ) {
328- changes . push ( { pullRequestModel : pr , status : latestEvent } ) ;
329- }
330- }
331- }
332- for ( const key of unseenKeys ) {
333- this . _model . deleteKey ( key ) ;
334- }
335- this . _model . set ( changes ) ;
336- if ( ! this . _model . isInitialized ) {
337- if ( ( initialized === this . _reposManager . folderManagers . length ) && ( this . _reposManager . folderManagers . length > 0 ) ) {
338- Logger . debug ( `Copilot PR state initialized with ${ this . _model . keys ( ) . length } PRs` , CopilotStateModel . ID ) ;
339- this . _model . setInitialized ( ) ;
340- }
341- return true ;
342- } else {
343- return true ;
344- }
345- } finally {
346- // Ensure the stored promise is cleared so subsequent calls start a new run
347- this . _getStateChangesPromise = undefined ;
348- }
349- } ) ( ) ;
350285
351- return this . _getStateChangesPromise ;
352- }
353286}
0 commit comments