Skip to content

Commit dec82ba

Browse files
authored
Refresh the PRs tree when newly linked PRs are seen (#7101)
1 parent 20ee0f5 commit dec82ba

11 files changed

Lines changed: 129 additions & 79 deletions

src/common/timelineEvent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ export interface CrossReferencedEvent {
133133
extensionUrl: string;
134134
title: string;
135135
isIssue: boolean;
136+
owner: string;
137+
repo: string;
136138
};
137139
willCloseTarget: boolean;
138140
}

src/github/copilotPrWatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export class CopilotPRWatcher extends Disposable {
145145
const prs = await folderManager.getPullRequestsForCategory(githubRepository, await variableSubstitution(query, undefined, await folderManager.getPullRequestDefaults(), await this._getCurrentUser(folderManager)));
146146
for (const pr of prs?.items ?? []) {
147147
unseenKeys.delete(this._model.makeKey(pr.remote.owner, pr.remote.repositoryName, pr.number));
148-
const copilotEvents = await pr.getCopilotTimelineEvents();
148+
const copilotEvents = await pr.githubRepository.getCopilotTimelineEvents(pr);
149149
if (copilotEvents.length === 0) {
150150
continue;
151151
}

src/github/folderRepositoryManager.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { AuthProvider, GitHubServerType } from '../common/authentication';
1313
import { commands, contexts } from '../common/executeCommands';
1414
import { InMemFileChange, SlimFileChange } from '../common/file';
1515
import { findLocalRepoRemoteFromGitHubRef } from '../common/githubRef';
16-
import { Disposable } from '../common/lifecycle';
16+
import { Disposable, disposeAll } from '../common/lifecycle';
1717
import Logger from '../common/logger';
1818
import { Protocol, ProtocolType } from '../common/protocol';
1919
import { GitHubRemote, parseRemote, parseRepositoryRemotes, Remote } from '../common/remote';
@@ -213,6 +213,10 @@ export class FolderRepositoryManager extends Disposable {
213213
private _onDidChangeGithubRepositories = this._register(new vscode.EventEmitter<GitHubRepository[]>());
214214
readonly onDidChangeGithubRepositories: vscode.Event<GitHubRepository[]> = this._onDidChangeGithubRepositories.event;
215215

216+
private _onDidChangePullRequestsEvents: vscode.Disposable[] = [];
217+
private readonly _onDidChangeAnyPullRequests = this._register(new vscode.EventEmitter<void>());
218+
readonly onDidChangeAnyPullRequests: vscode.Event<void> = this._onDidChangeAnyPullRequests.event;
219+
216220
private _onDidDispose = this._register(new vscode.EventEmitter<void>());
217221
readonly onDidDispose: vscode.Event<void> = this._onDidDispose.event;
218222

@@ -240,6 +244,7 @@ export class FolderRepositoryManager extends Disposable {
240244
);
241245

242246
this._register(_credentialStore.onDidInitialize(() => this.updateRepositories()));
247+
this._register({ dispose: () => disposeAll(this._onDidChangePullRequestsEvents) });
243248

244249
this.cleanStoredRepoState();
245250
}
@@ -524,7 +529,11 @@ export class FolderRepositoryManager extends Disposable {
524529
}
525530
}
526531

532+
disposeAll(this._onDidChangePullRequestsEvents);
527533
this._githubRepositories = repositories;
534+
for (const repo of this._githubRepositories) {
535+
this._onDidChangePullRequestsEvents.push(repo.onDidChangePullRequests(() => this._onDidChangeAnyPullRequests.fire()));
536+
}
528537
oldRepositories.filter(old => this._githubRepositories.indexOf(old) < 0).forEach(repo => repo.dispose());
529538

530539
const repositoriesAdded =
@@ -1728,7 +1737,7 @@ export class FolderRepositoryManager extends Disposable {
17281737
*/
17291738
this.telemetry.sendTelemetryEvent('pr.merge.success');
17301739
this._onDidMergePullRequest.fire();
1731-
return { merged: true, message: '', timeline: await parseCombinedTimelineEvents(result.data?.mergePullRequest.pullRequest.timelineItems.nodes ?? [], await pullRequest.getCopilotTimelineEvents(), pullRequest.githubRepository) };
1740+
return { merged: true, message: '', timeline: await parseCombinedTimelineEvents(result.data?.mergePullRequest.pullRequest.timelineItems.nodes ?? [], await pullRequest.githubRepository.getCopilotTimelineEvents(pullRequest), pullRequest.githubRepository) };
17321741
})
17331742
.catch(e => {
17341743
/* __GDPR__

src/github/githubRepository.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import * as buffer from 'buffer';
77
import { ApolloQueryResult, DocumentNode, FetchResult, MutationOptions, NetworkStatus, QueryOptions } from 'apollo-boost';
88
import * as vscode from 'vscode';
99
import { AuthenticationError, AuthProvider, GitHubServerType, isSamlError } from '../common/authentication';
10+
import { COPILOT_ACCOUNTS } from '../common/comment';
1011
import { Disposable } from '../common/lifecycle';
1112
import Logger from '../common/logger';
1213
import { GitHubRemote, parseRemote } from '../common/remote';
1314
import { ITelemetry } from '../common/telemetry';
15+
import * as Common from '../common/timelineEvent';
16+
import { compareIgnoreCase, formatError } from '../common/utils';
1417
import { PRCommentControllerRegistry } from '../view/pullRequestCommentControllerRegistry';
1518
import { mergeQuerySchemaWithShared, OctokitCommon, Schema } from './common';
1619
import { CredentialStore, GitHub } from './credentials';
@@ -39,6 +42,7 @@ import {
3942
RepoProjectsResponse,
4043
RevertPullRequestResponse,
4144
SuggestedActorsResponse,
45+
TimelineEventsResponse,
4246
ViewerPermissionResponse,
4347
} from './graphql';
4448
import {
@@ -67,11 +71,14 @@ import {
6771
getOverrideBranch,
6872
isInCodespaces,
6973
parseAccount,
74+
parseCombinedTimelineEvents,
7075
parseGraphQLIssue,
7176
parseGraphQLPullRequest,
7277
parseGraphQLViewerPermission,
7378
parseMergeMethod,
7479
parseMilestone,
80+
parseSelectRestTimelineEvents,
81+
restPaginate,
7582
} from './utils';
7683

7784
export const PULL_REQUEST_PAGE_SIZE = 20;
@@ -152,6 +159,8 @@ export class GitHubRepository extends Disposable {
152159

153160
private _onDidAddPullRequest: vscode.EventEmitter<PullRequestModel> = this._register(new vscode.EventEmitter());
154161
public readonly onDidAddPullRequest: vscode.Event<PullRequestModel> = this._onDidAddPullRequest.event;
162+
private _onDidChangePullRequests: vscode.EventEmitter<void> = this._register(new vscode.EventEmitter());
163+
public readonly onDidChangePullRequests: vscode.Event<void> = this._onDidChangePullRequests.event;
155164

156165
public get hub(): GitHub {
157166
if (!this._hub) {
@@ -1443,6 +1452,81 @@ export class GitHubRepository extends Disposable {
14431452
return this._credentialStore.isCurrentUser(login);
14441453
}
14451454

1455+
1456+
/**
1457+
* TODO: @alexr00 we should delete this https://github.com/microsoft/vscode-pull-request-github/issues/6965
1458+
*/
1459+
async getCopilotTimelineEvents(issueModel: IssueModel): Promise<Common.TimelineEvent[]> {
1460+
if (!COPILOT_ACCOUNTS[issueModel.author.login]) {
1461+
return [];
1462+
}
1463+
1464+
Logger.debug(`Fetch Copilot timeline events of issue #${issueModel.number} - enter`, GitHubRepository.ID);
1465+
1466+
const { octokit, remote } = await this.ensure();
1467+
try {
1468+
const timeline = await restPaginate<typeof octokit.api.issues.listEventsForTimeline, OctokitCommon.ListEventsForTimelineResponse>(octokit.api.issues.listEventsForTimeline, {
1469+
issue_number: issueModel.number,
1470+
owner: remote.owner,
1471+
repo: remote.repositoryName,
1472+
per_page: 100
1473+
});
1474+
1475+
return parseSelectRestTimelineEvents(issueModel, timeline);
1476+
} catch (e) {
1477+
Logger.error(`Error fetching Copilot timeline events of issue #${issueModel.number} - ${formatError(e)}`, GitHubRepository.ID);
1478+
return [];
1479+
}
1480+
}
1481+
1482+
async getIssueTimelineEvents(issueModel: IssueModel): Promise<Common.TimelineEvent[]> {
1483+
Logger.debug(`Fetch timeline events of issue #${issueModel.number} - enter`, GitHubRepository.ID);
1484+
const { query, remote, schema } = await this.ensure();
1485+
1486+
try {
1487+
const { data } = await query<TimelineEventsResponse>({
1488+
query: schema.IssueTimelineEvents,
1489+
variables: {
1490+
owner: remote.owner,
1491+
name: remote.repositoryName,
1492+
number: issueModel.number,
1493+
},
1494+
});
1495+
1496+
if (data.repository === null) {
1497+
Logger.error('Unexpected null repository when getting issue timeline events', GitHubRepository.ID);
1498+
return [];
1499+
}
1500+
const ret = data.repository.pullRequest.timelineItems.nodes;
1501+
const events = await parseCombinedTimelineEvents(ret, await this.getCopilotTimelineEvents(issueModel), this);
1502+
1503+
const crossRefs = new Map(events
1504+
.filter((event): event is Common.CrossReferencedEvent => {
1505+
if ((event.event === Common.EventType.CrossReferenced) && !event.source.isIssue) {
1506+
return (compareIgnoreCase(event.source.owner, issueModel.remote.owner) === 0 && compareIgnoreCase(event.source.repo, issueModel.remote.repositoryName) === 0);
1507+
}
1508+
return false;
1509+
1510+
}).map((event: Common.CrossReferencedEvent) => {
1511+
return [event.source.url, event];
1512+
}));
1513+
1514+
for (const model of this._pullRequestModels.values()) {
1515+
if (crossRefs.has(model.html_url)) {
1516+
crossRefs.delete(model.html_url);
1517+
}
1518+
}
1519+
if (crossRefs.size > 0) {
1520+
this._onDidChangePullRequests.fire();
1521+
}
1522+
1523+
return events;
1524+
} catch (e) {
1525+
console.log(e);
1526+
return [];
1527+
}
1528+
}
1529+
14461530
/**
14471531
* Get the status checks of the pull request, those for the last commit.
14481532
*

src/github/issueModel.ts

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7-
import { COPILOT_ACCOUNTS, IComment } from '../common/comment';
7+
import { IComment } from '../common/comment';
88
import Logger from '../common/logger';
99
import { Remote } from '../common/remote';
10-
import { ClosedEvent, EventType, TimelineEvent } from '../common/timelineEvent';
10+
import { ClosedEvent, EventType } from '../common/timelineEvent';
1111
import { formatError } from '../common/utils';
12-
import { OctokitCommon } from './common';
1312
import { GitHubRepository } from './githubRepository';
1413
import {
1514
AddIssueCommentResponse,
@@ -18,11 +17,10 @@ import {
1817
LatestCommit,
1918
LatestReviewThread,
2019
LatestUpdatesResponse,
21-
TimelineEventsResponse,
2220
UpdateIssueResponse,
2321
} from './graphql';
2422
import { GithubItemStateEnum, IAccount, IIssueEditData, IMilestone, IProject, IProjectItem, Issue } from './interface';
25-
import { convertRESTIssueToRawPullRequest, parseCombinedTimelineEvents, parseGraphQlIssueComment, parseSelectRestTimelineEvents, restPaginate } from './utils';
23+
import { convertRESTIssueToRawPullRequest, parseGraphQlIssueComment } from './utils';
2624

2725
export class IssueModel<TItem extends Issue = Issue> {
2826
static ID = 'IssueModel';
@@ -326,61 +324,6 @@ export class IssueModel<TItem extends Issue = Issue> {
326324
return this.item.projectItems;
327325
}
328326

329-
/**
330-
* TODO: @alexr00 we should delete this https://github.com/microsoft/vscode-pull-request-github/issues/6965
331-
*/
332-
async getCopilotTimelineEvents(): Promise<TimelineEvent[]> {
333-
if (!COPILOT_ACCOUNTS[this.author.login]) {
334-
return [];
335-
}
336-
337-
Logger.debug(`Fetch Copilot timeline events of issue #${this.number} - enter`, IssueModel.ID);
338-
339-
const { octokit, remote } = await this.githubRepository.ensure();
340-
try {
341-
const timeline = await restPaginate<typeof octokit.api.issues.listEventsForTimeline, OctokitCommon.ListEventsForTimelineResponse>(octokit.api.issues.listEventsForTimeline, {
342-
issue_number: this.number,
343-
owner: remote.owner,
344-
repo: remote.repositoryName,
345-
per_page: 100
346-
});
347-
348-
return parseSelectRestTimelineEvents(this, timeline);
349-
} catch (e) {
350-
Logger.error(`Error fetching Copilot timeline events of issue #${this.number} - ${formatError(e)}`, IssueModel.ID);
351-
return [];
352-
}
353-
}
354-
355-
async getIssueTimelineEvents(): Promise<TimelineEvent[]> {
356-
Logger.debug(`Fetch timeline events of issue #${this.number} - enter`, IssueModel.ID);
357-
const githubRepository = this.githubRepository;
358-
const { query, remote, schema } = await githubRepository.ensure();
359-
360-
try {
361-
const { data } = await query<TimelineEventsResponse>({
362-
query: schema.IssueTimelineEvents,
363-
variables: {
364-
owner: remote.owner,
365-
name: remote.repositoryName,
366-
number: this.number,
367-
},
368-
});
369-
370-
if (data.repository === null) {
371-
Logger.error('Unexpected null repository when getting issue timeline events', IssueModel.ID);
372-
return [];
373-
}
374-
const ret = data.repository.pullRequest.timelineItems.nodes;
375-
const events = await parseCombinedTimelineEvents(ret, await this.getCopilotTimelineEvents(), githubRepository);
376-
377-
return events;
378-
} catch (e) {
379-
console.log(e);
380-
return [];
381-
}
382-
}
383-
384327
protected getUpdatesQuery(schema: any): any {
385328
return schema.LatestIssueUpdates;
386329
}

src/github/issueOverview.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
237237
issueModel.remote.repositoryName,
238238
issueModel.number,
239239
),
240-
issueModel.getIssueTimelineEvents(),
240+
issueModel.githubRepository.getIssueTimelineEvents(issueModel),
241241
this._folderRepositoryManager.getPullRequestRepositoryAccessAndMergeMethods(issueModel),
242242
issueModel.canEdit(),
243243
this._folderRepositoryManager.getAssignableUsers(),
@@ -440,7 +440,7 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
440440
if (allAssignees) {
441441
const newAssignees: IAccount[] = allAssignees.map(item => item.user);
442442
await this._item.replaceAssignees(newAssignees);
443-
const events = await this._item.getIssueTimelineEvents();
443+
const events = await this._item.githubRepository.getIssueTimelineEvents(this._item);
444444
const reply: ChangeAssigneesReply = {
445445
assignees: newAssignees,
446446
events
@@ -507,7 +507,7 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
507507
const newAssignees = (this._item.assignees ?? []).concat(currentUser);
508508
await this._item.replaceAssignees(newAssignees);
509509
}
510-
const events = await this._item.getIssueTimelineEvents();
510+
const events = await this._item.githubRepository.getIssueTimelineEvents(this._item);
511511
const reply: ChangeAssigneesReply = {
512512
assignees: this._item.assignees ?? [],
513513
events
@@ -525,7 +525,7 @@ export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> extends W
525525
const newAssignees = (this._item.assignees ?? []).concat(copilotUser);
526526
await this._item.replaceAssignees(newAssignees);
527527
}
528-
const events = await this._item.getIssueTimelineEvents();
528+
const events = await this._item.githubRepository.getIssueTimelineEvents(this._item);
529529
const reply: ChangeAssigneesReply = {
530530
assignees: this._item.assignees ?? [],
531531
events

src/github/pullRequestModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1195,7 +1195,7 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
11951195

11961196

11971197
const ret = data?.repository?.pullRequest.timelineItems.nodes ?? [];
1198-
const events = await parseCombinedTimelineEvents(ret, await this.getCopilotTimelineEvents(), this.githubRepository);
1198+
const events = await parseCombinedTimelineEvents(ret, await this.githubRepository.getCopilotTimelineEvents(this), this.githubRepository);
11991199

12001200
this.addReviewTimelineEventComments(events, reviewThreads);
12011201
insertNewCommitsSinceReview(events, latestReviewCommitInfo?.sha, currentUser, this.head);

src/github/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,9 @@ export async function parseCombinedTimelineEvents(
12161216
extensionUrl: extensionUrl.toString(),
12171217
number: crossRefEv.source.number,
12181218
title: crossRefEv.source.title,
1219-
isIssue
1219+
isIssue,
1220+
owner: crossRefEv.source.repository.owner.login,
1221+
repo: crossRefEv.source.repository.name,
12201222
},
12211223
willCloseTarget: crossRefEv.willCloseTarget
12221224
});

src/view/prsTreeDataProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ export class PullRequestsTreeDataProvider extends Disposable implements vscode.T
212212
}
213213

