Skip to content

Commit 5db43db

Browse files
Copilotalexr00
andauthored
Fix avatar display in tree views for GitHub Enterprise (#8421)
* Initial plan * Fix avatar display in tree views for GitHub Enterprise - Add enterprise detection in PR tree views - Use github ThemeIcon placeholder for enterprise instead of fetching avatars - Update pullRequestNode, issuesView, commitNode, and repositoryChangesNode Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Fix type error in commitNode iconPath Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Refactor: Extract helper method to reduce code duplication in pullRequestNode Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Use avatarUrl check instead of isEnterprise flag Check for 'githubusercontent.com' in avatar URLs to determine if fetching should be attempted, rather than relying on the remote's enterprise flag. Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Extract avatar URL check into utility function Add DataUri.isGitHubDotComAvatar() utility to check if an avatar URL is from GitHub.com. Use this utility in all tree view nodes instead of inline .includes() checks. Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Revert proposed changes * Fix test * Fix test --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 140f531 commit 5db43db

File tree

6 files changed

+48
-16
lines changed

6 files changed

+48
-16
lines changed

src/common/uri.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,16 @@ export namespace DataUri {
290290
return asImageDataURI(contents);
291291
}
292292

293+
/**
294+
* Checks if an avatar URL is from GitHub.com (as opposed to GitHub Enterprise).
295+
* GitHub.com avatar URLs contain 'githubusercontent.com', while enterprise avatar URLs do not.
296+
* @param avatarUrl The avatar URL to check
297+
* @returns true if the avatar is from GitHub.com, false otherwise
298+
*/
299+
export function isGitHubDotComAvatar(avatarUrl: string | undefined): boolean {
300+
return avatarUrl?.includes('githubusercontent.com') ?? false;
301+
}
302+
293303
export async function avatarCirclesAsImageDataUris(context: vscode.ExtensionContext, users: (IAccount | ITeam)[], height: number, width: number, localOnly?: boolean): Promise<(vscode.Uri | undefined)[]> {
294304
let cacheLogOrder: string[];
295305
const cacheLog = cacheLogUri(context);

src/issues/issuesView.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,15 @@ export class IssuesTreeData
101101
}
102102

103103
if (avatarUser) {
104-
treeItem.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.context, [avatarUser], 16, 16))[0] ??
105-
(element.isOpen
106-
? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open'))
107-
: new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('github.issues.closed')));
104+
// For enterprise, use placeholder icon instead of trying to fetch avatar
105+
if (!DataUri.isGitHubDotComAvatar(avatarUser.avatarUrl)) {
106+
treeItem.iconPath = new vscode.ThemeIcon('github');
107+
} else {
108+
treeItem.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.context, [avatarUser], 16, 16))[0] ??
109+
(element.isOpen
110+
? new vscode.ThemeIcon('issues', new vscode.ThemeColor('issues.open'))
111+
: new vscode.ThemeIcon('issue-closed', new vscode.ThemeColor('github.issues.closed')));
112+
}
108113
} else {
109114
// Use GitHub codicon when assignee setting is selected but no assignees exist
110115
treeItem.iconPath = new vscode.ThemeIcon('github');

src/test/view/prsTree.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ describe('GitHub Pull Requests view', function () {
186186
p.databaseId(1111);
187187
p.number(1111);
188188
p.title('zero');
189-
p.author(a => a.login('me').avatarUrl('https://avatars.com/me.jpg').url('https://github.com/me'));
189+
p.author(a => a.login('me').avatarUrl('https://githubusercontent.com/me.jpg').url('https://githubusercontent.com/me'));
190190
p.baseRef!(b => b.repository(br => br.url('https://github.com/aaa/bbb')));
191191
p.baseRepository(r => r.url('https://github.com/aaa/bbb'));
192192
}),
@@ -203,7 +203,7 @@ describe('GitHub Pull Requests view', function () {
203203
p.databaseId(2222);
204204
p.number(2222);
205205
p.title('one');
206-
p.author(a => a.login('you').avatarUrl('https://avatars.com/you.jpg'));
206+
p.author(a => a.login('you').avatarUrl('https://githubusercontent.com/you.jpg'));
207207
p.baseRef!(b => b.repository(br => br.url('https://github.com/aaa/bbb')));
208208
p.baseRepository(r => r.url('https://github.com/aaa/bbb'));
209209
}),
@@ -257,7 +257,7 @@ describe('GitHub Pull Requests view', function () {
257257
assert.strictEqual(localItem0.description, 'by @me');
258258
assert.strictEqual(localItem0.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed);
259259
assert.strictEqual(localItem0.contextValue, 'pullrequest:local:nonactive:hasHeadRef');
260-
assert.deepStrictEqual(localItem0.iconPath!.toString(), 'https://avatars.com/me.jpg');
260+
assert.deepStrictEqual(localItem0.iconPath!.toString(), 'https://githubusercontent.com/me.jpg');
261261

262262
const label1 = (localItem1.label as vscode.TreeItemLabel2).label;
263263
assert.ok(label1 instanceof vscode.MarkdownString);
@@ -266,7 +266,7 @@ describe('GitHub Pull Requests view', function () {
266266
assert.strictEqual(localItem1.description, 'by @you');
267267
assert.strictEqual(localItem1.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed);
268268
assert.strictEqual(localItem1.contextValue, 'pullrequest:local:active:hasHeadRef');
269-
assert.deepStrictEqual(localItem1.iconPath!.toString(), 'https://avatars.com/you.jpg');
269+
assert.deepStrictEqual(localItem1.iconPath!.toString(), 'https://githubusercontent.com/you.jpg');
270270
});
271271
});
272272
});

