@webex/internal-plugin-dss 3.0.0-beta.136 → 3.0.0-beta.137

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.ts ADDED
@@ -0,0 +1,25 @@
1
+ /*!
2
+ * Copyright (c) 2015-2022 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ export default {
6
+ dss: {
7
+ /**
8
+ * Debounce wait (ms) before sending a dss request (gap between lookups that will trigger a request)
9
+ * @type {Number}
10
+ */
11
+ batcherWait: 50,
12
+
13
+ /**
14
+ * Maximum queue size before sending a dss request
15
+ * @type {Number}
16
+ */
17
+ batcherMaxCalls: 50,
18
+
19
+ /**
20
+ * Debounce max wait (ms) before sending a dss request (time from first lookup that will trigger a request)
21
+ * @type {Number}
22
+ */
23
+ batcherMaxWait: 150,
24
+ },
25
+ };
package/src/constants.ts CHANGED
@@ -12,3 +12,8 @@ export const SEARCH_TYPES = {
12
12
  ROOM: 'ROOM',
13
13
  ROBOT: 'ROBOT',
14
14
  };
15
+ export const LOOKUP_DATA_PATH = 'lookupResult.entities';
16
+ export const LOOKUP_FOUND_PATH = 'lookupResult.entitiesFound';
17
+ export const LOOKUP_NOT_FOUND_PATH = 'lookupResult.entitiesNotFound';
18
+ export const LOOKUP_REQUEST_KEY = 'lookupValues';
19
+ export const SEARCH_DATA_PATH = 'directoryEntities';
@@ -0,0 +1,129 @@
1
+ /*!
2
+ * Copyright (c) 2015-2022 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+ /* eslint-disable no-underscore-dangle */
5
+
6
+ import {Batcher} from '@webex/webex-core';
7
+
8
+ /**
9
+ * @class
10
+ */
11
+ const DssBatcher = Batcher.extend({
12
+ namespace: 'DSS',
13
+
14
+ props: {
15
+ resource: {
16
+ type: 'string',
17
+ required: true,
18
+ setOnce: true,
19
+ allowNull: false,
20
+ },
21
+ dataPath: {
22
+ type: 'string',
23
+ required: true,
24
+ setOnce: true,
25
+ allowNull: false,
26
+ },
27
+ entitiesFoundPath: {
28
+ type: 'string',
29
+ required: true,
30
+ setOnce: true,
31
+ allowNull: false,
32
+ },
33
+ entitiesNotFoundPath: {
34
+ type: 'string',
35
+ required: true,
36
+ setOnce: true,
37
+ allowNull: false,
38
+ },
39
+ requestKey: {
40
+ type: 'string',
41
+ required: true,
42
+ setOnce: true,
43
+ allowNull: false,
44
+ },
45
+ },
46
+
47
+ /**
48
+ * Submits the DSS request
49
+ * @param {Object} payload
50
+ * @returns {Promise<Array>}
51
+ */
52
+ submitHttpRequest(payload) {
53
+ return this.parent._request({
54
+ dataPath: this.dataPath,
55
+ foundPath: this.entitiesFoundPath,
56
+ notFoundPath: this.entitiesNotFoundPath,
57
+ resource: this.resource,
58
+ params: {
59
+ lookupValues: payload,
60
+ },
61
+ });
62
+ },
63
+
64
+ /**
65
+ * Actions taken when the http request returns a success
66
+ * @param {Promise<Array>} res
67
+ * @returns {Promise<undefined>}
68
+ */
69
+ handleHttpSuccess(res) {
70
+ const successItems = res.foundArray.map((requestValue, index) => ({
71
+ requestValue,
72
+ entity: res.resultArray[index],
73
+ }));
74
+ const failureItems = res.notFoundArray.map((requestValue) => ({requestValue, entity: null}));
75
+
76
+ return Promise.all(successItems.concat(failureItems).map((item) => this.acceptItem(item)));
77
+ },
78
+
79
+ /**
80
+ * Checks if the item was found
81
+ * @param {Object} item
82
+ * @returns {Promise<Boolean>}
83
+ */
84
+ didItemFail(item) {
85
+ return Promise.resolve(item.entity === null);
86
+ },
87
+
88
+ /**
89
+ * Finds the Defer for the specified item and resolves its promise with null
90
+ * @param {Object} item
91
+ * @returns {Promise<undefined>}
92
+ */
93
+ handleItemFailure(item) {
94
+ return this.getDeferredForResponse(item).then((defer) => {
95
+ defer.resolve(null);
96
+ });
97
+ },
98
+
99
+ /**
100
+ * Finds the Defer for the specified item and resolves its promise
101
+ * @param {Object} item
102
+ * @returns {Promise<undefined>}
103
+ */
104
+ handleItemSuccess(item) {
105
+ return this.getDeferredForResponse(item).then((defer) => {
106
+ defer.resolve(item.entity);
107
+ });
108
+ },
109
+
110
+ /**
111
+ * Returns a promise with the unique key for the item
112
+ * @param {Object} item
113
+ * @returns {Promise}
114
+ */
115
+ fingerprintRequest(item) {
116
+ return Promise.resolve(item);
117
+ },
118
+
119
+ /**
120
+ * Returns a promise with the unique key for the item
121
+ * @param {Object} item
122
+ * @returns {Promise}
123
+ */
124
+ fingerprintResponse(item) {
125
+ return Promise.resolve(item.requestValue);
126
+ },
127
+ });
128
+
129
+ export default DssBatcher;
package/src/dss.ts CHANGED
@@ -22,7 +22,13 @@ import {
22
22
  DSS_SERVICE_NAME,
23
23
  DSS_SEARCH_MERCURY_EVENT,
24
24
  DSS_RESULT,
25
+ LOOKUP_DATA_PATH,
26
+ LOOKUP_FOUND_PATH,
27
+ LOOKUP_NOT_FOUND_PATH,
28
+ LOOKUP_REQUEST_KEY,
29
+ SEARCH_DATA_PATH,
25
30
  } from './constants';
