Skip to content

Commit 4edd753

Browse files
committed
Improves contributors view performance w/ stats
- Defers stats loading to improve initial load performance - Adds loading indicator when stats are being computed in background
1 parent cafd29f commit 4edd753

File tree

1 file changed

+103
-72
lines changed

1 file changed

+103
-72
lines changed

src/views/nodes/contributorsNode.ts

Lines changed: 103 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ContributorNode } from './contributorNode';
1616
export class ContributorsNode extends CacheableChildrenViewNode<
1717
'contributors',
1818
ViewsWithContributorsNode,
19-
ContributorNode
19+
ContributorNode | MessageNode | ActionMessageNode
2020
> {
2121
constructor(
2222
uri: GitUri,
@@ -47,81 +47,16 @@ export class ContributorsNode extends CacheableChildrenViewNode<
4747

4848
async getChildren(): Promise<ViewNode[]> {
4949
if (this.children == null) {
50-
let rev = this.options?.ref;
51-
const all = rev == null && (this.options?.all ?? configuration.get('views.contributors.showAllBranches'));
52-
53-
const svc = this.view.container.git.getRepositoryService(this.uri.repoPath!);
54-
55-
// If there is no ref and we aren't getting all branches, get the upstream of the current branch if there is one
56-
if (rev == null && !all) {
57-
try {
58-
const branch = await svc.branches.getBranch();
59-
if (branch?.upstream?.name != null && !branch.upstream.missing) {
60-
rev = '@{u}';
61-
}
62-
} catch {}
63-
}
64-
6550
const stats = this.options?.stats ?? configuration.get('views.contributors.showStatistics');
51+
const children = await this.getContributors(stats, true);
6652

67-
let timeout: number;
68-
const overrideMaxWait = this.getState('overrideMaxWait');
69-
if (overrideMaxWait) {
70-
timeout = overrideMaxWait;
71-
this.deleteState('overrideMaxWait');
72-
} else {
73-
timeout = configuration.get('views.contributors.maxWait') * 1000;
74-
}
75-
76-
const result = await svc.contributors.getContributors(
77-
rev,
78-
{ all: all, merges: this.options?.showMergeCommits, stats: stats },
79-
undefined,
80-
timeout || undefined,
81-
);
82-
if (!result.contributors.length) {
83-
return [new MessageNode(this.view, this, 'No contributors could be found.')];
84-
}
85-
86-
const children: ContributorNode[] = [];
87-
if (result.cancelled) {
88-
children.push(
89-
new ActionMessageNode(
90-
this.view,
91-
this,
92-
n => {
93-
n.update({
94-
iconPath: new ThemeIcon('loading~spin'),
95-
message: 'Loading contributors...',
96-
description: `waiting for ${(timeout * 2) / 1000}s`,
97-
tooltip: null,
98-
});
99-
this.storeState('overrideMaxWait', timeout * 2);
100-
void this.triggerChange(true);
101-
},
102-
stats ? 'Showing incomplete contributors and statistics' : 'Showing incomplete contributors',
103-
result.cancelled.reason === 'timedout' ? `timed out after ${timeout / 1000}s` : 'cancelled',
104-
'Click to retry and wait longer for contributors',
105-
new ThemeIcon('warning', new ThemeColor('list.warningForeground' satisfies CoreColors)),
106-
) as unknown as ContributorNode,
107-
);
53+
if (stats) {
54+
queueMicrotask(async () => {
55+
this.children = await this.getContributors(true, false);
56+
void this.triggerChange();
57+
});
10858
}
10959

110-
sortContributors(result.contributors);
111-
const presenceMap = this.view.container.vsls.active
112-
? await this.getPresenceMap(result.contributors)
113-
: undefined;
114-
115-
for (const c of result.contributors) {
116-
children.push(
117-
new ContributorNode(this.uri, this.view, this, c, {
118-
all: all,
119-
ref: rev,
120-
presence: presenceMap,
121-
showMergeCommits: this.options?.showMergeCommits,
122-
}),
123-
);
124-
}
12560
this.children = children;
12661
}
12762

@@ -142,6 +77,8 @@ export class ContributorsNode extends CacheableChildrenViewNode<
14277
if (this.children == null) return;
14378

14479
for (const child of this.children) {
80+
if (!child.is('contributor')) continue;
81+
14582
if (child.contributor.email === email) {
14683
void child.triggerChange();
14784
}
@@ -156,4 +93,98 @@ export class ContributorsNode extends CacheableChildrenViewNode<
15693

15794
return this.view.container.vsls.getContactsPresence([email]);
15895
}
96+
97+
private async getContributors(
98+
stats?: boolean,
99+
deferStats?: boolean,
100+
): Promise<(ContributorNode | MessageNode | ActionMessageNode)[]> {
101+
let rev = this.options?.ref;
102+
const all = rev == null && (this.options?.all ?? configuration.get('views.contributors.showAllBranches'));
103+
104+
const svc = this.view.container.git.getRepositoryService(this.uri.repoPath!);
105+
106+
// If there is no ref and we aren't getting all branches, get the upstream of the current branch if there is one
107+
if (rev == null && !all) {
108+
try {
109+
const branch = await svc.branches.getBranch();
110+
if (branch?.upstream?.name != null && !branch.upstream.missing) {
111+
rev = '@{u}';
112+
}
113+
} catch {}
114+
}
115+
116+
let timeout: number;
117+
const overrideMaxWait = this.getState('overrideMaxWait');
118+
if (overrideMaxWait) {
119+
timeout = overrideMaxWait;
120+
this.deleteState('overrideMaxWait');
121+
} else {
122+
timeout = configuration.get('views.contributors.maxWait') * 1000;
123+
}
124+
125+
const result = await svc.contributors.getContributors(
126+
rev,
127+
{ all: all, merges: this.options?.showMergeCommits, stats: !deferStats && stats },
128+
this.view.cancellation,
129+
timeout || undefined,
130+
);
131+
if (!result.contributors.length) {
132+
return [new MessageNode(this.view, this, 'No contributors could be found.')];
133+
}
134+
135+
const children: (ContributorNode | MessageNode | ActionMessageNode)[] = [];
136+
if (result.cancelled) {
137+
children.push(
138+
new ActionMessageNode(
139+
this.view,
140+
this,
141+
n => {
142+
n.update({
143+
iconPath: new ThemeIcon('loading~spin'),
144+
message: 'Loading contributors...',
145+
description: `waiting for ${(timeout * 2) / 1000}s`,
146+
tooltip: null,
147+
});
148+
this.storeState('overrideMaxWait', timeout * 2);
149+
void this.triggerChange(true);
150+
},
151+
stats ? 'Showing incomplete contributors and statistics' : 'Showing incomplete contributors',
152+
result.cancelled.reason === 'timedout' ? `timed out after ${timeout / 1000}s` : 'cancelled',
153+
'Click to retry and wait longer for contributors',
154+
new ThemeIcon('warning', new ThemeColor('list.warningForeground' satisfies CoreColors)),
155+
),
156+
);
157+
}
158+
159+
if (deferStats && stats) {
160+
children.push(
161+
new MessageNode(
162+
this.view,
163+
this,
164+
'Loading statistics...',
165+
undefined,
166+
undefined,
167+
new ThemeIcon('loading~spin'),
168+
),
169+
);
170+
}
171+
172+
sortContributors(result.contributors);
173+
const presenceMap = this.view.container.vsls.active
174+
? await this.getPresenceMap(result.contributors)
175+
: undefined;
176+
177+
for (const c of result.contributors) {
178+
children.push(
179+
new ContributorNode(this.uri, this.view, this, c, {
180+
all: all,
181+
ref: rev,
182+
presence: presenceMap,
183+
showMergeCommits: this.options?.showMergeCommits,
184+
}),
185+
);
186+
}
187+
188+
return children;
189+
}
159190
}

0 commit comments

Comments
 (0)