Skip to content

Commit 0d329aa

Browse files
[mgt-get] Adding support for resource caching (#763)
* Adding support for mgt-get caching * Adding new React types. * Refresh + polling support and images * Update packages/mgt/src/components/mgt-get/mgt-get.ts Co-authored-by: Nikola Metulev <[email protected]> * Formatting code * Adding the caching story Co-authored-by: Nikola Metulev <[email protected]>
1 parent 4b6af3a commit 0d329aa

File tree

4 files changed

+172
-54
lines changed

4 files changed

+172
-54
lines changed

packages/mgt-react/src/generated/react.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export type GetProps = {
2828
type?: ResponseType;
2929
maxPages?: number;
3030
pollingRate?: number;
31+
cacheEnabled?: boolean;
32+
cacheInvalidationPeriod?: number;
3133
templateConverters?: MgtElement.TemplateContext;
3234
templateContext?: MgtElement.TemplateContext;
3335
direction?: string;

packages/mgt/src/components/mgt-get/mgt-get.ts

Lines changed: 150 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { customElement, html, property } from 'lit-element';
99
import { Providers, ProviderState, MgtTemplatedComponent, equals } from '@microsoft/mgt-element';
1010
import { prepScopes } from '../../utils/GraphHelpers';
1111
import { getPhotoForResource } from '../../graph/graph.photos';
12+
import { CacheItem, CacheSchema, CacheService, CacheStore } from '../../utils/Cache';
1213

1314
/**
1415
* Enumeration to define what types of query are available
@@ -28,6 +29,45 @@ export enum ResponseType {
2829
image = 'image'
2930
}
3031

32+
/**
33+
* Definition of cache structure
34+
*/
35+
const cacheSchema: CacheSchema = {
36+
name: 'responses',
37+
stores: {
38+
responses: {}
39+
},
40+
version: 1
41+
};
42+
43+
/**
44+
* Object to be stored in cache representing a generic query
45+
*/
46+
interface CacheResponse extends CacheItem {
47+
/**
48+
* json representing a resonse as string
49+
*/
50+
response?: string;
51+
}
52+
53+
/**
54+
* Defines the expiration time
55+
*/
56+
const getResponseInvalidationTime = (currentInvalidationPeriod: number): number =>
57+
currentInvalidationPeriod ||
58+
CacheService.config.response.invalidationPeriod ||
59+
CacheService.config.defaultInvalidationPeriod;
60+
61+
/**
62+
* Whether the response store is enabled
63+
*/
64+
const responseCacheEnabled = (): boolean => CacheService.config.response.isEnabled && CacheService.config.isEnabled;
65+
66+
/**
67+
* Name of the response store name
68+
*/
69+
const responsesStore: string = 'responses';
70+
3171
/**
3272
* Custom element for making Microsoft Graph get queries
3373
*
@@ -124,6 +164,33 @@ export class MgtGet extends MgtTemplatedComponent {
124164
})
125165
public pollingRate: number = 0;
126166

167+
/**
168+
* Enables cache on the response from the specified resource
169+
* default = false
170+
*
171+
* @type {boolean}
172+
* @memberof MgtGet
173+
*/
174+
@property({
175+
attribute: 'cache-enabled',
176+
reflect: true,
177+
type: Boolean
178+
})
179+
public cacheEnabled: boolean = false;
180+
181+
/**
182+
* Invalidation period of the cache for the responses in milliseconds
183+
*
184+
* @type {number}
185+
* @memberof MgtGet
186+
*/
187+
@property({
188+
attribute: 'cache-invalidation-period',
189+
reflect: true,
190+
type: Number
191+
})
192+
public cacheInvalidationPeriod: number = 0;
193+
127194
/**
128195
* Gets or sets the response of the request
129196
*
@@ -141,6 +208,7 @@ export class MgtGet extends MgtTemplatedComponent {
141208
@property({ attribute: false }) public error: any;
142209

143210
private isPolling: boolean = false;
211+
private isRefreshing: boolean = false;
144212

145213
/**
146214
* Synchronizes property values when attributes change.
@@ -164,7 +232,9 @@ export class MgtGet extends MgtTemplatedComponent {
164232
* @memberof MgtGet
165233
*/
166234
public refresh(hardRefresh = false) {
235+
this.isRefreshing = true;
167236
this.requestStateUpdate(hardRefresh);
237+
this.isRefreshing = false;
168238
}
169239

170240
/**
@@ -235,74 +305,92 @@ export class MgtGet extends MgtTemplatedComponent {
235305

236306
if (this.resource) {
237307
try {
238-
let uri = this.resource;
239-
let isDeltaLink = false;
308+
let cache: CacheStore<CacheResponse>;
309+
const key = `${this.version}${this.resource}`;
310+
let response = null;
240311

241-
// if we had a response earlier with a delta link, use it instead
242-
if (this.response && this.response['@odata.deltaLink']) {
243-
uri = this.response['@odata.deltaLink'];
244-
isDeltaLink = true;
245-
} else {
246-
isDeltaLink = new URL(uri, 'https://graph.microsoft.com').pathname.endsWith('delta');
312+
if (this.shouldRetrieveCache()) {
313+
cache = CacheService.getCache<CacheResponse>(cacheSchema, responsesStore);
314+
const result: CacheResponse = responseCacheEnabled() ? await cache.getValue(key) : null;
315+
if (result && getResponseInvalidationTime(this.cacheInvalidationPeriod) > Date.now() - result.timeCached) {
316+
response = JSON.parse(result.response);
317+
}
247318
}
248319

249-
const graph = provider.graph.forComponent(this);
250-
let request = graph.api(uri).version(this.version);
320+
if (!response) {
321+
let uri = this.resource;
322+
let isDeltaLink = false;
251323

252-
if (this.scopes && this.scopes.length) {
253-
request = request.middlewareOptions(prepScopes(...this.scopes));
254-
}
324+
// if we had a response earlier with a delta link, use it instead
325+
if (this.response && this.response['@odata.deltaLink']) {
326+
uri = this.response['@odata.deltaLink'];
327+
isDeltaLink = true;
328+
} else {
329+
isDeltaLink = new URL(uri, 'https://graph.microsoft.com').pathname.endsWith('delta');
330+
}
255331

256-
let response = null;
257-
if (this.type === ResponseType.json) {
258-
response = await request.get();
332+
const graph = provider.graph.forComponent(this);
333+
let request = graph.api(uri).version(this.version);
259334

260-
if (isDeltaLink && this.response && Array.isArray(this.response.value) && Array.isArray(response.value)) {
261-
response.value = this.response.value.concat(response.value);
335+
if (this.scopes && this.scopes.length) {
336+
request = request.middlewareOptions(prepScopes(...this.scopes));
262337
}
263338

264-
if (!this.isPolling && !equals(this.response, response)) {
265-
this.response = response;
266-
}
339+
if (this.type === ResponseType.json) {
340+
response = await request.get();
267341

268-
// get more pages if there are available
269-
if (response && Array.isArray(response.value) && response['@odata.nextLink']) {
270-
let pageCount = 1;
271-
let page = response;
272-
273-
while (
274-
(pageCount < this.maxPages || this.maxPages <= 0 || (isDeltaLink && this.pollingRate)) &&
275-
page &&
276-
page['@odata.nextLink']
277-
) {
278-
pageCount++;
279-
const nextResource = page['@odata.nextLink'].split(this.version)[1];
280-
page = await graph.client
281-
.api(nextResource)
282-
.version(this.version)
283-
.get();
284-
if (page && page.value && page.value.length) {
285-
page.value = response.value.concat(page.value);
286-
response = page;
287-
if (!this.isPolling) {
288-
this.response = response;
342+
if (isDeltaLink && this.response && Array.isArray(this.response.value) && Array.isArray(response.value)) {
343+
response.value = this.response.value.concat(response.value);
344+
}
345+
346+
if (!this.isPolling && !equals(this.response, response)) {
347+
this.response = response;
348+
}
349+
350+
// get more pages if there are available
351+
if (response && Array.isArray(response.value) && response['@odata.nextLink']) {
352+
let pageCount = 1;
353+
let page = response;
354+
355+
while (
356+
(pageCount < this.maxPages || this.maxPages <= 0 || (isDeltaLink && this.pollingRate)) &&
357+
page &&
358+
page['@odata.nextLink']
359+
) {
360+
pageCount++;
361+
const nextResource = page['@odata.nextLink'].split(this.version)[1];
362+
page = await graph.client
363+
.api(nextResource)
364+
.version(this.version)
365+
.get();
366+
if (page && page.value && page.value.length) {
367+
page.value = response.value.concat(page.value);
368+
response = page;
369+
if (!this.isPolling) {
370+
this.response = response;
371+
}
289372
}
290373
}
291374
}
292-
}
293-
} else {
294-
if (this.resource.indexOf('/photo/$value') === -1) {
295-
throw new Error('Only /photo/$value endpoints support the image type');
296-
}
375+
} else {
376+
if (this.resource.indexOf('/photo/$value') === -1) {
377+
throw new Error('Only /photo/$value endpoints support the image type');
378+
}
297379

298-
// Sanitizing the resource to ensure getPhotoForResource gets the right format
299-
const sanitizedResource = this.resource.replace('/photo/$value', '');
300-
const photoResponse = await getPhotoForResource(graph, sanitizedResource, this.scopes);
380+
// Sanitizing the resource to ensure getPhotoForResource gets the right format
381+
const sanitizedResource = this.resource.replace('/photo/$value', '');
382+
const photoResponse = await getPhotoForResource(graph, sanitizedResource, this.scopes);
301383

302-
if (photoResponse) {
303-
response = {
304-
image: photoResponse.photo
305-
};
384+
if (photoResponse) {
385+
response = {
386+
image: photoResponse.photo
387+
};
388+
}
389+
}
390+
391+
if (this.shouldUpdateCache() && response) {
392+
cache = CacheService.getCache<CacheResponse>(cacheSchema, responsesStore);
393+
cache.putValue(key, { response: JSON.stringify(response) });
306394
}
307395
}
308396

@@ -330,4 +418,12 @@ export class MgtGet extends MgtTemplatedComponent {
330418

331419
this.fireCustomEvent('dataChange', { response: this.response, error: this.error });
332420
}
421+
422+
private shouldRetrieveCache(): boolean {
423+
return responseCacheEnabled() && this.cacheEnabled && !(this.isRefreshing || this.isPolling);
424+
}
425+
426+
private shouldUpdateCache(): boolean {
427+
return responseCacheEnabled() && this.cacheEnabled;
428+
}
333429
}

packages/mgt/src/utils/Cache.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ export interface CacheConfig {
6565
* @memberof CacheConfig
6666
*/
6767
users: CacheOptions;
68+
69+
/**
70+
* Cache options for a generic response store
71+
*
72+
* @type {CacheOptions}
73+
* @memberof CacheConfig
74+
*/
75+
response: CacheOptions;
6876
}
6977

7078
/**
@@ -153,6 +161,10 @@ export class CacheService {
153161
users: {
154162
invalidationPeriod: null,
155163
isEnabled: true
164+
},
165+
response: {
166+
invalidationPeriod: null,
167+
isEnabled: true
156168
}
157169
};
158170

stories/components/get.stories.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,11 @@ export const UsingImageType = () => html`
157157
</template>
158158
</mgt-get>
159159
`;
160+
161+
export const UsingCaching = () => html`
162+
<mgt-get resource="me" caching-enabled="true">
163+
<template>
164+
Hello {{ displayName }}
165+
</template>
166+
</mgt-get>
167+
`;

0 commit comments

Comments
 (0)