@webex/internal-plugin-metrics 3.12.0-task-refactor.1 → 3.12.0-webex-services-ready.1
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/dist/batcher.js +3 -0
- package/dist/batcher.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js +23 -0
- package/dist/call-diagnostic/call-diagnostic-metrics-batcher.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +68 -50
- package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics.js +60 -8
- package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics.util.js +44 -4
- package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
- package/dist/call-diagnostic/config.js +17 -3
- package/dist/call-diagnostic/config.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/generic-metrics.js +8 -6
- package/dist/generic-metrics.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.types.js.map +1 -1
- package/dist/new-metrics.js +56 -20
- package/dist/new-metrics.js.map +1 -1
- package/dist/prelogin-metrics-batcher.js +23 -0
- package/dist/prelogin-metrics-batcher.js.map +1 -1
- package/dist/prelogin-metrics.js +106 -0
- package/dist/prelogin-metrics.js.map +1 -0
- package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +9 -0
- package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +57 -20
- package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +23 -2
- package/dist/types/call-diagnostic/config.d.ts +6 -19
- package/dist/types/config.d.ts +16 -15
- package/dist/types/index.d.ts +2 -1
- package/dist/types/metrics.types.d.ts +4 -4
- package/dist/types/new-metrics.d.ts +12 -0
- package/dist/types/prelogin-metrics.d.ts +47 -0
- package/package.json +11 -11
- package/src/batcher.js +4 -0
- package/src/call-diagnostic/call-diagnostic-metrics-batcher.ts +26 -0
- package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +139 -70
- package/src/call-diagnostic/call-diagnostic-metrics.ts +52 -1
- package/src/call-diagnostic/call-diagnostic-metrics.util.ts +49 -2
- package/src/call-diagnostic/config.ts +15 -0
- package/src/config.js +1 -0
- package/src/generic-metrics.ts +6 -6
- package/src/index.ts +2 -0
- package/src/metrics.types.ts +3 -3
- package/src/new-metrics.ts +42 -0
- package/src/prelogin-metrics-batcher.ts +26 -0
- package/src/prelogin-metrics.ts +94 -0
- package/test/unit/spec/batcher.js +43 -0
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +183 -11
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +324 -366
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +724 -159
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +92 -3
- package/test/unit/spec/prelogin-metrics-batcher.ts +190 -36
- package/test/unit/spec/prelogin-metrics.ts +132 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import GenericMetrics from './generic-metrics';
|
|
2
|
+
import { EventPayload } from './metrics.types';
|
|
3
|
+
import PreLoginMetricsBatcher from './prelogin-metrics-batcher';
|
|
4
|
+
/**
|
|
5
|
+
* @description Util class to handle PreLogin Metrics
|
|
6
|
+
* @export
|
|
7
|
+
* @class PreLoginMetrics
|
|
8
|
+
*/
|
|
9
|
+
export default class PreLoginMetrics extends GenericMetrics {
|
|
10
|
+
private preLoginMetricsBatcher;
|
|
11
|
+
/**
|
|
12
|
+
* Constructor
|
|
13
|
+
* @param {PreLoginMetricsBatcher} preLoginMetricsBatcher - Pre-login metrics batcher
|
|
14
|
+
* @param {any} attrs - Attributes
|
|
15
|
+
* @param {any} options - Options
|
|
16
|
+
* @constructor
|
|
17
|
+
*/
|
|
18
|
+
constructor(preLoginMetricsBatcher: typeof PreLoginMetricsBatcher, attrs?: any, options?: {
|
|
19
|
+
parent?: any;
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Submit a business metric to our metrics endpoint.
|
|
23
|
+
* Routes to the correct table with the correct schema payload by table.
|
|
24
|
+
* @see https://confluence-eng-gpk2.cisco.com/conf/display/WAP/Business+metrics++-%3E+ROMA
|
|
25
|
+
* @param {Object} options - The options object
|
|
26
|
+
* @param {string} options.name - Name of the metric
|
|
27
|
+
* @param {string} options.preLoginId - ID to identify pre-login user
|
|
28
|
+
* @param {EventPayload} options.payload - User payload of the metric
|
|
29
|
+
* @param {EventPayload} [options.metadata] - Optional metadata to include outside of eventPayload.value
|
|
30
|
+
* @returns {Promise<void>} Promise that resolves when the metric is submitted
|
|
31
|
+
*/
|
|
32
|
+
submitPreLoginEvent({ name, preLoginId, payload, metadata, }: {
|
|
33
|
+
name: string;
|
|
34
|
+
preLoginId: string;
|
|
35
|
+
payload: EventPayload;
|
|
36
|
+
metadata?: EventPayload;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Builds a formatted event object for metrics submission.
|
|
40
|
+
* @param {string} metricName - Metric name
|
|
41
|
+
* @param {string} preLoginId - Pre-login user identifier
|
|
42
|
+
* @param {EventPayload} payload - Metric payload data
|
|
43
|
+
* @param {EventPayload} metadata - Additional metadata to include in the event
|
|
44
|
+
* @returns {object} Formatted metrics event object with type, eventPayload, and timestamp
|
|
45
|
+
*/
|
|
46
|
+
private buildEvent;
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -24,23 +24,23 @@
|
|
|
24
24
|
"@sinonjs/fake-timers": "^6.0.1",
|
|
25
25
|
"@webex/babel-config-legacy": "0.0.0",
|
|
26
26
|
"@webex/eslint-config-legacy": "0.0.0",
|
|
27
|
+
"@webex/event-dictionary-ts": "^1.0.2191",
|
|
27
28
|
"@webex/jest-config-legacy": "0.0.0",
|
|
28
29
|
"@webex/legacy-tools": "0.0.0",
|
|
29
|
-
"@webex/test-helper-chai": "3.12.0-
|
|
30
|
-
"@webex/test-helper-mocha": "3.12.0-
|
|
31
|
-
"@webex/test-helper-mock-webex": "3.12.0-
|
|
32
|
-
"@webex/test-helper-test-users": "3.12.0-
|
|
30
|
+
"@webex/test-helper-chai": "3.12.0-webex-services-ready.1",
|
|
31
|
+
"@webex/test-helper-mocha": "3.12.0-webex-services-ready.1",
|
|
32
|
+
"@webex/test-helper-mock-webex": "3.12.0-webex-services-ready.1",
|
|
33
|
+
"@webex/test-helper-test-users": "3.12.0-webex-services-ready.1",
|
|
33
34
|
"eslint": "^8.24.0",
|
|
34
35
|
"prettier": "^2.7.1",
|
|
35
36
|
"sinon": "^9.2.4"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@webex/common": "3.12.0-
|
|
39
|
-
"@webex/common-timers": "3.12.0-
|
|
40
|
-
"@webex/
|
|
41
|
-
"@webex/test-helper-
|
|
42
|
-
"@webex/
|
|
43
|
-
"@webex/webex-core": "3.12.0-task-refactor.1",
|
|
39
|
+
"@webex/common": "3.12.0-webex-services-ready.1",
|
|
40
|
+
"@webex/common-timers": "3.12.0-webex-services-ready.1",
|
|
41
|
+
"@webex/test-helper-chai": "3.12.0-webex-services-ready.1",
|
|
42
|
+
"@webex/test-helper-mock-webex": "3.12.0-webex-services-ready.1",
|
|
43
|
+
"@webex/webex-core": "3.12.0-webex-services-ready.1",
|
|
44
44
|
"ip-anonymize": "^0.1.0",
|
|
45
45
|
"lodash": "^4.17.21",
|
|
46
46
|
"uuid": "^3.3.2"
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"test:style": "eslint ./src/**/*.*",
|
|
54
54
|
"test:unit": "webex-legacy-tools test --unit --runner mocha"
|
|
55
55
|
},
|
|
56
|
-
"version": "3.12.0-
|
|
56
|
+
"version": "3.12.0-webex-services-ready.1"
|
|
57
57
|
}
|
package/src/batcher.js
CHANGED
|
@@ -75,6 +75,10 @@ const MetricsBatcher = Batcher.extend({
|
|
|
75
75
|
*/
|
|
76
76
|
handleHttpError(reason) {
|
|
77
77
|
if (reason instanceof WebexHttpError.NetworkOrCORSError) {
|
|
78
|
+
if (!this.config.batcherRetryOnNetworkError) {
|
|
79
|
+
return Reflect.apply(Batcher.prototype.handleHttpError, this, [reason]);
|
|
80
|
+
}
|
|
81
|
+
|
|
78
82
|
this.logger.warn(
|
|
79
83
|
'metrics-batcher: received network error submitting metrics, reenqueuing payload'
|
|
80
84
|
);
|
|
@@ -55,6 +55,8 @@ const CallDiagnosticEventsBatcher = Batcher.extend({
|
|
|
55
55
|
`CallDiagnosticEventsBatcher: @submitHttpRequest#${batchId}. Request successful.`
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
+
this.handleHttpResponseStatus(res?.statusCode, payload);
|
|
59
|
+
|
|
58
60
|
return res;
|
|
59
61
|
})
|
|
60
62
|
.catch((err) => {
|
|
@@ -64,9 +66,33 @@ const CallDiagnosticEventsBatcher = Batcher.extend({
|
|
|
64
66
|
`error: ${generateCommonErrorMetadata(err)}`
|
|
65
67
|
);
|
|
66
68
|
|
|
69
|
+
this.handleHttpResponseStatus(err?.statusCode, payload);
|
|
70
|
+
|
|
67
71
|
return Promise.reject(err);
|
|
68
72
|
});
|
|
69
73
|
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* React to the HTTP status code returned by the metrics endpoint.
|
|
77
|
+
* Only items submitted with `markTelemetryOptOutOnResponse: true` opt into
|
|
78
|
+
* this behavior.
|
|
79
|
+
* @param {number | undefined} statusCode
|
|
80
|
+
* @param {any[]} payload Items flushed in this HTTP batch.
|
|
81
|
+
* @returns {void}
|
|
82
|
+
*/
|
|
83
|
+
handleHttpResponseStatus(statusCode: number | undefined, payload: any[]) {
|
|
84
|
+
const shouldMark =
|
|
85
|
+
Array.isArray(payload) &&
|
|
86
|
+
payload.some((item) => item?.markTelemetryOptOutOnResponse === true);
|
|
87
|
+
|
|
88
|
+
if (!shouldMark) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (statusCode === 200) {
|
|
93
|
+
this.webex.internal.newMetrics?.callDiagnosticMetrics?.setIsTelemetryOptOutAutomatic(true);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
70
96
|
});
|
|
71
97
|
|
|
72
98
|
export default CallDiagnosticEventsBatcher;
|
|
@@ -193,7 +193,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
193
193
|
*/
|
|
194
194
|
public getShowInterstitialTime() {
|
|
195
195
|
return this.getDiffBetweenTimestamps(
|
|
196
|
-
'client.interstitial-window.
|
|
196
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
197
197
|
'internal.client.interstitial-window.click.joinbutton'
|
|
198
198
|
);
|
|
199
199
|
}
|
|
@@ -224,11 +224,17 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
224
224
|
* @returns - latency
|
|
225
225
|
*/
|
|
226
226
|
public getCallInitJoinReq() {
|
|
227
|
-
|
|
228
|
-
'internal.client.interstitial-window.
|
|
229
|
-
'client.locus.join.request'
|
|
230
|
-
{maximum: 1200000}
|
|
227
|
+
const interstitialShowedToJoinReq = this.getDiffBetweenTimestamps(
|
|
228
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
229
|
+
'client.locus.join.request'
|
|
231
230
|
);
|
|
231
|
+
const showInterstitialTime = this.getShowInterstitialTime() || 0;
|
|
232
|
+
|
|
233
|
+
if (typeof interstitialShowedToJoinReq !== 'number') {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return clamp(interstitialShowedToJoinReq - showInterstitialTime, 0, 1200000);
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
/**
|
|
@@ -303,10 +309,39 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
303
309
|
* @returns - latency
|
|
304
310
|
*/
|
|
305
311
|
public getStayLobbyTime() {
|
|
306
|
-
return this.getDiffBetweenTimestamps(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
312
|
+
return this.getDiffBetweenTimestamps('client.lobby.entered', 'client.lobby.exited');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Stay lobby time capped by a certain timestamp.
|
|
317
|
+
* This is to handle the case where the target end timestamp could happen before the lobby is exited,
|
|
318
|
+
* for example media-engine.ready or client.ice.end
|
|
319
|
+
* This is supposed to be called AFTER the end timestamp happens
|
|
320
|
+
* @param endTimestampKey name of the target end event
|
|
321
|
+
* @returns - latency
|
|
322
|
+
*/
|
|
323
|
+
public getStayLobbyTimeCappedBy(endTimestampKey: MetricEventNames) {
|
|
324
|
+
const lobbyStartTimestamp = this.latencyTimestamps.get('client.lobby.entered'); // might not exist (some meetings don't have lobby)
|
|
325
|
+
|
|
326
|
+
if (typeof lobbyStartTimestamp !== 'number') {
|
|
327
|
+
// no lobby in the meeting, stayLobbyTime is 0
|
|
328
|
+
return 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const lobbyEndTimestamp = this.latencyTimestamps.get('client.lobby.exited'); // might not exist (if user still in lobby at the time of measurement)
|
|
332
|
+
const maximumEndTimestamp = this.latencyTimestamps.get(endTimestampKey); // must exist
|
|
333
|
+
|
|
334
|
+
if (typeof maximumEndTimestamp !== 'number') {
|
|
335
|
+
// the provided timestamp to be used as a cap should exist, return undefined if it doesn't
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const endTimestamp =
|
|
340
|
+
typeof lobbyEndTimestamp === 'number'
|
|
341
|
+
? Math.min(lobbyEndTimestamp, maximumEndTimestamp)
|
|
342
|
+
: maximumEndTimestamp;
|
|
343
|
+
|
|
344
|
+
return clamp(endTimestamp - lobbyStartTimestamp, 0, this.MAX_INTEGER);
|
|
310
345
|
}
|
|
311
346
|
|
|
312
347
|
/**
|
|
@@ -334,14 +369,6 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
334
369
|
* @returns - latency
|
|
335
370
|
*/
|
|
336
371
|
public getClickToInterstitial() {
|
|
337
|
-
// for normal join (where green join button exists before interstitial, i.e reminder, space list etc)
|
|
338
|
-
if (this.latencyTimestamps.get('internal.client.meeting.click.joinbutton')) {
|
|
339
|
-
return this.getDiffBetweenTimestamps(
|
|
340
|
-
'internal.client.meeting.click.joinbutton',
|
|
341
|
-
'internal.client.meeting.interstitial-window.showed'
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
372
|
const clickToInterstitialLatency = this.precomputedLatencies.get(
|
|
346
373
|
'internal.click.to.interstitial'
|
|
347
374
|
);
|
|
@@ -358,14 +385,6 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
358
385
|
* @returns - latency
|
|
359
386
|
*/
|
|
360
387
|
public getClickToInterstitialWithUserDelay() {
|
|
361
|
-
// for normal join (where green join button exists before interstitial, i.e reminder, space list etc)
|
|
362
|
-
if (this.latencyTimestamps.get('internal.client.meeting.click.joinbutton')) {
|
|
363
|
-
return this.getDiffBetweenTimestamps(
|
|
364
|
-
'internal.client.meeting.click.joinbutton',
|
|
365
|
-
'internal.client.meeting.interstitial-window.showed'
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
388
|
const clickToInterstitialWithUserDelayLatency = this.precomputedLatencies.get(
|
|
370
389
|
'internal.click.to.interstitial.with.user.delay'
|
|
371
390
|
);
|
|
@@ -382,10 +401,17 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
382
401
|
* @returns - latency
|
|
383
402
|
*/
|
|
384
403
|
public getInterstitialToJoinOK() {
|
|
385
|
-
|
|
386
|
-
'internal.client.interstitial-window.
|
|
404
|
+
const interstitialShowedToJoinResp = this.getDiffBetweenTimestamps(
|
|
405
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
387
406
|
'client.locus.join.response'
|
|
388
407
|
);
|
|
408
|
+
const showInterstitialTime = this.getShowInterstitialTime() || 0;
|
|
409
|
+
|
|
410
|
+
if (typeof interstitialShowedToJoinResp !== 'number') {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return clamp(interstitialShowedToJoinResp - showInterstitialTime, 0, this.MAX_INTEGER);
|
|
389
415
|
}
|
|
390
416
|
|
|
391
417
|
/**
|
|
@@ -393,11 +419,7 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
393
419
|
* @returns - latency
|
|
394
420
|
*/
|
|
395
421
|
public getCallInitMediaEngineReady() {
|
|
396
|
-
return this.
|
|
397
|
-
'internal.client.interstitial-window.click.joinbutton',
|
|
398
|
-
'client.media-engine.ready',
|
|
399
|
-
{maximum: 1200000}
|
|
400
|
-
);
|
|
422
|
+
return this.getInterstitialToMediaOKJMT();
|
|
401
423
|
}
|
|
402
424
|
|
|
403
425
|
/**
|
|
@@ -405,20 +427,22 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
405
427
|
* @returns - latency
|
|
406
428
|
*/
|
|
407
429
|
public getInterstitialToMediaOKJMT() {
|
|
408
|
-
const
|
|
409
|
-
'internal.client.interstitial-window.
|
|
430
|
+
const interstitialShowedToIceEnd = this.getDiffBetweenTimestamps(
|
|
431
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
432
|
+
'client.ice.end'
|
|
410
433
|
);
|
|
434
|
+
const showInterstitialTime = this.getShowInterstitialTime() || 0;
|
|
435
|
+
const stayLobbyTimeCappedByIceEnd = this.getStayLobbyTimeCappedBy('client.ice.end');
|
|
411
436
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return clamp(interstitialToMediaOKJmt, 0, this.MAX_INTEGER);
|
|
437
|
+
if (
|
|
438
|
+
typeof interstitialShowedToIceEnd === 'number' &&
|
|
439
|
+
typeof stayLobbyTimeCappedByIceEnd === 'number'
|
|
440
|
+
) {
|
|
441
|
+
return clamp(
|
|
442
|
+
interstitialShowedToIceEnd - showInterstitialTime - stayLobbyTimeCappedByIceEnd,
|
|
443
|
+
0,
|
|
444
|
+
this.MAX_INTEGER
|
|
445
|
+
);
|
|
422
446
|
}
|
|
423
447
|
|
|
424
448
|
return undefined;
|
|
@@ -430,10 +454,21 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
430
454
|
*/
|
|
431
455
|
public getTotalJMT() {
|
|
432
456
|
const clickToInterstitial = this.getClickToInterstitial();
|
|
433
|
-
const
|
|
457
|
+
const interstitialShowedToJoinLocusResponse = this.getDiffBetweenTimestamps(
|
|
458
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
459
|
+
'client.locus.join.response'
|
|
460
|
+
);
|
|
461
|
+
const showInterstitialTime = this.getShowInterstitialTime() || 0;
|
|
434
462
|
|
|
435
|
-
if (
|
|
436
|
-
|
|
463
|
+
if (
|
|
464
|
+
typeof clickToInterstitial === 'number' &&
|
|
465
|
+
typeof interstitialShowedToJoinLocusResponse === 'number'
|
|
466
|
+
) {
|
|
467
|
+
return clamp(
|
|
468
|
+
clickToInterstitial + interstitialShowedToJoinLocusResponse - showInterstitialTime,
|
|
469
|
+
0,
|
|
470
|
+
this.MAX_INTEGER
|
|
471
|
+
);
|
|
437
472
|
}
|
|
438
473
|
|
|
439
474
|
return undefined;
|
|
@@ -445,13 +480,20 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
445
480
|
*/
|
|
446
481
|
public getTotalJMTWithUserDelay() {
|
|
447
482
|
const clickToInterstitialWithUserDelay = this.getClickToInterstitialWithUserDelay();
|
|
448
|
-
const
|
|
483
|
+
const interstitialShowedToJoinLocusResponse = this.getDiffBetweenTimestamps(
|
|
484
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
485
|
+
'client.locus.join.response'
|
|
486
|
+
);
|
|
449
487
|
|
|
450
488
|
if (
|
|
451
489
|
typeof clickToInterstitialWithUserDelay === 'number' &&
|
|
452
|
-
typeof
|
|
490
|
+
typeof interstitialShowedToJoinLocusResponse === 'number'
|
|
453
491
|
) {
|
|
454
|
-
return clamp(
|
|
492
|
+
return clamp(
|
|
493
|
+
clickToInterstitialWithUserDelay + interstitialShowedToJoinLocusResponse,
|
|
494
|
+
0,
|
|
495
|
+
this.MAX_INTEGER
|
|
496
|
+
);
|
|
455
497
|
}
|
|
456
498
|
|
|
457
499
|
return undefined;
|
|
@@ -478,21 +520,28 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
478
520
|
*/
|
|
479
521
|
public getTotalMediaJMT() {
|
|
480
522
|
const clickToInterstitial = this.getClickToInterstitial();
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
523
|
+
const interstitialShowedToMediaEngineReady = this.getDiffBetweenTimestamps(
|
|
524
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
525
|
+
'client.media-engine.ready'
|
|
526
|
+
);
|
|
527
|
+
const showInterstitialTime = this.getShowInterstitialTime() || 0;
|
|
528
|
+
const stayLobbyTimeCappedByMediaEngineReady = this.getStayLobbyTimeCappedBy(
|
|
529
|
+
'client.media-engine.ready'
|
|
530
|
+
);
|
|
484
531
|
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
|
|
532
|
+
if (
|
|
533
|
+
typeof clickToInterstitial === 'number' &&
|
|
534
|
+
typeof interstitialShowedToMediaEngineReady === 'number' &&
|
|
535
|
+
typeof stayLobbyTimeCappedByMediaEngineReady === 'number'
|
|
536
|
+
) {
|
|
537
|
+
return clamp(
|
|
538
|
+
clickToInterstitial +
|
|
539
|
+
interstitialShowedToMediaEngineReady -
|
|
540
|
+
showInterstitialTime -
|
|
541
|
+
stayLobbyTimeCappedByMediaEngineReady,
|
|
488
542
|
0,
|
|
489
|
-
|
|
543
|
+
this.MAX_INTEGER
|
|
490
544
|
);
|
|
491
|
-
if (this.getMeeting()?.allowMediaInLobby) {
|
|
492
|
-
return clamp(totalMediaJMT, 0, this.MAX_INTEGER);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return clamp(totalMediaJMT - lobbyTime, 0, this.MAX_INTEGER);
|
|
496
545
|
}
|
|
497
546
|
|
|
498
547
|
return undefined;
|
|
@@ -504,12 +553,17 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
504
553
|
*/
|
|
505
554
|
public getTotalMediaJMTWithUserDelay() {
|
|
506
555
|
const clickToInterstitialWithUserDelay = this.getClickToInterstitialWithUserDelay();
|
|
507
|
-
const
|
|
508
|
-
|
|
556
|
+
const interstitialShowedToMediaEngineReady = this.getDiffBetweenTimestamps(
|
|
557
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
558
|
+
'client.media-engine.ready'
|
|
559
|
+
);
|
|
509
560
|
|
|
510
|
-
if (
|
|
561
|
+
if (
|
|
562
|
+
typeof clickToInterstitialWithUserDelay === 'number' &&
|
|
563
|
+
typeof interstitialShowedToMediaEngineReady === 'number'
|
|
564
|
+
) {
|
|
511
565
|
return clamp(
|
|
512
|
-
clickToInterstitialWithUserDelay +
|
|
566
|
+
clickToInterstitialWithUserDelay + interstitialShowedToMediaEngineReady,
|
|
513
567
|
0,
|
|
514
568
|
this.MAX_INTEGER
|
|
515
569
|
);
|
|
@@ -523,11 +577,26 @@ export default class CallDiagnosticLatencies extends WebexPlugin {
|
|
|
523
577
|
* @returns - latency
|
|
524
578
|
*/
|
|
525
579
|
public getClientJMT() {
|
|
526
|
-
const
|
|
527
|
-
|
|
580
|
+
const clickToInterstitialForClientJmt = this.precomputedLatencies.get(
|
|
581
|
+
'internal.click.to.interstitial.for.client.jmt'
|
|
582
|
+
);
|
|
583
|
+
const interstitialShowedToLocusJoinRequest = this.getDiffBetweenTimestamps(
|
|
584
|
+
'internal.client.meeting.interstitial-window.showed',
|
|
585
|
+
'client.locus.join.request'
|
|
586
|
+
);
|
|
587
|
+
const showInterstitialTime = this.getShowInterstitialTime() || 0;
|
|
528
588
|
|
|
529
|
-
if (
|
|
530
|
-
|
|
589
|
+
if (
|
|
590
|
+
typeof clickToInterstitialForClientJmt === 'number' &&
|
|
591
|
+
typeof interstitialShowedToLocusJoinRequest === 'number'
|
|
592
|
+
) {
|
|
593
|
+
return clamp(
|
|
594
|
+
clickToInterstitialForClientJmt +
|
|
595
|
+
interstitialShowedToLocusJoinRequest -
|
|
596
|
+
showInterstitialTime,
|
|
597
|
+
0,
|
|
598
|
+
this.MAX_INTEGER
|
|
599
|
+
);
|
|
531
600
|
}
|
|
532
601
|
|
|
533
602
|
return undefined;
|
|
@@ -108,6 +108,8 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
108
108
|
private isMercuryConnected = false;
|
|
109
109
|
private eventLimitTracker: Map<string, number> = new Map();
|
|
110
110
|
private eventLimitWarningsLogged: Set<string> = new Set();
|
|
111
|
+
private isTelemetryOptOutManual = false;
|
|
112
|
+
private isTelemetryOptOutAutomatic = false;
|
|
111
113
|
|
|
112
114
|
// the default validator before piping an event to the batcher
|
|
113
115
|
// this function can be overridden by the user
|
|
@@ -145,6 +147,37 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
145
147
|
return null;
|
|
146
148
|
}
|
|
147
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Returns the telemetryOptOut value of the current user
|
|
152
|
+
* @returns one of 'manual', 'automatic', undefined
|
|
153
|
+
*/
|
|
154
|
+
public getTelemetryOptOut() {
|
|
155
|
+
if (this.isTelemetryOptOutManual) {
|
|
156
|
+
return 'manual';
|
|
157
|
+
}
|
|
158
|
+
if (this.isTelemetryOptOutAutomatic) {
|
|
159
|
+
return 'automatic';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Sets the manual telemetry opt-out status for the current user
|
|
167
|
+
* @param value - boolean value indicating manual telemetry opt-out status
|
|
168
|
+
*/
|
|
169
|
+
public setIsTelemetryOptOutManual(value: boolean) {
|
|
170
|
+
this.isTelemetryOptOutManual = value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Sets the automatic telemetry opt-out status for the current user
|
|
175
|
+
* @param value - boolean value indicating automatic telemetry opt-out status
|
|
176
|
+
*/
|
|
177
|
+
public setIsTelemetryOptOutAutomatic(value: boolean) {
|
|
178
|
+
this.isTelemetryOptOutAutomatic = value;
|
|
179
|
+
}
|
|
180
|
+
|
|
148
181
|
/**
|
|
149
182
|
* Returns if the meeting has converged architecture enabled
|
|
150
183
|
* @param options.meetingId
|
|
@@ -190,6 +223,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
190
223
|
|
|
191
224
|
// if ConvergedArchitecture enable and isConvergedWebinarWebcast -- then webcast
|
|
192
225
|
if (meetingInfo?.enableConvergedArchitecture && meetingInfo?.enableEvent) {
|
|
226
|
+
// if enableConvergedWebinarLargeScale - then large scale webinar
|
|
227
|
+
if (meetingInfo?.enableConvergedWebinarLargeScale) {
|
|
228
|
+
return WEBEX_SUB_SERVICE_TYPES.LARGE_SCALE_WEBINAR;
|
|
229
|
+
}
|
|
230
|
+
|
|
193
231
|
return meetingInfo?.isConvergedWebinarWebcast
|
|
194
232
|
? WEBEX_SUB_SERVICE_TYPES.WEBCAST
|
|
195
233
|
: WEBEX_SUB_SERVICE_TYPES.WEBINAR;
|
|
@@ -366,6 +404,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
366
404
|
if (meeting?.locusInfo?.fullState) {
|
|
367
405
|
identifiers.locusUrl = meeting.locusUrl;
|
|
368
406
|
identifiers.locusId = meeting.locusUrl && meeting.locusUrl.split('/').pop();
|
|
407
|
+
identifiers.locusSessionId = meeting.locusInfo.fullState.sessionId;
|
|
369
408
|
identifiers.locusStartTime =
|
|
370
409
|
meeting.locusInfo.fullState && meeting.locusInfo.fullState.lastActive;
|
|
371
410
|
}
|
|
@@ -601,6 +640,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
601
640
|
mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
|
|
602
641
|
startTime: new Date().toISOString(),
|
|
603
642
|
},
|
|
643
|
+
webexSubServiceType: this.getSubServiceType(meeting),
|
|
604
644
|
};
|
|
605
645
|
|
|
606
646
|
// merge any new properties, or override existing ones
|
|
@@ -983,7 +1023,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
983
1023
|
sessionCorrelationId,
|
|
984
1024
|
});
|
|
985
1025
|
|
|
986
|
-
// create common event object
|
|
1026
|
+
// create common event object structure
|
|
987
1027
|
const commonEventObject = {
|
|
988
1028
|
name,
|
|
989
1029
|
canProceed: true,
|
|
@@ -996,6 +1036,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
996
1036
|
'loginType' in meeting.callStateForMetrics
|
|
997
1037
|
? meeting.callStateForMetrics.loginType
|
|
998
1038
|
: this.getCurLoginType(),
|
|
1039
|
+
telemetryOptOut: this.getTelemetryOptOut(),
|
|
999
1040
|
isConvergedArchitectureEnabled: this.getIsConvergedArchitectureEnabled({
|
|
1000
1041
|
meetingId,
|
|
1001
1042
|
}),
|
|
@@ -1004,6 +1045,9 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
1004
1045
|
webexSubServiceType: this.getSubServiceType(meeting),
|
|
1005
1046
|
// @ts-ignore
|
|
1006
1047
|
webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
|
|
1048
|
+
isVipMeeting: meeting?.meetingInfo?.vipmeeting || false,
|
|
1049
|
+
isAutomatedUser:
|
|
1050
|
+
typeof window !== 'undefined' && typeof navigator !== 'undefined' && !!navigator?.webdriver, // if webdriver is true, it's most likely in a test environment
|
|
1007
1051
|
};
|
|
1008
1052
|
|
|
1009
1053
|
const joinFlowVersion = options.joinFlowVersion ?? meeting.callStateForMetrics?.joinFlowVersion;
|
|
@@ -1123,8 +1167,11 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
1123
1167
|
isMercuryConnected: this.isMercuryConnected,
|
|
1124
1168
|
},
|
|
1125
1169
|
loginType: this.getCurLoginType(),
|
|
1170
|
+
telemetryOptOut: this.getTelemetryOptOut(),
|
|
1126
1171
|
// @ts-ignore
|
|
1127
1172
|
webClientPreload: this.webex.meetings?.config?.metrics?.webClientPreload,
|
|
1173
|
+
isAutomatedUser:
|
|
1174
|
+
typeof window !== 'undefined' && typeof navigator !== 'undefined' && !!navigator?.webdriver, // if webdriver is true, it's most likely in a test environment
|
|
1128
1175
|
};
|
|
1129
1176
|
|
|
1130
1177
|
if (options.joinFlowVersion) {
|
|
@@ -1317,6 +1364,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
1317
1364
|
const finalEvent = {
|
|
1318
1365
|
eventPayload: event,
|
|
1319
1366
|
type: ['diagnostic-event'],
|
|
1367
|
+
markTelemetryOptOutOnResponse: true,
|
|
1320
1368
|
};
|
|
1321
1369
|
|
|
1322
1370
|
return this.callDiagnosticEventsBatcher.request(finalEvent);
|
|
@@ -1326,6 +1374,7 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
1326
1374
|
* Prepare the event and send the request to metrics-a service, pre login.
|
|
1327
1375
|
* @param event
|
|
1328
1376
|
* @param preLoginId
|
|
1377
|
+
* @param markTelemetryOptOutOnResponse
|
|
1329
1378
|
* @returns
|
|
1330
1379
|
*/
|
|
1331
1380
|
submitToCallDiagnosticsPreLogin = (event: Event, preLoginId?: string): Promise<any> => {
|
|
@@ -1333,7 +1382,9 @@ export default class CallDiagnosticMetrics extends StatelessWebexPlugin {
|
|
|
1333
1382
|
const finalEvent = {
|
|
1334
1383
|
eventPayload: event,
|
|
1335
1384
|
type: ['diagnostic-event'],
|
|
1385
|
+
markTelemetryOptOutOnResponse: true,
|
|
1336
1386
|
};
|
|
1387
|
+
|
|
1337
1388
|
this.preLoginMetricsBatcher.savePreLoginId(preLoginId);
|
|
1338
1389
|
|
|
1339
1390
|
return this.preLoginMetricsBatcher.request(finalEvent);
|
|
@@ -179,6 +179,43 @@ export const isSdpOfferCreationError = (rawError: any) => {
|
|
|
179
179
|
return false;
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
+
export const isWebrtcApiNotAvailableError = (
|
|
183
|
+
rawError: {code: number; message: string; name: string} | unknown
|
|
184
|
+
) => {
|
|
185
|
+
if ((rawError as {name: string}).name === ERROR_DESCRIPTIONS.WEBRTC_API_NOT_AVAILABLE) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return false;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Checks if the given error is a browser media error by its name.
|
|
194
|
+
* Returns true if the error name matches any known browser media error name in the mapping.
|
|
195
|
+
*
|
|
196
|
+
* @param {Object} rawError - The error object to check.
|
|
197
|
+
* @returns {boolean} True if the error is a browser media error, false otherwise.
|
|
198
|
+
*/
|
|
199
|
+
export const isBrowserMediaError = (rawError) => {
|
|
200
|
+
// eslint-disable-next-line no-use-before-define
|
|
201
|
+
if (isBrowserMediaErrorName(rawError.name)) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return false;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Returns the client error code mapped to the given browser media error name.
|
|
210
|
+
* If the error name is not found in the mapping, returns undefined.
|
|
211
|
+
*
|
|
212
|
+
* @param {Object} rawError - The error object containing the error name.
|
|
213
|
+
* @returns {string|undefined} The mapped client error code, or undefined if not found.
|
|
214
|
+
*/
|
|
215
|
+
export const getBrowserMediaErrorCode = (rawError) => {
|
|
216
|
+
return BROWSER_MEDIA_ERROR_NAME_TO_CLIENT_ERROR_CODES_MAP[rawError.name];
|
|
217
|
+
};
|
|
218
|
+
|
|
182
219
|
/**
|
|
183
220
|
* MDN Media Devices getUserMedia() method returns a name if it errs
|
|
184
221
|
* Documentation can be found here: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
|
|
@@ -247,7 +284,8 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
|
|
|
247
284
|
devicePairingType: webex.devicemanager.getPairedMethod(),
|
|
248
285
|
deviceURL: pairedDevice.url,
|
|
249
286
|
isPersonalDevice: pairedDevice.mode === 'personal',
|
|
250
|
-
productName:
|
|
287
|
+
productName:
|
|
288
|
+
pairedDevice.devices?.length > 0 ? pairedDevice.devices[0]?.productName : undefined,
|
|
251
289
|
};
|
|
252
290
|
item.eventPayload.event.pairingState = 'paired';
|
|
253
291
|
item.eventPayload.event.pairedDevice = devicePayload;
|
|
@@ -324,7 +362,6 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
|
|
|
324
362
|
joinTimes.totalMediaJMT = cdl.getTotalMediaJMT();
|
|
325
363
|
joinTimes.interstitialToMediaOKJMT = cdl.getInterstitialToMediaOKJMT();
|
|
326
364
|
joinTimes.callInitMediaEngineReady = cdl.getCallInitMediaEngineReady();
|
|
327
|
-
joinTimes.stayLobbyTime = cdl.getStayLobbyTime();
|
|
328
365
|
joinTimes.totalMediaJMTWithUserDelay = cdl.getTotalMediaJMTWithUserDelay();
|
|
329
366
|
joinTimes.totalJMTWithUserDelay = cdl.getTotalJMTWithUserDelay();
|
|
330
367
|
break;
|
|
@@ -332,6 +369,11 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
|
|
|
332
369
|
case 'client.media.tx.start':
|
|
333
370
|
audioSetupDelay.joinRespTxStart = cdl.getAudioJoinRespTxStart();
|
|
334
371
|
videoSetupDelay.joinRespTxStart = cdl.getVideoJoinRespTxStart();
|
|
372
|
+
break;
|
|
373
|
+
|
|
374
|
+
case 'client.lobby.exited':
|
|
375
|
+
joinTimes.stayLobbyTime = cdl.getStayLobbyTime();
|
|
376
|
+
break;
|
|
335
377
|
}
|
|
336
378
|
|
|
337
379
|
if (!isEmpty(joinTimes)) {
|
|
@@ -348,6 +390,11 @@ export const prepareDiagnosticMetricItem = (webex: any, item: any) => {
|
|
|
348
390
|
|
|
349
391
|
item.eventPayload.origin = Object.assign(origin, item.eventPayload.origin);
|
|
350
392
|
|
|
393
|
+
// Mark call milestones in logs for easier filtering and analysis
|
|
394
|
+
if (eventName) {
|
|
395
|
+
webex.logger.log('Milestone,CallDiagnostic', eventName);
|
|
396
|
+
}
|
|
397
|
+
|
|
351
398
|
webex.logger.log(
|
|
352
399
|
`CallDiagnosticLatencies,prepareDiagnosticMetricItem: ${JSON.stringify({
|
|
353
400
|
latencies: Object.fromEntries(cdl.latencyTimestamps),
|