@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.
- package/dist/behavioral-metrics.js +63 -0
- package/dist/behavioral-metrics.js.map +1 -0
- package/dist/business-metrics.js +169 -0
- package/dist/business-metrics.js.map +1 -0
- package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics.js +27 -11
- package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
- package/dist/call-diagnostic/call-diagnostic-metrics.util.js +14 -4
- package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
- package/dist/call-diagnostic/config.js +13 -3
- package/dist/call-diagnostic/config.js.map +1 -1
- package/dist/{behavioral/behavioral-metrics.js → generic-metrics.js} +77 -92
- package/dist/generic-metrics.js.map +1 -0
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.types.js.map +1 -1
- package/dist/new-metrics.js +124 -24
- package/dist/new-metrics.js.map +1 -1
- package/dist/operational-metrics.js +56 -0
- package/dist/operational-metrics.js.map +1 -0
- package/dist/rtcMetrics/constants.js +11 -0
- package/dist/rtcMetrics/constants.js.map +1 -0
- package/dist/rtcMetrics/index.js +202 -0
- package/dist/rtcMetrics/index.js.map +1 -0
- package/dist/types/behavioral-metrics.d.ts +25 -0
- package/dist/types/business-metrics.d.ts +47 -0
- package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +27 -6
- package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +2 -1
- package/dist/types/call-diagnostic/config.d.ts +3 -0
- package/dist/types/generic-metrics.d.ts +63 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/metrics.types.d.ts +27 -14
- package/dist/types/new-metrics.d.ts +42 -9
- package/dist/types/operational-metrics.d.ts +19 -0
- package/dist/types/rtcMetrics/constants.d.ts +4 -0
- package/dist/types/rtcMetrics/index.d.ts +71 -0
- package/package.json +12 -12
- package/src/behavioral-metrics.ts +40 -0
- package/src/business-metrics.ts +118 -0
- package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +1 -1
- package/src/call-diagnostic/call-diagnostic-metrics.ts +30 -9
- package/src/call-diagnostic/call-diagnostic-metrics.util.ts +17 -4
- package/src/call-diagnostic/config.ts +12 -0
- package/src/generic-metrics.ts +146 -0
- package/src/index.ts +7 -1
- package/src/metrics.types.ts +32 -16
- package/src/new-metrics.ts +100 -13
- package/src/operational-metrics.ts +24 -0
- package/src/rtcMetrics/constants.ts +3 -0
- package/src/rtcMetrics/index.ts +186 -0
- package/test/unit/spec/behavioral/behavioral-metrics.ts +51 -10
- package/test/unit/spec/business/business-metrics.ts +182 -0
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +2 -1
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +4 -6
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +418 -12
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +22 -8
- package/test/unit/spec/new-metrics.ts +32 -3
- package/test/unit/spec/operational/operational-metrics.ts +115 -0
- package/test/unit/spec/prelogin-metrics-batcher.ts +3 -1
- package/test/unit/spec/rtcMetrics/index.ts +155 -0
- package/dist/behavioral/behavioral-metrics.js.map +0 -1
- package/dist/behavioral/config.js +0 -11
- package/dist/behavioral/config.js.map +0 -1
- package/dist/types/behavioral/behavioral-metrics.d.ts +0 -63
- package/dist/types/behavioral/config.d.ts +0 -1
- package/src/behavioral/behavioral-metrics.ts +0 -179
- package/src/behavioral/config.ts +0 -3
package/src/new-metrics.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?:
|
|
166
|
+
payload?: EventPayload;
|
|
112
167
|
}) {
|
|
113
|
-
if (!this.
|
|
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
|
-
|
|
208
|
+
table,
|
|
133
209
|
}: {
|
|
134
|
-
name:
|
|
135
|
-
payload
|
|
136
|
-
|
|
210
|
+
name: string;
|
|
211
|
+
payload: EventPayload;
|
|
212
|
+
table?: Table;
|
|
137
213
|
}) {
|
|
138
|
-
|
|
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,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.
|
|
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('#
|
|
153
|
+
describe('#isReadyToSubmitEvents', () => {
|
|
115
154
|
it('should return true when we have a deviceId, false when deviceId is empty or undefined', async () => {
|
|
116
|
-
|
|
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.
|
|
159
|
+
assert.equal(false, behavioralMetrics.isReadyToSubmitEvents());
|
|
120
160
|
|
|
121
161
|
delete webex.internal.device.url;
|
|
122
|
-
assert.equal(false, behavioralMetrics.
|
|
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.
|
|
129
|
-
|
|
130
|
-
|
|
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']);
|