src/view/treeNodes/commitNode.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { LabelOnlyNode, TreeNode, TreeNodeParent } from './treeNode';
2020
export class CommitNode extends TreeNode implements vscode.TreeItem {
2121
public sha: string;
2222
public collapsibleState: vscode.TreeItemCollapsibleState;
23-
public iconPath: vscode.Uri | undefined;
23+
public iconPath: vscode.Uri | vscode.ThemeIcon | undefined;
2424
public contextValue?: string;
2525

2626
constructor(
@@ -40,8 +40,13 @@ export class CommitNode extends TreeNode implements vscode.TreeItem {
4040

4141
async getTreeItem(): Promise<vscode.TreeItem> {
4242
if (this.commit.author) {
43-
const author: IAccount = { id: this.commit.author.node_id, login: this.commit.author.login, url: this.commit.author.url, avatarUrl: this.commit.author.avatar_url, accountType: this.commit.author.type as AccountType };
44-
this.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.pullRequestManager.context, [author], 16, 16))[0];
43+
// For enterprise, use placeholder icon instead of trying to fetch avatar
44+
if (!DataUri.isGitHubDotComAvatar(this.commit.author.avatar_url)) {
45+
this.iconPath = new vscode.ThemeIcon('github');
46+
} else {
47+
const author: IAccount = { id: this.commit.author.node_id, login: this.commit.author.login, url: this.commit.author.url, avatarUrl: this.commit.author.avatar_url, accountType: this.commit.author.type as AccountType };
48+
this.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this.pullRequestManager.context, [author], 16, 16))[0];
49+
}
4550
}
4651
return this;
4752
}

src/view/treeNodes/pullRequestNode.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,20 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
270270
});
271271
}
272272

273+
private async _getAuthorIcon(): Promise<vscode.Uri | vscode.ThemeIcon> {
274+
// For enterprise, use placeholder icon instead of trying to fetch avatar
275+
if (!DataUri.isGitHubDotComAvatar(this.pullRequestModel.author.avatarUrl)) {
276+
return new vscode.ThemeIcon('github');
277+
}
278+
return (await DataUri.avatarCirclesAsImageDataUris(this._folderReposManager.context, [this.pullRequestModel.author], 16, 16))[0]
279+
?? new vscode.ThemeIcon('github');
280+
}
281+
273282
private async _getIcon(): Promise<vscode.Uri | vscode.ThemeIcon | { light: vscode.Uri; dark: vscode.Uri }> {
274283
const copilotWorkingStatus = await this.pullRequestModel.copilotWorkingStatus();
275284
const theme = this._folderReposManager.themeWatcher.themeData;
276285
if (copilotWorkingStatus === CopilotWorkingStatus.NotCopilotIssue) {
277-
return (await DataUri.avatarCirclesAsImageDataUris(this._folderReposManager.context, [this.pullRequestModel.author], 16, 16))[0]
278-
?? new vscode.ThemeIcon('github');
286+
return this._getAuthorIcon();
279287
}
280288
switch (copilotWorkingStatus) {
281289
case CopilotWorkingStatus.InProgress:
@@ -294,8 +302,7 @@ export class PRNode extends TreeNode implements vscode.CommentingRangeProvider2
294302
dark: DataUri.copilotErrorAsImageDataURI(getIconForeground(theme, 'dark'), getListErrorForeground(theme, 'dark'))
295303
};
296304
default:
297-
return (await DataUri.avatarCirclesAsImageDataUris(this._folderReposManager.context, [this.pullRequestModel.author], 16, 16))[0]
298-
?? new vscode.ThemeIcon('github');
305+
return this._getAuthorIcon();
299306
}
300307
}
301308

src/view/treeNodes/repositoryChangesNode.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,12 @@ export class RepositoryChangesNode extends TreeNode implements vscode.TreeItem {
111111

112112
override async getTreeItem(): Promise<vscode.TreeItem> {
113113
this.setLabel();
114-
this.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this._pullRequestManager.context, [this.pullRequestModel.author], 16, 16))[0];
114+
// For enterprise, use placeholder icon instead of trying to fetch avatar
115+
if (!DataUri.isGitHubDotComAvatar(this.pullRequestModel.author.avatarUrl)) {
116+
this.iconPath = new vscode.ThemeIcon('github');
117+
} else {
118+
this.iconPath = (await DataUri.avatarCirclesAsImageDataUris(this._pullRequestManager.context, [this.pullRequestModel.author], 16, 16))[0];
119+
}
115120
this.description = undefined;
116121
if (this.parent.children?.length && this.parent.children.length > 1) {
117122
const allSameOwner = this.parent.children.every(child => {

0 commit comments

Comments
 (0)