Skip to content

Commit 03841ed

Browse files
Fixed cache to cache image state when no image is available (#670)
* factored get image logic to account for 404 * added comment * fixed JSON.parse exception when reading from cache * Prevented undefined user responses from being cached Co-authored-by: Shane Weaver <[email protected]>
1 parent 3f13955 commit 03841ed

File tree

3 files changed

+83
-88
lines changed

3 files changed

+83
-88
lines changed

packages/mgt/src/graph/graph.people.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ export function getEmailFromGraphEntity(entity: IDynamicPerson): string {
247247
* @returns {(Promise<Contact[]>)}
248248
* @memberof Graph
249249
*/
250-
export async function findContactByEmail(graph: IGraph, email: string): Promise<Contact[]> {
250+
export async function findContactsByEmail(graph: IGraph, email: string): Promise<Contact[]> {
251251
const scopes = 'contacts.read';
252252
let cache: CacheStore<CachePerson>;
253253
if (peopleCacheEnabled()) {
@@ -271,17 +271,3 @@ export async function findContactByEmail(graph: IGraph, email: string): Promise<
271271

272272
return result ? result.value : null;
273273
}
274-
275-
/**
276-
* async promise, returns Graph contact and/or Person associated with the email provided
277-
* Uses: Graph.findPerson(email) and Graph.findContactByEmail(email)
278-
*
279-
* @param {string} email
280-
* @returns {(Promise<Array<Person | Contact>>)}
281-
* @memberof Graph
282-
*/
283-
export function findUserByEmail(graph: IGraph, email: string): Promise<Array<Person | Contact>> {
284-
return Promise.all([findPeople(graph, email), findContactByEmail(graph, email)]).then(([people, contacts]) => {
285-
return ((people as any[]) || []).concat(contacts || []);
286-
});
287-
}

packages/mgt/src/graph/graph.photos.ts

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
1111
import { CacheItem, CacheSchema, CacheService, CacheStore } from '../utils/Cache';
1212
import { prepScopes } from '../utils/GraphHelpers';
1313
import { blobToBase64 } from '../utils/Utils';
14-
import { findContactByEmail, findUserByEmail, getEmailFromGraphEntity } from './graph.people';
14+
import { findContactsByEmail, getEmailFromGraphEntity } from './graph.people';
15+
import { findUsers } from './graph.user';
1516
import { IDynamicPerson } from './types';
1617

1718
/**
@@ -76,9 +77,15 @@ export async function getPhotoForResource(graph: IGraph, resource: string, scope
7677
.middlewareOptions(prepScopes(...scopes))
7778
.get()) as Response;
7879

79-
if (!response.ok) {
80+
if (response.status === 404) {
81+
// 404 means the resource does not have a photo
82+
// we still want to cache that state
83+
// so we return an object that can be cached
84+
return { eTag: null, photo: null };
85+
} else if (!response.ok) {
8086
return null;
8187
}
88+
8289
const eTag = response.headers.get('eTag');
8390
const blob = await blobToBase64(await response.blob());
8491
return { eTag, photo: blob };
@@ -195,55 +202,51 @@ export async function myPhoto(graph: IGraph): Promise<string> {
195202
* @export
196203
*/
197204
export async function getPersonImage(graph: IGraph, person: IDynamicPerson) {
198-
let image: string;
199-
let email: string;
200-
201-
if ((person as MicrosoftGraph.Person).userPrincipalName) {
202-
// try to find a user by userPrincipalName
203-
const userPrincipalName = (person as MicrosoftGraph.Person).userPrincipalName;
204-
image = await getUserPhoto(graph, userPrincipalName);
205-
} else if ('personType' in person && (person as any).personType.subclass === 'PersonalContact') {
205+
// handle if contact
206+
if ('personType' in person && (person as any).personType.subclass === 'PersonalContact') {
206207
// if person is a contact, look for them and their photo in contact api
207-
email = getEmailFromGraphEntity(person);
208-
const contact = await findContactByEmail(graph, email);
208+
let email = getEmailFromGraphEntity(person);
209+
const contact = await findContactsByEmail(graph, email);
209210
if (contact && contact.length && contact[0].id) {
210-
image = await getContactPhoto(graph, contact[0].id);
211+
return await getContactPhoto(graph, contact[0].id);
211212
}
212-
} else if (person.id) {
213-
image = await getUserPhoto(graph, person.id);
213+
214+
return null;
215+
}
216+
217+
// handle if user
218+
if ((person as MicrosoftGraph.Person).userPrincipalName || person.id) {
219+
// try to find a user by userPrincipalName
220+
const id = (person as MicrosoftGraph.Person).userPrincipalName || person.id;
221+
return await getUserPhoto(graph, id);
214222
}
215-
if (image) {
216-
return image;
223+
224+
// else assume id is for user and try to get photo
225+
if (person.id) {
226+
let image = await getUserPhoto(graph, person.id);
227+
if (image) {
228+
return image;
229+
}
217230
}
218231

219-
// try to find a user by e-mail
220-
email = getEmailFromGraphEntity(person);
232+
// let's try to find a person by the email
233+
let email = getEmailFromGraphEntity(person);
221234

222235
if (email) {
223-
const users = await findUserByEmail(graph, email);
236+
// try to find user
237+
const users = await findUsers(graph, email, 1);
224238
if (users && users.length) {
225-
// Check for an OrganizationUser
226-
const orgUser = users.find(p => {
227-
return (p as any).personType && (p as any).personType.subclass === 'OrganizationUser';
228-
});
229-
if (orgUser) {
230-
// Lookup by userId
231-
const userId = (users[0] as MicrosoftGraph.Person).scoredEmailAddresses[0].address;
232-
image = await getUserPhoto(graph, userId);
233-
} else {
234-
// Lookup by contactId
235-
for (const user of users) {
236-
const contactId = user.id;
237-
image = await getContactPhoto(graph, contactId);
238-
if (image) {
239-
break;
240-
}
241-
}
242-
}
239+
return await getUserPhoto(graph, users[0].id);
240+
}
241+
242+
// if no user, try to find a contact
243+
const contacts = await findContactsByEmail(graph, email);
244+
if (contacts && contacts.length) {
245+
return await getContactPhoto(graph, contacts[0].id);
243246
}
244247
}
245248

246-
return image;
249+
return null;
247250
}
248251

249252
/**

packages/mgt/src/graph/graph.user.ts

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -111,20 +111,23 @@ export async function getMe(graph: IGraph): Promise<User> {
111111
* @memberof Graph
112112
*/
113113
export async function getUserWithPhoto(graph: IGraph, userId?: string): Promise<IDynamicPerson> {
114-
let person = null as IDynamicPerson;
115-
let cache: CacheStore<CacheUser>;
116114
let photo = null;
117-
let user: IDynamicPerson;
115+
let user: IDynamicPerson = null;
116+
118117
let cachedPhoto: CachePhoto;
118+
let cachedUser: CacheUser;
119+
119120
const resource = userId ? `users/${userId}` : 'me';
120121
const scopes = userId ? ['user.readbasic.all'] : ['user.read'];
121122

122123
// attempt to get user and photo from cache if enabled
123124
if (usersCacheEnabled()) {
124-
cache = CacheService.getCache<CacheUser>(cacheSchema, userStore);
125-
const cachedUser = await cache.getValue(userId || 'me');
125+
let cache = CacheService.getCache<CacheUser>(cacheSchema, userStore);
126+
cachedUser = await cache.getValue(userId || 'me');
126127
if (cachedUser && getUserInvalidationTime() > Date.now() - cachedUser.timeCached) {
127-
user = JSON.parse(cachedUser.user);
128+
user = cachedUser.user ? JSON.parse(cachedUser.user) : null;
129+
} else {
130+
cachedUser = null;
128131
}
129132
}
130133
if (photosCacheEnabled()) {
@@ -138,12 +141,15 @@ export async function getUserWithPhoto(graph: IGraph, userId?: string): Promise<
138141
// put current image into the cache to update the timestamp since etag is the same
139142
storePhotoInCache(userId || 'me', 'users', cachedPhoto);
140143
photo = cachedPhoto.photo;
144+
} else {
145+
cachedPhoto = null;
141146
}
142147
} catch (e) {}
143148
}
144149
}
145150

146-
if (!photo && !user) {
151+
// if both are not in the cache, batch get them
152+
if (!cachedPhoto && !cachedUser) {
147153
let eTag: string;
148154

149155
// batch calls
@@ -170,45 +176,41 @@ export async function getUserWithPhoto(graph: IGraph, userId?: string): Promise<
170176

171177
// store user & photo in their respective cache
172178
if (usersCacheEnabled()) {
179+
let cache = CacheService.getCache<CacheUser>(cacheSchema, userStore);
173180
cache.putValue(userId || 'me', { user: JSON.stringify(user) });
174181
}
175182
if (photosCacheEnabled()) {
176183
storePhotoInCache(userId || 'me', 'users', { eTag, photo: photo });
177184
}
178-
} else if (!photo) {
179-
// get photo from graph
180-
const resource = userId ? `users/${userId}` : 'me';
181-
const scopes = userId ? ['user.readbasic.all'] : ['user.read'];
185+
} else if (!cachedPhoto) {
186+
// if only photo or user is not cached, get it individually
182187
const response = await getPhotoForResource(graph, resource, scopes);
183188
if (response) {
184189
if (photosCacheEnabled()) {
185190
storePhotoInCache(userId || 'me', 'users', { eTag: response.eTag, photo: response.photo });
186191
}
187192
photo = response.photo;
188193
}
189-
} else if (!user) {
194+
} else if (!cachedUser) {
190195
// get user from graph
191-
const response = userId
192-
? await graph
193-
.api(`/users/${userId}`)
194-
.middlewareOptions(prepScopes('user.readbasic.all'))
195-
.get()
196-
: await graph
197-
.api('me')
198-
.middlewareOptions(prepScopes('user.read'))
199-
.get();
196+
const response = await graph
197+
.api(resource)
198+
.middlewareOptions(prepScopes(...scopes))
199+
.get();
200+
200201
if (response) {
201202
if (usersCacheEnabled()) {
203+
let cache = CacheService.getCache<CacheUser>(cacheSchema, userStore);
202204
cache.putValue(userId || 'me', { user: JSON.stringify(response) });
203205
}
204206
user = response;
205207
}
206208
}
209+
207210
if (user) {
208-
person = user;
209-
person.personImage = photo;
211+
user.personImage = photo;
210212
}
211-
return person;
213+
return user;
212214
}
213215

214216
/**
@@ -229,7 +231,7 @@ export async function getUser(graph: IGraph, userPrincipleName: string): Promise
229231
// is it stored and is timestamp good?
230232
if (user && getUserInvalidationTime() > Date.now() - user.timeCached) {
231233
// return without any worries
232-
return JSON.parse(user.user);
234+
return user.user ? JSON.parse(user.user) : null;
233235
}
234236
}
235237
// else we must grab it
@@ -271,7 +273,7 @@ export async function getUsersForUserIds(graph: IGraph, userIds: string[]): Prom
271273
user = await cache.getValue(id);
272274
}
273275
if (user && getUserInvalidationTime() > Date.now() - user.timeCached) {
274-
peopleDict[id] = JSON.parse(user.user);
276+
peopleDict[id] = user.user ? JSON.parse(user.user) : null;
275277
} else if (id !== '') {
276278
batch.get(id, `/users/${id}`, ['user.readbasic.all']);
277279
notInCache.push(id);
@@ -399,14 +401,18 @@ export async function findUsers(graph: IGraph, query: string, top: number = 10):
399401
}
400402
}
401403

402-
const graphResult = await graph
403-
.api('users')
404-
.filter(
405-
`startswith(displayName,'${query}') or startswith(givenName,'${query}') or startswith(surname,'${query}') or startswith(mail,'${query}') or startswith(userPrincipalName,'${query}')`
406-
)
407-
.top(top)
408-
.middlewareOptions(prepScopes(scopes))
409-
.get();
404+
let graphResult;
405+
406+
try {
407+
graphResult = await graph
408+
.api('users')
409+
.filter(
410+
`startswith(displayName,'${query}') or startswith(givenName,'${query}') or startswith(surname,'${query}') or startswith(mail,'${query}') or startswith(userPrincipalName,'${query}')`
411+
)
412+
.top(top)
413+
.middlewareOptions(prepScopes(scopes))
414+
.get();
415+
} catch {}
410416

411417
if (usersCacheEnabled() && graphResult) {
412418
item.results = graphResult.value.map(userStr => JSON.stringify(userStr));

0 commit comments

Comments
 (0)