@webex/internal-plugin-metrics 3.4.0 → 3.5.0-next.10

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 (69) hide show
  1. package/dist/behavioral-metrics.js +63 -0
  2. package/dist/behavioral-metrics.js.map +1 -0
  3. package/dist/business-metrics.js +169 -0
  4. package/dist/business-metrics.js.map +1 -0
  5. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +1 -1
  6. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  7. package/dist/call-diagnostic/call-diagnostic-metrics.js +27 -11
  8. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  9. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +14 -4
  10. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  11. package/dist/call-diagnostic/config.js +13 -3
  12. package/dist/call-diagnostic/config.js.map +1 -1
  13. package/dist/{behavioral/behavioral-metrics.js → generic-metrics.js} +77 -92
  14. package/dist/generic-metrics.js.map +1 -0
  15. package/dist/index.js +22 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/metrics.js +1 -1
  18. package/dist/metrics.types.js.map +1 -1
  19. package/dist/new-metrics.js +124 -24
  20. package/dist/new-metrics.js.map +1 -1
  21. package/dist/operational-metrics.js +56 -0
  22. package/dist/operational-metrics.js.map +1 -0
  23. package/dist/rtcMetrics/constants.js +11 -0
  24. package/dist/rtcMetrics/constants.js.map +1 -0
  25. package/dist/rtcMetrics/index.js +202 -0
  26. package/dist/rtcMetrics/index.js.map +1 -0
  27. package/dist/types/behavioral-metrics.d.ts +25 -0
  28. package/dist/types/business-metrics.d.ts +47 -0
  29. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +27 -6
  30. package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +2 -1
  31. package/dist/types/call-diagnostic/config.d.ts +3 -0
  32. package/dist/types/generic-metrics.d.ts +63 -0
  33. package/dist/types/index.d.ts +5 -2
  34. package/dist/types/metrics.types.d.ts +27 -14
  35. package/dist/types/new-metrics.d.ts +42 -9
  36. package/dist/types/operational-metrics.d.ts +19 -0
  37. package/dist/types/rtcMetrics/constants.d.ts +4 -0
  38. package/dist/types/rtcMetrics/index.d.ts +71 -0
  39. package/package.json +12 -12
  40. package/src/behavioral-metrics.ts +40 -0
  41. package/src/business-metrics.ts +118 -0
  42. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +1 -1
  43. package/src/call-diagnostic/call-diagnostic-metrics.ts +30 -9
  44. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +17 -4
  45. package/src/call-diagnostic/config.ts +12 -0
  46. package/src/generic-metrics.ts +146 -0
  47. package/src/index.ts +7 -1
  48. package/src/metrics.types.ts +32 -16
  49. package/src/new-metrics.ts +100 -13
  50. package/src/operational-metrics.ts +24 -0
  51. package/src/rtcMetrics/constants.ts +3 -0
  52. package/src/rtcMetrics/index.ts +186 -0
  53. package/test/unit/spec/behavioral/behavioral-metrics.ts +51 -10
  54. package/test/unit/spec/business/business-metrics.ts +182 -0
  55. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +2 -1
  56. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +4 -6
  57. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +418 -12
  58. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +22 -8
  59. package/test/unit/spec/new-metrics.ts +32 -3
  60. package/test/unit/spec/operational/operational-metrics.ts +115 -0
  61. package/test/unit/spec/prelogin-metrics-batcher.ts +3 -1
  62. package/test/unit/spec/rtcMetrics/index.ts +155 -0
  63. package/dist/behavioral/behavioral-metrics.js.map +0 -1
  64. package/dist/behavioral/config.js +0 -11
  65. package/dist/behavioral/config.js.map +0 -1
  66. package/dist/types/behavioral/behavioral-metrics.d.ts +0 -63
  67. package/dist/types/behavioral/config.d.ts +0 -1
  68. package/src/behavioral/behavioral-metrics.ts +0 -179
  69. package/src/behavioral/config.ts +0 -3
