@webex/internal-plugin-metrics 3.7.0 → 3.8.0

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.
Files changed (40) hide show
  1. package/dist/business-metrics.js +74 -100
  2. package/dist/business-metrics.js.map +1 -1
  3. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +24 -15
  4. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  5. package/dist/call-diagnostic/call-diagnostic-metrics.js +56 -7
  6. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  7. package/dist/call-diagnostic/config.js +19 -12
  8. package/dist/call-diagnostic/config.js.map +1 -1
  9. package/dist/call-diagnostic-events-batcher.js +59 -0
  10. package/dist/call-diagnostic-events-batcher.js.map +1 -0
  11. package/dist/generic-metrics.js +2 -2
  12. package/dist/generic-metrics.js.map +1 -1
  13. package/dist/metrics.js +1 -1
  14. package/dist/metrics.types.js.map +1 -1
  15. package/dist/new-metrics.js +28 -5
  16. package/dist/new-metrics.js.map +1 -1
  17. package/dist/types/business-metrics.d.ts +10 -28
  18. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +14 -1
  19. package/dist/types/call-diagnostic/config.d.ts +2 -0
  20. package/dist/types/generic-metrics.d.ts +2 -2
  21. package/dist/types/metrics.types.d.ts +9 -1
  22. package/dist/types/new-metrics.d.ts +15 -3
  23. package/package.json +12 -12
  24. package/src/business-metrics.ts +66 -76
  25. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +36 -14
  26. package/src/call-diagnostic/call-diagnostic-metrics.ts +72 -3
  27. package/src/call-diagnostic/config.ts +8 -0
  28. package/src/generic-metrics.ts +2 -2
  29. package/src/metrics.types.ts +10 -1
  30. package/src/new-metrics.ts +32 -4
  31. package/test/unit/spec/business/business-metrics.ts +2 -2
  32. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +85 -0
  33. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +243 -2
  34. package/test/unit/spec/new-metrics.ts +23 -0
  35. package/dist/behavioral/behavioral-metrics.js +0 -199
  36. package/dist/behavioral/behavioral-metrics.js.map +0 -1
  37. package/dist/behavioral/config.js +0 -11
  38. package/dist/behavioral/config.js.map +0 -1
  39. package/dist/types/behavioral/behavioral-metrics.d.ts +0 -63
  40. package/dist/types/behavioral/config.d.ts +0 -1
@@ -83,11 +83,16 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
83
83
  key === 'client.media.rx.start' ||
84
84
  key === 'client.media.tx.start' ||
85
85
  key === 'internal.client.meetinginfo.request' ||
86
- key === 'internal.client.meetinginfo.response'
86
+ key === 'internal.client.meetinginfo.response' ||
87
+ key === 'client.media-engine.remote-sdp-received'
87
88
  ) {
88
89
  this.saveFirstTimestampOnly(key, value);
89
90
  } else {
90
91
  this.latencyTimestamps.set(key, value);
92
+ // new offer/answer so reset the remote SDP timestamp
93
+ if (key === 'client.media-engine.local-sdp-generated') {
94
+ this.latencyTimestamps.delete('client.media-engine.remote-sdp-received');
95
+ }
91
96
  }
92
97
  }
93
98
 