214214
private refreshRepo(manager: FolderRepositoryManager): void {
215-
if (this._children.length === 0) {
216-
return this.refresh();
215+
if ((this._children.length === 0) || (this._children[0] instanceof CategoryTreeNode && this._children[0].folderRepoManager === manager)) {
216+
return this.refresh(undefined, true);
217217
}
218218
if (this._children[0] instanceof WorkspaceFolderNode) {
219219
const children: WorkspaceFolderNode[] = this._children as WorkspaceFolderNode[];

src/view/prsTreeModel.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,18 @@ export class PrsTreeModel extends Disposable {
3838
private readonly _queriedPullRequests: Map<string, PRStatusChange> = new Map();
3939

4040
private _cachedPRs: Map<FolderRepositoryManager, Map<string | PRType.LocalPullRequest | PRType.All, ItemsResponseResult<PullRequestModel>>> = new Map();
41+
private readonly _repoEvents: Map<FolderRepositoryManager, vscode.Disposable[]> = new Map();
4142

4243
constructor(private _telemetry: ITelemetry, private readonly _reposManager: RepositoriesManager, private readonly _context: vscode.ExtensionContext) {
4344
super();
4445
const repoEvents = (manager: FolderRepositoryManager) => {
45-
this._register(manager.onDidChangeActivePullRequest(() => {
46+
if (this._repoEvents.has(manager)) {
47+
disposeAll(this._repoEvents.get(manager)!);
48+
} else {
49+
this._repoEvents.set(manager, []);
50+
}
51+
52+
this._repoEvents.get(manager)!.push(manager.onDidChangeActivePullRequest(() => {
4653
this.clearRepo(manager);
4754
if (this._activePRDisposables.has(manager)) {
4855
disposeAll(this._activePRDisposables.get(manager)!);
@@ -55,8 +62,11 @@ export class PrsTreeModel extends Disposable {
5562
})]);
5663
}
5764
}));
65+
this._repoEvents.get(manager)!.push(manager.onDidChangeAnyPullRequests(() => {
66+
this._onDidChangeData.fire(manager);
67+
}));
5868
};
59-
69+
this._register({ dispose: () => this._repoEvents.forEach((disposables) => disposeAll(disposables)) });
6070

6171
for (const manager of this._reposManager.folderManagers) {
6272
repoEvents(manager);

0 commit comments

Comments
 (0)