Skip to content

Commit e33163c

Browse files
authored
Improving performance of large repo PRs by dividing APIs into critical and non-critical flows (#192)
* improving perf of large repo PRs to be opened quickly and loading rest of data async * adding a loading indicator to the tree view * refactoring some code for better readability * adding refresh for both failure and success cases * adding loading indicator to commit section node as well * fixing code readability and testing * making commits appear faster * splitting critical and non-critical data into two functions
1 parent 09b7067 commit e33163c

File tree

2 files changed

+114
-39
lines changed

2 files changed

+114
-39
lines changed

src/views/nodes/commitSectionNode.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import { Commit, PullRequest } from '../../bitbucket/model';
33
import { AbstractBaseNode } from './abstractBaseNode';
44
import { CommitNode } from './commitNode';
55
import { IssueNode } from './issueNode';
6+
import { SimpleNode } from './simpleNode';
67

78
export class CommitSectionNode extends AbstractBaseNode {
89
private pr: PullRequest;
910
private commits: Commit[];
10-
constructor(pr: PullRequest, commits: Commit[]) {
11+
private loading: boolean;
12+
13+
constructor(pr: PullRequest, commits: Commit[], loading?: boolean) {
1114
super();
1215
this.pr = pr;
1316
this.commits = commits;
17+
this.loading = loading ?? false;
1418
}
1519

1620
getTreeItem(): vscode.TreeItem {
@@ -19,7 +23,10 @@ export class CommitSectionNode extends AbstractBaseNode {
1923
return item;
2024
}
2125

22-
async getChildren(element?: IssueNode): Promise<CommitNode[]> {
26+
async getChildren(element?: IssueNode): Promise<CommitNode[] | SimpleNode[]> {
27+
if (this.commits.length === 0 && this.loading) {
28+
return [new SimpleNode('Loading...')];
29+
}
2330
return this.commits.map((commit) => new CommitNode(this.pr, commit));
2431
}
2532
}

src/views/pullrequest/pullRequestNode.ts

Lines changed: 105 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { parseISO } from 'date-fns';
22
import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
33
import * as vscode from 'vscode';
44
import { clientForSite } from '../../bitbucket/bbUtils';
5-
import { Commit, PaginatedComments, PaginatedPullRequests, PullRequest } from '../../bitbucket/model';
5+
import {
6+
Commit,
7+
PaginatedComments,
8+
PaginatedPullRequests,
9+
PullRequest,
10+
Task,
11+
type FileDiff,
12+
} from '../../bitbucket/model';
613
import { Commands } from '../../commands';
714
import { Logger } from '../../logger';
815
import { Resources } from '../../resources';
@@ -17,7 +24,8 @@ export const PullRequestContextValue = 'pullrequest';
1724
export class PullRequestTitlesNode extends AbstractBaseNode {
1825
private treeItem: vscode.TreeItem;
1926
public prHref: string;
20-
private childrenPromises: Promise<AbstractBaseNode[]>;
27+
private loadedChildren: AbstractBaseNode[] = [];
28+
private isLoading = false;
2129

2230
constructor(
2331
private pr: PullRequest,
@@ -33,7 +41,7 @@ export class PullRequestTitlesNode extends AbstractBaseNode {
3341
//grabbing all the PR data. Due to rate limits imposed by BBServer admins, mass preloading of all nodes is not feasible without
3442
//caching.
3543
if (shouldPreload) {
36-
this.childrenPromises = this.fetchDataAndProcessChildren();
44+
this.fetchDataAndProcessChildren();
3745
}
3846
}
3947

@@ -76,51 +84,111 @@ export class PullRequestTitlesNode extends AbstractBaseNode {
7684
return this.pr;
7785
}
7886

79-
async fetchDataAndProcessChildren(): Promise<AbstractBaseNode[] | [SimpleNode]> {
80-
if (!this.pr) {
81-
return [];
87+
refresh(): void {
88+
vscode.commands.executeCommand(Commands.RefreshPullRequestExplorerNode, this.treeItem.resourceUri);
89+
}
90+
91+
async criticalData(
92+
criticalPromise: Promise<[FileDiff[], PaginatedComments]>,
93+
): Promise<[FileDiff[], PaginatedComments, AbstractBaseNode[]]> {
94+
let fileChangedNodes: AbstractBaseNode[] = [];
95+
let files: FileDiff[] = [];
96+
let comments: PaginatedComments = { data: [] };
97+
try {
98+
[files, comments] = await criticalPromise;
99+
fileChangedNodes = await createFileChangesNodes(this.pr, comments, files, [], []);
100+
// update loadedChildren with critical data without commits
101+
this.loadedChildren = [
102+
new DescriptionNode(this.pr, this),
103+
...(this.pr.site.details.isCloud ? [new CommitSectionNode(this.pr, [], true)] : []),
104+
...fileChangedNodes,
105+
];
106+
} catch (error) {
107+
Logger.debug('error fetching pull request details', error);
108+
this.loadedChildren = [new SimpleNode('⚠️ Error: fetching pull request details failed')];
109+
this.isLoading = false;
110+
} finally {
111+
this.refresh();
112+
return [files, comments, fileChangedNodes];
113+
}
114+
}
115+
116+
async nonCriticalData(
117+
nonCriticalPromise: Promise<[string[], Task[]]>,
118+
fileDiffs: FileDiff[],
119+
allComments: PaginatedComments,
120+
commits: Commit[],
121+
): Promise<void> {
122+
try {
123+
const [conflictedFiles, tasks] = await nonCriticalPromise;
124+
const [jiraIssueNodes, bbIssueNodes, fileNodes] = await Promise.all([
125+
this.createRelatedJiraIssueNode(commits, allComments),
126+
this.createRelatedBitbucketIssueNode(commits, allComments),
127+
createFileChangesNodes(this.pr, allComments, fileDiffs, conflictedFiles, tasks),
128+
]);
129+
// update loadedChildren with additional data
130+
this.loadedChildren = [
131+
new DescriptionNode(this.pr, this),
132+
...(this.pr.site.details.isCloud ? [new CommitSectionNode(this.pr, commits)] : []),
133+
...jiraIssueNodes,
134+
...bbIssueNodes,
135+
...fileNodes,
136+
];
137+
} catch (error) {
138+
Logger.debug('error fetching additional pull request details', error);
139+
// Keep existing nodes if additional data fetch fails
140+
}
141+
}
142+
143+
async fetchDataAndProcessChildren(): Promise<void> {
144+
// Return early if already loading or no PR
145+
if (this.isLoading || !this.pr) {
146+
return;
82147
}
83148

149+
this.isLoading = true;
150+
this.loadedChildren = [new DescriptionNode(this.pr, this), new SimpleNode('Loading...')];
151+
let fileDiffs: FileDiff[] = [];
152+
let allComments: PaginatedComments = { data: [] };
153+
let fileChangedNodes: AbstractBaseNode[] = [];
84154
const bbApi = await clientForSite(this.pr.site);
85-
const promises = Promise.all([
155+
const criticalPromise = Promise.all([
86156
bbApi.pullrequests.getChangedFiles(this.pr),
87-
bbApi.pullrequests.getConflictedFiles(this.pr),
88-
bbApi.pullrequests.getCommits(this.pr),
89157
bbApi.pullrequests.getComments(this.pr),
158+
]);
159+
const commitsPromise = bbApi.pullrequests.getCommits(this.pr);
160+
const nonCriticalPromise = Promise.all([
161+
bbApi.pullrequests.getConflictedFiles(this.pr),
90162
bbApi.pullrequests.getTasks(this.pr),
91163
]);
92-
93-
return promises.then(
94-
async (result) => {
95-
const [fileDiffs, conflictedFiles, commits, allComments, tasks] = result;
96-
97-
const children: AbstractBaseNode[] = [new DescriptionNode(this.pr, this)];
98-
99-
//Only enable commit-level review nodes for BB Cloud (at least for now)
100-
if (this.pr.site.details.isCloud) {
101-
children.push(new CommitSectionNode(this.pr, commits));
102-
}
103-
104-
children.push(...(await this.createRelatedJiraIssueNode(commits, allComments)));
105-
children.push(...(await this.createRelatedBitbucketIssueNode(commits, allComments)));
106-
children.push(
107-
...(await createFileChangesNodes(this.pr, allComments, fileDiffs, conflictedFiles, tasks)),
108-
);
109-
return children;
110-
},
111-
(reason) => {
112-
Logger.debug('error fetching pull request details', reason);
113-
return [new SimpleNode('⚠️ Error: fetching pull request details failed')];
114-
},
115-
);
164+
// Critical data - files, comments, and fileChangedNodes
165+
[fileDiffs, allComments, fileChangedNodes] = await this.criticalData(criticalPromise);
166+
// get commitsData
167+
const commits = await commitsPromise;
168+
// update loadedChildren with commits data
169+
this.loadedChildren = [
170+
new DescriptionNode(this.pr, this),
171+
...(this.pr.site.details.isCloud ? [new CommitSectionNode(this.pr, commits)] : []),
172+
...fileChangedNodes,
173+
];
174+
// refresh TreeView
175+
this.refresh();
176+
// Additional data - conflicts, commits, tasks
177+
await this.nonCriticalData(nonCriticalPromise, fileDiffs, allComments, commits);
178+
// update Loading to false
179+
this.isLoading = false;
180+
// refresh TreeView
181+
this.refresh();
116182
}
117183

118184
async getChildren(element?: AbstractBaseNode): Promise<AbstractBaseNode[]> {
119-
if (!element) {
120-
//If the promise is undefined, we didn't begin preloading in the constructor, so we need to make the full call here
121-
return await (this.childrenPromises ?? this.fetchDataAndProcessChildren());
185+
if (element) {
186+
return element.getChildren();
187+
}
188+
if (!this.loadedChildren.length && !this.isLoading) {
189+
this.fetchDataAndProcessChildren();
122190
}
123-
return element.getChildren();
191+
return this.loadedChildren;
124192
}
125193

126194
private async createRelatedJiraIssueNode(

0 commit comments

Comments
 (0)