@@ -146,7 +151,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
146
151
  public getDiffBetweenTimestamps(a: MetricEventNames, b: MetricEventNames) {
147
152
  const start = this.latencyTimestamps.get(a);
148
153
  const end = this.latencyTimestamps.get(b);
149
- if (start && end) {
154
+
155
+ if (typeof start === 'number' && typeof end === 'number') {
150
156
  return end - start;
151
157
  }
152
158
 
@@ -188,7 +194,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
188
194
  public getU2CTime() {
189
195
  const u2cLatency = this.precomputedLatencies.get('internal.get.u2c.time');
190
196
 
191
- return u2cLatency ? Math.floor(u2cLatency) : undefined;
197
+ return typeof u2cLatency === 'number' ? Math.floor(u2cLatency) : undefined;
192
198
  }
193
199
 
194
200
  /**
@@ -291,7 +297,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
291
297
  * @returns - latency
292
298
  */
293
299
  public getPageJMT() {
294
- return this.precomputedLatencies.get('internal.client.pageJMT') || undefined;
300
+ const latency = this.precomputedLatencies.get('internal.client.pageJMT');
301
+
302
+ return typeof latency === 'number' ? latency : undefined;
295
303
  }
296
304
 
297
305
  /**
@@ -299,7 +307,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
299
307
  * @returns - latency
300
308
  */
301
309
  public getDownloadTimeJMT() {
302
- return this.precomputedLatencies.get('internal.download.time') || undefined;
310
+ const latency = this.precomputedLatencies.get('internal.download.time');
311
+
312
+ return typeof latency === 'number' ? latency : undefined;
303
313
  }
304
314
 
305
315
  /**
@@ -315,8 +325,15 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
315
325
  );
316
326
  }
317
327
 
318
- // for cross launch and guest flows
319
- return this.precomputedLatencies.get('internal.click.to.interstitial') || undefined;
328
+ const clickToInterstitialLatency = this.precomputedLatencies.get(
329
+ 'internal.click.to.interstitial'
330
+ );
331
+
332
+ if (typeof clickToInterstitialLatency === 'number') {
333
+ return clickToInterstitialLatency;
334
+ }
335
+
336
+ return undefined;
320
337
  }
321
338
 
322
339
  /**
@@ -353,7 +370,8 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
353
370
  // get the first timestamp
354
371
  const connectedMedia = this.latencyTimestamps.get('client.ice.end');
355
372
 
356
- const lobbyTime = this.getStayLobbyTime() || 0;
373
+ const lobbyTimeLatency = this.getStayLobbyTime();
374
+ const lobbyTime = typeof lobbyTimeLatency === 'number' ? lobbyTimeLatency : 0;
357
375
 
358
376
  if (interstitialJoinClickTimestamp && connectedMedia) {
359
377
  return connectedMedia - interstitialJoinClickTimestamp - lobbyTime;
@@ -370,7 +388,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
370
388
  const clickToInterstitial = this.getClickToInterstitial();
371
389
  const interstitialToJoinOk = this.getInterstitialToJoinOK();
372
390
 
373
- if (clickToInterstitial && interstitialToJoinOk) {
391
+ if (typeof clickToInterstitial === 'number' && typeof interstitialToJoinOk === 'number') {
374
392
  return clickToInterstitial + interstitialToJoinOk;
375
393
  }
376
394
 
@@ -422,7 +440,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
422
440
  const interstitialToJoinOk = this.getInterstitialToJoinOK();
423
441
  const joinConfJMT = this.getJoinConfJMT();
424
442
 
425
- if (interstitialToJoinOk && joinConfJMT) {
443
+ if (typeof interstitialToJoinOk === 'number' && typeof joinConfJMT === 'number') {
426
444
  return interstitialToJoinOk - joinConfJMT;
427
445
  }
428
446
 
@@ -449,7 +467,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
449
467
  public getReachabilityClustersReqResp() {
450
468
  const reachablityClusterReqResp = this.precomputedLatencies.get('internal.get.cluster.time');
451
469
 
452
- return reachablityClusterReqResp ? Math.floor(reachablityClusterReqResp) : undefined;
470
+ return typeof reachablityClusterReqResp === 'number'
471
+ ? Math.floor(reachablityClusterReqResp)
472
+ : undefined;
453
473
  }
454
474
 
455
475
  /**
@@ -472,7 +492,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
472
492
  public getExchangeCITokenJMT() {
473
493
  const exchangeCITokenJMT = this.precomputedLatencies.get('internal.exchange.ci.token.time');
474
494
 
475
- return exchangeCITokenJMT ? Math.floor(exchangeCITokenJMT) : undefined;
495
+ return typeof exchangeCITokenJMT === 'number' ? Math.floor(exchangeCITokenJMT) : undefined;
476
496
  }
477
497
 
478
498
  /**
@@ -481,7 +501,9 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
481
501
  public getRefreshCaptchaReqResp() {
482
502
  const refreshCaptchaReqResp = this.precomputedLatencies.get('internal.refresh.captcha.time');
483
503
 
484
- return refreshCaptchaReqResp ? Math.floor(refreshCaptchaReqResp) : undefined;
504
+ return typeof refreshCaptchaReqResp === 'number'
505
+ ? Math.floor(refreshCaptchaReqResp)
506
+ : undefined;
485
507
  }
486
508
 
487
509
  /**
@@ -493,7 +515,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
493
515
  'internal.api.fetch.intelligence.models'
494
516
  );
495
517
 
496
- return downloadIntelligenceModelsReqResp
518
+ return typeof downloadIntelligenceModelsReqResp === 'number'
497
519
  ? Math.floor(downloadIntelligenceModelsReqResp)
498
520
  : undefined;
499
521
  }
@@ -40,6 +40,7 @@ import {
40
40
  ClientEventPayloadError,
41
41
  ClientSubServiceType,
42
42
  BrowserLaunchMethodType,
43
+ DelayedClientEvent,
43
44
  } from '../metrics.types';
44
45
  import CallDiagnosticEventsBatcher from './call-diagnostic-metrics-batcher';
45
46
  import PreLoginMetricsBatcher from '../prelogin-metrics-batcher';
@@ -95,6 +96,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
95
96
  private logger: any; // to avoid adding @ts-ignore everywhere
96
97
  private hasLoggedBrowserSerial: boolean;
97
98
  private device: any;
99
+ private delayedClientEvents: DelayedClientEvent[] = [];
98
100
 
99
101
  // the default validator before piping an event to the batcher
100
102
  // this function can be overridden by the user
@@ -165,9 +167,23 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
165
167
  return WEBEX_SUB_SERVICE_TYPES.SCHEDULED_MEETING;
166
168
  }
167
169
  // if Scheduled, Webinar, not pmr - then Webinar
168
- if (meetingInfo?.webexScheduled && meetingInfo?.enableEvent && !meetingInfo?.pmr) {
170
+ if (
171
+ meetingInfo?.webexScheduled &&
172
+ meetingInfo?.enableEvent &&
173
+ !meetingInfo?.pmr &&
174
+ meetingInfo?.isConvergedWebinar
175
+ ) {
169
176
  return WEBEX_SUB_SERVICE_TYPES.WEBINAR;
170
177
  }
178
+ // if Scheduled, Webinar enable webcast - then webcast
179
+ if (
180
+ meetingInfo?.webexScheduled &&
181
+ meetingInfo?.enableEvent &&
182
+ !meetingInfo?.pmr &&
183
+ meetingInfo?.isConvergedWebinarWebcast
184
+ ) {
185
+ return WEBEX_SUB_SERVICE_TYPES.WEBCAST;
186
+ }
171
187
  }
172
188
 
173
189
  return undefined;
@@ -377,7 +393,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
377
393
  * @returns
378
394
  */
379
395
  prepareDiagnosticEvent(eventData: Event['event'], options: any) {
380
- const {meetingId} = options;
396
+ const {meetingId, triggeredTime} = options;
381
397
  const origin = this.getOrigin(options, meetingId);
382
398
 
383
399
  const event: Event = {
@@ -385,7 +401,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
385
401
  version: 1,
386
402
  origin,
387
403
  originTime: {
388
- triggered: new Date().toISOString(),
404
+ triggered: triggeredTime || new Date().toISOString(),
389
405
  // is overridden in prepareRequest batcher
390
406
  sent: 'not_defined_yet',
391
407
  },
@@ -716,6 +732,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
716
732
  meetingId,
717
733
  }),
718
734
  webexSubServiceType: this.getSubServiceType(meeting),
735
+ // @ts-ignore
736
+ webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
719
737
  };
720
738
 
721
739
  const joinFlowVersion = options.joinFlowVersion ?? meeting.callStateForMetrics?.joinFlowVersion;
@@ -723,6 +741,10 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
723
741
  clientEventObject.joinFlowVersion = joinFlowVersion;
724
742
  }
