Skip to content

Commit 215e0b6

Browse files
authored
Add a missing prs list refresh (#7084)
* Add a missing prs list refresh plus a little bit of refactoring * Fix tests * re-remove status bar * remove rest of status bar item
1 parent 940bcbb commit 215e0b6

13 files changed

Lines changed: 88 additions & 75 deletions

src/extension.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { TemporaryState } from './common/temporaryState';
2121
import { Schemes } from './common/uri';
2222
import { EXTENSION_ID, FOCUS_REVIEW_MODE } from './constants';
2323
import { createExperimentationService, ExperimentationTelemetry } from './experimentationService';
24-
import { CopilotStateModel } from './github/copilotPrWatcher';
2524
import { CopilotRemoteAgentManager } from './github/copilotRemoteAgent';
2625
import { CredentialStore } from './github/credentials';
2726
import { FolderRepositoryManager } from './github/folderRepositoryManager';
@@ -65,7 +64,7 @@ async function init(
6564
showPRController: ShowPullRequest,
6665
reposManager: RepositoriesManager,
6766
createPrHelper: CreatePullRequestHelper,
68-
copilotStateModel: CopilotStateModel
67+
copilotRemoteAgentManager: CopilotRemoteAgentManager,
6968
): Promise<void> {
7069
context.subscriptions.push(Logger);
7170
Logger.appendLine('Git repository found, initializing review manager and pr tree view.', ACTIVATION);
@@ -165,7 +164,7 @@ async function init(
165164
context.subscriptions.push(treeDecorationProviders);
166165
treeDecorationProviders.registerProviders([new FileTypeDecorationProvider(), new CommentDecorationProvider(reposManager)]);
167166

168-
const reviewsManager = new ReviewsManager(context, reposManager, reviewManagers, tree, changesTree, telemetry, credentialStore, git, copilotStateModel);
167+
const reviewsManager = new ReviewsManager(context, reposManager, reviewManagers, tree, changesTree, telemetry, credentialStore, git, copilotRemoteAgentManager);
169168
context.subscriptions.push(reviewsManager);
170169

171170
git.onDidChangeState(() => {
@@ -217,9 +216,6 @@ async function init(
217216

218217
context.subscriptions.push(new PRNotificationDecorationProvider(tree.notificationProvider));
219218

220-
const copilotRemoteAgentManager = new CopilotRemoteAgentManager(credentialStore, reposManager, copilotStateModel);
221-
context.subscriptions.push(copilotRemoteAgentManager);
222-
223219
registerCommands(context, reposManager, reviewsManager, telemetry, tree, copilotRemoteAgentManager);
224220

225221
const layout = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<string>(FILE_LIST_LAYOUT);
@@ -409,8 +405,10 @@ async function deferredActivate(context: vscode.ExtensionContext, apiImpl: GitAp
409405
const reposManager = new RepositoriesManager(credentialStore, telemetry);
410406
context.subscriptions.push(reposManager);
411407

412-
const copilotStateModel = new CopilotStateModel();
413-
const prTree = new PullRequestsTreeDataProvider(telemetry, context, reposManager, copilotStateModel);
408+
const copilotRemoteAgentManager = new CopilotRemoteAgentManager(credentialStore, reposManager);
409+
context.subscriptions.push(copilotRemoteAgentManager);
410+
411+
const prTree = new PullRequestsTreeDataProvider(telemetry, context, reposManager, copilotRemoteAgentManager);
414412
context.subscriptions.push(prTree);
415413
context.subscriptions.push(credentialStore.onDidGetSession(() => prTree.refresh(undefined, true)));
416414
Logger.appendLine('Looking for git repository', ACTIVATION);
@@ -433,7 +431,7 @@ async function deferredActivate(context: vscode.ExtensionContext, apiImpl: GitAp
433431
readOnlyMessage.isTrusted = { enabledCommands: ['pr.checkoutFromReadonlyFile'] };
434432
context.subscriptions.push(vscode.workspace.registerFileSystemProvider(Schemes.Pr, inMemPRFileSystemProvider, { isReadonly: readOnlyMessage }));
435433

436-
await init(context, apiImpl, credentialStore, repositories, prTree, liveshareApiPromise, showPRController, reposManager, createPrHelper, copilotStateModel);
434+
await init(context, apiImpl, credentialStore, repositories, prTree, liveshareApiPromise, showPRController, reposManager, createPrHelper, copilotRemoteAgentManager);
437435
}
438436

439437
export async function deactivate() {

src/github/copilotApi.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import fetch from 'cross-fetch';
77
import JSZip from 'jszip';
88
import { AuthProvider } from '../common/authentication';
9+
import { Remote } from '../common/remote';
910
import { OctokitCommon } from './common';
1011
import { CredentialStore } from './credentials';
1112
import { LoggingOctokit } from './loggingOctokit';
12-
import { PullRequestModel } from './pullRequestModel';
1313
import { hasEnterpriseUri } from './utils';
1414

1515
export interface RemoteAgentJobPayload {
@@ -78,11 +78,11 @@ export class CopilotApi {
7878
}
7979
}
8080

81-
public async getWorkflowRunsFromAction(pullRequest: PullRequestModel): Promise<OctokitCommon.ListWorkflowRunsForRepo> {
81+
public async getWorkflowRunsFromAction(remote: Remote): Promise<OctokitCommon.ListWorkflowRunsForRepo> {
8282
const runs = await this.octokit.api.actions.listWorkflowRunsForRepo(
8383
{
84-
owner: pullRequest.githubRepository.remote.owner,
85-
repo: pullRequest.githubRepository.remote.repositoryName,
84+
owner: remote.owner,
85+
repo: remote.repositoryName,
8686
event: 'dynamic'
8787
}
8888
);
@@ -115,10 +115,10 @@ export class CopilotApi {
115115
return copilotSteps;
116116
}
117117

118-
public async getAllSessions(pullRequest: PullRequestModel | undefined): Promise<SessionInfo[]> {
118+
public async getAllSessions(pullRequestId: number | undefined): Promise<SessionInfo[]> {
119119
const response = await fetch(
120-
pullRequest
121-
? `https://api.githubcopilot.com/agents/sessions/resource/pull/${pullRequest.id}`
120+
pullRequestId
121+
? `https://api.githubcopilot.com/agents/sessions/resource/pull/${pullRequestId}`
122122
: 'https://api.githubcopilot.com/agents/sessions',
123123
{
124124
headers: {

src/github/copilotRemoteAgent.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import vscode from 'vscode';
77
import { Repository } from '../api/api';
88
import { AuthProvider } from '../common/authentication';
99
import { Disposable } from '../common/lifecycle';
10+
import { Remote } from '../common/remote';
1011
import { CODING_AGENT, CODING_AGENT_AUTO_COMMIT_AND_PUSH, CODING_AGENT_ENABLED } from '../common/settingKeys';
1112
import { toOpenPullRequestWebviewUri } from '../common/uri';
1213
import { CopilotApi, RemoteAgentJobPayload } from './copilotApi';
1314
import { CopilotPRWatcher, CopilotStateModel } from './copilotPrWatcher';
1415
import { CredentialStore } from './credentials';
15-
import { PullRequestModel } from './pullRequestModel';
1616
import { RepositoriesManager } from './repositoriesManager';
1717

1818
type RemoteAgentSuccessResult = { link: string; state: 'success'; number: number; webviewUri: vscode.Uri; llmDetails: string };
@@ -25,25 +25,29 @@ export interface IAPISessionLogs {
2525
}
2626

2727
export class CopilotRemoteAgentManager extends Disposable {
28-
private readonly _onDidChangeEnabled = new vscode.EventEmitter<boolean>();
29-
public readonly onDidChangeEnabled: vscode.Event<boolean> = this._onDidChangeEnabled.event;
3028
public static ID = 'CopilotRemoteAgentManager';
3129
private readonly workflowRunUrlBase = 'https://github.com/microsoft/vscode/actions/runs/';
3230

33-
constructor(private credentialStore: CredentialStore, public repositoriesManager: RepositoriesManager, stateModel: CopilotStateModel) {
31+
private readonly _stateModel: CopilotStateModel;
32+
private readonly _onDidChangeStates = this._register(new vscode.EventEmitter<void>());
33+
readonly onDidChangeStates = this._onDidChangeStates.event;
34+
private readonly _onDidChangeNotifications = this._register(new vscode.EventEmitter<void>());
35+
readonly onDidChangeNotifications = this._onDidChangeNotifications.event;
36+
private readonly _onDidCreatePullRequest = this._register(new vscode.EventEmitter<number>());
37+
readonly onDidCreatePullRequest = this._onDidCreatePullRequest.event;
38+
39+
constructor(private credentialStore: CredentialStore, public repositoriesManager: RepositoriesManager) {
3440
super();
3541
this._register(this.credentialStore.onDidChangeSessions((e: vscode.AuthenticationSessionsChangeEvent) => {
3642
if (e.provider.id === 'github') {
3743
this._copilotApiPromise = undefined; // Invalidate cached session
3844
}
3945
}));
40-
this._register(vscode.workspace.onDidChangeConfiguration(e => {
41-
if (e.affectsConfiguration(`${CODING_AGENT}.${CODING_AGENT_ENABLED}`)) {
42-
this._onDidChangeEnabled.fire(this.enabled());
43-
}
44-
}));
45-
this._register(new CopilotPRWatcher(this.repositoriesManager, stateModel));
4646

47+
this._stateModel = new CopilotStateModel();
48+
this._register(new CopilotPRWatcher(this.repositoriesManager, this._stateModel));
49+
this._register(this._stateModel.onDidChangeStates(() => this._onDidChangeStates.fire()));
50+
this._register(this._stateModel.onDidChangeNotifications(() => this._onDidChangeNotifications.fire()));
4751
}
4852

4953
private _copilotApiPromise: Promise<CopilotApi | undefined> | undefined;
@@ -258,6 +262,7 @@ export class CopilotRemoteAgentManager extends Disposable {
258262
const { pull_request } = await capiClient.postRemoteAgentJob(owner, repo, payload);
259263
const webviewUri = await toOpenPullRequestWebviewUri({ owner, repo, pullRequestNumber: pull_request.number });
260264
const prLlmString = `The remote agent has begun work. The user can track progress by visiting ${pull_request.html_url} or from the PR extension.`;
265+
this._onDidCreatePullRequest.fire(pull_request.number);
261266
return {
262267
state: 'success',
263268
number: pull_request.number,
@@ -267,15 +272,15 @@ export class CopilotRemoteAgentManager extends Disposable {
267272
};
268273
}
269274

270-
async getSessionLogsFromAction(pullRequest: PullRequestModel) {
275+
async getSessionLogsFromAction(remote: Remote, pullRequestId: number) {
271276
const capi = await this.copilotApi;
272277
if (!capi) {
273278
return [];
274279
}
275-
const runs = await capi.getWorkflowRunsFromAction(pullRequest);
280+
const runs = await capi.getWorkflowRunsFromAction(remote);
276281
const padawanRuns = runs
277282
.filter(run => run.path && run.path.startsWith('dynamic/copilot-swe-agent'))
278-
.filter(run => run.pull_requests?.some(pr => pr.id === pullRequest.id));
283+
.filter(run => run.pull_requests?.some(pr => pr.id === pullRequestId));
279284

280285
const lastRun = this.getLatestRun(padawanRuns);
281286

@@ -286,13 +291,13 @@ export class CopilotRemoteAgentManager extends Disposable {
286291
return await capi.getLogsFromZipUrl(lastRun.logs_url);
287292
}
288293

289-
async getSessionLogsFromPullRequest(pullRequest: PullRequestModel): Promise<IAPISessionLogs> {
294+
async getSessionLogsFromPullRequest(pullRequestId: number): Promise<IAPISessionLogs> {
290295
const capi = await this.copilotApi;
291296
if (!capi) {
292297
return { sessionId: '', logs: '' };
293298
}
294299

295-
const sessions = await capi.getAllSessions(pullRequest);
300+
const sessions = await capi.getAllSessions(pullRequestId);
296301
const completedSessions = sessions.filter(s => s.state === 'completed');
297302
if (completedSessions.length === 0) {
298303
return { sessionId: '', logs: '' };
@@ -302,13 +307,13 @@ export class CopilotRemoteAgentManager extends Disposable {
302307
return { sessionId: mostRecentSession.id, logs };
303308
}
304309

305-
async getSessionUrlFromPullRequest(pullRequest: PullRequestModel): Promise<string | undefined> {
310+
async getSessionUrlFromPullRequest(pullRequestId: number | undefined): Promise<string | undefined> {
306311
const capi = await this.copilotApi;
307312
if (!capi) {
308313
return undefined;
309314
}
310315

311-
const sessions = await capi.getAllSessions(pullRequest);
316+
const sessions = await capi.getAllSessions(pullRequestId);
312317
const completedSessions = sessions.filter(s => s.state === 'completed');
313318
if (completedSessions.length === 0) {
314319
return undefined;
@@ -336,4 +341,12 @@ export class CopilotRemoteAgentManager extends Disposable {
336341
return dateB - dateA;
337342
})[0];
338343
}
344+
345+
clearNotifications() {
346+
this._stateModel.clearNotifications();
347+
}
348+
349+
get notifications(): ReadonlySet<string> {
350+
return this._stateModel.notifications;
351+
}
339352
}

src/github/pullRequestOverview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
474474
} else {
475475
const copilotApi = await getCopilotApi(this._folderRepositoryManager.credentialStore, this._item.remote.authProviderId);
476476
if (copilotApi) {
477-
const session = (await copilotApi.getAllSessions(this._item))[0];
477+
const session = (await copilotApi.getAllSessions(this._item.id))[0];
478478
if (session.state !== 'completed') {
479479
result = await this._item.githubRepository.cancelWorkflow(session.workflow_run_id);
480480
}

src/lm/tools/activePullRequestTool.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class ActivePullRequestTool implements vscode.LanguageModelTool<FetchIssu
7373
model: vscode.LanguageModelChat,
7474
cancellationToken: vscode.CancellationToken
7575
) {
76-
const logs = await this.copilotRemoteAgentManager.getSessionLogsFromAction(pullRequest);
76+
const logs = await this.copilotRemoteAgentManager.getSessionLogsFromAction(pullRequest.remote, pullRequest.id);
7777
// Summarize the Copilot agent's thinking process using the model
7878
const messages = [
7979
vscode.LanguageModelChatMessage.Assistant('You are an expert summarizer. The following logs show the thinking process and performed actions of a GitHub Copilot agent that was in charge of working on the current pull request. Read the logs and always maintain the thinking process. You can remove information on the tool call results that you think are not necessary for building context.'),
@@ -98,7 +98,7 @@ export class ActivePullRequestTool implements vscode.LanguageModelTool<FetchIssu
9898
): Promise<string | string[]> {
9999
let copilotSteps: string | string[] = [];
100100
try {
101-
const logsResponseText = await this.copilotRemoteAgentManager.getSessionLogsFromPullRequest(pullRequest);
101+
const logsResponseText = await this.copilotRemoteAgentManager.getSessionLogsFromPullRequest(pullRequest.id);
102102
copilotSteps = this.parseCopilotEventStream(logsResponseText.logs);
103103
if (copilotSteps.length === 0) {
104104
throw new Error('Empty Copilot agent logs received');

src/test/view/prsTree.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { DataUri } from '../../common/uri';
3131
import { IAccount, ITeam } from '../../github/interface';
3232
import { asPromise } from '../../common/utils';
3333
import { CreatePullRequestHelper } from '../../view/createPullRequestHelper';
34-
import { CopilotStateModel } from '../../github/copilotPrWatcher';
34+
import { CopilotRemoteAgentManager } from '../../github/copilotRemoteAgent';
3535

3636
describe('GitHub Pull Requests view', function () {
3737
let sinon: SinonSandbox;
@@ -41,7 +41,7 @@ describe('GitHub Pull Requests view', function () {
4141
let credentialStore: CredentialStore;
4242
let reposManager: RepositoriesManager;
4343
let createPrHelper: CreatePullRequestHelper;
44-
let copilotStateModel: CopilotStateModel;
44+
let copilotManager: CopilotRemoteAgentManager;
4545

4646
beforeEach(function () {
4747
sinon = createSandbox();
@@ -54,9 +54,9 @@ describe('GitHub Pull Requests view', function () {
5454
credentialStore,
5555
telemetry,
5656
);
57-
copilotStateModel = new CopilotStateModel();
58-
provider = new PullRequestsTreeDataProvider(telemetry, context, reposManager, copilotStateModel);
5957
credentialStore = new CredentialStore(telemetry, context);
58+
copilotManager = new CopilotRemoteAgentManager(credentialStore, reposManager);
59+
provider = new PullRequestsTreeDataProvider(telemetry, context, reposManager, copilotManager);
6060
createPrHelper = new CreatePullRequestHelper();
6161

6262
// For tree view unit tests, we don't test the authentication flow, so `showSignInNotification` returns

src/test/view/reviewCommentController.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ import { WebviewViewCoordinator } from '../../view/webviewViewCoordinator';
3737
import { GitHubServerType } from '../../common/authentication';
3838
import { CreatePullRequestHelper } from '../../view/createPullRequestHelper';
3939
import { mergeQuerySchemaWithShared } from '../../github/common';
40-
import { GitHubRef } from '../../common/githubRef';
4140
import { AccountType } from '../../github/interface';
42-
import { CopilotStateModel } from '../../github/copilotPrWatcher';
41+
import { CopilotRemoteAgentManager } from '../../github/copilotRemoteAgent';
4342
const schema = mergeQuerySchemaWithShared(require('../../github/queries.gql'), require('../../github/queriesShared.gql')) as any;
4443

4544
const protocol = new Protocol('https://github.com/github/test.git');
@@ -63,7 +62,7 @@ describe('ReviewCommentController', function () {
6362
let reviewManager: ReviewManager;
6463
let reposManager: RepositoriesManager;
6564
let gitApiImpl: GitApiImpl;
66-
let copilotStateModel: CopilotStateModel;
65+
let copilotManager: CopilotRemoteAgentManager;
6766

6867
beforeEach(async function () {
6968
sinon = createSandbox();
@@ -76,8 +75,8 @@ describe('ReviewCommentController', function () {
7675
repository = new MockRepository();
7776
repository.addRemote('origin', 'git@github.com:aaa/bbb');
7877
reposManager = new RepositoriesManager(credentialStore, telemetry);
79-
copilotStateModel = new CopilotStateModel();
80-
provider = new PullRequestsTreeDataProvider(telemetry, context, reposManager, copilotStateModel);
78+
copilotManager = new CopilotRemoteAgentManager(credentialStore, reposManager);
79+
provider = new PullRequestsTreeDataProvider(telemetry, context, reposManager, copilotManager);
8180
const activePrViewCoordinator = new WebviewViewCoordinator(context);
8281
const createPrHelper = new CreatePullRequestHelper();
8382
Resource.initialize(context);

src/view/prStatusDecorationProvider.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as vscode from 'vscode';
77
import { Disposable } from '../common/lifecycle';
88
import { COPILOT_QUERY, createPRNodeUri, fromPRNodeUri, Schemes } from '../common/uri';
9-
import { CopilotStateModel } from '../github/copilotPrWatcher';
9+
import { CopilotRemoteAgentManager } from '../github/copilotRemoteAgent';
1010
import { getStatusDecoration } from '../github/markdownUtils';
1111
import { PrsTreeModel } from './prsTreeModel';
1212

@@ -17,7 +17,7 @@ export class PRStatusDecorationProvider extends Disposable implements vscode.Fil
1717
>();
1818
onDidChangeFileDecorations: vscode.Event<vscode.Uri | vscode.Uri[]> = this._onDidChangeFileDecorations.event;
1919

20-
constructor(private readonly _prsTreeModel: PrsTreeModel, private readonly _copilotStateModel: CopilotStateModel) {
20+
constructor(private readonly _prsTreeModel: PrsTreeModel, private readonly _copilotManager: CopilotRemoteAgentManager) {
2121
super();
2222
this._register(vscode.window.registerFileDecorationProvider(this));
2323
this._register(
@@ -26,7 +26,7 @@ export class PRStatusDecorationProvider extends Disposable implements vscode.Fil
2626
})
2727
);
2828

29-
this._register(this._copilotStateModel.onDidChangeNotifications(() => {
29+
this._register(this._copilotManager.onDidChangeNotifications(() => {
3030
this._onDidChangeFileDecorations.fire(COPILOT_QUERY);
3131
}));
3232
}
@@ -56,9 +56,9 @@ export class PRStatusDecorationProvider extends Disposable implements vscode.Fil
5656

5757
private _queryDecoration(uri: vscode.Uri): vscode.ProviderResult<vscode.FileDecoration> {
5858
if (uri.path === 'copilot') {
59-
if (this._copilotStateModel.notifications.size > 0) {
59+
if (this._copilotManager.notifications.size > 0) {
6060
return {
61-
tooltip: vscode.l10n.t('Coding agent has made changes', this._copilotStateModel.notifications.size),
61+
tooltip: vscode.l10n.t('Coding agent has made changes', this._copilotManager.notifications.size),
6262
badge: new vscode.ThemeIcon('copilot') as any,
6363
color: new vscode.ThemeColor('pullRequests.notification'),
6464
};

0 commit comments

Comments
 (0)