31
+ import DssBatcher from './dss-batcher';
26
32
 
27
33
  const DSS = WebexPlugin.extend({
28
34
  namespace: 'DSS',
@@ -35,6 +41,18 @@ const DSS = WebexPlugin.extend({
35
41
  */
36
42
  registered: false,
37
43
 
44
+ /**
45
+ * Initializer
46
+ * @private
47
+ * @param {Object} attrs
48
+ * @param {Object} options
49
+ * @returns {undefined}
50
+ */
51
+ initialize(...args) {
52
+ Reflect.apply(WebexPlugin.prototype.initialize, this, args);
53
+ this.batchers = {};
54
+ },
55
+
38
56
  /**
39
57
  * Explicitly sets up the DSS plugin by connecting to mercury, and listening for DSS events.
40
58
  * @returns {Promise}
@@ -114,6 +132,7 @@ const DSS = WebexPlugin.extend({
114
132
  },
115
133
 
116
134
  /**
135
+ * constructs the event name based on request id
117
136
  * @param {UUID} requestId the id of the request
118
137
  * @returns {string}
119
138
  */
@@ -122,6 +141,7 @@ const DSS = WebexPlugin.extend({
122
141
  },
123
142
 
124
143
  /**
144
+ * Takes incoming data and triggers correct events
125
145
  * @param {Object} data the event data
126
146
  * @returns {undefined}
127
147
  */
@@ -135,39 +155,67 @@ const DSS = WebexPlugin.extend({
135
155
  * @param {Object} options
136
156
  * @param {string} options.resource the URL to query
137
157
  * @param {string} options.params additional params for the body of the request
138
- * @param {string} options.dataPath to path to get the data in the result object
139
- * @returns {Promise} Resolves with an array of entities found
158
+ * @param {string} options.dataPath the path to get the data in the result object
159
+ * @param {string} options.foundPath the path to get the lookups of the found data (optional)
160
+ * @param {string} options.notFoundPath the path to get the lookups of the not found data (optional)
161
+ * @returns {Promise<Object>} result Resolves with an object
162
+ * @returns {Array} result.resultArray an array of entities found
163
+ * @returns {Array} result.foundArray an array of the lookups of the found entities (if foundPath provided)
164
+ * @returns {Array} result.notFoundArray an array of the lookups of the not found entities (if notFoundPath provided)
140
165
  */
141
166
  _request(options) {
142
- const {resource, params, dataPath} = options;
167
+ const {resource, params, dataPath, foundPath, notFoundPath} = options;
143
168
 
144
169
  const requestId = uuid.v4();
145
170
  const eventName = this._getResultEventName(requestId);
146
171
  const result = {};
147
172
  let expectedSeqNums;
173
+ let notFoundArray;
148
174
 
149
175
  return new Promise((resolve) => {
150
176
  this.listenTo(this, eventName, (data) => {
151
- const resultData = get(data, dataPath);
177
+ const resultData = get(data, dataPath, []);
178
+ let found;
152
179
 
153
- result[data.sequence] = resultData;
180
+ if (foundPath) {
181
+ found = get(data, foundPath, []);
182
+ }
183
+ result[data.sequence] = foundPath ? {resultData, found} : {resultData};
154
184
 
155
185
  if (data.finished) {
156
186
  expectedSeqNums = range(data.sequence + 1).map(String);
187
+ if (notFoundPath) {
188
+ notFoundArray = get(data, notFoundPath, []);
189
+ }
157
190
  }
158
191
 
159
192
  const done = isEqual(expectedSeqNums, Object.keys(result));
160
193
 
161
194
  if (done) {
162
- const resultArray = [];
195
+ const resultArray: any[] = [];
196
+ const foundArray: any[] = [];
197
+
163
198
  expectedSeqNums.forEach((index) => {
164
199
  const seqResult = result[index];
200
+
165
201
  if (seqResult) {
166
- resultArray.push(...seqResult);
202
+ resultArray.push(...seqResult.resultData);
203
+ if (foundPath) {
204
+ foundArray.push(...seqResult.found);
205
+ }
167
206
  }
168
207
  });
169
-
170
- resolve(resultArray);
208
+ const resolveValue: {resultArray: any[]; foundArray?: any[]; notFoundArray?: any[]} = {
209
+ resultArray,
210
+ };
211
+
212
+ if (foundPath) {
213
+ resolveValue.foundArray = foundArray;
214
+ }
215
+ if (notFoundPath) {
216
+ resolveValue.notFoundArray = notFoundArray;
217
+ }
218
+ resolve(resolveValue);
171
219
  this.stopListening(this, eventName);
172
220
  }
173
221
  });
@@ -181,59 +229,120 @@ const DSS = WebexPlugin.extend({
181
229
  });
182
230
  },
183
231
 
232
+ /**
233
+ * Uses a batcher to make the request to the directory service
234
+ * @param {Object} options
235
+ * @param {string} options.resource the URL to query
236
+ * @param {string} options.value the id or email to lookup
237
+ * @returns {Promise} Resolves with an array of entities found
238
+ */
239
+ _batchedLookup(options) {
240
+ const {resource, lookupValue} = options;
241
+ const dataPath = LOOKUP_DATA_PATH;
242
+ const entitiesFoundPath = LOOKUP_FOUND_PATH;
243
+ const entitiesNotFoundPath = LOOKUP_NOT_FOUND_PATH;
244
+ const requestKey = LOOKUP_REQUEST_KEY;
245
+
246
+ this.batchers[resource] =
247
+ this.batchers[resource] ||
248
+ new DssBatcher({
249
+ resource,
250
+ dataPath,
251
+ entitiesFoundPath,
252
+ entitiesNotFoundPath,
253
+ requestKey,
254
+ parent: this,
255
+ });
256
+
257
+ return this.batchers[resource].request(lookupValue);
258
+ },
259
+
184
260
  /**
185
261
  * Retrieves detailed information about an entity
186
262
  * @param {Object} options
187
263
  * @param {UUID} options.id the id of the entity to lookup
188
- * @returns {Promise} Resolves with an array of entities found
264
+ * @returns {Promise} Resolves with the entity found or null if not found
189
265
  */
190
266
  lookupDetail(options: LookupDetailOptions) {
191
267
  const {id} = options;
192
268
 
269
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`;
270
+
193
271
  return this._request({
194
- dataPath: 'lookupResult.entities',
195
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`,
272
+ dataPath: LOOKUP_DATA_PATH,
273
+ foundPath: LOOKUP_FOUND_PATH,
274
+ resource,
275
+ }).then(({resultArray, foundArray}) => {
276
+ // TODO: find out what is actually returned!
277
+ if (foundArray[0] === id) {
278
+ return resultArray[0];
279
+ }
280
+
281
+ return null;
196
282
  });
197
283
  },
198
284
 
199
285
  /**
200
- * Retrieves basic information about a list entities within an organization
286
+ * Retrieves basic information about an entity within an organization
201
287
  * @param {Object} options
202
- * @param {UUID} options.ids the id of the entity to lookup
288
+ * @param {UUID} options.id the id of the entity to lookup
203
289
  * @param {UUID} options.entityProviderType the provider to query (optional)
204
- * @returns {Promise} Resolves with an array of entities found
290
+ * @param {Boolean} options.shouldBatch whether to batch the query, set to false for single immediate result (defaults to true)
291
+ * @returns {Promise} Resolves with the entity found or null if not found
205
292
  */
206
293
  lookup(options: LookupOptions) {
207
- const {ids, entityProviderType} = options;
294
+ const {id, entityProviderType, shouldBatch = true} = options;
208
295
 
209
296
  const resource = entityProviderType
210
297
  ? `/lookup/orgid/${this.webex.internal.device.orgId}/entityprovidertype/${entityProviderType}`
211
298
  : `/lookup/orgid/${this.webex.internal.device.orgId}/identities`;
212
299
 
300
+ if (shouldBatch) {
301
+ return this._batchedLookup({
302
+ resource,
303
+ lookupValue: id,
304
+ });
305
+ }
306
+
213
307
  return this._request({
214
- dataPath: 'lookupResult.entities',
308
+ dataPath: LOOKUP_DATA_PATH,
309
+ foundPath: LOOKUP_FOUND_PATH,
215
310
  resource,
216
311
  params: {
217
- lookupValues: ids,
312
+ [LOOKUP_REQUEST_KEY]: [id],
218
313
  },
314
+ }).then(({resultArray, foundArray}) => {
315
+ if (foundArray[0] === id) {
316
+ return resultArray[0];
317
+ }
318
+
319
+ return null;
219
320
  });
220
321
  },
221
322
 
222
323
  /**
223
- * Retrieves basic information about a list entities within an organization
324
+ * Retrieves basic information about an enitity within an organization
224
325
  * @param {Object} options
225
- * @param {UUID} options.emails the emails of the entities to lookup
226
- * @returns {Promise} Resolves with an array of entities found
326
+ * @param {UUID} options.email the email of the entity to lookup
327
+ * @returns {Promise} Resolves with the entity found or rejects if not found
227
328
  */
228
329
  lookupByEmail(options: LookupByEmailOptions) {
229
- const {emails} = options;
330
+ const {email} = options;
331
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/emails`;
230
332
 
231
333
  return this._request({
232
- dataPath: 'lookupResult.entities',
233
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/emails`,
334
+ dataPath: LOOKUP_DATA_PATH,
335
+ foundPath: LOOKUP_FOUND_PATH,
336
+ resource,
234
337
  params: {
235
- lookupValues: emails,
338
+ [LOOKUP_REQUEST_KEY]: [email],
236
339
  },
340
+ }).then(({resultArray, foundArray}) => {
341
+ if (foundArray[0] === email) {
342
+ return resultArray[0];
343
+ }
344
+
345
+ return null;
237
346
  });
238
347
  },
239
348
 
@@ -249,14 +358,14 @@ const DSS = WebexPlugin.extend({
249
358
  const {requestedTypes, resultSize, queryString} = options;
250
359
 
251
360
  return this._request({
252
- dataPath: 'directoryEntities',
361
+ dataPath: SEARCH_DATA_PATH,
253
362
  resource: `/search/orgid/${this.webex.internal.device.orgId}/entities`,
254
363
  params: {
255
364
  queryString,
256
365
  resultSize,
257
366
  requestedTypes,
258
367
  },
259
- });
368
+ }).then(({resultArray}) => resultArray);
260
369
  },
261
370
  });
262
371
 
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import {registerInternalPlugin} from '@webex/webex-core';
2
2
 
3
3
  import DSS from './dss';
4
+ import config from './config';
4
5
 
5
- registerInternalPlugin('dss', DSS);
6
+ registerInternalPlugin('dss', DSS, {config});
6
7
 
7
8
  export {default} from './dss';
package/src/types.ts CHANGED
@@ -11,12 +11,13 @@ export enum EntityProviderType {
11
11
  }
12
12
 
13
13
  export interface LookupOptions {
14
- ids: string[];
14
+ id: string;
15
15
  entityProviderType?: EntityProviderType;
16
+ shouldBatch?: boolean;
16
17
  }
17
18
 
18
19
  export interface LookupByEmailOptions {
19
- emails: string[];
20
+ email: string;
20
21
  }
21
22
 
22
23
  // eslint-disable-next-line no-shadow
@@ -0,0 +1,146 @@
1
+ /*!
2
+ * Copyright (c) 2015-2022 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+ /* eslint-disable no-underscore-dangle */
5
+
6
+ import {expect} from '@webex/test-helper-chai';
7
+ import sinon from 'sinon';
8
+ import DSS from '@webex/internal-plugin-dss';
9
+ import DssBatcher from '@webex/internal-plugin-dss/src/dss-batcher';
10
+ import MockWebex from '@webex/test-helper-mock-webex';
11
+ import {Defer} from '@webex/common';
12
+
13
+ describe('plugin-dss', () => {
14
+ describe('DssBatcher', () => {
15
+ let batcher;
16
+ let webex;
17
+
18
+ beforeEach(() => {
19
+ webex = MockWebex({
20
+ canAuthorize: false,
21
+ children: {
22
+ dss: DSS,
23
+ },
24
+ });
25
+ batcher = new DssBatcher({
26
+ resource: 'fakeResource',
27
+ dataPath: 'fakeDataPath',
28
+ entitiesFoundPath: 'fakeEntitiesFoundPath',
29
+ entitiesNotFoundPath: 'fakeEntitiesNotFoundPath',
30
+ requestKey: 'fakeRequestKey',
31
+ parent: webex.internal.dss,
32
+ });
33
+ webex.internal.dss.batchers.fakeResource = batcher;
34
+ });
35
+
36
+ describe('#submitHttpRequest', () => {
37
+ it('calls dss._request with expected params', async () => {
38
+ webex.internal.dss._request = sinon.stub().returns(
39
+ Promise.resolve('some return value')
40
+ );
41
+
42
+ const result = await batcher.submitHttpRequest(['id1']);
43
+
44
+ expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
45
+ {
46
+ dataPath: 'fakeDataPath',
47
+ foundPath: 'fakeEntitiesFoundPath',
48
+ notFoundPath: 'fakeEntitiesNotFoundPath',
49
+ resource: 'fakeResource',
50
+ params: {
51
+ lookupValues: ['id1'],
52
+ },
53
+ },
54
+ ]);
55
+ expect(result).to.equal('some return value');
56
+ });
57
+ });
58
+
59
+ describe('#handleHttpSuccess', () => {
60
+ it('calls acceptItem on each found or not found entity', async () => {
61
+ batcher.acceptItem = sinon.stub().returns(Promise.resolve(undefined));
62
+ const res = {
63
+ resultArray: [
64
+ 'item1',
65
+ 'item2',
66
+ ],
67
+ foundArray: [
68
+ 'id1',
69
+ 'id2',
70
+ ],
71
+ notFoundArray: [
72
+ 'id3',
73
+ 'id4',
74
+ ],
75
+ };
76
+ const result = await batcher.handleHttpSuccess(res);
77
+
78
+ expect(batcher.acceptItem.getCalls().map((call) => call.args)).to.deep.equal([
79
+ [{requestValue: 'id1', entity: 'item1'}],
80
+ [{requestValue: 'id2', entity: 'item2'}],
81
+ [{requestValue: 'id3', entity: null}],
82
+ [{requestValue: 'id4', entity: null}],
83
+ ]);
84
+ expect(result).to.deep.equal([undefined, undefined, undefined, undefined]);
85
+ });
86
+ });
87
+
88
+ describe('#didItemFail', () => {
89
+ it('returns true if item.entity is null', async () => {
90
+ const result = await batcher.didItemFail({entity: null});
91
+
92
+ expect(result).to.be.true;
93
+ });
94
+
95
+ it('returns true if item.entity is not null', async () => {
96
+ const result = await batcher.didItemFail({entity: 'something'});
97
+
98
+ expect(result).to.be.false;
99
+ });
100
+ });
101
+
102
+ describe('#handleItemFailure', () => {
103
+ it('resolves defer for item with null', async () => {
104
+ const defer = new Defer();
105
+
106
+ batcher.getDeferredForResponse = sinon.stub().returns(Promise.resolve(defer));
107
+ const result = await batcher.handleItemFailure({requestValue: 'some request'});
108
+ const deferValue = await defer.promise;
109
+
110
+ expect(batcher.getDeferredForResponse.getCall(0).args).to.deep.equal([{requestValue: 'some request'}]);
111
+ expect(result).to.be.undefined;
112
+ expect(deferValue).to.be.null;
113
+ });
114
+ });
115
+
116
+ describe('#handleItemSuccess', () => {
117
+ it('resolves defer for item with item.entity', async () => {
118
+ const defer = new Defer();
119
+
120
+ batcher.getDeferredForResponse = sinon.stub().returns(Promise.resolve(defer));
121
+ const result = await batcher.handleItemSuccess({entity: 'some entity'});
122
+ const deferValue = await defer.promise;
123
+
124
+ expect(batcher.getDeferredForResponse.getCall(0).args).to.deep.equal([{entity: 'some entity'}]);
125
+ expect(result).to.be.undefined;
126
+ expect(deferValue).to.equal('some entity');
127
+ });
128
+ });
129
+
130
+ describe('#fingerprintRequest', () => {
131
+ it('returns request', async () => {
132
+ const result = await batcher.fingerprintRequest('some request');
133
+
134
+ expect(result).to.equal('some request');
135
+ });
136
+ });
137
+
138
+ describe('#fingerprintResponse', () => {
139
+ it('returns response requestValue', async () => {
140
+ const result = await batcher.fingerprintResponse({requestValue: 'some request'});
141
+
142
+ expect(result).to.equal('some request');
143
+ });
144
+ });
145
+ });
146
+ });