725
743
 
744
+ if (options.meetingJoinPhase) {
745
+ clientEventObject.meetingJoinPhase = options.meetingJoinPhase;
746
+ }
747
+
726
748
  return clientEventObject;
727
749
  }
728
750
 
@@ -764,12 +786,18 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
764
786
  webClientDomain: window.location.hostname,
765
787
  },
766
788
  loginType: this.getCurLoginType(),
789
+ // @ts-ignore
790
+ webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
767
791
  };
768
792
 
769
793
  if (options.joinFlowVersion) {
770
794
  clientEventObject.joinFlowVersion = options.joinFlowVersion;
771
795
  }
772
796
 
797
+ if (options.meetingJoinPhase) {
798
+ clientEventObject.meetingJoinPhase = options.meetingJoinPhase;
799
+ }
800
+
773
801
  return clientEventObject;
774
802
  }
775
803
 
@@ -834,17 +862,36 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
834
862
  * @param arg.event - event key
835
863
  * @param arg.payload - additional payload to be merged with default payload
836
864
  * @param arg.options - payload
865
+ * @param arg.delaySubmitEvent - a boolean value indicating whether to delay the submission of client events.
837
866
  * @throws
838
867
  */
839
868
  public submitClientEvent({
840
869
  name,
841
870
  payload,
842
871
  options,
872
+ delaySubmitEvent,
843
873
  }: {
844
874
  name: ClientEvent['name'];
845
875
  payload?: ClientEventPayload;
846
876
  options?: SubmitClientEventOptions;
877
+ delaySubmitEvent?: boolean;
847
878
  }) {
879
+ if (delaySubmitEvent) {
880
+ // Preserve the time when the event was triggered if delaying the submission to Call Diagnostics
881
+ const delayedOptions = {
882
+ ...options,
883
+ triggeredTime: new Date().toISOString(),
884
+ };
885
+
886
+ this.delayedClientEvents.push({
887
+ name,
888
+ payload,
889
+ options: delayedOptions,
890
+ });
891
+
892
+ return Promise.resolve();
893
+ }
894
+
848
895
  this.logger.log(
849
896
  CALL_DIAGNOSTIC_LOG_IDENTIFIER,
850
897
  'CallDiagnosticMetrics: @submitClientEvent. Submit Client Event CA event.',
@@ -861,6 +908,28 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
861
908
  return this.submitToCallDiagnostics(diagnosticEvent);
862
909
  }
863
910
 
911
+ /**
912
+ * Submit Delayed Client Event CA events. Clears delayedClientEvents array after submission.
913
+ */
914
+ public submitDelayedClientEvents() {
915
+ this.logger.log(
916
+ CALL_DIAGNOSTIC_LOG_IDENTIFIER,
917
+ 'CallDiagnosticMetrics: @submitDelayedClientEvents. Submitting delayed client events.'
918
+ );
919
+
920
+ if (this.delayedClientEvents.length === 0) {
921
+ return Promise.resolve();
922
+ }
923
+
924
+ const promises = this.delayedClientEvents.map((delayedSubmitClientEventParams) => {
925
+ return this.submitClientEvent(delayedSubmitClientEventParams);
926
+ });
927
+
928
+ this.delayedClientEvents = [];
929
+
930
+ return Promise.all(promises);
931
+ }
932
+
864
933
  /**
865
934
  * Prepare the event and send the request to metrics-a service.
866
935
  * @param event
@@ -17,12 +17,14 @@ export const DTLS_HANDSHAKE_FAILED_CLIENT_CODE = 2008;
17
17
  export const ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE = 2010;
18
18
  export const ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE = 2009;
19
19
  export const ICE_AND_REACHABILITY_FAILED_CLIENT_CODE = 2011;
20
+ export const MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE = 2012;
20
21
  export const WBX_APP_API_URL = 'wbxappapi'; // MeetingInfo WebexAppApi response object normally contains a body.url that includes the string 'wbxappapi'
21
22
 
22
23
  export const WEBEX_SUB_SERVICE_TYPES: Record<string, ClientSubServiceType> = {
23
24
  PMR: 'PMR',
24
25
  SCHEDULED_MEETING: 'ScheduledMeeting',
25
26
  WEBINAR: 'Webinar',
27
+ WEBCAST: 'Webcast',
26
28
  };
27
29
 
28
30
  // Found in https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
@@ -127,6 +129,7 @@ export const ERROR_DESCRIPTIONS = {
127
129
  ICE_FAILED_WITHOUT_TURN_TLS: 'ICEFailedWithoutTURN_TLS',
128
130
  ICE_FAILED_WITH_TURN_TLS: 'ICEFailedWithTURN_TLS',
129
131
  ICE_AND_REACHABILITY_FAILED: 'ICEAndReachabilityFailed',
132
+ MULTISTREAM_NOT_AVAILABLE: 'MultistreamNotAvailable',
130
133
  SDP_OFFER_CREATION_ERROR: 'SdpOfferCreationError',
131
134
  SDP_OFFER_CREATION_ERROR_MISSING_CODEC: 'SdpOfferCreationErrorMissingCodec',
132
135
  WDM_RESTRICTED_REGION: 'WdmRestrictedRegion',
@@ -409,6 +412,11 @@ export const CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD: Record<number, Partial<ClientEv
409
412
  category: 'expected',
410
413
  fatal: true,
411
414
  },
415
+ [MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE]: {
416
+ errorDescription: ERROR_DESCRIPTIONS.MULTISTREAM_NOT_AVAILABLE,
417
+ category: 'expected',
418
+ fatal: false,
419
+ },
412
420
  2050: {
413
421
  errorDescription: ERROR_DESCRIPTIONS.SDP_OFFER_CREATION_ERROR,
414
422
  category: 'media',
@@ -37,7 +37,7 @@ export default abstract class GenericMetrics extends StatelessWebexPlugin {
37
37
  }
38
38
 
39
39
  /**
40
- * Submit a buisness metric to our metrics endpoint.
40
+ * Submit a business metric to our metrics endpoint.
41
41
  * @param {string} kind of metric for logging
42
42
  * @param {string} name of the metric
43
43
  * @param {object} event
@@ -105,7 +105,7 @@ export default abstract class GenericMetrics extends StatelessWebexPlugin {
105
105
  }
106
106
 
107
107
  /**
108
- * Returns true once we have the deviceId we need to submit behavioral/operational/buisness events
108
+ * Returns true once we have the deviceId we need to submit behavioral/operational/business events
109
109
  * @returns {boolean}
110
110
  */
111
111
  public isReadyToSubmitEvents(): boolean {
@@ -20,7 +20,7 @@ export type BrowserLaunchMethodType = NonNullable<
20
20
  RawEvent['origin']['clientInfo']
21
21
  >['browserLaunchMethod'];
22
22
 
23
- export type MetricEventProduct = 'webex' | 'wxcc_desktop';
23
+ export type MetricEventProduct = 'webex' | 'wxcc_desktop' | 'wxcc_crm';
24
24
 
25
25
  export type MetricEventAgent = 'user' | 'browser' | 'system' | 'sdk' | 'redux' | 'service' | 'api';
26
26
 
@@ -111,6 +111,7 @@ export type MetricEventVerb =
111
111
  | 'exit';
112
112
 
113
113
  export type MetricEventJoinFlowVersion = 'Other' | 'NewFTE';
114
+ export type MetricEventMeetingJoinPhase = 'pre-join' | 'join' | 'in-meeting';
114
115
 
115
116
  export type SubmitClientEventOptions = {
116
117
  meetingId?: string;
@@ -126,6 +127,8 @@ export type SubmitClientEventOptions = {
126
127
  webexConferenceIdStr?: string;
127
128
  globalMeetingId?: string;
128
129
  joinFlowVersion?: MetricEventJoinFlowVersion;
130
+ meetingJoinPhase?: MetricEventMeetingJoinPhase;
131
+ triggeredTime?: string;
129
132
  };
130
133
 
131
134
  export type SubmitMQEOptions = {
@@ -317,3 +320,9 @@ export interface IMetricsAttributes {
317
320
  meetingId?: string;
318
321
  callId?: string;
319
322
  }
323
+
324
+ export interface DelayedClientEvent {
325
+ name: ClientEvent['name'];
326
+ payload?: RecursivePartial<ClientEvent['payload']>;
327
+ options?: SubmitClientEventOptions;
328
+ }
@@ -45,6 +45,11 @@ class Metrics extends WebexPlugin {
45
45
  businessMetrics: BusinessMetrics;
46
46
  isReady = false;
47
47
 
48
+ /**
49
+ * Whether or not to delay the submission of client events.
50
+ */
51
+ delaySubmitClientEvents = false;
52
+
48
53
  /**
49
54
  * Constructor
50
55
  * @param args
@@ -141,7 +146,7 @@ class Metrics extends WebexPlugin {
141
146
  }
142
147
 
143
148
  /**
144
- * @returns true once we have the deviceId we need to submit buisness events
149
+ * @returns true once we have the deviceId we need to submit business events
145
150
  */
146
151
  isReadyToSubmitBusinessEvents() {
147
152
  this.lazyBuildBusinessMetrics();
@@ -200,17 +205,19 @@ class Metrics extends WebexPlugin {
200
205
  }
201
206
 
202
207
  /**
203
- * Buisness event
208
+ * Business event
204
209
  * @param args
205
210
  */
206
211
  submitBusinessEvent({
207
212
  name,
208
213
  payload,
209
214
  table,
215
+ metadata,
210
216
  }: {
211
217
  name: string;
212
218
  payload: EventPayload;
213
219
  table?: Table;
220
+ metadata?: EventPayload;
214
221
  }) {
215
222
  if (!this.isReady) {
216
223
  // @ts-ignore
@@ -223,7 +230,7 @@ class Metrics extends WebexPlugin {
223
230
 
224
231
  this.lazyBuildBusinessMetrics();
225
232
 
226
- return this.businessMetrics.submitBusinessEvent({name, payload, table});
233
+ return this.businessMetrics.submitBusinessEvent({name, payload, table, metadata});
227
234
  }
228
235
 
229
236
  /**
@@ -288,7 +295,12 @@ class Metrics extends WebexPlugin {
288
295
  options: {meetingId: options?.meetingId},
289
296
  });
290
297
 
291
- return this.callDiagnosticMetrics.submitClientEvent({name, payload, options});
298
+ return this.callDiagnosticMetrics.submitClientEvent({
299
+ name,
300
+ payload,
301
+ options,
302
+ delaySubmitEvent: this.delaySubmitClientEvents,
303
+ });
292
304
  }
293
305
 
294
306
  /**
@@ -387,6 +399,22 @@ class Metrics extends WebexPlugin {
387
399
  public isServiceErrorExpected(serviceErrorCode: number): boolean {
388
400
  return this.callDiagnosticMetrics.isServiceErrorExpected(serviceErrorCode);
389
401
  }
402
+
403
+ /**
404
+ * Sets the value of delaySubmitClientEvents. If set to true, client events will be delayed until submitDelayedClientEvents is called. If
405
+ * set to false, delayed client events will be submitted.
406
+ *
407
+ * @param {boolean} shouldDelay - A boolean value indicating whether to delay the submission of client events.
408
+ */
409
+ public setDelaySubmitClientEvents(shouldDelay: boolean) {
410
+ this.delaySubmitClientEvents = shouldDelay;
411
+
412
+ if (!shouldDelay) {
413
+ return this.callDiagnosticMetrics.submitDelayedClientEvents();
414
+ }
415
+
416
+ return Promise.resolve();
417
+ }
390
418
  }
391
419
 
392
420
  export default Metrics;
@@ -154,11 +154,12 @@ describe('internal-plugin-metrics', () => {
154
154
  businessMetrics.clientMetricsBatcher.request = request;
155
155
 
156
156
  assert.equal(requestCalls.length, 0)
157
- businessMetrics.submitBusinessEvent({ name: "foobar", payload: {bar:"gee"}, table: 'business_metrics' })
157
+ businessMetrics.submitBusinessEvent({ name: "foobar", payload: {bar: "gee"}, table: 'business_metrics', metadata: {asdf: 'hjkl'} })
158
158
  assert.equal(requestCalls.length, 1)
159
159
  assert.deepEqual(requestCalls[0], {
160
160
  eventPayload: {
161
161
  key: 'foobar',
162
+ asdf: 'hjkl',
162
163
  appType: 'Web Client',
163
164
  client_timestamp: requestCalls[0].eventPayload.client_timestamp, // This is to bypass time check, which is checked below.
164
165
  value: {
@@ -173,7 +174,6 @@ describe('internal-plugin-metrics', () => {
173
174
  os: getOSNameInternal(),
174
175
  app: {version: 'webex-version'},
175
176
  device: {id: 'deviceId'},
176
- locale: 'language',
177
177
  }
178
178
  },
179
179
  type: ['business'],
@@ -283,6 +283,18 @@ describe('internal-plugin-metrics', () => {
283
283
  cdl.saveTimestamp({key: 'internal.client.meetinginfo.response', value: 20});
284
284
  assert.deepEqual(saveFirstTimestamp.callCount, 1);
285
285
  });
286
+
287
+ it('calls saveFirstTimestamp for remote SDP received', () => {
288
+ const saveFirstTimestamp = sinon.stub(cdl, 'saveFirstTimestampOnly');
289
+ cdl.saveTimestamp({key: 'client.media-engine.remote-sdp-received', value: 10});
290
+ assert.deepEqual(saveFirstTimestamp.callCount, 1);
291
+ });
292
+
293
+ it('clears timestamp for remote SDP received when local SDP generated', () => {
294
+ cdl.saveTimestamp({key: 'client.media-engine.remote-sdp-received', value: 10});
295
+ cdl.saveTimestamp({key: 'client.media-engine.local-sdp-generated', value: 20});
296
+ assert.isUndefined(cdl.latencyTimestamps.get('client.media-engine.remote-sdp-received'));
297
+ });
286
298
  });
287
299
 
288
300
  it('calculates getShowInterstitialTime correctly', () => {
@@ -425,6 +437,15 @@ describe('internal-plugin-metrics', () => {
425
437
  assert.deepEqual(cdl.getClickToInterstitial(), 5);
426
438
  });
427
439
 
440
+ it('calculates getClickToInterstitial without join button timestamp when it is 0', () => {
441
+ cdl.saveLatency('internal.click.to.interstitial', 0);
442
+ cdl.saveTimestamp({
443
+ key: 'internal.client.meeting.interstitial-window.showed',
444
+ value: 20,
445
+ });
446
+ assert.deepEqual(cdl.getClickToInterstitial(), 0);
447
+ });
448
+
428
449
  it('calculates getInterstitialToJoinOK correctly', () => {
429
450
  cdl.saveTimestamp({
430
451
  key: 'internal.client.interstitial-window.click.joinbutton',
@@ -437,6 +458,18 @@ describe('internal-plugin-metrics', () => {
437
458
  assert.deepEqual(cdl.getInterstitialToJoinOK(), 10);
438
459
  });
439
460
 
461
+ it('calculates getInterstitialToJoinOK correctly when one value is not a number', () => {
462
+ cdl.saveTimestamp({
463
+ key: 'internal.client.interstitial-window.click.joinbutton',
464
+ value: 'ten' as unknown as number,
465
+ });
466
+ cdl.saveTimestamp({
467
+ key: 'client.locus.join.response',
468
+ value: 20,
469
+ });
470
+ assert.deepEqual(cdl.getInterstitialToJoinOK(), undefined);
471
+ });
472
+
440
473
  it('calculates getCallInitMediaEngineReady correctly', () => {
441
474
  cdl.saveTimestamp({
442
475
  key: 'internal.client.interstitial-window.click.joinbutton',
@@ -469,6 +502,58 @@ describe('internal-plugin-metrics', () => {
469
502
  assert.deepEqual(cdl.getTotalJMT(), 45);
470
503
  });
471
504
 
505
+ it('calculates getTotalJMT correctly when clickToInterstitial is 0', () => {
506
+ cdl.saveLatency('internal.click.to.interstitial', 0);
507
+ cdl.saveTimestamp({
508
+ key: 'internal.client.interstitial-window.click.joinbutton',
509
+ value: 20,
510
+ });
511
+ cdl.saveTimestamp({
512
+ key: 'client.locus.join.response',
513
+ value: 40,
514
+ });
515
+ assert.deepEqual(cdl.getTotalJMT(), 20);
516
+ });
517
+
518
+ it('calculates getTotalJMT correctly when interstitialToJoinOk is 0', () => {
519
+ cdl.saveTimestamp({
520
+ key: 'internal.client.interstitial-window.click.joinbutton',
521
+ value: 40,
522
+ });
523
+ cdl.saveLatency('internal.click.to.interstitial', 12);
524
+ cdl.saveTimestamp({
525
+ key: 'client.locus.join.response',
526
+ value: 40,
527
+ });
528
+ assert.deepEqual(cdl.getTotalJMT(), 12);
529
+ });
530
+
531
+ it('calculates getTotalJMT correctly when both clickToInterstitial and interstitialToJoinOk are 0', () => {
532
+ cdl.saveTimestamp({
533
+ key: 'internal.client.interstitial-window.click.joinbutton',
534
+ value: 40,
535
+ });
536
+ cdl.saveLatency('internal.click.to.interstitial', 0);
537
+ cdl.saveTimestamp({
538
+ key: 'client.locus.join.response',
539
+ value: 40,
540
+ });
541
+ assert.deepEqual(cdl.getTotalJMT(), 0);
542
+ });
543
+
544
+ it('calculates getTotalJMT correctly when both clickToInterstitial is not a number', () => {
545
+ cdl.saveTimestamp({
546
+ key: 'internal.client.interstitial-window.click.joinbutton',
547
+ value: 40,
548
+ });
549
+ cdl.saveLatency('internal.click.to.interstitial', 'eleven' as unknown as number);
550
+ cdl.saveTimestamp({
551
+ key: 'client.locus.join.response',
552
+ value: 40,
553
+ });
554
+ assert.deepEqual(cdl.getTotalJMT(), undefined);
555
+ });
556
+
472
557
  it('calculates getTotalMediaJMT correctly', () => {
473
558
  cdl.saveTimestamp({
474
559
  key: 'internal.client.meeting.click.joinbutton',