@webex/internal-plugin-metrics 3.12.0-next.6 → 3.12.0-next.61

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 (98) hide show
  1. package/.repo-context.json +466 -0
  2. package/.sdd/manifest.json +626 -0
  3. package/AGENTS.md +208 -0
  4. package/README.md +274 -0
  5. package/dist/automated-user.js +19 -0
  6. package/dist/automated-user.js.map +1 -0
  7. package/dist/batcher.js +3 -0
  8. package/dist/batcher.js.map +1 -1
  9. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +613 -53
  10. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  11. package/dist/call-diagnostic/call-diagnostic-metrics.js +240 -103
  12. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  13. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +5 -0
  14. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  15. package/dist/call-diagnostic/config.js +19 -4
  16. package/dist/call-diagnostic/config.js.map +1 -1
  17. package/dist/config.js +8 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/index.js +25 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/metrics.js +118 -5
  22. package/dist/metrics.js.map +1 -1
  23. package/dist/metrics.types.js +4 -0
  24. package/dist/metrics.types.js.map +1 -1
  25. package/dist/network-telemetry.js +610 -0
  26. package/dist/network-telemetry.js.map +1 -0
  27. package/dist/new-metrics.js +63 -17
  28. package/dist/new-metrics.js.map +1 -1
  29. package/dist/privacy-and-security-permission-enricher/constants.js +18 -0
  30. package/dist/privacy-and-security-permission-enricher/constants.js.map +1 -0
  31. package/dist/privacy-and-security-permission-enricher/index.js +143 -0
  32. package/dist/privacy-and-security-permission-enricher/index.js.map +1 -0
  33. package/dist/privacy-and-security-permission-enricher/types.js +7 -0
  34. package/dist/privacy-and-security-permission-enricher/types.js.map +1 -0
  35. package/dist/privacy-and-security-permission-enricher/utils.js +71 -0
  36. package/dist/privacy-and-security-permission-enricher/utils.js.map +1 -0
  37. package/dist/types/automated-user.d.ts +2 -0
  38. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +200 -5
  39. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +77 -17
  40. package/dist/types/call-diagnostic/config.d.ts +6 -0
  41. package/dist/types/config.d.ts +9 -0
  42. package/dist/types/index.d.ts +5 -3
  43. package/dist/types/metrics.types.d.ts +11 -5
  44. package/dist/types/network-telemetry.d.ts +108 -0
  45. package/dist/types/new-metrics.d.ts +17 -1
  46. package/dist/types/privacy-and-security-permission-enricher/constants.d.ts +6 -0
  47. package/dist/types/privacy-and-security-permission-enricher/index.d.ts +40 -0
  48. package/dist/types/privacy-and-security-permission-enricher/types.d.ts +14 -0
  49. package/dist/types/privacy-and-security-permission-enricher/utils.d.ts +5 -0
  50. package/dist/types/unhandled-exception-telemetry/index.d.ts +60 -0
  51. package/dist/types/unhandled-exception-telemetry/utils.d.ts +6 -0
  52. package/dist/unhandled-exception-telemetry/index.js +331 -0
  53. package/dist/unhandled-exception-telemetry/index.js.map +1 -0
  54. package/dist/unhandled-exception-telemetry/utils.js +105 -0
  55. package/dist/unhandled-exception-telemetry/utils.js.map +1 -0
  56. package/docs/adr/index.md +27 -0
  57. package/docs/architecture.md +437 -0
  58. package/docs/getting-started.md +131 -0
  59. package/docs/index.md +52 -0
  60. package/docs/specs/README.md +88 -0
  61. package/package.json +12 -11
  62. package/schemas/repo-context.schema.json +796 -0
  63. package/src/automated-user.ts +16 -0
  64. package/src/batcher.js +4 -0
  65. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +737 -60
  66. package/src/call-diagnostic/call-diagnostic-metrics.ts +184 -42
  67. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +5 -0
  68. package/src/call-diagnostic/config.ts +16 -0
  69. package/src/call-diagnostic/docs/README.md +535 -0
  70. package/src/config.js +8 -0
  71. package/src/docs/README.md +703 -0
  72. package/src/index.ts +16 -0
  73. package/src/metrics.js +123 -5
  74. package/src/metrics.types.ts +29 -6
  75. package/src/network-telemetry.ts +757 -0
  76. package/src/new-metrics.ts +56 -6
  77. package/src/privacy-and-security-permission-enricher/constants.ts +33 -0
  78. package/src/privacy-and-security-permission-enricher/docs/README.md +356 -0
  79. package/src/privacy-and-security-permission-enricher/index.ts +142 -0
  80. package/src/privacy-and-security-permission-enricher/types.ts +21 -0
  81. package/src/privacy-and-security-permission-enricher/utils.ts +82 -0
  82. package/src/rtcMetrics/docs/README.md +447 -0
  83. package/src/unhandled-exception-telemetry/docs/README.md +536 -0
  84. package/src/unhandled-exception-telemetry/index.ts +404 -0
  85. package/src/unhandled-exception-telemetry/utils.ts +101 -0
  86. package/test/unit/spec/automated-user.ts +43 -0
  87. package/test/unit/spec/batcher.js +43 -0
  88. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +10 -1
  89. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +1336 -131
  90. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +1265 -207
  91. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +27 -0
  92. package/test/unit/spec/metrics.js +14 -5
  93. package/test/unit/spec/network-telemetry.ts +610 -0
  94. package/test/unit/spec/new-metrics.ts +203 -50
  95. package/test/unit/spec/prelogin-metrics-batcher.ts +71 -36
  96. package/test/unit/spec/privacy-and-security-permission-enricher.ts +318 -0
  97. package/test/unit/spec/unhandled-exception-telemetry/utils.ts +109 -0
  98. package/test/unit/spec/unhandled-exception-telemetry.ts +689 -0
@@ -9,6 +9,7 @@ import {
9
9
  CallDiagnosticMetrics,
10
10
  getOSNameInternal,
11
11
  CallDiagnosticUtils,
12
+ AutomatedUserUtils,
12
13
  config,
13
14
  } from '@webex/internal-plugin-metrics';
14
15
  import uuid from 'uuid';
