@webex/internal-plugin-dss 2.59.8 → 2.60.0-next.2

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/dss.ts CHANGED
@@ -2,10 +2,13 @@
2
2
  /*!
3
3
  * Copyright (c) 2015-2022 Cisco Systems, Inc. See LICENSE file.
4
4
  */
5
+ /* eslint-disable no-underscore-dangle */
5
6
  import uuid from 'uuid';
6
7
  import {WebexPlugin} from '@webex/webex-core';
7
8
  import '@webex/internal-plugin-mercury';
8
9
  import {range, isEqual, get} from 'lodash';
10
+
11
+ import {Timer} from '@webex/common-timers';
9
12
  import type {
10
13
  SearchOptions,
11
14
  LookupDetailOptions,
@@ -13,7 +16,6 @@ import type {
13
16
  LookupByEmailOptions,
14
17
  SearchPlaceOptions,
15
18
  } from './types';
16
-
17
19
  import {
18
20
  DSS_REGISTERED,
19
21
  DSS_UNREGISTERED,
@@ -22,7 +24,15 @@ import {
22
24
  DSS_SERVICE_NAME,
23
25
  DSS_SEARCH_MERCURY_EVENT,
24
26
  DSS_RESULT,
27
+ LOOKUP_DATA_PATH,
28
+ LOOKUP_FOUND_PATH,
29
+ LOOKUP_NOT_FOUND_PATH,
30
+ LOOKUP_REQUEST_KEY,
31
+ SEARCH_DATA_PATH,
25
32
  } from './constants';
33
+ import DssBatcher from './dss-batcher';
34
+ import {DssTimeoutError} from './dss-errors';
35
+ import {BatcherOptions, RequestOptions, RequestResult} from './types';
26
36
 
27
37
  const DSS = WebexPlugin.extend({
28
38
  namespace: 'DSS',
@@ -35,6 +45,18 @@ const DSS = WebexPlugin.extend({
35
45
  */
36
46
  registered: false,
37
47
 
48
+ /**
49
+ * Initializer
50
+ * @private
51
+ * @param {Object} attrs
52
+ * @param {Object} options
53
+ * @returns {undefined}
54
+ */
55
+ initialize(...args) {
56
+ Reflect.apply(WebexPlugin.prototype.initialize, this, args);
57
+ this.batchers = {};
58
+ },
59
+
38
60
  /**
39
61
  * Explicitly sets up the DSS plugin by connecting to mercury, and listening for DSS events.
40
62
  * @returns {Promise}
@@ -114,6 +136,7 @@ const DSS = WebexPlugin.extend({
114
136
  },
115
137
 
116
138
  /**
139
+ * constructs the event name based on request id
117
140
  * @param {UUID} requestId the id of the request
118
141
  * @returns {string}
119
142
  */
@@ -122,6 +145,7 @@ const DSS = WebexPlugin.extend({
122
145
  },
123
146
 
124
147
  /**
148
+ * Takes incoming data and triggers correct events
125
149
  * @param {Object} data the event data
126
150
  * @returns {undefined}
127
151
  */
@@ -134,40 +158,78 @@ const DSS = WebexPlugin.extend({
134
158
  * Makes the request to the directory service
135
159
  * @param {Object} options
136
160
  * @param {string} options.resource the URL to query
137
- * @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
161
+ * @param {Mixed} options.params additional params for the body of the request
162
+ * @param {string} options.dataPath the path to get the data in the result object
163
+ * @param {string} [options.foundPath] the path to get the lookups of the found data
164
+ * @param {string} [options.notFoundPath] the path to get the lookups of the not found data
165
+ * @returns {Promise<Object>} result Resolves with an object
166
+ * @returns {Array} result.resultArray an array of entities found
167
+ * @returns {Array} result.foundArray an array of the lookups of the found entities (if foundPath provided)
168
+ * @returns {Array} result.notFoundArray an array of the lookups of the not found entities (if notFoundPath provided)
169
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
140
170
  */
141
- _request(options) {
142
- const {resource, params, dataPath} = options;
171
+ _request(options: RequestOptions): Promise<RequestResult> {
172
+ const {resource, params, dataPath, foundPath, notFoundPath} = options;
143
173
 
174
+ const timeout = this.config.requestTimeout;
144
175
  const requestId = uuid.v4();
145
176
  const eventName = this._getResultEventName(requestId);
146
177
  const result = {};
147
- let expectedSeqNums;
178
+ let expectedSeqNums: string[];
179
+ let notFoundArray: unknown[];
180
+
181
+ return new Promise((resolve, reject) => {
182
+ const timer = new Timer(() => {
183
+ this.stopListening(this, eventName);
184
+ reject(new DssTimeoutError({requestId, timeout, resource, params}));
185
+ }, timeout);
148
186
 
149
- return new Promise((resolve) => {
150
187
  this.listenTo(this, eventName, (data) => {
151
- const resultData = get(data, dataPath);
188
+ timer.reset();
189
+ const resultData = get(data, dataPath, []);
190
+ let found;
152
191
 
153
- result[data.sequence] = resultData;
192
+ if (foundPath) {
193
+ found = get(data, foundPath, []);
194
+ }
195
+ result[data.sequence] = foundPath ? {resultData, found} : {resultData};
154
196
 
155
197
  if (data.finished) {
156
198
  expectedSeqNums = range(data.sequence + 1).map(String);
199
+ if (notFoundPath) {
200
+ notFoundArray = get(data, notFoundPath, []);
201
+ }
157
202
  }
158
203
 
159
204
  const done = isEqual(expectedSeqNums, Object.keys(result));
160
205
 
161
206
  if (done) {
162
- const resultArray = [];
207
+ timer.cancel();
208
+
209
+ const resultArray: any[] = [];
210
+ const foundArray: any[] = [];
211
+
163
212
  expectedSeqNums.forEach((index) => {
164
213
  const seqResult = result[index];
214
+
165
215
  if (seqResult) {
166
- resultArray.push(...seqResult);
216
+ resultArray.push(...seqResult.resultData);
217
+ if (foundPath) {
218
+ foundArray.push(...seqResult.found);
219
+ }
167
220
  }
168
221
  });
169
-
170
- resolve(resultArray);
222
+ const resolveValue: RequestResult = {
223
+ resultArray,
224
+ };
225
+
226
+ if (foundPath) {
227
+ resolveValue.foundArray = foundArray;
228
+ }
229
+ if (notFoundPath) {
230
+ resolveValue.notFoundArray = notFoundArray;
231
+ }
232
+ resolve(resolveValue);
171
233
  this.stopListening(this, eventName);
172
234
  }
173
235
  });
@@ -178,62 +240,128 @@ const DSS = WebexPlugin.extend({
178
240
  contentType: 'application/json',
179
241
  body: {requestId, ...params},
180
242
  });
243
+ timer.start();
181
244
  });
182
245
  },
183
246
 
247
+ /**
248
+ * Uses a batcher to make the request to the directory service
249
+ * @param {Object} options
250
+ * @param {string} options.resource the URL to query
251
+ * @param {string} options.value the id or email to lookup
252
+ * @returns {Promise} Resolves with an array of entities found
253
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
254
+ */
255
+ _batchedLookup(options: BatcherOptions) {
256
+ const {resource, lookupValue} = options;
257
+ const dataPath = LOOKUP_DATA_PATH;
258
+ const entitiesFoundPath = LOOKUP_FOUND_PATH;
259
+ const entitiesNotFoundPath = LOOKUP_NOT_FOUND_PATH;
260
+ const requestKey = LOOKUP_REQUEST_KEY;
261
+
262
+ this.batchers[resource] =
263
+ this.batchers[resource] ||
264
+ new DssBatcher({
265
+ resource,
266
+ dataPath,
267
+ entitiesFoundPath,
268
+ entitiesNotFoundPath,
269
+ requestKey,
270
+ parent: this,
271
+ });
272
+
273
+ return this.batchers[resource].request(lookupValue);
274
+ },
275
+
184
276
  /**
185
277
  * Retrieves detailed information about an entity
186
278
  * @param {Object} options
187
279
  * @param {UUID} options.id the id of the entity to lookup
188
- * @returns {Promise} Resolves with an array of entities found
280
+ * @returns {Promise} Resolves with the entity found or null if not found
281
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
189
282
  */
190
283
  lookupDetail(options: LookupDetailOptions) {
191
284
  const {id} = options;
192
285
 
286
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`;
287
+
193
288
  return this._request({
194
- dataPath: 'lookupResult.entities',
195
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`,
289
+ dataPath: LOOKUP_DATA_PATH,
290
+ foundPath: LOOKUP_FOUND_PATH,
291
+ resource,
292
+ }).then(({resultArray, foundArray}) => {
293
+ // TODO: find out what is actually returned!
294
+ if (foundArray[0] === id) {
295
+ return resultArray[0];
296
+ }
297
+
298
+ return null;
196
299
  });
197
300
  },
198
301
 
199
302
  /**
200
- * Retrieves basic information about a list entities within an organization
303
+ * Retrieves basic information about an entity within an organization
201
304
  * @param {Object} options
202
- * @param {UUID} options.ids the id of the entity to lookup
203
- * @param {UUID} options.entityProviderType the provider to query (optional)
204
- * @returns {Promise} Resolves with an array of entities found
305
+ * @param {UUID} options.id the id of the entity to lookup
306
+ * @param {UUID} [options.entityProviderType] the provider to query
307
+ * @param {Boolean} options.shouldBatch whether to batch the query, set to false for single immediate result (defaults to true)
308
+ * @returns {Promise} Resolves with the entity found or null if not found
309
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
205
310
  */
206
311
  lookup(options: LookupOptions) {
207
- const {ids, entityProviderType} = options;
312
+ const {id, entityProviderType, shouldBatch = true} = options;
208
313
 
209
314
  const resource = entityProviderType
210
315
  ? `/lookup/orgid/${this.webex.internal.device.orgId}/entityprovidertype/${entityProviderType}`
211
316
  : `/lookup/orgid/${this.webex.internal.device.orgId}/identities`;
212
317
 
318
+ if (shouldBatch) {
319
+ return this._batchedLookup({
320
+ resource,
321
+ lookupValue: id,
322
+ });
323
+ }
324
+
213
325
  return this._request({
214
- dataPath: 'lookupResult.entities',
326
+ dataPath: LOOKUP_DATA_PATH,
327
+ foundPath: LOOKUP_FOUND_PATH,
215
328
  resource,
216
329
  params: {
217
- lookupValues: ids,
330
+ [LOOKUP_REQUEST_KEY]: [id],
218
331
  },
332
+ }).then(({resultArray, foundArray}) => {
333
+ if (foundArray[0] === id) {
334
+ return resultArray[0];
335
+ }
336
+
337
+ return null;
219
338
  });
220
339
  },
221
340
 
222
341
  /**
223
- * Retrieves basic information about a list entities within an organization
342
+ * Retrieves basic information about an enitity within an organization
224
343
  * @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
344
+ * @param {UUID} options.email the email of the entity to lookup
345
+ * @returns {Promise} Resolves with the entity found or rejects if not found
346
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
227
347
  */
228
348
  lookupByEmail(options: LookupByEmailOptions) {
229
- const {emails} = options;
349
+ const {email} = options;
350
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/emails`;
230
351
 
231
352
  return this._request({
232
- dataPath: 'lookupResult.entities',
233
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/emails`,
353
+ dataPath: LOOKUP_DATA_PATH,
354
+ foundPath: LOOKUP_FOUND_PATH,
355
+ resource,
234
356
  params: {
235
- lookupValues: emails,
357
+ [LOOKUP_REQUEST_KEY]: [email],
236
358
  },
359
+ }).then(({resultArray, foundArray}) => {
360
+ if (foundArray[0] === email) {
361
+ return resultArray[0];
362
+ }
363
+
364
+ return null;
237
365
  });
238
366
  },
239
367
 
@@ -244,19 +372,20 @@ const DSS = WebexPlugin.extend({
244
372
  * @param {string[]} options.queryString A query string that will be transformed into a Directory search filter query. It is used to search the following fields: username, givenName, familyName, displayName and email
245
373
  * @param {number} options.resultSize The maximum number of results returned from each provider
246
374
  * @returns {Promise} Resolves with an array of entities found
375
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
247
376
  */
248
377
  search(options: SearchOptions) {
249
378
  const {requestedTypes, resultSize, queryString} = options;
250
379
 
251
380
  return this._request({
252
- dataPath: 'directoryEntities',
381
+ dataPath: SEARCH_DATA_PATH,
253
382
  resource: `/search/orgid/${this.webex.internal.device.orgId}/entities`,
254
383
  params: {
255
384
  queryString,
256
385
  resultSize,
257
386
  requestedTypes,
258
387
  },
259
- });
388
+ }).then(({resultArray}) => resultArray);
260
389
  },
261
390
 
262
391
  /**
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
@@ -1,3 +1,17 @@
1
+ export interface RequestOptions {
2
+ resource: string;
3
+ dataPath: string;
4
+ foundPath?: string;
5
+ notFoundPath?: string;
6
+ params?: Record<string, unknown>;
7
+ }
8
+
9
+ export interface RequestResult {
10
+ foundArray?: any[];
11
+ notFoundArray?: any[];
12
+ resultArray: any[];
13
+ }
14
+
1
15
  export interface LookupDetailOptions {
2
16
  id: string;
3
17
  }
@@ -10,14 +24,16 @@ export enum EntityProviderType {
10
24
  }
11
25
 
12
26
  export interface LookupOptions {
13
- ids: string[];
27
+ id: string;
14
28
  entityProviderType?: EntityProviderType;
29
+ shouldBatch?: boolean;
15
30
  }
16
31
 
17
32
  export interface LookupByEmailOptions {
18
- emails: string[];
33
+ email: string;
19
34
  }
20
35
 
36
+ // eslint-disable-next-line no-shadow
21
37
  export enum SearchType {
22
38
  PERSON = 'PERSON',
23
39
  CALLING_SERVICE = 'CALLING_SERVICE',
@@ -32,6 +48,11 @@ export interface SearchOptions {
32
48
  queryString: string;
33
49
  }
34
50
 
51
+ export interface BatcherOptions {
52
+ resource: string;
53
+ lookupValue: string;
54
+ }
55
+
35
56
  export interface SearchPlaceOptions {
36
57
  resultSize: number;
37
58
  queryString: string;
@@ -0,0 +1,139 @@
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(Promise.resolve('some return value'));
39
+
40
+ const result = await batcher.submitHttpRequest(['id1']);
41
+
42
+ expect(webex.internal.dss._request.getCall(0).args).to.deep.equal([
43
+ {
44
+ dataPath: 'fakeDataPath',
45
+ foundPath: 'fakeEntitiesFoundPath',
46
+ notFoundPath: 'fakeEntitiesNotFoundPath',
47
+ resource: 'fakeResource',
48
+ params: {
49
+ lookupValues: ['id1'],
50
+ },
51
+ },
52
+ ]);
53
+ expect(result).to.equal('some return value');
54
+ });
55
+ });
56
+
57
+ describe('#handleHttpSuccess', () => {
58
+ it('calls acceptItem on each found or not found entity', async () => {
59
+ batcher.acceptItem = sinon.stub().returns(Promise.resolve(undefined));
60
+ const res = {
61
+ resultArray: ['item1', 'item2'],
62
+ foundArray: ['id1', 'id2'],
63
+ notFoundArray: ['id3', 'id4'],
64
+ };
65
+ const result = await batcher.handleHttpSuccess(res);
66
+
67
+ expect(batcher.acceptItem.getCalls().map((call) => call.args)).to.deep.equal([
68
+ [{requestValue: 'id1', entity: 'item1'}],
69
+ [{requestValue: 'id2', entity: 'item2'}],
70
+ [{requestValue: 'id3', entity: null}],
71
+ [{requestValue: 'id4', entity: null}],
72
+ ]);
73
+ expect(result).to.deep.equal([undefined, undefined, undefined, undefined]);
74
+ });
75
+ });
76
+
77
+ describe('#didItemFail', () => {
78
+ it('returns true if item.entity is null', async () => {
79
+ const result = await batcher.didItemFail({entity: null});
80
+
81
+ expect(result).to.be.true;
82
+ });
83
+
84
+ it('returns true if item.entity is not null', async () => {
85
+ const result = await batcher.didItemFail({entity: 'something'});
86
+
87
+ expect(result).to.be.false;
88
+ });
89
+ });
90
+
91
+ describe('#handleItemFailure', () => {
92
+ it('resolves defer for item with null', async () => {
93
+ const defer = new Defer();
94
+
95
+ batcher.getDeferredForResponse = sinon.stub().returns(Promise.resolve(defer));
96
+ const result = await batcher.handleItemFailure({requestValue: 'some request'});
97
+ const deferValue = await defer.promise;
98
+
99
+ expect(batcher.getDeferredForResponse.getCall(0).args).to.deep.equal([
100
+ {requestValue: 'some request'},
101
+ ]);
102
+ expect(result).to.be.undefined;
103
+ expect(deferValue).to.be.null;
104
+ });
105
+ });
106
+
107
+ describe('#handleItemSuccess', () => {
108
+ it('resolves defer for item with item.entity', async () => {
109
+ const defer = new Defer();
110
+
111
+ batcher.getDeferredForResponse = sinon.stub().returns(Promise.resolve(defer));
112
+ const result = await batcher.handleItemSuccess({entity: 'some entity'});
113
+ const deferValue = await defer.promise;
114
+
115
+ expect(batcher.getDeferredForResponse.getCall(0).args).to.deep.equal([
116
+ {entity: 'some entity'},
117
+ ]);
118
+ expect(result).to.be.undefined;
119
+ expect(deferValue).to.equal('some entity');
120
+ });
121
+ });
122
+
123
+ describe('#fingerprintRequest', () => {
124
+ it('returns request', async () => {
125
+ const result = await batcher.fingerprintRequest('some request');
126
+
127
+ expect(result).to.equal('some request');
128
+ });
129
+ });
130
+
131
+ describe('#fingerprintResponse', () => {
132
+ it('returns response requestValue', async () => {
133
+ const result = await batcher.fingerprintResponse({requestValue: 'some request'});
134
+
135
+ expect(result).to.equal('some request');
136
+ });
137
+ });
138
+ });
139
+ });