@webex/internal-plugin-dss 3.0.0-beta.21 → 3.0.0-beta.211

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,17 +2,19 @@
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,
12
15
  LookupOptions,
13
16
  LookupByEmailOptions,
14
17
  } from './types';
15
-
16
18
  import {
17
19
  DSS_REGISTERED,
18
20
  DSS_UNREGISTERED,
@@ -21,7 +23,15 @@ import {
21
23
  DSS_SERVICE_NAME,
22
24
  DSS_SEARCH_MERCURY_EVENT,
23
25
  DSS_RESULT,
26
+ LOOKUP_DATA_PATH,
27
+ LOOKUP_FOUND_PATH,
28
+ LOOKUP_NOT_FOUND_PATH,
29
+ LOOKUP_REQUEST_KEY,
30
+ SEARCH_DATA_PATH,
24
31
  } from './constants';
32
+ import DssBatcher from './dss-batcher';
33
+ import {DssTimeoutError} from './dss-errors';
34
+ import {BatcherOptions, RequestOptions, RequestResult} from './types';
25
35
 
26
36
  const DSS = WebexPlugin.extend({
27
37
  namespace: 'DSS',
@@ -34,6 +44,18 @@ const DSS = WebexPlugin.extend({
34
44
  */
35
45
  registered: false,
36
46
 
47
+ /**
48
+ * Initializer
49
+ * @private
50
+ * @param {Object} attrs
51
+ * @param {Object} options
52
+ * @returns {undefined}
53
+ */
54
+ initialize(...args) {
55
+ Reflect.apply(WebexPlugin.prototype.initialize, this, args);
56
+ this.batchers = {};
57
+ },
58
+
37
59
  /**
38
60
  * Explicitly sets up the DSS plugin by connecting to mercury, and listening for DSS events.
39
61
  * @returns {Promise}
@@ -113,6 +135,7 @@ const DSS = WebexPlugin.extend({
113
135
  },
114
136
 
115
137
  /**
138
+ * constructs the event name based on request id
116
139
  * @param {UUID} requestId the id of the request
117
140
  * @returns {string}
118
141
  */
@@ -121,6 +144,7 @@ const DSS = WebexPlugin.extend({
121
144
  },
122
145
 
123
146
  /**
147
+ * Takes incoming data and triggers correct events
124
148
  * @param {Object} data the event data
125
149
  * @returns {undefined}
126
150
  */
@@ -133,40 +157,78 @@ const DSS = WebexPlugin.extend({
133
157
  * Makes the request to the directory service
134
158
  * @param {Object} options
135
159
  * @param {string} options.resource the URL to query
136
- * @param {string} options.params additional params for the body of the request
137
- * @param {string} options.dataPath to path to get the data in the result object
138
- * @returns {Promise} Resolves with an array of entities found
160
+ * @param {Mixed} options.params additional params for the body of the request
161
+ * @param {string} options.dataPath the path to get the data in the result object
162
+ * @param {string} [options.foundPath] the path to get the lookups of the found data
163
+ * @param {string} [options.notFoundPath] the path to get the lookups of the not found data
164
+ * @returns {Promise<Object>} result Resolves with an object
165
+ * @returns {Array} result.resultArray an array of entities found
166
+ * @returns {Array} result.foundArray an array of the lookups of the found entities (if foundPath provided)
167
+ * @returns {Array} result.notFoundArray an array of the lookups of the not found entities (if notFoundPath provided)
168
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
139
169
  */
140
- _request(options) {
141
- const {resource, params, dataPath} = options;
170
+ _request(options: RequestOptions): Promise<RequestResult> {
171
+ const {resource, params, dataPath, foundPath, notFoundPath} = options;
142
172
 
173
+ const timeout = this.config.requestTimeout;
143
174
  const requestId = uuid.v4();
144
175
  const eventName = this._getResultEventName(requestId);
145
176
  const result = {};
146
- let expectedSeqNums;
177
+ let expectedSeqNums: string[];
178
+ let notFoundArray: unknown[];
179
+
180
+ return new Promise((resolve, reject) => {
181
+ const timer = new Timer(() => {
182
+ this.stopListening(this, eventName);
183
+ reject(new DssTimeoutError({requestId, timeout, resource, params}));
184
+ }, timeout);
147
185
 
148
- return new Promise((resolve) => {
149
186
  this.listenTo(this, eventName, (data) => {
150
- const resultData = get(data, dataPath);
187
+ timer.reset();
188
+ const resultData = get(data, dataPath, []);
189
+ let found;
151
190
 
152
- result[data.sequence] = resultData;
191
+ if (foundPath) {
192
+ found = get(data, foundPath, []);
193
+ }
194
+ result[data.sequence] = foundPath ? {resultData, found} : {resultData};
153
195
 
154
196
  if (data.finished) {
155
197
  expectedSeqNums = range(data.sequence + 1).map(String);
198
+ if (notFoundPath) {
199
+ notFoundArray = get(data, notFoundPath, []);
200
+ }
156
201
  }
157
202
 
158
203
  const done = isEqual(expectedSeqNums, Object.keys(result));
159
204
 
160
205
  if (done) {
161
- const resultArray = [];
206
+ timer.cancel();
207
+
208
+ const resultArray: any[] = [];
209
+ const foundArray: any[] = [];
210
+
162
211
  expectedSeqNums.forEach((index) => {
163
212
  const seqResult = result[index];
213
+
164
214
  if (seqResult) {
165
- resultArray.push(...seqResult);
215
+ resultArray.push(...seqResult.resultData);
216
+ if (foundPath) {
217
+ foundArray.push(...seqResult.found);
218
+ }
166
219
  }
167
220
  });
168
-
169
- resolve(resultArray);
221
+ const resolveValue: RequestResult = {
222
+ resultArray,
223
+ };
224
+
225
+ if (foundPath) {
226
+ resolveValue.foundArray = foundArray;
227
+ }
228
+ if (notFoundPath) {
229
+ resolveValue.notFoundArray = notFoundArray;
230
+ }
231
+ resolve(resolveValue);
170
232
  this.stopListening(this, eventName);
171
233
  }
172
234
  });
@@ -177,62 +239,128 @@ const DSS = WebexPlugin.extend({
177
239
  contentType: 'application/json',
178
240
  body: {requestId, ...params},
179
241
  });
242
+ timer.start();
180
243
  });
181
244
  },
182
245
 
246
+ /**
247
+ * Uses a batcher to make the request to the directory service
248
+ * @param {Object} options
249
+ * @param {string} options.resource the URL to query
250
+ * @param {string} options.value the id or email to lookup
251
+ * @returns {Promise} Resolves with an array of entities found
252
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
253
+ */
254
+ _batchedLookup(options: BatcherOptions) {
255
+ const {resource, lookupValue} = options;
256
+ const dataPath = LOOKUP_DATA_PATH;
257
+ const entitiesFoundPath = LOOKUP_FOUND_PATH;
258
+ const entitiesNotFoundPath = LOOKUP_NOT_FOUND_PATH;
259
+ const requestKey = LOOKUP_REQUEST_KEY;
260
+
261
+ this.batchers[resource] =
262
+ this.batchers[resource] ||
263
+ new DssBatcher({
264
+ resource,
265
+ dataPath,
266
+ entitiesFoundPath,
267
+ entitiesNotFoundPath,
268
+ requestKey,
269
+ parent: this,
270
+ });
271
+
272
+ return this.batchers[resource].request(lookupValue);
273
+ },
274
+
183
275
  /**
184
276
  * Retrieves detailed information about an entity
185
277
  * @param {Object} options
186
278
  * @param {UUID} options.id the id of the entity to lookup
187
- * @returns {Promise} Resolves with an array of entities found
279
+ * @returns {Promise} Resolves with the entity found or null if not found
280
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
188
281
  */
189
282
  lookupDetail(options: LookupDetailOptions) {
190
283
  const {id} = options;
191
284
 
285
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`;
286
+
192
287
  return this._request({
193
- dataPath: 'lookupResult.entities',
194
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/identity/${id}/detail`,
288
+ dataPath: LOOKUP_DATA_PATH,
289
+ foundPath: LOOKUP_FOUND_PATH,
290
+ resource,
291
+ }).then(({resultArray, foundArray}) => {
292
+ // TODO: find out what is actually returned!
293
+ if (foundArray[0] === id) {
294
+ return resultArray[0];
295
+ }
296
+
297
+ return null;
195
298
  });
196
299
  },
197
300
 
198
301
  /**
199
- * Retrieves basic information about a list entities within an organization
302
+ * Retrieves basic information about an entity within an organization
200
303
  * @param {Object} options
201
- * @param {UUID} options.ids the id of the entity to lookup
202
- * @param {UUID} options.entityProviderType the provider to query (optional)
203
- * @returns {Promise} Resolves with an array of entities found
304
+ * @param {UUID} options.id the id of the entity to lookup
305
+ * @param {UUID} [options.entityProviderType] the provider to query
306
+ * @param {Boolean} options.shouldBatch whether to batch the query, set to false for single immediate result (defaults to true)
307
+ * @returns {Promise} Resolves with the entity found or null if not found
308
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
204
309
  */
205
310
  lookup(options: LookupOptions) {
206
- const {ids, entityProviderType} = options;
311
+ const {id, entityProviderType, shouldBatch = true} = options;
207
312
 
208
313
  const resource = entityProviderType
209
314
  ? `/lookup/orgid/${this.webex.internal.device.orgId}/entityprovidertype/${entityProviderType}`
210
315
  : `/lookup/orgid/${this.webex.internal.device.orgId}/identities`;
211
316
 
317
+ if (shouldBatch) {
318
+ return this._batchedLookup({
319
+ resource,
320
+ lookupValue: id,
321
+ });
322
+ }
323
+
212
324
  return this._request({
213
- dataPath: 'lookupResult.entities',
325
+ dataPath: LOOKUP_DATA_PATH,
326
+ foundPath: LOOKUP_FOUND_PATH,
214
327
  resource,
215
328
  params: {
216
- lookupValues: ids,
329
+ [LOOKUP_REQUEST_KEY]: [id],
217
330
  },
331
+ }).then(({resultArray, foundArray}) => {
332
+ if (foundArray[0] === id) {
333
+ return resultArray[0];
334
+ }
335
+
336
+ return null;
218
337
  });
219
338
  },
220
339
 
221
340
  /**
222
- * Retrieves basic information about a list entities within an organization
341
+ * Retrieves basic information about an enitity within an organization
223
342
  * @param {Object} options
224
- * @param {UUID} options.emails the emails of the entities to lookup
225
- * @returns {Promise} Resolves with an array of entities found
343
+ * @param {UUID} options.email the email of the entity to lookup
344
+ * @returns {Promise} Resolves with the entity found or rejects if not found
345
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
226
346
  */
227
347
  lookupByEmail(options: LookupByEmailOptions) {
228
- const {emails} = options;
348
+ const {email} = options;
349
+ const resource = `/lookup/orgid/${this.webex.internal.device.orgId}/emails`;
229
350
 
230
351
  return this._request({
231
- dataPath: 'lookupResult.entities',
232
- resource: `/lookup/orgid/${this.webex.internal.device.orgId}/emails`,
352
+ dataPath: LOOKUP_DATA_PATH,
353
+ foundPath: LOOKUP_FOUND_PATH,
354
+ resource,
233
355
  params: {
234
- lookupValues: emails,
356
+ [LOOKUP_REQUEST_KEY]: [email],
235
357
  },
358
+ }).then(({resultArray, foundArray}) => {
359
+ if (foundArray[0] === email) {
360
+ return resultArray[0];
361
+ }
362
+
363
+ return null;
236
364
  });
237
365
  },
238
366
 
@@ -243,19 +371,20 @@ const DSS = WebexPlugin.extend({
243
371
  * @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
244
372
  * @param {number} options.resultSize The maximum number of results returned from each provider
245
373
  * @returns {Promise} Resolves with an array of entities found
374
+ * @throws {DssTimeoutError} when server does not respond in the specified timeframe
246
375
  */
247
376
  search(options: SearchOptions) {
248
377
  const {requestedTypes, resultSize, queryString} = options;
249
378
 
250
379
  return this._request({
251
- dataPath: 'directoryEntities',
380
+ dataPath: SEARCH_DATA_PATH,
252
381
  resource: `/search/orgid/${this.webex.internal.device.orgId}/entities`,
253
382
  params: {
254
383
  queryString,
255
384
  resultSize,
256
385
  requestedTypes,
257
386
  },
258
- });
387
+ }).then(({resultArray}) => resultArray);
259
388
  },
260
389
  });
261
390
 
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,8 +1,21 @@
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
  }
4
18
 
5
- // eslint-disable-next-line no-shadow
6
19
  export enum EntityProviderType {
7
20
  CI_USER = 'CI_USER',
8
21
  CI_MACHINE = 'CI_MACHINE',
@@ -11,12 +24,13 @@ export enum EntityProviderType {
11
24
  }
12
25
 
13
26
  export interface LookupOptions {
14
- ids: string[];
27
+ id: string;
15
28
  entityProviderType?: EntityProviderType;
29
+ shouldBatch?: boolean;
16
30
  }
17
31
 
18
32
  export interface LookupByEmailOptions {
19
- emails: string[];
33
+ email: string;
20
34
  }
21
35
 
22
36
  // eslint-disable-next-line no-shadow
@@ -33,3 +47,8 @@ export interface SearchOptions {
33
47
  resultSize: number;
34
48
  queryString: string;
35
49
  }
50
+
51
+ export interface BatcherOptions {
52
+ resource: string;
53
+ lookupValue: string;
54
+ }
@@ -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
+ });