Skip to content

Commit 2c498f6

Browse files
author
ole
authored
Avatar fix (#362)
* removed "debugger" line in Header/author/index.tsx * support for getOriginUrl() on remotes in IGitService * Fixed avatar issues (#349, #287) using remote contributors and added avatar cache extension setting (default 1 hour) * Always request github users API (when avatar cache expires) and check for modifications * Updated version and CHANGELOG.md * set default value for _enabled in log.ts * Slightly improved the use of getOriginType() through getOriginUrl() * CodeFactor fixes
1 parent 58e534e commit 2c498f6

File tree

13 files changed

+152
-288
lines changed

13 files changed

+152
-288
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Version 0.4.7
2+
- Always request github users API (when avatar cache expires) and check for modifications
3+
- Fixed avatar issues (#349, #287) using remote contributors and added avatar cache extension setting (default 1 hour)
4+
15
## Version 0.4.6
26
- Handle cases where folder/file names conflicts with brancch names [#205](https://github.com/DonJayamanne/gitHistoryVSCode/issues/205), [#340](https://github.com/DonJayamanne/gitHistoryVSCode/issues/340)
37
- Adds support for multi-root workspace folders [#346](https://github.com/DonJayamanne/gitHistoryVSCode/issues/346)

browser/src/actions/results.ts

Lines changed: 6 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,6 @@ export const notifyIsFetchingCommit = createAction(Actions.IS_FETCHING_COMMIT);
2020
export const fetchedAvatar = createAction<Avatar[]>(Actions.FETCHED_AVATARS);
2121
export const fetchedAuthors = createAction<ActionedUser[]>(Actions.FETCHED_AUTHORS);
2222

23-
// function buildQueryString(settings: ISettings): string {
24-
// if (!settings) {
25-
// return '/log';
26-
// }
27-
// const queryArgs = [];
28-
// if (settings.searchText && settings.searchText.length > 0) {
29-
// queryArgs.push(`searchText=${encodeURIComponent(settings.searchText)}`);
30-
// }
31-
// if (settings.pageIndex && settings.pageIndex > 0) {
32-
// queryArgs.push(`pageIndex=${settings.pageIndex}`);
33-
// }
34-
// if (settings.selectedBranchType) {
35-
// switch (settings.selectedBranchType) {
36-
// case Branch.All: {
37-
// queryArgs.push(`branchType=ALL`);
38-
// break;
39-
// }
40-
// case Branch.Current: {
41-
// queryArgs.push(`branchType=CURRENT`);
42-
// break;
43-
// }
44-
// }
45-
// }
46-
47-
// return `/log` + (queryArgs.length === 0 ? '' : `?${queryArgs.join('&')}`);
48-
// }
49-
5023
function getQueryUrl(store: RootState, baseUrl: string, args: string[] = []): string {
5124
const id = store.settings.id || '';
5225
const queryArgs = args.concat([`id=${encodeURIComponent(id)}`]);
@@ -60,54 +33,13 @@ export const actionACommit = (logEntry: LogEntry) => {
6033
return axios.post(url, logEntry);
6134
};
6235
};
63-
export const fetchAvatar = (user: ActionedUser) => {
64-
// tslint:disable-next-line:no-any
65-
return async (dispatch: Dispatch<any>, getState: () => RootState) => {
66-
const state = getState();
67-
const url = getQueryUrl(state, '/avatar', [`name=${encodeURIComponent(user.name)}`, `email=${encodeURIComponent(user.email)}`]);
68-
axios.get(url)
69-
.then(result => {
70-
dispatch(fetchedAvatar([result.data as Avatar]));
71-
})
72-
.catch(err => {
73-
// tslint:disable-next-line:no-debugger
74-
console.error('Git History: Avatar request failed');
75-
console.error(err);
76-
});
77-
};
78-
};
79-
// export const fetchAvatars = (users: ActionedUser[]) => {
80-
// // tslint:disable-next-line:no-any
81-
// return async (dispatch: Dispatch<any>, getState: () => RootState) => {
82-
// const state = getState();
83-
// const url = getQueryUrl(state, '/avatar', [`name=${encodeURIComponent(user.name)}`, `email=${encodeURIComponent(user.email)}`]);
84-
// axios.get(url)
85-
// .then(result => {
86-
// dispatch(fetchedAvatar(result.data as Avatar));
87-
// })
88-
// .catch(err => {
89-
// // tslint:disable-next-line:no-debugger
90-
// console.error('Git History: Avatar request failed');
91-
// console.error(err);
92-
// });
93-
// };
94-
// };
36+
9537
// tslint:disable-next-line:no-any
96-
export const fetchAvatars = async (users: ActionedUser[], dispatch: Dispatch<any>, getState: () => RootState) => {
38+
export const fetchAvatars = async (dispatch: Dispatch<any>, getState: () => RootState) => {
9739
const state = getState();
98-
const unidentifiedUsers = users
99-
.filter(a => !state.avatars.find(avatar => avatar.name === a.name && avatar.email === a.email))
100-
.reduce((accumulator, user) => {
101-
if (accumulator.findIndex(item => item.email === user.email && item.name === user.name) === -1) {
102-
accumulator.push(user);
103-
}
104-
return accumulator;
105-
}, []);
106-
if (unidentifiedUsers.length === 0) {
107-
return;
108-
}
10940
const url = getQueryUrl(state, '/avatars');
110-
axios.post(url, unidentifiedUsers)
41+
42+
axios.post(url)
11143
.then(result => {
11244
dispatch(fetchedAvatar(result.data as Avatar[]));
11345
})
@@ -270,19 +202,10 @@ function fetchCommits(dispatch: Dispatch<any>, store: RootState, pageIndex?: num
270202
if (Array.isArray(result.data.items)) {
271203
result.data.items.forEach(item => {
272204
fixDates(item);
273-
/*if (Array.isArray(item.committedFiles)) {
274-
item.committedFiles.forEach(f => {
275-
fixFileUri(f.oldUri);
276-
fixFileUri(f.uri);
277-
});
278-
}*/
279205
});
280206
}
281-
//fixFileUri(result.data.file);
282207
dispatch(addResults(result.data));
283-
if (result.data && Array.isArray(result.data.items) && result.data.items.length > 0) {
284-
fetchAvatars(result.data.items.map(item => item.author), dispatch, () => store);
285-
}
208+
fetchAvatars(dispatch, () => store);
286209
})
287210
.catch(err => {
288211
// tslint:disable-next-line:no-debugger
@@ -298,17 +221,9 @@ function fetchCommit(dispatch: Dispatch<any>, store: RootState, hash: string) {
298221
.then((result: { data: LogEntry }) => {
299222
if (result.data) {
300223
fixDates(result.data);
301-
/*if (Array.isArray(result.data.committedFiles)) {
302-
result.data.committedFiles.forEach(f => {
303-
fixFileUri(f.oldUri);
304-
fixFileUri(f.uri);
305-
});
306-
}*/
307224
}
308225
dispatch(updateCommit(result.data));
309-
if (result.data && result.data.author) {
310-
fetchAvatars([result.data.author], dispatch, () => store);
311-
}
226+
fetchAvatars(dispatch, () => store);
312227
})
313228
.catch(err => {
314229
// tslint:disable-next-line:no-debugger

browser/src/components/Header/author/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class Author extends React.Component<AuthorProps, AuthorState> {
3838
if (this.props.lineHistory) {
3939
return null;
4040
}
41-
debugger;
41+
4242
const title = !this.props.author || this.props.author === '' ? 'All Authors' : this.props.author;
4343
const selectedAuthor = !this.props.author || this.props.author === '' ? '[ALL]' : this.props.author;
4444
const authors = Array.isArray(this.props.authors) ? this.props.authors : [];

browser/src/components/LogView/Commit/Avatar/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type AvatarProps = {
1212
function Avatar(props: AvatarProps) {
1313
let avatarUrl = '';
1414
if (props.result) {
15-
const avatar = props.avatars.find(item => item.name === props.result.name && item.email === props.result.email);
15+
const avatar = props.avatars.find(item => item.name === props.result.name || item.login === props.result.name || item.email === props.result.email);
1616
avatarUrl = avatar ? avatar.avatarUrl : '';
1717
}
1818
if (avatarUrl) {

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "githistory",
33
"displayName": "Git History",
44
"description": "View git log, file history, compare branches or commits",
5-
"version": "0.4.6",
5+
"version": "0.4.7",
66
"publisher": "donjayamanne",
77
"author": {
88
"name": "Don Jayamanne",
@@ -334,6 +334,12 @@
334334
"default": 100,
335335
"scope": "window",
336336
"description": "Default number of items to be displayed in Git History Viewer"
337+
},
338+
"gitHistory.avatarCacheExpiration": {
339+
"type": "integer",
340+
"default": 60,
341+
"scope": "window",
342+
"description": "Avatar image cache expiration (default: 60 minutes / 0 = cache disabled)"
337343
}
338344
}
339345
}

src/adapter/avatar/base.ts

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,44 @@ import { injectable, unmanaged } from 'inversify';
22
import { IStateStore, IStateStoreFactory } from '../../application/types/stateStore';
33
import { IWorkspaceService } from '../../application/types/workspace';
44
import { IServiceContainer } from '../../ioc/types';
5-
import { ActionedUser, Avatar } from '../../types';
5+
import { Avatar, AvatarResponse, IGitService } from '../../types';
66
import { GitOriginType } from '../repository/types';
77
import { IAvatarProvider } from './types';
88

9-
type CachedAvatar = Avatar & { retry: boolean; dateTimeMs: number };
10-
type CachedPromise = { dateTimeMs: number; promise: Promise<Avatar | undefined> };
119
@injectable()
1210
export abstract class BaseAvatarProvider implements IAvatarProvider {
1311
protected readonly httpProxy: string;
1412
private readonly avatarStateStore: IStateStore;
15-
private readonly getAvatarPromises: Map<string, CachedPromise>;
1613
public constructor(protected serviceContainer: IServiceContainer, @unmanaged() private remoteRepoType: GitOriginType) {
1714
const workspace = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
1815
this.httpProxy = workspace.getConfiguration('http').get('proxy', '');
1916
const stateStoreFactory = this.serviceContainer.get<IStateStoreFactory>(IStateStoreFactory);
2017
this.avatarStateStore = stateStoreFactory.createStore();
21-
this.getAvatarPromises = new Map<string, CachedPromise>();
2218
}
23-
public async getAvatar(user: ActionedUser): Promise<Avatar | undefined> {
24-
const key = `Git:Avatar:${user.name}:${user.email}`;
25-
const cachedInfo = this.avatarStateStore.has(key) ? await this.avatarStateStore.get<CachedAvatar>(key)! : undefined;
26-
const retry = cachedInfo && cachedInfo.retry && (cachedInfo.dateTimeMs + (60 * 60 * 1000) < new Date().getTime());
27-
if (!cachedInfo || retry) {
28-
try {
29-
// If we're in the process of getting the avatar for a user, then don't request it again.
30-
// This way we avoid multiple requests for the same user (minimize 403 errors).
31-
let cachedPromise: CachedPromise | undefined;
32-
if (this.getAvatarPromises.has(key)) {
33-
cachedPromise = this.getAvatarPromises.get(key)!;
34-
if (cachedPromise.dateTimeMs + (60 * 60 * 1000) < new Date().getTime()) {
35-
this.getAvatarPromises.delete(key);
36-
cachedPromise = undefined;
37-
}
38-
}
39-
if (!cachedPromise) {
40-
cachedPromise = { dateTimeMs: Date.now(), promise: this.getAvatarImplementation(user) };
41-
this.getAvatarPromises.set(key, cachedPromise);
42-
}
43-
const avatar = await cachedPromise!.promise;
44-
// const avatar = await this.getAvatarImplementation(user);
45-
const url = avatar ? avatar.url : undefined;
46-
const avatarUrl = avatar ? avatar.avatarUrl : undefined;
47-
const dateTimeMs = new Date().getTime();
48-
// If we don't have any errors, then never retry to get avatar, even if it is null.
49-
await this.avatarStateStore.set<CachedAvatar>(key, { url, retry: false, avatarUrl, dateTimeMs, name: user.name, email: user.email });
50-
} catch (ex) {
51-
// If we have errors in retreivig the info, then we can retry later.
52-
const dateTimeMs = new Date().getTime();
53-
await this.avatarStateStore.set<CachedAvatar>(key, { url: undefined, retry: true, avatarUrl: undefined, dateTimeMs, name: user.name, email: user.email });
54-
}
19+
20+
public async getAvatars(repository: IGitService) : Promise<Avatar[]> {
21+
const workspace = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
22+
const cacheExpiration = workspace.getConfiguration('gitHistory').get<number>('avatarCacheExpiration', 60); // in minutes (zero to disable cache)
23+
24+
const remoteUrl = await repository.getOriginUrl();
25+
const key = `Git:Avatars:${remoteUrl}`;
26+
27+
const cachedAvatars = await this.avatarStateStore.get<AvatarResponse>(key);
28+
29+
const retry = cacheExpiration === 0 || !cachedAvatars || (cachedAvatars && cachedAvatars.timestamp && (cachedAvatars.timestamp + (cacheExpiration * 60 * 1000)) < new Date().getTime());
30+
31+
if (retry) {
32+
const avatars = await this.getAvatarsImplementation(repository);
33+
await this.avatarStateStore.set<AvatarResponse>(key, { timestamp: new Date().getTime(), items: avatars });
34+
return avatars;
35+
} else if (cachedAvatars) {
36+
return cachedAvatars.items;
5537
}
5638

57-
const info = await this.avatarStateStore.get<Avatar>(key)!;
58-
return info && info.avatarUrl ? info : undefined;
39+
return [];
5940
}
6041
public supported(remoteRepo: GitOriginType): boolean {
6142
return remoteRepo === this.remoteRepoType;
6243
}
63-
protected abstract getAvatarImplementation(user: ActionedUser): Promise<Avatar | undefined>;
44+
protected abstract getAvatarsImplementation(repository: IGitService) : Promise<Avatar[]>;
6445
}

0 commit comments

Comments
 (0)