Skip to content

Commit f9a3a53

Browse files
Caching Added for Presence and Groups (#593)
* Caching experimentation * moved cache files * working cache * first stab at completing the P0 entities * cleaned up some caching code, started logout-clear work * cache organizational changes, inital cache clearing system implemented * added cache options for enabling/disabling and changing invalidation time for cache stores * got rid of unused class, build should now pass * added MS header * default invalidation was accidentally 6 minutes, reverted to expected 60 minutes * refactor in response to Nikola's PR comments * Fixed mispelling of 'invalidation' * made edits in response to Beth's comments * added group and presence caching * refactor before PR * got rid of agenda from cache config since it's not part of this PR * fixed presence one-by-one fallback, accounted for top & groupTypes params in group caching * Fixed up presence and groups in response to Nikola's comments * fix db name Co-authored-by: Nikola Metulev <[email protected]> Co-authored-by: Nikola Metulev <[email protected]>
1 parent 3d5dfbf commit f9a3a53

File tree

5 files changed

+175
-12
lines changed

5 files changed

+175
-12
lines changed

packages/mgt/src/graph/graph.groups.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import { IGraph } from '@microsoft/mgt-element';
99
import { Group } from '@microsoft/microsoft-graph-types';
10+
import { CacheItem, CacheSchema, CacheService, CacheStore } from '../utils/Cache';
1011
import { prepScopes } from '../utils/GraphHelpers';
1112

1213
/**
@@ -46,6 +47,42 @@ export enum GroupType {
4647
distribution = 1 << 3
4748
}
4849

50+
/**
51+
* Definition of cache structure
52+
*/
53+
const cacheSchema: CacheSchema = {
54+
name: 'groups',
55+
stores: {
56+
groupsQuery: {}
57+
},
58+
version: 1
59+
};
60+
61+
/**
62+
* Object to be stored in cache representing individual people
63+
*/
64+
interface CacheGroupQuery extends CacheItem {
65+
/**
66+
* json representing a person stored as string
67+
*/
68+
groups?: string[];
69+
/**
70+
* top number of results
71+
*/
72+
top?: number;
73+
}
74+
75+
/**
76+
* Defines the expiration time
77+
*/
78+
const getGroupsInvalidationTime = (): number =>
79+
CacheService.config.groups.invalidationPeriod || CacheService.config.defaultInvalidationPeriod;
80+
81+
/**
82+
* Whether the groups store is enabled
83+
*/
84+
const groupsCacheEnabled = (): boolean => CacheService.config.groups.isEnabled && CacheService.config.isEnabled;
85+
4986
/**
5087
* Searches the Graph for Groups
5188
*
@@ -64,6 +101,21 @@ export async function findGroups(
64101
): Promise<Group[]> {
65102
const scopes = 'Group.Read.All';
66103

104+
let cache: CacheStore<CacheGroupQuery>;
105+
const key = query || '*' + groupTypes;
106+
107+
if (groupsCacheEnabled()) {
108+
cache = CacheService.getCache(cacheSchema, 'groupsQuery');
109+
const cacheGroupQuery = await cache.getValue(key);
110+
if (cacheGroupQuery && getGroupsInvalidationTime() > Date.now() - cacheGroupQuery.timeCached) {
111+
if (cacheGroupQuery.top >= top) {
112+
// if request is less than the cache's requests, return a slice of the results
113+
return cacheGroupQuery.groups.map(x => JSON.parse(x)).slice(0, top + 1);
114+
}
115+
// if the new request needs more results than what's presently in the cache, graph must be called again
116+
}
117+
}
118+
67119
let filterQuery = '';
68120
if (query !== '') {
69121
filterQuery = `(startswith(displayName,'${query}') or startswith(mailNickname,'${query}') or startswith(mail,'${query}'))`;
@@ -101,5 +153,10 @@ export async function findGroups(
101153
.top(top)
102154
.middlewareOptions(prepScopes(scopes))
103155
.get();
156+
157+
if (groupsCacheEnabled() && result) {
158+
cache.putValue(key, { groups: result.value.map(x => JSON.stringify(x)), top: top });
159+
}
160+
104161
return result ? result.value : null;
105162
}

packages/mgt/src/graph/graph.presence.ts

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,42 @@
88
import { IGraph } from '@microsoft/mgt-element';
99
import { Presence } from '@microsoft/microsoft-graph-types-beta';
1010
import { BetaGraph } from '../BetaGraph';
11+
import { CacheItem, CacheSchema, CacheService, CacheStore } from '../utils/Cache';
1112
import { prepScopes } from '../utils/GraphHelpers';
1213
import { IDynamicPerson } from './types';
1314

15+
/**
16+
* Definition of cache structure
17+
*/
18+
const cacheSchema: CacheSchema = {
19+
name: 'presence',
20+
stores: {
21+
presence: {}
22+
},
23+
version: 1
24+
};
25+
26+
/**
27+
* Object to be stored in cache representing individual people
28+
*/
29+
interface CachePresence extends CacheItem {
30+
/**
31+
* json representing a person stored as string
32+
*/
33+
presence?: string;
34+
}
35+
36+
/**
37+
* Defines the expiration time
38+
*/
39+
const getPresenceInvalidationTime = (): number =>
40+
CacheService.config.presence.invalidationPeriod || CacheService.config.defaultInvalidationPeriod;
41+
42+
/**
43+
* Whether the groups store is enabled
44+
*/
45+
const presenceCacheEnabled = (): boolean => CacheService.config.presence.isEnabled && CacheService.config.isEnabled;
46+
1447
/**
1548
* async promise, allows developer to get my presense
1649
*
@@ -20,11 +53,25 @@ import { IDynamicPerson } from './types';
2053
export async function getMyPresence(graph: IGraph): Promise<Presence> {
2154
const betaGraph = BetaGraph.fromGraph(graph);
2255
const scopes = 'presence.read';
56+
let cache: CacheStore<CachePresence>;
57+
58+
if (presenceCacheEnabled()) {
59+
cache = CacheService.getCache(cacheSchema, 'presence');
60+
const presence = await cache.getValue('me');
61+
if (presence && getPresenceInvalidationTime() > Date.now() - presence.timeCached) {
62+
return JSON.parse(presence.presence);
63+
}
64+
}
65+
2366
const result = await betaGraph
2467
.api('/me/presence')
2568
.middlewareOptions(prepScopes(scopes))
2669
.get();
2770

71+
if (presenceCacheEnabled()) {
72+
cache.putValue('me', { presence: result });
73+
}
74+
2875
return result;
2976
}
3077

@@ -37,10 +84,23 @@ export async function getMyPresence(graph: IGraph): Promise<Presence> {
3784
export async function getUserPresence(graph: IGraph, userId: string): Promise<Presence> {
3885
const betaGraph = BetaGraph.fromGraph(graph);
3986
const scopes = ['presence.read', 'presence.read.all'];
87+
let cache: CacheStore<CachePresence>;
88+
89+
if (presenceCacheEnabled()) {
90+
cache = CacheService.getCache(cacheSchema, 'presence');
91+
const presence = await cache.getValue(userId);
92+
if (presence && getPresenceInvalidationTime() > Date.now() - presence.timeCached) {
93+
return JSON.parse(presence.presence);
94+
}
95+
}
96+
4097
const result = await betaGraph
4198
.api(`/users/${userId}/presence`)
4299
.middlewareOptions(prepScopes(...scopes))
43100
.get();
101+
if (presenceCacheEnabled()) {
102+
cache.putValue(userId, { presence: JSON.stringify(result) });
103+
}
44104

45105
return result;
46106
}
@@ -55,30 +115,65 @@ export async function getUsersPresenceByPeople(graph: IGraph, people?: IDynamicP
55115
if (!people || people.length === 0) {
56116
return {};
57117
}
118+
58119
const betaGraph = BetaGraph.fromGraph(graph);
59120
const batch = betaGraph.createBatch();
121+
const peoplePresence = {};
122+
let cache: CacheStore<CachePresence>;
123+
124+
if (presenceCacheEnabled()) {
125+
cache = CacheService.getCache(cacheSchema, 'presence');
126+
}
60127

61128
for (const person of people) {
62129
if (person !== '' && person.id) {
63130
const id = person.id;
64-
batch.get(id, `/users/${id}/presence`, ['presence.read', 'presence.read.all']);
131+
peoplePresence[id] = null;
132+
let presence: CachePresence;
133+
if (presenceCacheEnabled()) {
134+
presence = await cache.getValue(id);
135+
}
136+
if (
137+
presenceCacheEnabled() &&
138+
presence &&
139+
getPresenceInvalidationTime() > Date.now() - (await presence).timeCached
140+
) {
141+
peoplePresence[id] = JSON.parse(presence.presence);
142+
} else {
143+
batch.get(id, `/users/${id}/presence`, ['presence.read', 'presence.read.all']);
144+
}
65145
}
66146
}
67147

68148
try {
69-
const peoplePresence = {};
70149
const response = await batch.executeAll();
71150

72151
for (const r of response.values()) {
73152
peoplePresence[r.id] = r.content;
153+
if (presenceCacheEnabled()) {
154+
cache.putValue(r.id, { presence: JSON.stringify(r.content) });
155+
}
74156
}
75157

76158
return peoplePresence;
77159
} catch (_) {
78160
try {
79-
const peoplePresence = {};
161+
/**
162+
* individual calls to getUserPresence as fallback
163+
* must filter out the contacts, which will either 404 or have PresenceUnknown response
164+
* caching will be handled by getUserPresence
165+
*/
80166
const response = await Promise.all(
81-
people.filter(person => person && person.id).map(person => getUserPresence(betaGraph, person.id))
167+
people
168+
.filter(
169+
person =>
170+
person &&
171+
person.id &&
172+
!peoplePresence[person.id] &&
173+
'personType' in person &&
174+
(person as any).personType.subclass === 'OrganizationUser'
175+
)
176+
.map(person => getUserPresence(betaGraph, person.id))
82177
);
83178

84179
for (const r of response) {

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ export async function getUsersForUserIds(graph: IGraph, userIds: string[]): Prom
241241
notInCache.push(id);
242242
}
243243
}
244-
245244
try {
246245
const responses = await batch.executeAll();
247246
// iterate over userIds to ensure the order of ids

packages/mgt/src/utils/Cache.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,27 @@ export interface CacheConfig {
4545
*/
4646
people: CacheOptions;
4747
/**
48-
* Cache options for users store
48+
* Cache options for photos store
4949
*
5050
* @type {CacheOptions}
5151
* @memberof CacheConfig
5252
*/
53-
users: CacheOptions;
53+
photos: CacheOptions;
5454
/**
55-
* Cache options for photos store
55+
* Cache options for presence store
5656
*
5757
* @type {CacheOptions}
5858
* @memberof CacheConfig
5959
*/
60-
photos: CacheOptions;
60+
61+
presence: CacheOptions;
62+
/**
63+
* Cache options for users store
64+
*
65+
* @type {CacheOptions}
66+
* @memberof CacheConfig
67+
*/
68+
users: CacheOptions;
6169
}
6270

6371
/**
@@ -139,6 +147,10 @@ export class CacheService {
139147
invalidationPeriod: null,
140148
isEnabled: true
141149
},
150+
presence: {
151+
invalidationPeriod: 300000,
152+
isEnabled: true
153+
},
142154
users: {
143155
invalidationPeriod: null,
144156
isEnabled: true

samples/examples/cache.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<script type="module" src="../../packages/mgt/dist/es6/mock/mgt-mock-provider.js"></script>
5-
5+
<title>MGT Cache Sample</title>
66
<script type="module">
77
import { CacheService } from '../../packages/mgt/dist/es6/index.js';
88

@@ -41,18 +41,18 @@
4141
<mgt-mock-provider></mgt-mock-provider>
4242

4343
<button id="CacheButton" type="button"></button>
44-
<!-- <button id="DisableCacheButton" type="button">Disable Cache</button> -->
4544
<button id="ClearCacheButton" type="button">Clear Cache</button>
4645

4746
<mgt-login></mgt-login>
4847
<mgt-person person-query="me" view="twoLines" person-card="hover" show-presence></mgt-person>
4948
<mgt-person user-id="4782e723-f4f4-4af3-a76e-25e3bab0d896" view="twoLines"></mgt-person>
5049
<mgt-people-picker></mgt-people-picker>
50+
<mgt-people-picker type="group"></mgt-people-picker>
5151
<mgt-people-picker group-id="02bd9fd6-8f93-4758-87c3-1fb73740a315"></mgt-people-picker>
5252
<mgt-teams-channel-picker></mgt-teams-channel-picker>
5353
<mgt-tasks data-source="todo"></mgt-tasks>
5454
<mgt-agenda group-by-day></mgt-agenda>
55-
<mgt-people show-presence></mgt-people>
55+
<mgt-people show-presence show-max="10"></mgt-people>
5656
<mgt-people group-id="02bd9fd6-8f93-4758-87c3-1fb73740a315"></mgt-people>
5757
<mgt-people user-ids="4782e723-f4f4-4af3-a76e-25e3bab0d896, f5289423-7233-4d60-831a-fe107a8551cc"></mgt-people>
5858
</body>

0 commit comments

Comments
 (0)