@@ -9,6 +9,7 @@ import { customElement, html, property } from 'lit-element';
99import { Providers , ProviderState , MgtTemplatedComponent , equals } from '@microsoft/mgt-element' ;
1010import { prepScopes } from '../../utils/GraphHelpers' ;
1111import { 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}
0 commit comments