@@ -34,6 +35,7 @@ describe('internal-plugin-metrics', () => {
34
35
  var now = new Date();
35
36
 
36
37
  let cd: CallDiagnosticMetrics;
38
+ let isAutomatedUserStub: sinon.SinonStub;
37
39
 
38
40
  const fakeMeeting = {
39
41
  id: '1',
@@ -79,12 +81,20 @@ describe('internal-plugin-metrics', () => {
79
81
  emailInput: 'test@test.com',
80
82
  };
81
83
 
84
+ const fakeMeeting6 = {
85
+ ...fakeMeeting,
86
+ id: '6',
87
+ correlationId: 'correlationId6',
88
+ callStateForMetrics: {meetingJoinPhase: 'in-meeting'},
89
+ };
90
+
82
91
  const fakeMeetings = {
83
92
  1: fakeMeeting,
84
93
  2: fakeMeeting2,
85
94
  3: fakeMeeting3,
86
95
  4: fakeMeeting4,
87
96
  5: fakeMeeting5,
97
+ 6: fakeMeeting6,
88
98
  };
89
99
 
90
100
  let webex;
@@ -155,6 +165,7 @@ describe('internal-plugin-metrics', () => {
155
165
  sinon.useFakeTimers(now.getTime());
156
166
  cd = new CallDiagnosticMetrics({}, {parent: webex});
157
167
  sinon.stub(uuid, 'v4').returns('my-fake-id');
168
+ isAutomatedUserStub = sinon.stub(AutomatedUserUtils, 'isAutomatedUser').returns(false);
158
169
  cd.setDeviceInfo(webex.internal.device);
159
170
  });
160
171
 
@@ -361,6 +372,23 @@ describe('internal-plugin-metrics', () => {
361
372
  });
362
373
  });
363
374
 
375
+ it('should build origin correctly when the meetings plugin is not available (before webex is ready)', () => {
376
+ // meetings plugin, geoHintInfo and meetingCollection are all absent before webex.ready
377
+ webex.meetings = undefined;
378
+
379
+ //@ts-ignore
380
+ const res = cd.getOrigin(
381
+ {subClientType: 'WEB_APP', clientType: 'TEAMS_CLIENT'},
382
+ fakeMeeting.id
383
+ );
384
+
385
+ assert.equal(res.clientInfo.clientType, 'TEAMS_CLIENT');
386
+ assert.equal(res.clientInfo.subClientType, 'WEB_APP');
387
+ assert.isUndefined(res.clientInfo.publicNetworkPrefix);
388
+ assert.isUndefined(res.clientInfo.localNetworkPrefix);
389
+ assert.equal(res.name, 'endpoint');
390
+ });
391
+
364
392
  it('builds origin correctly, when overriding clientVersion', () => {
365
393
  webex.meetings.config.metrics.clientVersion = '43.9.0.1234';
366
394
 
@@ -391,6 +419,64 @@ describe('internal-plugin-metrics', () => {
391
419
  });
392
420
  });
393
421
 
422
+ it('builds origin correctly with browser details provided by the host client', () => {
423
+ // host resolves wrapper browsers (WebOS, Electron) to the engine that determines capability
424
+ webex.meetings.config.metrics.browser = 'WebOS Browser';
425
+ webex.meetings.config.metrics.browserVersion = '108.0.0.0';
426
+
427
+ //@ts-ignore
428
+ const res = cd.getOrigin(
429
+ {subClientType: 'WEB_APP', clientType: 'TEAMS_CLIENT'},
430
+ fakeMeeting.id
431
+ );
432
+
433
+ assert.equal(res.clientInfo.browser, 'WebOS Browser');
434
+ assert.equal(res.clientInfo.browserVersion, '108.0.0.0');
435
+ });
436
+
437
+ it('falls back to SDK browser detection when the host provides nothing', () => {
438
+ //@ts-ignore
439
+ const res = cd.getOrigin(
440
+ {subClientType: 'WEB_APP', clientType: 'TEAMS_CLIENT'},
441
+ fakeMeeting.id
442
+ );
443
+
444
+ assert.equal(res.clientInfo.browser, getBrowserName());
445
+ assert.equal(res.clientInfo.browserVersion, getBrowserVersion());
446
+ });
447
+
448
+ it('builds origin correctly with the browser support flags from config', () => {
449
+ // `false` is the meaningful "unsupported family" signal, so it must survive a truthy check
450
+ webex.meetings.config.metrics.isSupportedBrowserFamily = false;
451
+ webex.meetings.config.metrics.isOutdatedBrowserVersion = true;
452
+
453
+ //@ts-ignore
454
+ const res = cd.getOrigin(
455
+ {subClientType: 'WEB_APP', clientType: 'TEAMS_CLIENT'},
456
+ fakeMeeting.id
457
+ );
458
+
459
+ assert.deepEqual(res, {
460
+ clientInfo: {
461
+ browser: getBrowserName(),
462
+ browserVersion: getBrowserVersion(),
463
+ clientType: 'TEAMS_CLIENT',
464
+ clientVersion: 'webex-js-sdk/webex-version',
465
+ publicNetworkPrefix: '1.3.4.0',
466
+ localNetworkPrefix: '192.168.1.80',
467
+ os: getOSNameInternal(),
468
+ osVersion: getOSVersion() || 'unknown',
469
+ subClientType: 'WEB_APP',
470
+ isSupportedBrowserFamily: false,
471
+ isOutdatedBrowserVersion: true,
472
+ },
473
+ environment: 'meeting_evn',
474
+ name: 'endpoint',
475
+ networkType: 'unknown',
476
+ userAgent,
477
+ });
478
+ });
479
+
394
480
  it('should build origin correctly with no meeting or stats analyzer', () => {
395
481
  //@ts-ignore
396
482
  const res = cd.getOrigin();
@@ -637,6 +723,63 @@ describe('internal-plugin-metrics', () => {
637
723
  });
638
724
  });
639
725
 
726
+ it('should build identifiers correctly with a meeting that has meetingInfo with locusClusterUrl', () => {
727
+ const res = cd.getIdentifiers({
728
+ mediaConnections: [
729
+ {mediaAgentAlias: 'mediaAgentAlias', mediaAgentGroupId: 'mediaAgentGroupId'},
730
+ ],
731
+ meeting: {
732
+ ...fakeMeeting,
733
+ meetingInfo: {
734
+ ...fakeMeeting.meetingInfo,
735
+ locusClusterUrl: 'locus-k.wbx2.com',
736
+ },
737
+ },
738
+ });
739
+
740
+ assert.deepEqual(res, {
741
+ correlationId: 'correlationId',
742
+ deviceId: 'deviceUrl',
743
+ locusCluster: 'locus-k.wbx2.com',
744
+ locusId: 'url',
745
+ locusSessionId: 'locusSessionId',
746
+ locusStartTime: 'lastActive',
747
+ locusUrl: 'locus/url',
748
+ mediaAgentAlias: 'mediaAgentAlias',
749
+ mediaAgentGroupId: 'mediaAgentGroupId',
750
+ orgId: 'orgId',
751
+ userId: 'userId',
752
+ });
753
+ });
754
+
755
+ it('should build identifiers correctly given locusCluster', () => {
756
+ const res = cd.getIdentifiers({
757
+ correlationId: 'correlationId',
758
+ locusCluster: 'locus-k.wbx2.com',
759
+ });
760
+
761
+ assert.deepEqual(res, {
762
+ correlationId: 'correlationId',
763
+ deviceId: 'deviceUrl',
764
+ locusCluster: 'locus-k.wbx2.com',
765
+ locusUrl: 'locus-url',
766
+ orgId: 'orgId',
767
+ userId: 'userId',
768
+ });
769
+ });
770
+
771
+ it('should prefer meetingInfo locusClusterUrl over the locusCluster option', () => {
772
+ const res = cd.getIdentifiers({
773
+ meeting: {
774
+ ...fakeMeeting,
775
+ meetingInfo: {...fakeMeeting.meetingInfo, locusClusterUrl: 'locus-k.wbx2.com'},
776
+ },
777
+ locusCluster: 'locus-a.wbx2.com',
778
+ });
779
+
780
+ assert.equal(res.locusCluster, 'locus-k.wbx2.com');
781
+ });
782
+
640
783
  it('should build identifiers correctly with a meeting that has sessionCorrelationId', () => {
641
784
  const res = cd.getIdentifiers({
642
785
  mediaConnections: [
@@ -763,6 +906,60 @@ describe('internal-plugin-metrics', () => {
763
906
  userId: 'preLoginId',
764
907
  });
765
908
  });
909
+
910
+ it('should use the userId from credentials when no device userId is available', () => {
911
+ cd.device.userId = undefined;
912
+ webex.credentials.getUserId = sinon.stub().returns('credentials-user-id');
913
+
914
+ const res = cd.getIdentifiers({
915
+ correlationId: 'correlationId',
916
+ });
917
+
918
+ assert.deepEqual(res, {
919
+ correlationId: 'correlationId',
920
+ locusUrl: 'locus-url',
921
+ deviceId: 'deviceUrl',
922
+ orgId: 'orgId',
923
+ userId: 'credentials-user-id',
924
+ });
925
+ });
926
+
927
+ it('should prefer the device userId over the credentials userId', () => {
928
+ webex.credentials.getUserId = sinon.stub().returns('credentials-user-id');
929
+
930
+ const res = cd.getIdentifiers({
931
+ correlationId: 'correlationId',
932
+ });
933
+
934
+ assert.equal(res.userId, 'userId');
935
+ assert.notCalled(webex.credentials.getUserId);
936
+ });
937
+
938
+ it('should fall back to preLoginId when neither the device nor credentials provide a userId', () => {
939
+ cd.device.userId = undefined;
940
+ webex.credentials.getUserId = sinon.stub().throws(new Error('no user token available'));
941
+
942
+ const res = cd.getIdentifiers({
943
+ correlationId: 'correlationId',
944
+ preLoginId: 'preLoginId',
945
+ });
946
+
947
+ assert.equal(res.userId, 'preLoginId');
948
+ });
949
+ });
950
+
951
+ describe('#getUserIdFromCredentials', () => {
952
+ it('should return the userId from credentials', () => {
953
+ webex.credentials.getUserId = sinon.stub().returns('credentials-user-id');
954
+
955
+ assert.equal(cd.getUserIdFromCredentials(), 'credentials-user-id');
956
+ });
957
+
958
+ it('should return undefined when credentials cannot provide a userId', () => {
959
+ webex.credentials.getUserId = sinon.stub().throws(new Error('no user token available'));
960
+
961
+ assert.isUndefined(cd.getUserIdFromCredentials());
962
+ });
766
963
  });
767
964
 
768
965
  it('should prepare diagnostic event successfully', () => {
@@ -857,6 +1054,103 @@ describe('internal-plugin-metrics', () => {
857
1054
  });
858
1055
  });
859
1056
 
1057
+ describe('#prepareDiagnosticEvent isAutomatedUser field', () => {
1058
+ it('should set isAutomatedUser to false when window is not defined', () => {
1059
+ const options = {meetingId: fakeMeeting.id};
1060
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
1061
+
1062
+ const res = cd.prepareDiagnosticEvent(
1063
+ {
1064
+ canProceed: true,
1065
+ identifiers: {correlationId: 'test-id'},
1066
+ name: 'client.alert.displayed',
1067
+ isAutomatedUser: false,
1068
+ userActivation: undefined,
1069
+ },
1070
+ options
1071
+ );
1072
+
1073
+ // In the test environment, isAutomatedUser should be false since we're not in a webdriver environment
1074
+ assert.isFalse(
1075
+ res.event.isAutomatedUser,
1076
+ 'isAutomatedUser should be false in non-webdriver test environment'
1077
+ );
1078
+ });
1079
+
1080
+ it('should include isAutomatedUser field in the returned event', () => {
1081
+ const options = {meetingId: fakeMeeting.id};
1082
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
1083
+
1084
+ const res = cd.prepareDiagnosticEvent(
1085
+ {
1086
+ canProceed: true,
1087
+ identifiers: {correlationId: 'test-id'},
1088
+ name: 'client.alert.displayed',
1089
+ isAutomatedUser: false,
1090
+ userActivation: undefined,
1091
+ },
1092
+ options
1093
+ );
1094
+
1095
+ // Verify the isAutomatedUser field is present in the event
1096
+ assert.isDefined(res.event.isAutomatedUser, 'isAutomatedUser field should be defined');
1097
+ assert.isBoolean(res.event.isAutomatedUser, 'isAutomatedUser should be a boolean');
1098
+ });
1099
+
1100
+ it('should set isAutomatedUser to true when the user is automated', () => {
1101
+ isAutomatedUserStub.returns(true);
1102
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
1103
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
1104
+ cd.setMercuryConnectedStatus(true);
1105
+
1106
+ cd.submitClientEvent({
1107
+ name: 'client.alert.displayed',
1108
+ options: {correlationId: 'correlationId'},
1109
+ });
1110
+
1111
+ assert.isTrue(
1112
+ prepareDiagnosticEventSpy.firstCall.args[0].isAutomatedUser,
1113
+ 'isAutomatedUser should be true for an automated user'
1114
+ );
1115
+ });
1116
+ });
1117
+
1118
+ describe('#getUserActivation', () => {
1119
+ let originalDescriptor;
1120
+
1121
+ beforeEach(() => {
1122
+ originalDescriptor = Object.getOwnPropertyDescriptor(global, 'navigator');
1123
+ });
1124
+
1125
+ afterEach(() => {
1126
+ if (originalDescriptor) {
1127
+ Object.defineProperty(global, 'navigator', originalDescriptor);
1128
+ } else {
1129
+ delete (global as any).navigator;
1130
+ }
1131
+ });
1132
+
1133
+ it('should return the userActivation state when navigator.userActivation is available', () => {
1134
+ Object.defineProperty(global, 'navigator', {
1135
+ value: {userActivation: {hasBeenActive: true, isActive: false}},
1136
+ configurable: true,
1137
+ writable: true,
1138
+ });
1139
+
1140
+ assert.deepEqual(cd.getUserActivation(), {hasBeenActive: true, isActive: false});
1141
+ });
1142
+
1143
+ it('should return undefined when navigator.userActivation is not available', () => {
1144
+ Object.defineProperty(global, 'navigator', {
1145
+ value: {},
1146
+ configurable: true,
1147
+ writable: true,
1148
+ });
1149
+
1150
+ assert.isUndefined(cd.getUserActivation());
1151
+ });
1152
+ });
1153
+
860
1154
  describe('#submitClientEvent', () => {
861
1155
  it('should submit client event successfully with meetingId', () => {
862
1156
  const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
@@ -884,6 +1178,7 @@ describe('internal-plugin-metrics', () => {
884
1178
  webexConferenceIdStr: undefined,
885
1179
  sessionCorrelationId: undefined,
886
1180
  globalMeetingId: undefined,
1181
+ locusCluster: undefined,
887
1182
  });
888
1183
  assert.notCalled(generateClientEventErrorPayloadSpy);
889
1184
  assert.calledWith(
@@ -907,11 +1202,15 @@ describe('internal-plugin-metrics', () => {
907
1202
  userId: 'userId',
908
1203
  },
909
1204
  loginType: 'login-ci',
1205
+ telemetryOptOut: undefined,
910
1206
  name: 'client.alert.displayed',
911
1207
  userType: 'host',
912
1208
  isConvergedArchitectureEnabled: undefined,
913
1209
  webexSubServiceType: undefined,
914
1210
  webClientPreload: undefined,
1211
+ isVipMeeting: false,
1212
+ isAutomatedUser: false,
1213
+ userActivation: undefined,
915
1214
  },
916
1215
  options
917
1216
  );
@@ -935,11 +1234,15 @@ describe('internal-plugin-metrics', () => {
935
1234
  userId: 'userId',
936
1235
  },
937
1236
  loginType: 'login-ci',
1237
+ telemetryOptOut: undefined,
938
1238
  name: 'client.alert.displayed',
939
1239
  userType: 'host',
940
1240
  isConvergedArchitectureEnabled: undefined,
941
1241
  webexSubServiceType: undefined,
942
1242
  webClientPreload: undefined,
1243
+ isVipMeeting: false,
1244
+ isAutomatedUser: false,
1245
+ userActivation: undefined,
943
1246
  },
944
1247
  eventId: 'my-fake-id',
945
1248
  origin: {
@@ -974,11 +1277,15 @@ describe('internal-plugin-metrics', () => {
974
1277
  userId: 'userId',
975
1278
  },
976
1279
  loginType: 'login-ci',
1280
+ telemetryOptOut: undefined,
977
1281
  name: 'client.alert.displayed',
978
1282
  userType: 'host',
979
1283
  isConvergedArchitectureEnabled: undefined,
980
1284
  webexSubServiceType: undefined,
981
1285
  webClientPreload: undefined,
1286
+ isVipMeeting: false,
1287
+ isAutomatedUser: false,
1288
+ userActivation: undefined,
982
1289
  },
983
1290
  eventId: 'my-fake-id',
984
1291
  origin: {
@@ -1026,6 +1333,7 @@ describe('internal-plugin-metrics', () => {
1026
1333
  webexConferenceIdStr: undefined,
1027
1334
  sessionCorrelationId: undefined,
1028
1335
  globalMeetingId: undefined,
1336
+ locusCluster: undefined,
1029
1337
  });
1030
1338
  assert.notCalled(generateClientEventErrorPayloadSpy);
1031
1339
  assert.calledWith(
@@ -1049,11 +1357,15 @@ describe('internal-plugin-metrics', () => {
1049
1357
  userId: 'userId',
1050
1358
  },
1051
1359
  loginType: 'login-ci',
1360
+ telemetryOptOut: undefined,
1052
1361
  name: 'client.alert.displayed',
1053
1362
  userType: 'host',
1054
1363
  isConvergedArchitectureEnabled: undefined,
1055
1364
  webexSubServiceType: undefined,
1056
1365
  webClientPreload: undefined,
1366
+ isVipMeeting: false,
1367
+ isAutomatedUser: false,
1368
+ userActivation: undefined,
1057
1369
  },
1058
1370
  options
1059
1371
  );
@@ -1077,11 +1389,15 @@ describe('internal-plugin-metrics', () => {
1077
1389
  userId: 'userId',
1078
1390
  },
1079
1391
  loginType: 'login-ci',
1392
+ telemetryOptOut: undefined,
1080
1393
  name: 'client.alert.displayed',
1081
1394
  userType: 'host',
1082
1395
  isConvergedArchitectureEnabled: undefined,
1083
1396
  webexSubServiceType: undefined,
1084
1397
  webClientPreload: undefined,
1398
+ isVipMeeting: false,
1399
+ isAutomatedUser: false,
1400
+ userActivation: undefined,
1085
1401
  },
1086
1402
  eventId: 'my-fake-id',
1087
1403
  origin: {
@@ -1116,11 +1432,15 @@ describe('internal-plugin-metrics', () => {
1116
1432
  userId: 'userId',
1117
1433
  },
1118
1434
  loginType: 'login-ci',
1435
+ telemetryOptOut: undefined,
1119
1436
  name: 'client.alert.displayed',
1120
1437
  userType: 'host',
1121
1438
  isConvergedArchitectureEnabled: undefined,
1122
1439
  webexSubServiceType: undefined,
1123
1440
  webClientPreload: undefined,
1441
+ isVipMeeting: false,
1442
+ isAutomatedUser: false,
1443
+ userActivation: undefined,
1124
1444
  },
1125
1445
  eventId: 'my-fake-id',
1126
1446
  origin: {
@@ -1168,6 +1488,7 @@ describe('internal-plugin-metrics', () => {
1168
1488
  webexConferenceIdStr: undefined,
1169
1489
  globalMeetingId: undefined,
1170
1490
  sessionCorrelationId: undefined,
1491
+ locusCluster: undefined,
1171
1492
  });
1172
1493
  assert.notCalled(generateClientEventErrorPayloadSpy);
1173
1494
  assert.calledWith(
@@ -1192,11 +1513,15 @@ describe('internal-plugin-metrics', () => {
1192
1513
  userId: 'userId',
1193
1514
  },
1194
1515
  loginType: 'login-ci',
1516
+ telemetryOptOut: undefined,
1195
1517
  name: 'client.alert.displayed',
1196
1518
  userType: 'host',
1197
1519
  isConvergedArchitectureEnabled: undefined,
1198
1520
  webexSubServiceType: undefined,
1199
1521
  webClientPreload: undefined,
1522
+ isVipMeeting: false,
1523
+ isAutomatedUser: false,
1524
+ userActivation: undefined,
1200
1525
  },
1201
1526
  options
1202
1527
  );
@@ -1221,11 +1546,15 @@ describe('internal-plugin-metrics', () => {
1221
1546
  userId: 'userId',
1222
1547
  },
1223
1548
  loginType: 'login-ci',
1549
+ telemetryOptOut: undefined,
1224
1550
  name: 'client.alert.displayed',
1225
1551
  userType: 'host',
1226
1552
  isConvergedArchitectureEnabled: undefined,
1227
1553
  webexSubServiceType: undefined,
1228
1554
  webClientPreload: undefined,
1555
+ isVipMeeting: false,
1556
+ isAutomatedUser: false,
1557
+ userActivation: undefined,
1229
1558
  },
1230
1559
  eventId: 'my-fake-id',
1231
1560
  origin: {
@@ -1261,11 +1590,15 @@ describe('internal-plugin-metrics', () => {
1261
1590
  userId: 'userId',
1262
1591
  },
1263
1592
  loginType: 'login-ci',
1593
+ telemetryOptOut: undefined,
1264
1594
  name: 'client.alert.displayed',
1265
1595
  userType: 'host',
1266
1596
  isConvergedArchitectureEnabled: undefined,
1267
1597
  webexSubServiceType: undefined,
1268
1598
  webClientPreload: undefined,
1599
+ isVipMeeting: false,
1600
+ isAutomatedUser: false,
1601
+ userActivation: undefined,
1269
1602
  },
1270
1603
  eventId: 'my-fake-id',
1271
1604
  origin: {
@@ -1312,6 +1645,7 @@ describe('internal-plugin-metrics', () => {
1312
1645
  webexConferenceIdStr: undefined,
1313
1646
  globalMeetingId: undefined,
1314
1647
  sessionCorrelationId: undefined,
1648
+ locusCluster: undefined,
1315
1649
  });
1316
1650
  assert.notCalled(generateClientEventErrorPayloadSpy);
1317
1651
  assert.calledWith(
@@ -1336,11 +1670,15 @@ describe('internal-plugin-metrics', () => {
1336
1670
  userId: 'userId',
1337
1671
  },
1338
1672
  loginType: 'login-ci',
1673
+ telemetryOptOut: undefined,
1339
1674
  webClientPreload: undefined,
1340
1675
  name: 'client.alert.displayed',
1341
1676
  userType: 'host',
1342
1677
  isConvergedArchitectureEnabled: undefined,
1343
1678
  webexSubServiceType: undefined,
1679
+ isVipMeeting: false,
1680
+ isAutomatedUser: false,
1681
+ userActivation: undefined,
1344
1682
  },
1345
1683
  options
1346
1684
  );
@@ -1365,11 +1703,15 @@ describe('internal-plugin-metrics', () => {
1365
1703
  userId: 'userId',
1366
1704
  },
1367
1705
  loginType: 'login-ci',
1706
+ telemetryOptOut: undefined,
1368
1707
  webClientPreload: undefined,
1369
1708
  name: 'client.alert.displayed',
1370
1709
  userType: 'host',
1371
1710
  isConvergedArchitectureEnabled: undefined,
1372
1711
  webexSubServiceType: undefined,
1712
+ isVipMeeting: false,
1713
+ isAutomatedUser: false,
1714
+ userActivation: undefined,
1373
1715
  },
1374
1716
  eventId: 'my-fake-id',
1375
1717
  origin: {
@@ -1405,11 +1747,15 @@ describe('internal-plugin-metrics', () => {
1405
1747
  userId: 'userId',
1406
1748
  },
1407
1749
  loginType: 'login-ci',
1750
+ telemetryOptOut: undefined,
1408
1751
  webClientPreload: undefined,
1409
1752
  name: 'client.alert.displayed',
1410
1753
  userType: 'host',
1411
1754
  isConvergedArchitectureEnabled: undefined,
1412
1755
  webexSubServiceType: undefined,
1756
+ isVipMeeting: false,
1757
+ isAutomatedUser: false,
1758
+ userActivation: undefined,
1413
1759
  },
1414
1760
  eventId: 'my-fake-id',
1415
1761
  origin: {
@@ -1456,6 +1802,7 @@ describe('internal-plugin-metrics', () => {
1456
1802
  webexConferenceIdStr: undefined,
1457
1803
  globalMeetingId: undefined,
1458
1804
  sessionCorrelationId: undefined,
1805
+ locusCluster: undefined,
1459
1806
  });
1460
1807
  assert.notCalled(generateClientEventErrorPayloadSpy);
1461
1808
  assert.calledWith(
@@ -1480,6 +1827,7 @@ describe('internal-plugin-metrics', () => {
1480
1827
  userId: 'userId',
1481
1828
  },
1482
1829
  loginType: 'login-ci',
1830
+ telemetryOptOut: undefined,
1483
1831
  name: 'client.alert.displayed',
1484
1832
  userType: 'host',
1485
1833
  userNameInput: 'test',
@@ -1487,6 +1835,9 @@ describe('internal-plugin-metrics', () => {
1487
1835
  isConvergedArchitectureEnabled: undefined,
1488
1836
  webexSubServiceType: undefined,
1489
1837
  webClientPreload: undefined,
1838
+ isVipMeeting: false,
1839
+ isAutomatedUser: false,
1840
+ userActivation: undefined,
1490
1841
  },
1491
1842
  options
1492
1843
  );
@@ -1511,6 +1862,7 @@ describe('internal-plugin-metrics', () => {
1511
1862
  userId: 'userId',
1512
1863
  },
1513
1864
  loginType: 'login-ci',
1865
+ telemetryOptOut: undefined,
1514
1866
  name: 'client.alert.displayed',
1515
1867
  userType: 'host',
1516
1868
  userNameInput: 'test',
@@ -1518,6 +1870,9 @@ describe('internal-plugin-metrics', () => {
1518
1870
  isConvergedArchitectureEnabled: undefined,
1519
1871
  webexSubServiceType: undefined,
1520
1872
  webClientPreload: undefined,
1873
+ isVipMeeting: false,
1874
+ isAutomatedUser: false,
1875
+ userActivation: undefined,
1521
1876
  },
1522
1877
  eventId: 'my-fake-id',
1523
1878
  origin: {
@@ -1553,6 +1908,7 @@ describe('internal-plugin-metrics', () => {
1553
1908
  userId: 'userId',
1554
1909
  },
1555
1910
  loginType: 'login-ci',
1911
+ telemetryOptOut: undefined,
1556
1912
  name: 'client.alert.displayed',
1557
1913
  userType: 'host',
1558
1914
  userNameInput: 'test',
@@ -1560,6 +1916,9 @@ describe('internal-plugin-metrics', () => {
1560
1916
  isConvergedArchitectureEnabled: undefined,
1561
1917
  webexSubServiceType: undefined,
1562
1918
  webClientPreload: undefined,
1919
+ isVipMeeting: false,
1920
+ isAutomatedUser: false,
1921
+ userActivation: undefined,
1563
1922
  },
1564
1923
  eventId: 'my-fake-id',
1565
1924
  origin: {
@@ -1662,6 +2021,7 @@ describe('internal-plugin-metrics', () => {
1662
2021
  globalMeetingId: 'globalMeetingId1',
1663
2022
  sessionCorrelationId: 'sessionCorrelationId1',
1664
2023
  preLoginId: undefined,
2024
+ locusCluster: undefined,
1665
2025
  });
1666
2026
 
1667
2027
  assert.notCalled(generateClientEventErrorPayloadSpy);
@@ -1684,8 +2044,12 @@ describe('internal-plugin-metrics', () => {
1684
2044
  userId: 'userId',
1685
2045
  },
1686
2046
  loginType: 'login-ci',
2047
+ telemetryOptOut: undefined,
1687
2048
  name: 'client.alert.displayed',
1688
2049
  webClientPreload: undefined,
2050
+ isAutomatedUser: false,
2051
+ userActivation: undefined,
2052
+ meetingJoinPhase: 'pre-join',
1689
2053
  },
1690
2054
  options
1691
2055
  );
@@ -1707,8 +2071,12 @@ describe('internal-plugin-metrics', () => {
1707
2071
  userId: 'userId',
1708
2072
  },
1709
2073
  loginType: 'login-ci',
2074
+ telemetryOptOut: undefined,
1710
2075
  name: 'client.alert.displayed',
1711
2076
  webClientPreload: undefined,
2077
+ isAutomatedUser: false,
2078
+ userActivation: undefined,
2079
+ meetingJoinPhase: 'pre-join',
1712
2080
  },
1713
2081
  eventId: 'my-fake-id',
1714
2082
  origin: {
@@ -1760,6 +2128,7 @@ describe('internal-plugin-metrics', () => {
1760
2128
  globalMeetingId: 'globalMeetingId1',
1761
2129
  preLoginId: 'myPreLoginId',
1762
2130
  sessionCorrelationId: 'sessionCorrelationId1',
2131
+ locusCluster: undefined,
1763
2132
  });
1764
2133
 
1765
2134
  assert.notCalled(generateClientEventErrorPayloadSpy);
@@ -1782,8 +2151,12 @@ describe('internal-plugin-metrics', () => {
1782
2151
  userId: 'myPreLoginId',
1783
2152
  },
1784
2153
  loginType: 'login-ci',
2154
+ telemetryOptOut: undefined,
1785
2155
  name: 'client.alert.displayed',
1786
2156
  webClientPreload: undefined,
2157
+ isAutomatedUser: false,
2158
+ userActivation: undefined,
2159
+ meetingJoinPhase: 'pre-join',
1787
2160
  },
1788
2161
  options
1789
2162
  );
@@ -1811,13 +2184,47 @@ describe('internal-plugin-metrics', () => {
1811
2184
  },
1812
2185
  eventData: {webClientDomain: 'whatever', isMercuryConnected: true},
1813
2186
  loginType: 'login-ci',
2187
+ telemetryOptOut: undefined,
1814
2188
  webClientPreload: undefined,
2189
+ isAutomatedUser: false,
2190
+ userActivation: undefined,
2191
+ meetingJoinPhase: 'pre-join',
1815
2192
  },
1816
2193
  },
1817
2194
  options.preLoginId
1818
2195
  );
1819
2196
  });
1820
2197
 
2198
+ it('should add locusCluster from options to a pre-meeting client event', () => {
2199
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
2200
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
2201
+
2202
+ cd.submitClientEvent({
2203
+ name: 'client.meetinginfo.response',
2204
+ options: {correlationId: 'correlationId', locusCluster: 'locus-k.wbx2.com'},
2205
+ });
2206
+
2207
+ assert.equal(
2208
+ prepareDiagnosticEventSpy.firstCall.args[0].identifiers.locusCluster,
2209
+ 'locus-k.wbx2.com'
2210
+ );
2211
+ });
2212
+
2213
+ it('should add locusCluster from options to an in-meeting client event when the meeting has none', () => {
2214
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
2215
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
2216
+
2217
+ cd.submitClientEvent({
2218
+ name: 'client.interstitial-window.launched',
2219
+ options: {meetingId: fakeMeeting.id, locusCluster: 'locus-k.wbx2.com'},
2220
+ });
2221
+
2222
+ assert.equal(
2223
+ prepareDiagnosticEventSpy.firstCall.args[0].identifiers.locusCluster,
2224
+ 'locus-k.wbx2.com'
2225
+ );
2226
+ });
2227
+
1821
2228
  it('should submit client event successfully with emailInput and userNameInput as options', () => {
1822
2229
  cd.device.userId = undefined;
1823
2230
 
@@ -1849,6 +2256,7 @@ describe('internal-plugin-metrics', () => {
1849
2256
  globalMeetingId: 'globalMeetingId1',
1850
2257
  preLoginId: 'myPreLoginId',
1851
2258
  sessionCorrelationId: 'sessionCorrelationId1',
2259
+ locusCluster: undefined,
1852
2260
  });
1853
2261
 
1854
2262
  assert.notCalled(generateClientEventErrorPayloadSpy);
@@ -1873,8 +2281,12 @@ describe('internal-plugin-metrics', () => {
1873
2281
  userNameInput: 'current',
1874
2282
  emailInput: 'current',
1875
2283
  loginType: 'login-ci',
2284
+ telemetryOptOut: undefined,
1876
2285
  name: 'client.alert.displayed',
1877
2286
  webClientPreload: undefined,
2287
+ isAutomatedUser: false,
2288
+ userActivation: undefined,
2289
+ meetingJoinPhase: 'pre-join',
1878
2290
  },
1879
2291
  options
1880
2292
  );
@@ -1902,9 +2314,13 @@ describe('internal-plugin-metrics', () => {
1902
2314
  },
1903
2315
  eventData: {webClientDomain: 'whatever', isMercuryConnected: true},
1904
2316
  loginType: 'login-ci',
2317
+ telemetryOptOut: undefined,
1905
2318
  userNameInput: 'current',
1906
2319
  emailInput: 'current',
1907
2320
  webClientPreload: undefined,
2321
+ isAutomatedUser: false,
2322
+ userActivation: undefined,
2323
+ meetingJoinPhase: 'pre-join',
1908
2324
  },
1909
2325
  },
1910
2326
  options.preLoginId
@@ -1944,12 +2360,16 @@ describe('internal-plugin-metrics', () => {
1944
2360
  userId: 'userId',
1945
2361
  },
1946
2362
  loginType: 'fakeLoginType',
2363
+ telemetryOptOut: undefined,
1947
2364
  name: 'client.alert.displayed',
1948
2365
  userType: 'host',
1949
2366
  joinFlowVersion: 'Other',
1950
2367
  isConvergedArchitectureEnabled: undefined,
1951
2368
  webexSubServiceType: undefined,
1952
2369
  webClientPreload: undefined,
2370
+ isVipMeeting: false,
2371
+ isAutomatedUser: false,
2372
+ userActivation: undefined,
1953
2373
  },
1954
2374
  eventId: 'my-fake-id',
1955
2375
  origin: {
@@ -1999,12 +2419,16 @@ describe('internal-plugin-metrics', () => {
1999
2419
  userId: 'userId',
2000
2420
  },
2001
2421
  loginType: 'fakeLoginType',
2422
+ telemetryOptOut: undefined,
2002
2423
  name: 'client.alert.displayed',
2003
2424
  userType: 'host',
2004
2425
  joinFlowVersion: 'Other',
2005
2426
  isConvergedArchitectureEnabled: undefined,
2006
2427
  webexSubServiceType: undefined,
2007
2428
  webClientPreload: undefined,
2429
+ isVipMeeting: false,
2430
+ isAutomatedUser: false,
2431
+ userActivation: undefined,
2008
2432
  },
2009
2433
  eventId: 'my-fake-id',
2010
2434
  origin: {
@@ -2059,8 +2483,12 @@ describe('internal-plugin-metrics', () => {
2059
2483
  userId: 'userId',
2060
2484
  },
2061
2485
  loginType: 'login-ci',
2486
+ telemetryOptOut: undefined,
2062
2487
  name: 'client.alert.displayed',
2063
2488
  webClientPreload: true,
2489
+ isAutomatedUser: false,
2490
+ userActivation: undefined,
2491
+ meetingJoinPhase: 'pre-join',
2064
2492
  },
2065
2493
  options
2066
2494
  );
@@ -2082,8 +2510,12 @@ describe('internal-plugin-metrics', () => {
2082
2510
  userId: 'userId',
2083
2511
  },
2084
2512
  loginType: 'login-ci',
2513
+ telemetryOptOut: undefined,
2085
2514
  name: 'client.alert.displayed',
2086
2515
  webClientPreload: true,
2516
+ isAutomatedUser: false,
2517
+ userActivation: undefined,
2518
+ meetingJoinPhase: 'pre-join',
2087
2519
  },
2088
2520
  eventId: 'my-fake-id',
2089
2521
  origin: {
@@ -2098,20 +2530,25 @@ describe('internal-plugin-metrics', () => {
2098
2530
  });
2099
2531
  });
2100
2532
 
2101
- it('it should include errors if provided with meetingId', () => {
2102
- sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
2533
+ it('should submit client event with isVipMeeting: false when vipMeeting is false', () => {
2534
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
2103
2535
  const submitToCallDiagnosticsSpy = sinon.spy(cd, 'submitToCallDiagnostics');
2536
+ const generateClientEventErrorPayloadSpy = sinon.spy(cd, 'generateClientEventErrorPayload');
2537
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
2538
+
2539
+ webex.meetings.getBasicMeetingInformation = sinon.stub().returns({
2540
+ ...fakeMeeting,
2541
+ meetingInfo: {
2542
+ vipmeeting: false,
2543
+ },
2544
+ });
2104
2545
 
2105
2546
  const options = {
2106
- meetingId: fakeMeeting.id,
2547
+ correlationId: 'correlationId',
2107
2548
  webexConferenceIdStr: 'webexConferenceIdStr1',
2108
2549
  globalMeetingId: 'globalMeetingId1',
2109
- mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
2110
- rawError: {
2111
- body: {
2112
- errorCode: 2409005,
2113
- },
2114
- },
2550
+ sessionCorrelationId: 'sessionCorrelationId1',
2551
+ meetingId: fakeMeeting.id,
2115
2552
  };
2116
2553
  cd.setMercuryConnectedStatus(true);
2117
2554
  cd.submitClientEvent({
@@ -2119,8 +2556,10 @@ describe('internal-plugin-metrics', () => {
2119
2556
  options,
2120
2557
  });
2121
2558
 
2122
- assert.calledWith(submitToCallDiagnosticsSpy, {
2123
- event: {
2559
+ assert.notCalled(generateClientEventErrorPayloadSpy);
2560
+ assert.calledWith(
2561
+ prepareDiagnosticEventSpy,
2562
+ {
2124
2563
  canProceed: true,
2125
2564
  eventData: {
2126
2565
  webClientDomain: 'whatever',
@@ -2130,37 +2569,244 @@ describe('internal-plugin-metrics', () => {
2130
2569
  correlationId: 'correlationId',
2131
2570
  webexConferenceIdStr: 'webexConferenceIdStr1',
2132
2571
  globalMeetingId: 'globalMeetingId1',
2572
+ sessionCorrelationId: 'sessionCorrelationId1',
2133
2573
  deviceId: 'deviceUrl',
2134
2574
  locusId: 'url',
2135
2575
  locusSessionId: 'locusSessionId',
2136
2576
  locusStartTime: 'lastActive',
2137
2577
  locusUrl: 'locus/url',
2138
- mediaAgentAlias: 'alias',
2139
- mediaAgentGroupId: '1',
2140
2578
  orgId: 'orgId',
2141
2579
  userId: 'userId',
2142
2580
  },
2143
- errors: [
2144
- {
2145
- category: 'expected',
2146
- errorDescription: 'StartRecordingFailed',
2147
- fatal: true,
2148
- name: 'other',
2149
- shownToUser: false,
2150
- serviceErrorCode: 2409005,
2151
- errorCode: 4029,
2152
- rawErrorMessage: undefined,
2153
- },
2154
- ],
2155
2581
  loginType: 'login-ci',
2582
+ telemetryOptOut: undefined,
2156
2583
  name: 'client.alert.displayed',
2157
2584
  userType: 'host',
2158
2585
  isConvergedArchitectureEnabled: undefined,
2159
2586
  webexSubServiceType: undefined,
2160
2587
  webClientPreload: undefined,
2588
+ isVipMeeting: false,
2589
+ isAutomatedUser: false,
2590
+ userActivation: undefined,
2161
2591
  },
2162
- eventId: 'my-fake-id',
2163
- origin: {
2592
+ options
2593
+ );
2594
+ assert.calledWith(submitToCallDiagnosticsSpy, {
2595
+ event: {
2596
+ canProceed: true,
2597
+ eventData: {
2598
+ webClientDomain: 'whatever',
2599
+ isMercuryConnected: true,
2600
+ },
2601
+ identifiers: {
2602
+ correlationId: 'correlationId',
2603
+ webexConferenceIdStr: 'webexConferenceIdStr1',
2604
+ globalMeetingId: 'globalMeetingId1',
2605
+ sessionCorrelationId: 'sessionCorrelationId1',
2606
+ deviceId: 'deviceUrl',
2607
+ locusId: 'url',
2608
+ locusSessionId: 'locusSessionId',
2609
+ locusStartTime: 'lastActive',
2610
+ locusUrl: 'locus/url',
2611
+ orgId: 'orgId',
2612
+ userId: 'userId',
2613
+ },
2614
+ loginType: 'login-ci',
2615
+ telemetryOptOut: undefined,
2616
+ name: 'client.alert.displayed',
2617
+ userType: 'host',
2618
+ isConvergedArchitectureEnabled: undefined,
2619
+ webexSubServiceType: undefined,
2620
+ webClientPreload: undefined,
2621
+ isVipMeeting: false,
2622
+ isAutomatedUser: false,
2623
+ userActivation: undefined,
2624
+ },
2625
+ eventId: 'my-fake-id',
2626
+ origin: {
2627
+ origin: 'fake-origin',
2628
+ },
2629
+ originTime: {
2630
+ sent: 'not_defined_yet',
2631
+ triggered: now.toISOString(),
2632
+ },
2633
+ senderCountryCode: 'UK',
2634
+ version: 1,
2635
+ });
2636
+ });
2637
+
2638
+ it('should submit client event with isVipMeeting: true when vipMeeting is true', () => {
2639
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
2640
+ const submitToCallDiagnosticsSpy = sinon.spy(cd, 'submitToCallDiagnostics');
2641
+ const generateClientEventErrorPayloadSpy = sinon.spy(cd, 'generateClientEventErrorPayload');
2642
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
2643
+
2644
+ webex.meetings.getBasicMeetingInformation = sinon.stub().returns({
2645
+ ...fakeMeeting,
2646
+ meetingInfo: {
2647
+ vipmeeting: true,
2648
+ },
2649
+ });
2650
+
2651
+ const options = {
2652
+ correlationId: 'correlationId',
2653
+ webexConferenceIdStr: 'webexConferenceIdStr1',
2654
+ globalMeetingId: 'globalMeetingId1',
2655
+ sessionCorrelationId: 'sessionCorrelationId1',
2656
+ meetingId: fakeMeeting.id,
2657
+ };
2658
+ cd.setMercuryConnectedStatus(true);
2659
+ cd.submitClientEvent({
2660
+ name: 'client.alert.displayed',
2661
+ options,
2662
+ });
2663
+
2664
+ assert.notCalled(generateClientEventErrorPayloadSpy);
2665
+ assert.calledWith(
2666
+ prepareDiagnosticEventSpy,
2667
+ {
2668
+ canProceed: true,
2669
+ eventData: {
2670
+ webClientDomain: 'whatever',
2671
+ isMercuryConnected: true,
2672
+ },
2673
+ identifiers: {
2674
+ correlationId: 'correlationId',
2675
+ webexConferenceIdStr: 'webexConferenceIdStr1',
2676
+ globalMeetingId: 'globalMeetingId1',
2677
+ sessionCorrelationId: 'sessionCorrelationId1',
2678
+ deviceId: 'deviceUrl',
2679
+ locusId: 'url',
2680
+ locusSessionId: 'locusSessionId',
2681
+ locusStartTime: 'lastActive',
2682
+ locusUrl: 'locus/url',
2683
+ orgId: 'orgId',
2684
+ userId: 'userId',
2685
+ },
2686
+ loginType: 'login-ci',
2687
+ telemetryOptOut: undefined,
2688
+ name: 'client.alert.displayed',
2689
+ userType: 'host',
2690
+ isConvergedArchitectureEnabled: undefined,
2691
+ webexSubServiceType: undefined,
2692
+ webClientPreload: undefined,
2693
+ isVipMeeting: true,
2694
+ isAutomatedUser: false,
2695
+ userActivation: undefined,
2696
+ },
2697
+ options
2698
+ );
2699
+ assert.calledWith(submitToCallDiagnosticsSpy, {
2700
+ event: {
2701
+ canProceed: true,
2702
+ eventData: {
2703
+ webClientDomain: 'whatever',
2704
+ isMercuryConnected: true,
2705
+ },
2706
+ identifiers: {
2707
+ correlationId: 'correlationId',
2708
+ webexConferenceIdStr: 'webexConferenceIdStr1',
2709
+ globalMeetingId: 'globalMeetingId1',
2710
+ sessionCorrelationId: 'sessionCorrelationId1',
2711
+ deviceId: 'deviceUrl',
2712
+ locusId: 'url',
2713
+ locusSessionId: 'locusSessionId',
2714
+ locusStartTime: 'lastActive',
2715
+ locusUrl: 'locus/url',
2716
+ orgId: 'orgId',
2717
+ userId: 'userId',
2718
+ },
2719
+ loginType: 'login-ci',
2720
+ telemetryOptOut: undefined,
2721
+ name: 'client.alert.displayed',
2722
+ userType: 'host',
2723
+ isConvergedArchitectureEnabled: undefined,
2724
+ webexSubServiceType: undefined,
2725
+ webClientPreload: undefined,
2726
+ isVipMeeting: true,
2727
+ isAutomatedUser: false,
2728
+ userActivation: undefined,
2729
+ },
2730
+ eventId: 'my-fake-id',
2731
+ origin: {
2732
+ origin: 'fake-origin',
2733
+ },
2734
+ originTime: {
2735
+ sent: 'not_defined_yet',
2736
+ triggered: now.toISOString(),
2737
+ },
2738
+ senderCountryCode: 'UK',
2739
+ version: 1,
2740
+ });
2741
+ });
2742
+
2743
+ it('it should include errors if provided with meetingId', () => {
2744
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
2745
+ const submitToCallDiagnosticsSpy = sinon.spy(cd, 'submitToCallDiagnostics');
2746
+
2747
+ const options = {
2748
+ meetingId: fakeMeeting.id,
2749
+ webexConferenceIdStr: 'webexConferenceIdStr1',
2750
+ globalMeetingId: 'globalMeetingId1',
2751
+ mediaConnections: [{mediaAgentAlias: 'alias', mediaAgentGroupId: '1'}],
2752
+ rawError: {
2753
+ body: {
2754
+ errorCode: 2409005,
2755
+ },
2756
+ },
2757
+ };
2758
+ cd.setMercuryConnectedStatus(true);
2759
+ cd.submitClientEvent({
2760
+ name: 'client.alert.displayed',
2761
+ options,
2762
+ });
2763
+
2764
+ assert.calledWith(submitToCallDiagnosticsSpy, {
2765
+ event: {
2766
+ canProceed: true,
2767
+ eventData: {
2768
+ webClientDomain: 'whatever',
2769
+ isMercuryConnected: true,
2770
+ },
2771
+ identifiers: {
2772
+ correlationId: 'correlationId',
2773
+ webexConferenceIdStr: 'webexConferenceIdStr1',
2774
+ globalMeetingId: 'globalMeetingId1',
2775
+ deviceId: 'deviceUrl',
2776
+ locusId: 'url',
2777
+ locusSessionId: 'locusSessionId',
2778
+ locusStartTime: 'lastActive',
2779
+ locusUrl: 'locus/url',
2780
+ mediaAgentAlias: 'alias',
2781
+ mediaAgentGroupId: '1',
2782
+ orgId: 'orgId',
2783
+ userId: 'userId',
2784
+ },
2785
+ errors: [
2786
+ {
2787
+ category: 'expected',
2788
+ errorDescription: 'StartRecordingFailed',
2789
+ fatal: true,
2790
+ name: 'other',
2791
+ shownToUser: false,
2792
+ serviceErrorCode: 2409005,
2793
+ errorCode: 4029,
2794
+ rawErrorMessage: undefined,
2795
+ },
2796
+ ],
2797
+ loginType: 'login-ci',
2798
+ telemetryOptOut: undefined,
2799
+ name: 'client.alert.displayed',
2800
+ userType: 'host',
2801
+ isConvergedArchitectureEnabled: undefined,
2802
+ webexSubServiceType: undefined,
2803
+ webClientPreload: undefined,
2804
+ isVipMeeting: false,
2805
+ isAutomatedUser: false,
2806
+ userActivation: undefined,
2807
+ },
2808
+ eventId: 'my-fake-id',
2809
+ origin: {
2164
2810
  origin: 'fake-origin',
2165
2811
  },
2166
2812
  originTime: {
@@ -2235,11 +2881,15 @@ describe('internal-plugin-metrics', () => {
2235
2881
  },
2236
2882
  ],
2237
2883
  loginType: 'login-ci',
2884
+ telemetryOptOut: undefined,
2238
2885
  name: 'client.alert.displayed',
2239
2886
  userType: 'host',
2240
2887
  isConvergedArchitectureEnabled: undefined,
2241
2888
  webexSubServiceType: undefined,
2242
2889
  webClientPreload: undefined,
2890
+ isVipMeeting: false,
2891
+ isAutomatedUser: false,
2892
+ userActivation: undefined,
2243
2893
  },
2244
2894
  eventId: 'my-fake-id',
2245
2895
  origin: {
@@ -2311,8 +2961,12 @@ describe('internal-plugin-metrics', () => {
2311
2961
  },
2312
2962
  ],
2313
2963
  loginType: 'login-ci',
2964
+ telemetryOptOut: undefined,
2314
2965
  name: 'client.alert.displayed',
2315
2966
  webClientPreload: undefined,
2967
+ isAutomatedUser: false,
2968
+ userActivation: undefined,
2969
+ meetingJoinPhase: 'pre-join',
2316
2970
  },
2317
2971
  eventId: 'my-fake-id',
2318
2972
  origin: {
@@ -2386,8 +3040,12 @@ describe('internal-plugin-metrics', () => {
2386
3040
  },
2387
3041
  ],
2388
3042
  loginType: 'login-ci',
3043
+ telemetryOptOut: undefined,
2389
3044
  name: 'client.alert.displayed',
2390
3045
  webClientPreload: undefined,
3046
+ isAutomatedUser: false,
3047
+ userActivation: undefined,
3048
+ meetingJoinPhase: 'pre-join',
2391
3049
  },
2392
3050
  eventId: 'my-fake-id',
2393
3051
  origin: {
@@ -2468,11 +3126,15 @@ describe('internal-plugin-metrics', () => {
2468
3126
  },
2469
3127
  ],
2470
3128
  loginType: 'login-ci',
3129
+ telemetryOptOut: undefined,
2471
3130
  name: 'client.alert.displayed',
2472
3131
  userType: 'host',
2473
3132
  isConvergedArchitectureEnabled: undefined,
2474
3133
  webexSubServiceType: undefined,
2475
3134
  webClientPreload: undefined,
3135
+ isVipMeeting: false,
3136
+ isAutomatedUser: false,
3137
+ userActivation: undefined,
2476
3138
  },
2477
3139
  eventId: 'my-fake-id',
2478
3140
  origin: {
@@ -2563,16 +3225,18 @@ describe('internal-plugin-metrics', () => {
2563
3225
  });
2564
3226
 
2565
3227
  assert.calledThrice(submitToCallDiagnosticsStub);
2566
- });
3228
+ });
2567
3229
 
2568
- ([
2569
- ['client.media.render.start'],
2570
- ['client.media.render.stop'],
2571
- ['client.media.rx.start'],
2572
- ['client.media.rx.stop'],
2573
- ['client.media.tx.start'],
2574
- ['client.media.tx.stop']
2575
- ] as const).forEach(([name]) => {
3230
+ (
3231
+ [
3232
+ ['client.media.render.start'],
3233
+ ['client.media.render.stop'],
3234
+ ['client.media.rx.start'],
3235
+ ['client.media.rx.stop'],
3236
+ ['client.media.tx.start'],
3237
+ ['client.media.tx.stop'],
3238
+ ] as const
3239
+ ).forEach(([name]) => {
2576
3240
  it(`should only send ${name} once per mediaType`, () => {
2577
3241
  const options = {
2578
3242
  meetingId: fakeMeeting.id,
@@ -2616,7 +3280,8 @@ describe('internal-plugin-metrics', () => {
2616
3280
  });
2617
3281
 
2618
3282
  assert.notCalled(submitToCallDiagnosticsStub);
2619
- assert.neverCalledWithMatch(webex.logger.log,
3283
+ assert.neverCalledWithMatch(
3284
+ webex.logger.log,
2620
3285
  'call-diagnostic-events -> ',
2621
3286
  sinon.match(createEventLimitRegex(name, 'mediaType video'))
2622
3287
  );
@@ -2659,7 +3324,7 @@ describe('internal-plugin-metrics', () => {
2659
3324
  // Send event with different shareInstanceId
2660
3325
  cd.submitClientEvent({
2661
3326
  name,
2662
- payload: { ...payload, shareInstanceId: 'instance-2' },
3327
+ payload: {...payload, shareInstanceId: 'instance-2'},
2663
3328
  options,
2664
3329
  });
2665
3330
 
@@ -2667,89 +3332,88 @@ describe('internal-plugin-metrics', () => {
2667
3332
  });
2668
3333
  });
2669
3334
 
2670
- ([
2671
- ['client.roap-message.received'],
2672
- ['client.roap-message.sent']
2673
- ] as const).forEach(([name]) => {
2674
- it(`should not send third event of same type and not log warning again for ${name}`, () => {
2675
- const options = {
2676
- meetingId: fakeMeeting.id,
2677
- };
2678
- const payload = {
2679
- roap: {
2680
- messageType: 'OFFER' as const,
2681
- },
2682
- };
2683
- const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
2684
-
2685
- // Clear any existing call history to get accurate counts
2686
- webex.logger.log.resetHistory();
2687
-
2688
- // Send first event
2689
- cd.submitClientEvent({
2690
- name,
2691
- payload,
2692
- options,
2693
- });
2694
-
2695
- assert.calledOnce(submitToCallDiagnosticsStub);
2696
- submitToCallDiagnosticsStub.resetHistory();
2697
-
2698
- // Send second event (should trigger warning)
2699
- cd.submitClientEvent({
2700
- name,
2701
- payload,
2702
- options,
2703
- });
2704
-
2705
- assert.notCalled(submitToCallDiagnosticsStub);
2706
- assert.calledWith(
2707
- webex.logger.log,
2708
- 'call-diagnostic-events -> ',
2709
- sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
2710
- );
2711
- webex.logger.log.resetHistory();
2712
-
2713
- cd.submitClientEvent({
2714
- name,
2715
- payload,
2716
- options,
3335
+ ([['client.roap-message.received'], ['client.roap-message.sent']] as const).forEach(
3336
+ ([name]) => {
3337
+ it(`should not send third event of same type and not log warning again for ${name}`, () => {
3338
+ const options = {
3339
+ meetingId: fakeMeeting.id,
3340
+ };
3341
+ const payload = {
3342
+ roap: {
3343
+ messageType: 'OFFER' as const,
3344
+ },
3345
+ };
3346
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
3347
+
3348
+ // Clear any existing call history to get accurate counts
3349
+ webex.logger.log.resetHistory();
3350
+
3351
+ // Send first event
3352
+ cd.submitClientEvent({
3353
+ name,
3354
+ payload,
3355
+ options,
3356
+ });
3357
+
3358
+ assert.calledOnce(submitToCallDiagnosticsStub);
3359
+ submitToCallDiagnosticsStub.resetHistory();
3360
+
3361
+ // Send second event (should trigger warning)
3362
+ cd.submitClientEvent({
3363
+ name,
3364
+ payload,
3365
+ options,
3366
+ });
3367
+
3368
+ assert.notCalled(submitToCallDiagnosticsStub);
3369
+ assert.calledWith(
3370
+ webex.logger.log,
3371
+ 'call-diagnostic-events -> ',
3372
+ sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
3373
+ );
3374
+ webex.logger.log.resetHistory();
3375
+
3376
+ cd.submitClientEvent({
3377
+ name,
3378
+ payload,
3379
+ options,
3380
+ });
3381
+
3382
+ assert.notCalled(submitToCallDiagnosticsStub);
3383
+ assert.neverCalledWithMatch(
3384
+ webex.logger.log,
3385
+ 'call-diagnostic-events -> ',
3386
+ sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
3387
+ );
2717
3388
  });
2718
3389
 
2719
- assert.notCalled(submitToCallDiagnosticsStub);
2720
- assert.neverCalledWithMatch(
2721
- webex.logger.log,
2722
- 'call-diagnostic-events -> ',
2723
- sinon.match(createEventLimitRegex(name, 'ROAP type OFFER'))
2724
- );
2725
- });
3390
+ it(`should handle roap.type instead of roap.messageType for ${name}`, () => {
3391
+ const options = {
3392
+ meetingId: fakeMeeting.id,
3393
+ };
3394
+ const payload = {
3395
+ roap: {
3396
+ type: 'ANSWER' as const,
3397
+ },
3398
+ };
3399
+ const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
2726
3400
 
2727
- it(`should handle roap.type instead of roap.messageType for ${name}`, () => {
2728
- const options = {
2729
- meetingId: fakeMeeting.id,
2730
- };
2731
- const payload = {
2732
- roap: {
2733
- type: 'ANSWER' as const,
2734
- },
2735
- };
2736
- const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
3401
+ cd.submitClientEvent({
3402
+ name,
3403
+ payload,
3404
+ options,
3405
+ });
2737
3406
 
2738
- cd.submitClientEvent({
2739
- name,
2740
- payload,
2741
- options,
2742
- });
3407
+ cd.submitClientEvent({
3408
+ name,
3409
+ payload,
3410
+ options,
3411
+ });
2743
3412
 
2744
- cd.submitClientEvent({
2745
- name,
2746
- payload,
2747
- options,
3413
+ assert.calledOnce(submitToCallDiagnosticsStub);
2748
3414
  });
2749
-
2750
- assert.calledOnce(submitToCallDiagnosticsStub);
2751
- });
2752
- });
3415
+ }
3416
+ );
2753
3417
  });
2754
3418
  });
2755
3419
 
@@ -2760,7 +3424,10 @@ describe('internal-plugin-metrics', () => {
2760
3424
  cd.callDiagnosticEventsBatcher = {request: requestStub};
2761
3425
  //@ts-ignore
2762
3426
  cd.submitToCallDiagnostics({event: 'test'});
2763
- assert.calledWith(requestStub, {eventPayload: {event: 'test'}, type: ['diagnostic-event']});
3427
+ assert.calledWith(requestStub, {
3428
+ eventPayload: {event: 'test'},
3429
+ type: ['diagnostic-event'],
3430
+ });
2764
3431
  });
2765
3432
  });
2766
3433
 
@@ -2829,6 +3496,7 @@ describe('internal-plugin-metrics', () => {
2829
3496
  mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
2830
3497
  startTime: now.toISOString(),
2831
3498
  },
3499
+ webexSubServiceType: undefined,
2832
3500
  },
2833
3501
  options
2834
3502
  );
@@ -2869,6 +3537,7 @@ describe('internal-plugin-metrics', () => {
2869
3537
  mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
2870
3538
  startTime: now.toISOString(),
2871
3539
  },
3540
+ webexSubServiceType: undefined,
2872
3541
  },
2873
3542
  },
2874
3543
  });
@@ -2907,10 +3576,48 @@ describe('internal-plugin-metrics', () => {
2907
3576
  mediaEngineSoftwareVersion: getOSVersion() || 'unknown',
2908
3577
  startTime: now.toISOString(),
2909
3578
  },
3579
+ webexSubServiceType: undefined,
2910
3580
  },
2911
3581
  });
2912
3582
  });
2913
3583
 
3584
+ it('includes webexSubServiceType in the media quality event payload', () => {
3585
+ const meeting = {
3586
+ ...fakeMeeting,
3587
+ meetingInfo: {
3588
+ enableConvergedArchitecture: true,
3589
+ enableEvent: true,
3590
+ enableConvergedWebinarLargeScale: true,
3591
+ },
3592
+ };
3593
+ const prepareDiagnosticEventSpy = sinon.spy(cd, 'prepareDiagnosticEvent');
3594
+ sinon.stub(cd, 'getOrigin').returns({origin: 'fake-origin'});
3595
+ webex.meetings.getBasicMeetingInformation = sinon.stub().returns(meeting);
3596
+
3597
+ const options = {
3598
+ networkType: 'wifi' as const,
3599
+ meetingId: fakeMeeting.id,
3600
+ };
3601
+
3602
+ cd.submitMQE({
3603
+ name: 'client.mediaquality.event',
3604
+ payload: {
3605
+ //@ts-ignore
3606
+ intervals: [{}],
3607
+ },
3608
+ options,
3609
+ });
3610
+
3611
+ assert.calledOnceWithExactly(
3612
+ prepareDiagnosticEventSpy,
3613
+ sinon.match({
3614
+ name: 'client.mediaquality.event',
3615
+ webexSubServiceType: 'LargeScaleWebinar',
3616
+ }),
3617
+ options
3618
+ );
3619
+ });
3620
+
2914
3621
  it('throws if meeting id not provided', () => {
2915
3622
  assert.throws(() =>
2916
3623
  cd.submitMQE({
@@ -3190,7 +3897,7 @@ describe('internal-plugin-metrics', () => {
3190
3897
  serviceErrorCode: undefined,
3191
3898
  errorCode: 1026,
3192
3899
  rawErrorMessage: '{}\nundefined https://example.com\nWEBEX_TRACKING_ID: undefined\n',
3193
- httpStatusCode: 0,
3900
+ httpCode: 0,
3194
3901
  });
3195
3902
  });
3196
3903
 
@@ -3307,7 +4014,7 @@ describe('internal-plugin-metrics', () => {
3307
4014
  serviceErrorCode: undefined,
3308
4015
  errorCode: 1026,
3309
4016
  rawErrorMessage: '{}\nundefined https://example.com\nWEBEX_TRACKING_ID: undefined\n',
3310
- httpStatusCode: 0,
4017
+ httpCode: 0,
3311
4018
  });
3312
4019
  });
3313
4020
 
@@ -3329,7 +4036,7 @@ describe('internal-plugin-metrics', () => {
3329
4036
  serviceErrorCode: undefined,
3330
4037
  errorCode: 1010,
3331
4038
  rawErrorMessage: '{}\nundefined https://example.com\nWEBEX_TRACKING_ID: undefined\n',
3332
- httpStatusCode: 0,
4039
+ httpCode: 0,
3333
4040
  });
3334
4041
  });
3335
4042
 
@@ -3355,7 +4062,7 @@ describe('internal-plugin-metrics', () => {
3355
4062
  serviceErrorCode: undefined,
3356
4063
  errorCode: 1010,
3357
4064
  rawErrorMessage: '{}\nundefined https://example.com\nWEBEX_TRACKING_ID: undefined\n',
3358
- httpStatusCode: 0,
4065
+ httpCode: 0,
3359
4066
  });
3360
4067
  });
3361
4068
 
@@ -3460,8 +4167,144 @@ describe('internal-plugin-metrics', () => {
3460
4167
  });
3461
4168
  });
3462
4169
 
3463
- describe('httpStatusCode', () => {
3464
- it('should include httpStatusCode for browser media errors', () => {
4170
+ describe('httpCode', () => {
4171
+ it('should identify an outgoing Locus 429', () => {
4172
+ const rawErrorMessage =
4173
+ 'partition 371 on PARALLEL_EXECUTOR_PARTITION_KEY_QUEUE_FULL state';
4174
+ const [res] = cd.generateClientEventErrorPayload({
4175
+ message: rawErrorMessage,
4176
+ statusCode: 429,
4177
+ options: {uri: 'https://locus.example.com/locus/api/v1/loci/call'},
4178
+ });
4179
+
4180
+ assert.deepEqual(res, {
4181
+ category: 'signaling',
4182
+ errorCode: 1002,
4183
+ errorDescription: 'LocusRateLimitedOutgoing',
4184
+ fatal: true,
4185
+ name: 'locus.response',
4186
+ rawErrorMessage,
4187
+ serviceErrorCode: undefined,
4188
+ shownToUser: false,
4189
+ httpCode: 429,
4190
+ });
4191
+ });
4192
+
4193
+ [
4194
+ ['media', 'https://example.com/locus/api/v1/loci/123/media'],
4195
+ ['hashtree', 'https://example.com/locus/api/v1/loci/123/hashtree'],
4196
+ ['host-only Locus URL', 'https://locus.example.com/api/v1/resource'],
4197
+ ['query-only Locus URL', 'https://example.com/api/v1/resource?service=locus'],
4198
+ ].forEach(([operation, uri]) => {
4199
+ it(`should identify a Locus 429 for ${operation}`, () => {
4200
+ const [res] = cd.generateClientEventErrorPayload({
4201
+ message: 'Locus rate limited',
4202
+ statusCode: 429,
4203
+ options: {uri},
4204
+ });
4205
+
4206
+ assert.equal(res.errorCode, 1002);
4207
+ assert.equal(res.errorDescription, 'LocusRateLimitedOutgoing');
4208
+ assert.equal(res.httpCode, 429);
4209
+ });
4210
+ });
4211
+
4212
+ it('should identify an unavailable Locus 503', () => {
4213
+ const rawErrorMessage = 'Service unavailable';
4214
+ const [res] = cd.generateClientEventErrorPayload({
4215
+ message: rawErrorMessage,
4216
+ statusCode: 503,
4217
+ options: {uri: 'https://locus.example.com/locus/api/v1/loci/call'},
4218
+ });
4219
+
4220
+ assert.deepEqual(res, {
4221
+ category: 'signaling',
4222
+ errorCode: 1003,
4223
+ errorDescription: 'LocusUnavailable',
4224
+ fatal: true,
4225
+ name: 'locus.response',
4226
+ rawErrorMessage,
4227
+ serviceErrorCode: undefined,
4228
+ shownToUser: false,
4229
+ httpCode: 503,
4230
+ });
4231
+ });
4232
+
4233
+ it('should prefer a specific service error mapping over the Locus HTTP status', () => {
4234
+ const rawErrorMessage = 'Fraud detected';
4235
+ const [res] = cd.generateClientEventErrorPayload({
4236
+ body: {errorCode: 2423012},
4237
+ message: rawErrorMessage,
4238
+ statusCode: 429,
4239
+ options: {uri: 'https://locus.example.com/locus/api/v1/loci/call'},
4240
+ });
4241
+
4242
+ assert.deepEqual(res, {
4243
+ category: 'expected',
4244
+ errorCode: 12000,
4245
+ errorDescription: 'FraudDetection',
4246
+ fatal: true,
4247
+ name: 'locus.response',
4248
+ rawErrorMessage,
4249
+ serviceErrorCode: 2423012,
4250
+ shownToUser: true,
4251
+ httpCode: 429,
4252
+ });
4253
+ });
4254
+
4255
+ it('should retain an unmapped service error code on the Locus HTTP status payload', () => {
4256
+ const rawErrorMessage = 'Locus rate limited';
4257
+ const [res] = cd.generateClientEventErrorPayload({
4258
+ body: {errorCode: 2429999},
4259
+ message: rawErrorMessage,
4260
+ statusCode: 429,
4261
+ options: {uri: 'https://locus.example.com/locus/api/v1/loci/call'},
4262
+ });
4263
+
4264
+ assert.deepEqual(res, {
4265
+ category: 'signaling',
4266
+ errorCode: 1002,
4267
+ errorDescription: 'LocusRateLimitedOutgoing',
4268
+ fatal: true,
4269
+ name: 'locus.response',
4270
+ rawErrorMessage,
4271
+ serviceErrorCode: 2429999,
4272
+ shownToUser: false,
4273
+ httpCode: 429,
4274
+ });
4275
+ });
4276
+
4277
+ it('should not identify a non-Locus 429 as a Locus rate limit', () => {
4278
+ const [res] = cd.generateClientEventErrorPayload({
4279
+ message: 'Too many requests',
4280
+ statusCode: 429,
4281
+ options: {uri: 'https://example.com/api/v1/resource'},
4282
+ });
4283
+
4284
+ assert.deepEqual(res, {
4285
+ category: 'other',
4286
+ errorCode: 9999,
4287
+ errorDescription: 'UnknownError',
4288
+ fatal: true,
4289
+ name: 'other',
4290
+ rawErrorMessage: 'Too many requests',
4291
+ serviceErrorCode: 9999,
4292
+ shownToUser: false,
4293
+ httpCode: 429,
4294
+ });
4295
+ });
4296
+
4297
+ it('should keep the fallback classification when a 429 has no request URL', () => {
4298
+ const [res] = cd.generateClientEventErrorPayload({
4299
+ message: 'Too many requests',
4300
+ statusCode: 429,
4301
+ });
4302
+
4303
+ assert.equal(res.errorCode, 9999);
4304
+ assert.equal(res.httpCode, 429);
4305
+ });
4306
+
4307
+ it('should include httpCode for browser media errors', () => {
3465
4308
  const [res, cached] = cd.generateClientEventErrorPayload({
3466
4309
  name: 'PermissionDeniedError',
3467
4310
  message: 'bad times',
@@ -3479,11 +4322,11 @@ describe('internal-plugin-metrics', () => {
3479
4322
  rawErrorMessage: 'bad times',
3480
4323
  serviceErrorCode: undefined,
3481
4324
  shownToUser: false,
3482
- httpStatusCode: 401,
4325
+ httpCode: 401,
3483
4326
  });
3484
4327
  });
3485
4328
 
3486
- it('should include httpStatusCode for SdpOfferCreationErrors', () => {
4329
+ it('should include httpCode for SdpOfferCreationErrors', () => {
3487
4330
  const [res, cached] = cd.generateClientEventErrorPayload({
3488
4331
  name: 'SdpOfferCreationError',
3489
4332
  message: 'bad times',
@@ -3501,11 +4344,11 @@ describe('internal-plugin-metrics', () => {
3501
4344
  rawErrorMessage: 'bad times',
3502
4345
  serviceErrorCode: undefined,
3503
4346
  shownToUser: true,
3504
- httpStatusCode: 404,
4347
+ httpCode: 404,
3505
4348
  });
3506
4349
  });
3507
4350
 
3508
- it('should include httpStatusCode for service error codes', () => {
4351
+ it('should include httpCode for service error codes', () => {
3509
4352
  const [res, cached] = cd.generateClientEventErrorPayload({
3510
4353
  body: {errorCode: 58400},
3511
4354
  message: 'bad times',
@@ -3520,11 +4363,11 @@ describe('internal-plugin-metrics', () => {
3520
4363
  rawErrorMessage: 'bad times',
3521
4364
  serviceErrorCode: 58400,
3522
4365
  shownToUser: false,
3523
- httpStatusCode: 400,
4366
+ httpCode: 400,
3524
4367
  });
3525
4368
  });
3526
4369
 
3527
- it('should include httpStatusCode for locus service error codes', () => {
4370
+ it('should include httpCode for locus service error codes', () => {
3528
4371
  const [res, cached] = cd.generateClientEventErrorPayload({
3529
4372
  body: {errorCode: 2403001},
3530
4373
  message: 'bad times',
@@ -3539,11 +4382,11 @@ describe('internal-plugin-metrics', () => {
3539
4382
  rawErrorMessage: 'bad times',
3540
4383
  serviceErrorCode: 2403001,
3541
4384
  shownToUser: false,
3542
- httpStatusCode: 400,
4385
+ httpCode: 400,
3543
4386
  });
3544
4387
  });
3545
4388
 
3546
- it('should include httpStatusCode for meetingInfo service error codes', () => {
4389
+ it('should include httpCode for meetingInfo service error codes', () => {
3547
4390
  const [res, cached] = cd.generateClientEventErrorPayload({
3548
4391
  body: {data: {meetingInfo: {}}},
3549
4392
  message: 'bad times',
@@ -3558,11 +4401,11 @@ describe('internal-plugin-metrics', () => {
3558
4401
  rawErrorMessage: 'bad times',
3559
4402
  serviceErrorCode: undefined,
3560
4403
  shownToUser: false,
3561
- httpStatusCode: 400,
4404
+ httpCode: 400,
3562
4405
  });
3563
4406
  });
3564
4407
 
3565
- it('should include httpStatusCode for network errors', () => {
4408
+ it('should include httpCode for network errors', () => {
3566
4409
  const error = new WebexHttpError.NetworkOrCORSError({
3567
4410
  statusCode: 400,
3568
4411
  options: {service: '', headers: {}},
@@ -3577,11 +4420,11 @@ describe('internal-plugin-metrics', () => {
3577
4420
  rawErrorMessage: 'undefined\nundefined /undefined\nWEBEX_TRACKING_ID: undefined\n',
3578
4421
  serviceErrorCode: undefined,
3579
4422
  shownToUser: false,
3580
- httpStatusCode: 400,
4423
+ httpCode: 400,
3581
4424
  });
3582
4425
  });
3583
4426
 
3584
- it('should include httpStatusCode for unauthorized errors', () => {
4427
+ it('should include httpCode for unauthorized errors', () => {
3585
4428
  const error = new WebexHttpError.Unauthorized({
3586
4429
  statusCode: 401,
3587
4430
  options: {service: '', headers: {}},
@@ -3596,11 +4439,11 @@ describe('internal-plugin-metrics', () => {
3596
4439
  rawErrorMessage: 'undefined\nundefined /undefined\nWEBEX_TRACKING_ID: undefined\n',
3597
4440
  serviceErrorCode: undefined,
3598
4441
  shownToUser: false,
3599
- httpStatusCode: 401,
4442
+ httpCode: 401,
3600
4443
  });
3601
4444
  });
3602
4445
 
3603
- it('should include httpStatusCode for unknown errors', () => {
4446
+ it('should include httpCode for unknown errors', () => {
3604
4447
  const [res, cached] = cd.generateClientEventErrorPayload({
3605
4448
  message: 'bad times',
3606
4449
  statusCode: 404,
@@ -3614,7 +4457,7 @@ describe('internal-plugin-metrics', () => {
3614
4457
  serviceErrorCode: 9999,
3615
4458
  errorDescription: 'UnknownError',
3616
4459
  rawErrorMessage: 'bad times',
3617
- httpStatusCode: 404,
4460
+ httpCode: 404,
3618
4461
  });
3619
4462
  });
3620
4463
  });
@@ -3631,6 +4474,97 @@ describe('internal-plugin-metrics', () => {
3631
4474
  });
3632
4475
  });
3633
4476
 
4477
+ describe('#getTelemetryOptOut', () => {
4478
+ it('returns "manual" when manual telemetry opt-out is enabled', () => {
4479
+ cd.setIsTelemetryOptOutManual(true);
4480
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4481
+ });
4482
+
4483
+ it('returns "automatic" when automatic telemetry opt-out is enabled', () => {
4484
+ cd.setIsTelemetryOptOutAutomatic(true);
4485
+ assert.equal(cd.getTelemetryOptOut(), 'automatic');
4486
+ });
4487
+
4488
+ it('returns "manual" when manual opt-out takes precedence over automatic', () => {
4489
+ cd.setIsTelemetryOptOutManual(true);
4490
+ cd.setIsTelemetryOptOutAutomatic(true);
4491
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4492
+ });
4493
+
4494
+ it('returns undefined when neither manual nor automatic opt-out is set', () => {
4495
+ assert.isUndefined(cd.getTelemetryOptOut());
4496
+ });
4497
+
4498
+ it('returns undefined after disabling manual opt-out', () => {
4499
+ cd.setIsTelemetryOptOutManual(true);
4500
+ cd.setIsTelemetryOptOutManual(false);
4501
+ assert.isUndefined(cd.getTelemetryOptOut());
4502
+ });
4503
+ });
4504
+
4505
+ describe('#setIsTelemetryOptOutManual', () => {
4506
+ it('sets manual telemetry opt-out to true', () => {
4507
+ cd.setIsTelemetryOptOutManual(true);
4508
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4509
+ });
4510
+
4511
+ it('sets manual telemetry opt-out to false', () => {
4512
+ cd.setIsTelemetryOptOutManual(true);
4513
+ cd.setIsTelemetryOptOutManual(false);
4514
+ assert.isUndefined(cd.getTelemetryOptOut());
4515
+ });
4516
+
4517
+ it('can toggle manual telemetry opt-out multiple times', () => {
4518
+ cd.setIsTelemetryOptOutManual(true);
4519
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4520
+
4521
+ cd.setIsTelemetryOptOutManual(false);
4522
+ assert.isUndefined(cd.getTelemetryOptOut());
4523
+
4524
+ cd.setIsTelemetryOptOutManual(true);
4525
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4526
+ });
4527
+
4528
+ it('manual opt-out takes precedence when automatic is also set', () => {
4529
+ cd.setIsTelemetryOptOutAutomatic(true);
4530
+ cd.setIsTelemetryOptOutManual(true);
4531
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4532
+ });
4533
+ });
4534
+
4535
+ describe('#setIsTelemetryOptOutAutomatic', () => {
4536
+ it('sets automatic telemetry opt-out to true', () => {
4537
+ cd.setIsTelemetryOptOutAutomatic(true);
4538
+ assert.equal(cd.getTelemetryOptOut(), 'automatic');
4539
+ });
4540
+
4541
+ it('sets automatic telemetry opt-out to false', () => {
4542
+ cd.setIsTelemetryOptOutAutomatic(true);
4543
+ cd.setIsTelemetryOptOutAutomatic(false);
4544
+ assert.isUndefined(cd.getTelemetryOptOut());
4545
+ });
4546
+
4547
+ it('can toggle automatic telemetry opt-out multiple times', () => {
4548
+ cd.setIsTelemetryOptOutAutomatic(true);
4549
+ assert.equal(cd.getTelemetryOptOut(), 'automatic');
4550
+
4551
+ cd.setIsTelemetryOptOutAutomatic(false);
4552
+ assert.isUndefined(cd.getTelemetryOptOut());
4553
+
4554
+ cd.setIsTelemetryOptOutAutomatic(true);
4555
+ assert.equal(cd.getTelemetryOptOut(), 'automatic');
4556
+ });
4557
+
4558
+ it('does not override manual opt-out', () => {
4559
+ cd.setIsTelemetryOptOutManual(true);
4560
+ cd.setIsTelemetryOptOutAutomatic(true);
4561
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4562
+
4563
+ cd.setIsTelemetryOptOutAutomatic(false);
4564
+ assert.equal(cd.getTelemetryOptOut(), 'manual');
4565
+ });
4566
+ });
4567
+
3634
4568
  describe('#getSubServiceType', () => {
3635
4569
  it('returns subServicetype as PMR when PMR meeting', () => {
3636
4570
  fakeMeeting.meetingInfo = {
@@ -3680,6 +4614,15 @@ describe('internal-plugin-metrics', () => {
3680
4614
  assert.deepEqual(cd.getSubServiceType(fakeMeeting), 'Webcast');
3681
4615
  });
3682
4616
 
4617
+ it('returns subServicetype as LargeScaleWebinar when meeting is converged Webinar and enable large scale', () => {
4618
+ fakeMeeting.meetingInfo = {
4619
+ enableEvent: true,
4620
+ enableConvergedArchitecture: true,
4621
+ enableConvergedWebinarLargeScale: true,
4622
+ };
4623
+ assert.deepEqual(cd.getSubServiceType(fakeMeeting), 'LargeScaleWebinar');
4624
+ });
4625
+
3683
4626
  it('returns subServicetype as undefined when correct parameters are not found', () => {
3684
4627
  fakeMeeting.meetingInfo = {};
3685
4628
  assert.deepEqual(cd.getSubServiceType(fakeMeeting), undefined);
@@ -3705,6 +4648,18 @@ describe('internal-plugin-metrics', () => {
3705
4648
  });
3706
4649
 
3707
4650
  describe('#buildClientEventFetchRequestOptions', () => {
4651
+ it('should add the locusCluster option to a pre-meeting event', async () => {
4652
+ const fetchOptions = await cd.buildClientEventFetchRequestOptions({
4653
+ name: 'client.interstitial-window.launched',
4654
+ options: {correlationId: 'correlationId', locusCluster: 'locus-k.wbx2.com'},
4655
+ });
4656
+
4657
+ assert.equal(
4658
+ fetchOptions.body.metrics[0].eventPayload.event.identifiers.locusCluster,
4659
+ 'locus-k.wbx2.com'
4660
+ );
4661
+ });
4662
+
3708
4663
  [undefined, 'myPreLoginId'].forEach((preLoginId) => {
3709
4664
  it('returns expected options without preLoginId', async () => {
3710
4665
  const options = {
@@ -3740,12 +4695,16 @@ describe('internal-plugin-metrics', () => {
3740
4695
  userId: 'userId',
3741
4696
  },
3742
4697
  loginType: 'login-ci',
4698
+ telemetryOptOut: undefined,
3743
4699
  name: 'client.exit.app',
3744
4700
  trigger: 'user-interaction',
3745
4701
  userType: 'host',
3746
4702
  isConvergedArchitectureEnabled: undefined,
3747
4703
  webexSubServiceType: undefined,
3748
4704
  webClientPreload: undefined,
4705
+ isVipMeeting: false,
4706
+ isAutomatedUser: false,
4707
+ userActivation: undefined,
3749
4708
  },
3750
4709
  eventId: 'my-fake-id',
3751
4710
  origin: {
@@ -3900,6 +4859,80 @@ describe('internal-plugin-metrics', () => {
3900
4859
  options.meetingJoinPhase
3901
4860
  );
3902
4861
  });
4862
+
4863
+ it('includes expected meetingJoinPhase from meeting callStateForMetrics when in-meeting', async () => {
4864
+ // meetingId means in-meeting
4865
+ const options = {
4866
+ meetingId: fakeMeeting6.id,
4867
+ };
4868
+
4869
+ const fetchOptions = await cd.buildClientEventFetchRequestOptions({
4870
+ name: 'client.exit.app',
4871
+ payload: {trigger: 'user-interaction', canProceed: false},
4872
+ options,
4873
+ });
4874
+
4875
+ assert.equal(fetchOptions.body.metrics[0].eventPayload.event.meetingJoinPhase, 'in-meeting');
4876
+ });
4877
+
4878
+ it('prioritizes meetingJoinPhase from options over meeting callStateForMetrics', async () => {
4879
+ // meetingId means in-meeting
4880
+ const options = {
4881
+ meetingId: fakeMeeting6.id,
4882
+ meetingJoinPhase: 'join',
4883
+ };
4884
+
4885
+ const fetchOptions = await cd.buildClientEventFetchRequestOptions({
4886
+ name: 'client.exit.app',
4887
+ payload: {trigger: 'user-interaction', canProceed: false},
4888
+ options,
4889
+ });
4890
+
4891
+ assert.equal(
4892
+ fetchOptions.body.metrics[0].eventPayload.event.meetingJoinPhase,
4893
+ options.meetingJoinPhase
4894
+ );
4895
+ });
4896
+
4897
+ it('defaults meetingJoinPhase to pre-join during prejoin when not provided in options', async () => {
4898
+ // correlationId and no meeting id means prejoin
4899
+ const options = {
4900
+ correlationId: 'myCorrelationId',
4901
+ preLoginId: 'myPreLoginId',
4902
+ };
4903
+
4904
+ const fetchOptions = await cd.buildClientEventFetchRequestOptions({
4905
+ name: 'client.exit.app',
4906
+ payload: {trigger: 'user-interaction', canProceed: false},
4907
+ options,
4908
+ });
4909
+
4910
+ assert.equal(fetchOptions.body.metrics[0].eventPayload.event.meetingJoinPhase, 'pre-join');
4911
+ });
4912
+
4913
+ it('builds request options before webex is ready (no meetings plugin or internal metrics config)', async () => {
4914
+ // before webex.ready the meetings plugin and internal metrics config are not available
4915
+ webex.meetings = undefined;
4916
+ webex.internal.metrics = undefined;
4917
+
4918
+ const options = {
4919
+ correlationId: 'myCorrelationId',
4920
+ clientType: 'TEAMS_CLIENT',
4921
+ subClientType: 'WEB_APP',
4922
+ };
4923
+
4924
+ const fetchOptions = await cd.buildClientEventFetchRequestOptions({
4925
+ name: 'client.exit.app',
4926
+ payload: {trigger: 'user-interaction', canProceed: false},
4927
+ options,
4928
+ });
4929
+
4930
+ const eventPayload = fetchOptions.body.metrics[0].eventPayload;
4931
+ assert.equal(eventPayload.event.name, 'client.exit.app');
4932
+ assert.isUndefined(eventPayload.senderCountryCode);
4933
+ assert.isUndefined(fetchOptions.waitForServiceTimeout);
4934
+ assert.equal(fetchOptions.resource, 'clientmetrics');
4935
+ });
3903
4936
  });
3904
4937
 
3905
4938
  describe('#submitToCallDiagnosticsPreLogin', () => {
@@ -3913,7 +4946,10 @@ describe('internal-plugin-metrics', () => {
3913
4946
  cd.submitToCallDiagnosticsPreLogin({event: 'test'}, preLoginId);
3914
4947
  //@ts-ignore
3915
4948
  assert.calledWith(cd.preLoginMetricsBatcher.savePreLoginId, preLoginId);
3916
- assert.calledWith(requestStub, {eventPayload: {event: 'test'}, type: ['diagnostic-event']});
4949
+ assert.calledWith(requestStub, {
4950
+ eventPayload: {event: 'test'},
4951
+ type: ['diagnostic-event'],
4952
+ });
3917
4953
  });
3918
4954
  });
3919
4955
 
@@ -4114,12 +5150,14 @@ describe('internal-plugin-metrics', () => {
4114
5150
  payload: {
4115
5151
  meetingSummaryInfo: {
4116
5152
  featureName: 'syncSystemMuteStatus',
4117
- featureActions: [{
4118
- actionName: 'syncMeetingMicUnmuteStatusToSystem',
4119
- actionId: '14200',
4120
- isInitialValue: false,
4121
- clickCount: '1'
4122
- }]
5153
+ featureActions: [
5154
+ {
5155
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
5156
+ actionId: '14200',
5157
+ isInitialValue: false,
5158
+ clickCount: '1',
5159
+ },
5160
+ ],
4123
5161
  },
4124
5162
  },
4125
5163
  options,
@@ -4140,22 +5178,28 @@ describe('internal-plugin-metrics', () => {
4140
5178
  locusSessionId: 'locusSessionId',
4141
5179
  locusStartTime: 'lastActive',
4142
5180
  },
4143
- eventData: { webClientDomain: 'whatever'},
5181
+ eventData: {webClientDomain: 'whatever'},
4144
5182
  userType: 'host',
4145
5183
  loginType: 'login-ci',
5184
+ telemetryOptOut: undefined,
4146
5185
  isConvergedArchitectureEnabled: undefined,
4147
5186
  webexSubServiceType: undefined,
4148
5187
  webClientPreload: undefined,
5188
+ isVipMeeting: false,
5189
+ isAutomatedUser: false,
5190
+ userActivation: undefined,
4149
5191
  meetingSummaryInfo: {
4150
5192
  featureName: 'syncSystemMuteStatus',
4151
- featureActions: [{
4152
- actionName: 'syncMeetingMicUnmuteStatusToSystem',
4153
- actionId: '14200',
4154
- isInitialValue: false,
4155
- clickCount: '1'
4156
- }]
5193
+ featureActions: [
5194
+ {
5195
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
5196
+ actionId: '14200',
5197
+ isInitialValue: false,
5198
+ clickCount: '1',
5199
+ },
5200
+ ],
4157
5201
  },
4158
- key: "UcfFeatureUsage",
5202
+ key: 'UcfFeatureUsage',
4159
5203
  },
4160
5204
  options
4161
5205
  );
@@ -4168,7 +5212,7 @@ describe('internal-plugin-metrics', () => {
4168
5212
  },
4169
5213
  event: {
4170
5214
  canProceed: true,
4171
- eventData: { webClientDomain: 'whatever'},
5215
+ eventData: {webClientDomain: 'whatever'},
4172
5216
  identifiers: {
4173
5217
  correlationId: 'correlationId',
4174
5218
  deviceId: 'deviceUrl',
@@ -4180,21 +5224,27 @@ describe('internal-plugin-metrics', () => {
4180
5224
  userId: 'userId',
4181
5225
  },
4182
5226
  loginType: 'login-ci',
5227
+ telemetryOptOut: undefined,
4183
5228
  name: 'client.feature.meeting.summary',
4184
5229
  userType: 'host',
4185
5230
  isConvergedArchitectureEnabled: undefined,
4186
5231
  webexSubServiceType: undefined,
4187
5232
  webClientPreload: undefined,
5233
+ isVipMeeting: false,
5234
+ isAutomatedUser: false,
5235
+ userActivation: undefined,
4188
5236
  meetingSummaryInfo: {
4189
5237
  featureName: 'syncSystemMuteStatus',
4190
- featureActions: [{
4191
- actionName: 'syncMeetingMicUnmuteStatusToSystem',
4192
- actionId: '14200',
4193
- isInitialValue: false,
4194
- clickCount: '1'
4195
- }]
5238
+ featureActions: [
5239
+ {
5240
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
5241
+ actionId: '14200',
5242
+ isInitialValue: false,
5243
+ clickCount: '1',
5244
+ },
5245
+ ],
4196
5246
  },
4197
- key: "UcfFeatureUsage",
5247
+ key: 'UcfFeatureUsage',
4198
5248
  },
4199
5249
  originTime: {
4200
5250
  sent: 'not_defined_yet',
@@ -4235,12 +5285,14 @@ describe('internal-plugin-metrics', () => {
4235
5285
  payload: {
4236
5286
  meetingSummaryInfo: {
4237
5287
  featureName: 'syncSystemMuteStatus',
4238
- featureActions: [{
4239
- actionName: 'syncMeetingMicUnmuteStatusToSystem',
4240
- actionId: '14200',
4241
- isInitialValue: false,
4242
- clickCount: '1'
4243
- }]
5288
+ featureActions: [
5289
+ {
5290
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
5291
+ actionId: '14200',
5292
+ isInitialValue: false,
5293
+ clickCount: '1',
5294
+ },
5295
+ ],
4244
5296
  },
4245
5297
  },
4246
5298
  delaySubmitEvent: true,
@@ -4252,12 +5304,14 @@ describe('internal-plugin-metrics', () => {
4252
5304
  payload: {
4253
5305
  meetingSummaryInfo: {
4254
5306
  featureName: 'syncSystemVideoStatus',
4255
- featureActions: [{
4256
- actionName: 'syncMeetingVideoUnmuteStatusToSystem',
4257
- actionId: '13400',
4258
- isInitialValue: false,
4259
- clickCount: '1'
4260
- }]
5307
+ featureActions: [
5308
+ {
5309
+ actionName: 'syncMeetingVideoUnmuteStatusToSystem',
5310
+ actionId: '13400',
5311
+ isInitialValue: false,
5312
+ clickCount: '1',
5313
+ },
5314
+ ],
4261
5315
  },
4262
5316
  },
4263
5317
  delaySubmitEvent: true,
@@ -4275,12 +5329,14 @@ describe('internal-plugin-metrics', () => {
4275
5329
  payload: {
4276
5330
  meetingSummaryInfo: {
4277
5331
  featureName: 'syncSystemMuteStatus',
4278
- featureActions: [{
4279
- actionName: 'syncMeetingMicUnmuteStatusToSystem',
4280
- actionId: '14200',
4281
- isInitialValue: false,
4282
- clickCount: '1'
4283
- }]
5332
+ featureActions: [
5333
+ {
5334
+ actionName: 'syncMeetingMicUnmuteStatusToSystem',
5335
+ actionId: '14200',
5336
+ isInitialValue: false,
5337
+ clickCount: '1',
5338
+ },
5339
+ ],
4284
5340
  },
4285
5341
  },
4286
5342
  options: {
@@ -4293,12 +5349,14 @@ describe('internal-plugin-metrics', () => {
4293
5349
  payload: {
4294
5350
  meetingSummaryInfo: {
4295
5351
  featureName: 'syncSystemVideoStatus',
4296
- featureActions: [{
4297
- actionName: 'syncMeetingVideoUnmuteStatusToSystem',
4298
- actionId: '13400',
4299
- isInitialValue: false,
4300
- clickCount: '1'
4301
- }]
5352
+ featureActions: [
5353
+ {
5354
+ actionName: 'syncMeetingVideoUnmuteStatusToSystem',
5355
+ actionId: '13400',
5356
+ isInitialValue: false,
5357
+ clickCount: '1',
5358
+ },
5359
+ ],
4302
5360
  },
4303
5361
  },
4304
5362
  options: {
@@ -4322,17 +5380,17 @@ describe('internal-plugin-metrics', () => {
4322
5380
 
4323
5381
  it('should clear event limits for specific correlationId only', () => {
4324
5382
  // Use the actual correlationIds from our fakeMeeting fixtures
4325
- const correlationId1 = fakeMeeting.correlationId; // e.g. 'correlationId1'
4326
- const correlationId2 = fakeMeeting2.correlationId; // e.g. 'correlationId2'
4327
- const options1 = { meetingId: fakeMeeting.id };
4328
- const options2 = { meetingId: fakeMeeting2.id };
4329
- const payload = { mediaType: 'video' as const };
5383
+ const correlationId1 = fakeMeeting.correlationId; // e.g. 'correlationId1'
5384
+ const correlationId2 = fakeMeeting2.correlationId; // e.g. 'correlationId2'
5385
+ const options1 = {meetingId: fakeMeeting.id};
5386
+ const options2 = {meetingId: fakeMeeting2.id};
5387
+ const payload = {mediaType: 'video' as const};
4330
5388
 
4331
5389
  // Set up events for both correlations to trigger limits
4332
- cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options1 });
4333
- cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options2 });
4334
- cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options1 });
4335
- cd.submitClientEvent({ name: 'client.media.render.start', payload, options: options2 });
5390
+ cd.submitClientEvent({name: 'client.media.render.start', payload, options: options1});
5391
+ cd.submitClientEvent({name: 'client.media.render.start', payload, options: options2});
5392
+ cd.submitClientEvent({name: 'client.media.render.start', payload, options: options1});
5393
+ cd.submitClientEvent({name: 'client.media.render.start', payload, options: options2});
4336
5394
  assert.isTrue(cd.eventLimitTracker.size > 0);
4337
5395
  assert.isTrue(cd.eventLimitWarningsLogged.size > 0);
4338
5396
 
@@ -4343,17 +5401,17 @@ describe('internal-plugin-metrics', () => {
4343
5401
  const remainingWarningKeys = Array.from(cd.eventLimitWarningsLogged.keys());
4344
5402
 
4345
5403
  // Should have no keys with correlationId1
4346
- assert.isFalse(remainingTrackerKeys.some(key => key.split(':')[1] === correlationId1));
4347
- assert.isFalse(remainingWarningKeys.some(key => key.split(':')[1] === correlationId1));
5404
+ assert.isFalse(remainingTrackerKeys.some((key) => key.split(':')[1] === correlationId1));
5405
+ assert.isFalse(remainingWarningKeys.some((key) => key.split(':')[1] === correlationId1));
4348
5406
 
4349
5407
  // Should still have keys with correlationId2
4350
- assert.isTrue(remainingTrackerKeys.some(key => key.split(':')[1] === correlationId2));
4351
- assert.isTrue(remainingWarningKeys.some(key => key.split(':')[1] === correlationId2));
5408
+ assert.isTrue(remainingTrackerKeys.some((key) => key.split(':')[1] === correlationId2));
5409
+ assert.isTrue(remainingWarningKeys.some((key) => key.split(':')[1] === correlationId2));
4352
5410
  });
4353
5411
 
4354
5412
  it('should handle empty correlationId gracefully', () => {
4355
- const options = { meetingId: fakeMeeting.id };
4356
- const payload = { mediaType: 'video' as const };
5413
+ const options = {meetingId: fakeMeeting.id};
5414
+ const payload = {mediaType: 'video' as const};
4357
5415
 
4358
5416
  // Set up some tracking data
4359
5417
  cd.submitClientEvent({
@@ -4381,8 +5439,8 @@ describe('internal-plugin-metrics', () => {
4381
5439
  });
4382
5440
 
4383
5441
  it('should handle non-existent correlationId gracefully', () => {
4384
- const options = { meetingId: fakeMeeting.id };
4385
- const payload = { mediaType: 'video' as const };
5442
+ const options = {meetingId: fakeMeeting.id};
5443
+ const payload = {mediaType: 'video' as const};
4386
5444
 
4387
5445
  // Set up some tracking data
4388
5446
  cd.submitClientEvent({
@@ -4403,10 +5461,10 @@ describe('internal-plugin-metrics', () => {
4403
5461
 
4404
5462
  it('should clear multiple event types for the same correlationId', () => {
4405
5463
  const correlationId = fakeMeeting.correlationId;
4406
- const options = { meetingId: fakeMeeting.id };
4407
- const videoPayload = { mediaType: 'video' as const };
4408
- const audioPayload = { mediaType: 'audio' as const };
4409
- const roapPayload = { roap: { messageType: 'OFFER' as const } };
5464
+ const options = {meetingId: fakeMeeting.id};
5465
+ const videoPayload = {mediaType: 'video' as const};
5466
+ const audioPayload = {mediaType: 'audio' as const};
5467
+ const roapPayload = {roap: {messageType: 'OFFER' as const}};
4410
5468
 
4411
5469
  // Set up multiple event types for the same correlation
4412
5470
  cd.submitClientEvent({
@@ -4459,8 +5517,8 @@ describe('internal-plugin-metrics', () => {
4459
5517
 
4460
5518
  it('should allow events to be sent again after clearing limits for correlationId', () => {
4461
5519
  const correlationId = fakeMeeting.correlationId;
4462
- const options = { meetingId: fakeMeeting.id };
4463
- const payload = { mediaType: 'video' as const };
5520
+ const options = {meetingId: fakeMeeting.id};
5521
+ const payload = {mediaType: 'video' as const};
4464
5522
  const submitToCallDiagnosticsStub = sinon.stub(cd, 'submitToCallDiagnostics');
4465
5523
 
4466
5524
  // Send first event (should succeed)