@@ -6,7 +6,9 @@
6
6
  import {WebexPlugin} from '@webex/webex-core';
7
7
 
8
8
  import CallDiagnosticMetrics from './call-diagnostic/call-diagnostic-metrics';
9
- import BehavioralMetrics from './behavioral/behavioral-metrics';
9
+ import BehavioralMetrics from './behavioral-metrics';
10
+ import OperationalMetrics from './operational-metrics';
11
+ import BusinessMetrics from './business-metrics';
10
12
  import {
11
13
  RecursivePartial,
12
14
  MetricEventProduct,
@@ -14,11 +16,12 @@ import {
14
16
  MetricEventVerb,
15
17
  ClientEvent,
16
18
  FeatureEvent,
17
- BehavioralEventPayload,
19
+ EventPayload,
18
20
  OperationalEvent,
19
21
  MediaQualityEvent,
20
22
  InternalEvent,
21
23
  SubmitClientEventOptions,
24
+ Table,
22
25
  } from './metrics.types';
23
26
  import CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-latencies';
24
27
  import {setMetricTimings} from './call-diagnostic/call-diagnostic-metrics.util';
@@ -37,6 +40,9 @@ class Metrics extends WebexPlugin {
37
40
  // Helper classes to handle the different types of metrics
38
41
  callDiagnosticMetrics: CallDiagnosticMetrics;
39
42
  behavioralMetrics: BehavioralMetrics;
43
+ operationalMetrics: OperationalMetrics;
44
+ businessMetrics: BusinessMetrics;
45
+ isReady = false;
40
46
 
41
47
  /**
42
48
  * Constructor
@@ -61,8 +67,7 @@ class Metrics extends WebexPlugin {
61
67
  this.webex.once('ready', () => {
62
68
  // @ts-ignore
63
69
  this.callDiagnosticMetrics = new CallDiagnosticMetrics({}, {parent: this.webex});
64
- // @ts-ignore
65
- this.behavioralMetrics = new BehavioralMetrics({}, {parent: this.webex});
70
+ this.isReady = true;
66
71
  });
67
72
  }
68
73
 
@@ -86,11 +91,61 @@ class Metrics extends WebexPlugin {
86
91
  }
87
92
  }
88
93
 
94
+ /**
95
+ * if webex metrics is ready, build behavioral metric backend if not already done.
96
+ */
97
+ private lazyBuildBehavioralMetrics() {
98
+ if (this.isReady && !this.behavioralMetrics) {
99
+ // @ts-ignore
100
+ this.behavioralMetrics = new BehavioralMetrics({}, {parent: this.webex});
101
+ }
102
+ }
103
+
104
+ /**
105
+ * if webex metrics is ready, build operational metric backend if not already done.
106
+ */
107
+ private lazyBuildOperationalMetrics() {
108
+ if (this.isReady && !this.operationalMetrics) {
109
+ // @ts-ignore
110
+ this.operationalMetrics = new OperationalMetrics({}, {parent: this.webex});
111
+ }
112
+ }
113
+
114
+ /**
115
+ * if webex metrics is ready, build business metric backend if not already done.
116
+ */
117
+ private lazyBuildBusinessMetrics() {
118
+ if (this.isReady && !this.businessMetrics) {
119
+ // @ts-ignore
120
+ this.businessMetrics = new BusinessMetrics({}, {parent: this.webex});
121
+ }
122
+ }
123
+
89
124
  /**
90
125
  * @returns true once we have the deviceId we need to submit behavioral events to Amplitude
91
126
  */
92
127
  isReadyToSubmitBehavioralEvents() {
93
- return this.behavioralMetrics.isReadyToSubmitBehavioralEvents();
128
+ this.lazyBuildBehavioralMetrics();
129
+
130
+ return this.behavioralMetrics?.isReadyToSubmitEvents() ?? false;
131
+ }
132
+
133
+ /**
134
+ * @returns true once we have the deviceId we need to submit operational events
135
+ */
136
+ isReadyToSubmitOperationalEvents() {
137
+ this.lazyBuildOperationalMetrics();
138
+
139
+ return this.operationalMetrics?.isReadyToSubmitEvents() ?? false;
140
+ }
141
+
142
+ /**
143
+ * @returns true once we have the deviceId we need to submit buisness events
144
+ */
145
+ isReadyToSubmitBusinessEvents() {
146
+ this.lazyBuildBusinessMetrics();
147
+
148
+ return this.businessMetrics?.isReadyToSubmitEvents() ?? false;
94
149
  }
95
150
 
96
151
  /**
@@ -108,9 +163,9 @@ class Metrics extends WebexPlugin {
108
163
  agent: MetricEventAgent;
109
164
  target: string;
110
165
  verb: MetricEventVerb;
111
- payload?: BehavioralEventPayload;
166
+ payload?: EventPayload;
112
167
  }) {
113
- if (!this.behavioralMetrics) {
168
+ if (!this.isReady) {
114
169
  // @ts-ignore
115
170
  this.webex.logger.log(
116
171
  `NewMetrics: @submitBehavioralEvent. Attempted to submit before webex.ready: ${product}.${agent}.${target}.${verb}`
@@ -119,6 +174,8 @@ class Metrics extends WebexPlugin {
119
174
  return Promise.resolve();
120
175
  }
121
176
 
177
+ this.lazyBuildBehavioralMetrics();
178
+
122
179
  return this.behavioralMetrics.submitBehavioralEvent({product, agent, target, verb, payload});
123
180
  }
124
181
 
@@ -126,16 +183,46 @@ class Metrics extends WebexPlugin {
126
183
  * Operational event
127
184
  * @param args
128
185
  */
129
- submitOperationalEvent({
186
+ submitOperationalEvent({name, payload}: {name: string; payload?: EventPayload}) {
187
+ if (!this.isReady) {
188
+ // @ts-ignore
189
+ this.webex.logger.log(
190
+ `NewMetrics: @submitOperationalEvent. Attempted to submit before webex.ready: ${name}`
191
+ );
192
+
193
+ return Promise.resolve();
194
+ }
195
+
196
+ this.lazyBuildOperationalMetrics();
197
+
198
+ return this.operationalMetrics.submitOperationalEvent({name, payload});
199
+ }
200
+
201
+ /**
202
+ * Buisness event
203
+ * @param args
204
+ */
205
+ submitBusinessEvent({
130
206
  name,
131
207
  payload,
132
- options,
208
+ table,
133
209
  }: {
134
- name: OperationalEvent['name'];
135
- payload?: RecursivePartial<OperationalEvent['payload']>;
136
- options?: any;
210
+ name: string;
211
+ payload: EventPayload;
212
+ table?: Table;
137
213
  }) {
138
- throw new Error('Not implemented.');
214
+ if (!this.isReady) {
215
+ // @ts-ignore
216
+ this.webex.logger.log(
217
+ `NewMetrics: @submitBusinessEvent. Attempted to submit before webex.ready: ${name}`
218
+ );
219
+
220
+ return Promise.resolve();
221
+ }
222
+
223
+ this.lazyBuildBusinessMetrics();
224
+
225
+ return this.businessMetrics.submitBusinessEvent({name, payload, table});
139
226
  }
140
227
 
141
228
  /**
@@ -0,0 +1,24 @@
1
+ import GenericMetrics from './generic-metrics';
2
+ import {EventPayload} from './metrics.types';
3
+
4
+ /**
5
+ * @description Util class to handle Operational Metrics
6
+ * @export
7
+ * @class OperationalMetrics
8
+ */
9
+ export default class OperationalMetrics extends GenericMetrics {
10
+ /**
11
+ * Submit an operational metric to our metrics endpoint.
12
+ * @param {string} name of the metric
13
+ * @param {EventPayload} user payload of the metric
14
+ * @returns {Promise<any>}
15
+ */
16
+ public submitOperationalEvent({name, payload}: {name: string; payload: EventPayload}) {
17
+ const event = this.createTaggedEventObject({
18
+ type: ['operational'],
19
+ name,
20
+ payload,
21
+ });
22
+ this.submitEvent({kind: 'operational-events -> ', name, event});
23
+ }
24
+ }
@@ -0,0 +1,3 @@
1
+ const RTC_METRICS = {APP_ID: 'FFB51ED5-4319-4C55-8303-B1F2FCCDE231'};
2
+
3
+ export {RTC_METRICS as default};
@@ -0,0 +1,186 @@
1
+ /* eslint-disable class-methods-use-this */
2
+ import uuid from 'uuid';
3
+ import * as CallDiagnosticUtils from '../call-diagnostic/call-diagnostic-metrics.util';
4
+ import RTC_METRICS from './constants';
5
+
6
+ const parseJsonPayload = (payload: any[]): any | null => {
7
+ try {
8
+ if (payload && payload[0]) {
9
+ return JSON.parse(payload[0]);
10
+ }
11
+
12
+ return null;
13
+ } catch (_) {
14
+ return null;
15
+ }
16
+ };
17
+
18
+ /**
19
+ * Rtc Metrics
20
+ */
21
+ export default class RtcMetrics {
22
+ /**
23
+ * Array of MetricData items to be sent to the metrics service.
24
+ */
25
+ metricsQueue = [];
26
+
27
+ intervalId: number;
28
+
29
+ webex: any;
30
+
31
+ meetingId: string;
32
+
33
+ correlationId: string;
34
+
35
+ connectionId: string;
36
+
37
+ shouldSendMetricsOnNextStatsReport: boolean;
38
+
39
+ /**
40
+ * Initialize the interval.
41
+ *
42
+ * @param {object} webex - The main `webex` object.
43
+ * @param {string} meetingId - The meeting id.
44
+ * @param {string} correlationId - The correlation id.
45
+ */
46
+ constructor(webex, meetingId, correlationId) {
47
+ // `window` is used to prevent typescript from returning a NodeJS.Timer.
48
+ this.intervalId = window.setInterval(this.sendMetricsInQueue.bind(this), 30 * 1000);
49
+ this.meetingId = meetingId;
50
+ this.webex = webex;
51
+ this.correlationId = correlationId;
52
+ this.resetConnection();
53
+ }
54
+
55
+ /**
56
+ * Check to see if the metrics queue has any items.
57
+ *
58
+ * @returns {void}
59
+ */
60
+ public sendMetricsInQueue() {
61
+ if (this.metricsQueue.length) {
62
+ this.sendMetrics();
63
+ this.metricsQueue = [];
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Forces sending metrics when we get the next stats-report
69
+ *
70
+ * This is useful for cases when something important happens that affects the media connection,
71
+ * for example when we move from lobby into the meeting.
72
+ *
73
+ * @returns {void}
74
+ */
75
+ public sendNextMetrics() {
76
+ this.shouldSendMetricsOnNextStatsReport = true;
77
+ }
78
+
79
+ /**
80
+ * Add metrics items to the metrics queue.
81
+ *
82
+ * @param {object} data - An object with a payload array of metrics items.
83
+ *
84
+ * @returns {void}
85
+ */
86
+ addMetrics(data) {
87
+ if (data.payload.length) {
88
+ if (data.name === 'stats-report') {
89
+ data.payload = data.payload.map(this.anonymizeIp);
90
+ }
91
+
92
+ this.metricsQueue.push(data);
93
+
94
+ if (this.shouldSendMetricsOnNextStatsReport && data.name === 'stats-report') {
95
+ // this is the first useful set of data (WCME gives it to us after 5s), send it out immediately
96
+ // in case the user is unhappy and closes the browser early
97
+ this.sendMetricsInQueue();
98
+ this.shouldSendMetricsOnNextStatsReport = false;
99
+ }
100
+
101
+ try {
102
+ // If a connection fails, send the rest of the metrics in queue and get a new connection id.
103
+ const parsedPayload = parseJsonPayload(data.payload);
104
+ if (
105
+ data.name === 'onconnectionstatechange' &&
106
+ parsedPayload &&
107
+ parsedPayload.value === 'failed'
108
+ ) {
109
+ this.sendMetricsInQueue();
110
+ this.resetConnection();
111
+ }
112
+ } catch (e) {
113
+ console.error(e);
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Clear the metrics interval.
120
+ *
121
+ * @returns {void}
122
+ */
123
+ closeMetrics() {
124
+ this.sendMetricsInQueue();
125
+ clearInterval(this.intervalId);
126
+ }
127
+
128
+ /**
129
+ * Anonymize IP addresses.
130
+ *
131
+ * @param {array} stats - An RTCStatsReport organized into an array of strings.
132
+ * @returns {string}
133
+ */
134
+ anonymizeIp(stats: string): string {
135
+ const data = JSON.parse(stats);
136
+ // on local and remote candidates, anonymize the last 4 bits.
137
+ if (data.type === 'local-candidate' || data.type === 'remote-candidate') {
138
+ data.ip = CallDiagnosticUtils.anonymizeIPAddress(data.ip) || undefined;
139
+ data.address = CallDiagnosticUtils.anonymizeIPAddress(data.address) || undefined;
140
+ data.relatedAddress =
141
+ CallDiagnosticUtils.anonymizeIPAddress(data.relatedAddress) || undefined;
142
+ }
143
+
144
+ return JSON.stringify(data);
145
+ }
146
+
147
+ /**
148
+ * Set a new connection id.
149
+ *
150
+ * @returns {void}
151
+ */
152
+ private resetConnection() {
153
+ this.connectionId = uuid.v4();
154
+ this.shouldSendMetricsOnNextStatsReport = true;
155
+ }
156
+
157
+ /**
158
+ * Send metrics to the metrics service.
159
+ *
160
+ * @returns {void}
161
+ */
162
+ private sendMetrics() {
163
+ this.webex.request({
164
+ method: 'POST',
165
+ service: 'unifiedTelemetry',
166
+ resource: 'metric/v2',
167
+ headers: {
168
+ type: 'webrtcMedia',
169
+ appId: RTC_METRICS.APP_ID,
170
+ },
171
+ body: {
172
+ metrics: [
173
+ {
174
+ type: 'webrtc',
175
+ version: '1.1.0',
176
+ userId: this.webex.internal.device.userId,
177
+ meetingId: this.meetingId,
178
+ correlationId: this.correlationId,
179
+ connectionId: this.connectionId,
180
+ data: this.metricsQueue,
181
+ },
182
+ ],
183
+ },
184
+ });
185
+ }
186
+ }
@@ -74,6 +74,45 @@ describe('internal-plugin-metrics', () => {
74
74
  sinon.restore();
75
75
  });
76
76
 
77
+ describe('#sendEvent', () => {
78
+ it('should send correctly shaped behavioral event (check name building and internal tagged event building)', () => {
79
+ // For some reasons `jest` isn't available when testing form build server - so can't use `jest.fn()` here...
80
+ const requestCalls = [];
81
+ const request = function(arg) { requestCalls.push(arg) }
82
+
83
+ behavioralMetrics.clientMetricsBatcher.request = request;
84
+
85
+ assert.equal(requestCalls.length, 0)
86
+ behavioralMetrics.submitBehavioralEvent({ product: "webex", agent: "user", target: "foo", verb: "get", payload: {bar:"gee"} })
87
+ assert.equal(requestCalls.length, 1)
88
+ assert.deepEqual(requestCalls[0], {
89
+ context: {
90
+ app: {version: 'webex-version'},
91
+ device: {id: 'deviceId'},
92
+ locale: 'language',
93
+ os: {
94
+ name: getOSNameInternal(),
95
+ version: getOSVersion(),
96
+ },
97
+ },
98
+ metricName: 'webex.user.foo.get',
99
+ tags: {
100
+ browser: getBrowserName(),
101
+ browserHeight: window.innerHeight,
102
+ browserVersion: getBrowserVersion(),
103
+ browserWidth: window.innerWidth,
104
+ domain: window.location.hostname,
105
+ inIframe: false,
106
+ locale: window.navigator.language,
107
+ os: getOSNameInternal(),
108
+ bar:"gee"
109
+ },
110
+ timestamp: requestCalls[0].timestamp, // This is to bypass time check, which is correctly tested below.
111
+ type: ['behavioral'],
112
+ });
113
+ })
114
+ })
115
+
77
116
  describe('#getContext', () => {
78
117
  it('should build context correctly', () => {
79
118
  const res = behavioralMetrics.getContext();
@@ -96,7 +135,7 @@ describe('internal-plugin-metrics', () => {
96
135
 
97
136
  describe('#getDefaultTags', () => {
98
137
  it('should build tags correctly', () => {
99
- const res = behavioralMetrics.getDefaultTags();
138
+ const res = behavioralMetrics.getBrowserDetails();
100
139
 
101
140
  assert.deepEqual(res, {
102
141
  browser: getBrowserName(),
@@ -111,25 +150,27 @@ describe('internal-plugin-metrics', () => {
111
150
  });
112
151
  });
113
152
 
114
- describe('#isReadyToSubmitBehavioralEvents', () => {
153
+ describe('#isReadyToSubmitEvents', () => {
115
154
  it('should return true when we have a deviceId, false when deviceId is empty or undefined', async () => {
116
- assert.equal(true, behavioralMetrics.isReadyToSubmitBehavioralEvents());
155
+ let deviceIdUrl = webex.internal.device.url;
117
156
 
157
+ // testing case w/o device id url first, as the internal deviceId cache would bypass that flow.
118
158
  webex.internal.device.url = "";
119
- assert.equal(false, behavioralMetrics.isReadyToSubmitBehavioralEvents());
159
+ assert.equal(false, behavioralMetrics.isReadyToSubmitEvents());
120
160
 
121
161
  delete webex.internal.device.url;
122
- assert.equal(false, behavioralMetrics.isReadyToSubmitBehavioralEvents());
162
+ assert.equal(false, behavioralMetrics.isReadyToSubmitEvents());
163
+
164
+ webex.internal.device.url = deviceIdUrl;
165
+ assert.equal(true, behavioralMetrics.isReadyToSubmitEvents());
123
166
  });
124
167
  });
125
168
 
126
169
  describe('#createEventObject', () => {
127
170
  it('should build event object correctly', async () => {
128
- const res = behavioralMetrics.createEventObject({
129
- product: 'webex',
130
- agent: 'user',
131
- target: 'target',
132
- verb: 'create',
171
+ const res = behavioralMetrics.createTaggedEventObject({
172
+ type:['behavioral'],
173
+ name:'webex.user.target.create',
133
174
  payload: tags,
134
175
  });
135
176
 
@@ -0,0 +1,182 @@
1
+ import sinon from 'sinon';
2
+ import {assert} from '@webex/test-helper-chai';
3
+ import {BrowserDetection} from '@webex/common';
4
+ import {BusinessMetrics, config, getOSNameInternal} from '@webex/internal-plugin-metrics';
5
+ import uuid from 'uuid';
6
+
7
+ //@ts-ignore
8
+ global.window = {location: {hostname: 'whatever'}, navigator: {language: 'language'}};
9
+ process.env.NODE_ENV = 'test';
10
+
11
+ const {getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
12
+
13
+ describe('internal-plugin-metrics', () => {
14
+ describe('BusinessMetrics', () => {
15
+ let webex;
16
+ let now;
17
+ let businessMetrics: BusinessMetrics;
18
+
19
+ const tags = {key: 'val'};
20
+
21
+ beforeEach(() => {
22
+ now = new Date();
23
+
24
+ webex = {
25
+ canAuthorize: true,
26
+ version: 'webex-version',
27
+ internal: {
28
+ services: {
29
+ get: () => 'locus-url',
30
+ },
31
+ metrics: {
32
+ submitClientMetrics: sinon.stub(),
33
+ config: {...config.metrics},
34
+ },
35
+ newMetrics: {},
36
+ device: {
37
+ userId: 'userId',
38
+ url: 'https://wdm-intb.ciscospark.com/wdm/api/v1/devices/deviceId',
39
+ orgId: 'orgId',
40
+ },
41
+ },
42
+ meetings: {
43
+ config: {
44
+ metrics: {
45
+ clientType: 'TEAMS_CLIENT',
46
+ subClientType: 'WEB_APP',
47
+ clientName: 'Cantina',
48
+ },
49
+ },
50
+ geoHintInfo: {
51
+ clientAddress: '1.3.4.5',
52
+ countryCode: 'UK',
53
+ },
54
+ },
55
+ credentials: {
56
+ isUnverifiedGuest: false,
57
+ },
58
+ prepareFetchOptions: sinon.stub().callsFake((opts: any) => ({...opts, foo: 'bar'})),
59
+ request: sinon.stub().resolves({body: {}}),
60
+ logger: {
61
+ log: sinon.stub(),
62
+ error: sinon.stub(),
63
+ },
64
+ };
65
+
66
+ sinon.createSandbox();
67
+ sinon.useFakeTimers(now.getTime());
68
+ businessMetrics = new BusinessMetrics({}, {parent: webex});
69
+ sinon.stub(uuid, 'v4').returns('my-fake-id');
70
+ });
71
+
72
+ afterEach(() => {
73
+ sinon.restore();
74
+ });
75
+
76
+ describe('#sendEvent', () => {
77
+ it('should send correctly shaped business event (check name building and internal tagged event building) and default correctly', () => {
78
+ // For some reasons `jest` isn't available when testing form build server - so can't use `jest.fn()` here...
79
+ const requestCalls = [];
80
+ const request = function(arg) { requestCalls.push(arg) }
81
+
82
+ businessMetrics.clientMetricsBatcher.request = request;
83
+
84
+ assert.equal(requestCalls.length, 0)
85
+ businessMetrics.submitBusinessEvent({ name: "foobar", payload: {bar:"gee"} })
86
+ assert.equal(requestCalls.length, 1)
87
+ assert.deepEqual(requestCalls[0], {
88
+ eventPayload: {
89
+ appType: 'Web Client',
90
+ context: {
91
+ app: {version: 'webex-version'},
92
+ device: {id: 'deviceId'},
93
+ locale: 'language',
94
+ os: {
95
+ name: getOSNameInternal(),
96
+ version: getOSVersion(),
97
+ },
98
+ },
99
+ key: 'foobar',
100
+ browserDetails: {
101
+ browser: getBrowserName(),
102
+ browserHeight: window.innerHeight,
103
+ browserVersion: getBrowserVersion(),
104
+ browserWidth: window.innerWidth,
105
+ domain: window.location.hostname,
106
+ inIframe: false,
107
+ locale: window.navigator.language,
108
+ os: getOSNameInternal(),
109
+ },
110
+ client_timestamp: requestCalls[0].eventPayload.client_timestamp, // This is to bypass time check, which is checked below.
111
+ value: {
112
+ bar: "gee"
113
+ }
114
+ },
115
+ type: ['business'],
116
+ });
117
+ assert.isNumber(requestCalls[0].eventPayload.client_timestamp)
118
+ })
119
+
120
+ describe('when table is provided', () => {
121
+ it('should send correctly shaped business event with table: wbx_app_callend_metrics and ignore the key name', () => {
122
+ // For some reasons `jest` isn't available when testing form build server - so can't use `jest.fn()` here...
123
+ const requestCalls = [];
124
+ const request = function(arg) { requestCalls.push(arg) }
125
+
126
+ businessMetrics.clientMetricsBatcher.request = request;
127
+
128
+ assert.equal(requestCalls.length, 0)
129
+ businessMetrics.submitBusinessEvent({ name: "foobar", payload: {bar:"gee"}, table: 'wbxapp_callend_metrics' })
130
+ assert.equal(requestCalls.length, 1)
131
+ assert.deepEqual(requestCalls[0], {
132
+ eventPayload: {
133
+ key: 'callEnd',
134
+ client_timestamp: requestCalls[0].eventPayload.client_timestamp, // This is to bypass time check, which is checked below.
135
+ appType: 'Web Client',
136
+ value: {
137
+ bar: 'gee'
138
+ }
139
+ },
140
+ type: ['business'],
141
+ });
142
+ assert.isNumber(requestCalls[0].eventPayload.client_timestamp)
143
+ });
144
+
145
+ it('should send correctly shaped business event with table: business_metrics', () => {
146
+ // For some reasons `jest` isn't available when testing form build server - so can't use `jest.fn()` here...
147
+ const requestCalls = [];
148
+ const request = function(arg) { requestCalls.push(arg) }
149
+
150
+ businessMetrics.clientMetricsBatcher.request = request;
151
+
152
+ assert.equal(requestCalls.length, 0)
153
+ businessMetrics.submitBusinessEvent({ name: "foobar", payload: {bar:"gee"}, table: 'business_metrics' })
154
+ assert.equal(requestCalls.length, 1)
155
+ assert.deepEqual(requestCalls[0], {
156
+ eventPayload: {
157
+ key: 'foobar',
158
+ appType: 'Web Client',
159
+ client_timestamp: requestCalls[0].eventPayload.client_timestamp, // This is to bypass time check, which is checked below.
160
+ value: {
161
+ bar: "gee",
162
+ browser: getBrowserName(),
163
+ browserHeight: window.innerHeight,
164
+ browserVersion: getBrowserVersion(),
165
+ browserWidth: window.innerWidth,
166
+ domain: window.location.hostname,
167
+ inIframe: false,
168
+ locale: window.navigator.language,
169
+ os: getOSNameInternal(),
170
+ app: {version: 'webex-version'},
171
+ device: {id: 'deviceId'},
172
+ locale: 'language',
173
+ }
174
+ },
175
+ type: ['business'],
176
+ });
177
+ assert.isNumber(requestCalls[0].eventPayload.client_timestamp)
178
+ });
179
+ });
180
+ })
181
+ });
182
+ });
@@ -441,7 +441,7 @@ describe('plugin-metrics', () => {
441
441
  // item also gets assigned a delay property but the key is a Symbol and haven't been able to test that..
442
442
  assert.deepEqual(calls.args[0].eventPayload, {
443
443
  event: 'my.event',
444
- origin: {buildType: 'test', networkType: 'unknown'},
444
+ origin: {buildType: 'test', networkType: 'unknown', upgradeChannel: 'test'},
445
445
  });
446
446
 
447
447
  assert.deepEqual(calls.args[0].type, ['diagnostic-event']);
@@ -455,6 +455,7 @@ describe('plugin-metrics', () => {
455
455
  origin: {
456
456
  buildType: 'test',
457
457
  networkType: 'unknown',
458
+ upgradeChannel: 'test',
458
459
  },
459
460
  });
460
461
  assert.deepEqual(prepareDiagnosticMetricItemCalls[0].args[1].type, ['diagnostic-event']);