@webex/internal-plugin-metrics 3.3.1 → 3.4.0-next.2

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 (49) 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 +62 -0
  4. package/dist/business-metrics.js.map +1 -0
  5. package/dist/call-diagnostic/call-diagnostic-metrics.js +3 -0
  6. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  7. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +14 -11
  8. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  9. package/dist/call-diagnostic/config.js +19 -13
  10. package/dist/call-diagnostic/config.js.map +1 -1
  11. package/dist/generic-metrics.js +184 -0
  12. package/dist/generic-metrics.js.map +1 -0
  13. package/dist/index.js +21 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/metrics.js +1 -1
  16. package/dist/metrics.types.js.map +1 -1
  17. package/dist/new-metrics.js +116 -23
  18. package/dist/new-metrics.js.map +1 -1
  19. package/dist/operational-metrics.js +56 -0
  20. package/dist/operational-metrics.js.map +1 -0
  21. package/dist/types/behavioral-metrics.d.ts +25 -0
  22. package/dist/types/business-metrics.d.ts +19 -0
  23. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +2 -2
  24. package/dist/types/call-diagnostic/call-diagnostic-metrics.util.d.ts +3 -2
  25. package/dist/types/call-diagnostic/config.d.ts +2 -0
  26. package/dist/types/generic-metrics.d.ts +63 -0
  27. package/dist/types/index.d.ts +4 -1
  28. package/dist/types/metrics.types.d.ts +47 -15
  29. package/dist/types/new-metrics.d.ts +39 -11
  30. package/dist/types/operational-metrics.d.ts +19 -0
  31. package/package.json +11 -11
  32. package/src/behavioral-metrics.ts +40 -0
  33. package/src/business-metrics.ts +30 -0
  34. package/src/call-diagnostic/call-diagnostic-metrics.ts +3 -0
  35. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +16 -17
  36. package/src/call-diagnostic/config.ts +9 -2
  37. package/src/generic-metrics.ts +146 -0
  38. package/src/index.ts +6 -0
  39. package/src/metrics.types.ts +82 -18
  40. package/src/new-metrics.ts +95 -18
  41. package/src/operational-metrics.ts +24 -0
  42. package/test/unit/spec/behavioral/behavioral-metrics.ts +205 -0
  43. package/test/unit/spec/business/business-metrics.ts +120 -0
  44. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +48 -52
  45. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +1 -1
  46. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +9 -0
  47. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +41 -22
  48. package/test/unit/spec/operational/operational-metrics.ts +115 -0
  49. package/test/unit/spec/prelogin-metrics-batcher.ts +1 -1
@@ -16,6 +16,7 @@ export const MISSING_ROAP_ANSWER_CLIENT_CODE = 2007;
16
16
  export const DTLS_HANDSHAKE_FAILED_CLIENT_CODE = 2008;
17
17
  export const ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE = 2010;
18
18
  export const ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE = 2009;
19
+ export const ICE_AND_REACHABILITY_FAILED_CLIENT_CODE = 2011;
19
20
  export const WBX_APP_API_URL = 'wbxappapi'; // MeetingInfo WebexAppApi response object normally contains a body.url that includes the string 'wbxappapi'
20
21
 
21
22
  export const WEBEX_SUB_SERVICE_TYPES: Record<string, ClientSubServiceType> = {
@@ -125,6 +126,7 @@ export const ERROR_DESCRIPTIONS = {
125
126
  DTLS_HANDSHAKE_FAILED: 'DTLSHandshakeFailed',
126
127
  ICE_FAILED_WITHOUT_TURN_TLS: 'ICEFailedWithoutTURN_TLS',
127
128
  ICE_FAILED_WITH_TURN_TLS: 'ICEFailedWithTURN_TLS',
129
+ ICE_AND_REACHABILITY_FAILED: 'ICEAndReachabilityFailed',
128
130
  SDP_OFFER_CREATION_ERROR: 'SdpOfferCreationError',
129
131
  SDP_OFFER_CREATION_ERROR_MISSING_CODEC: 'SdpOfferCreationErrorMissingCodec',
130
132
  };
@@ -377,7 +379,7 @@ export const CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD: Record<number, Partial<ClientEv
377
379
  },
378
380
  [MISSING_ROAP_ANSWER_CLIENT_CODE]: {
379
381
  errorDescription: ERROR_DESCRIPTIONS.MISSING_ROAP_ANSWER,
380
- category: 'signaling',
382
+ category: 'media',
381
383
  fatal: true,
382
384
  },
383
385
  [DTLS_HANDSHAKE_FAILED_CLIENT_CODE]: {
@@ -392,7 +394,12 @@ export const CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD: Record<number, Partial<ClientEv
392
394
  },
393
395
  [ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE]: {
394
396
  errorDescription: ERROR_DESCRIPTIONS.ICE_FAILED_WITH_TURN_TLS,
395
- category: 'network',
397
+ category: 'media',
398
+ fatal: true,
399
+ },
400
+ [ICE_AND_REACHABILITY_FAILED_CLIENT_CODE]: {
401
+ errorDescription: ERROR_DESCRIPTIONS.ICE_AND_REACHABILITY_FAILED,
402
+ category: 'expected',
396
403
  fatal: true,
397
404
  },
398
405
  2050: {
@@ -0,0 +1,146 @@
1
+ import {StatelessWebexPlugin} from '@webex/webex-core';
2
+ import {BrowserDetection} from '@webex/common';
3
+ import {merge} from 'lodash';
4
+ import ClientMetricsBatcher from './client-metrics-batcher';
5
+ import {getOSNameInternal} from './metrics';
6
+ import {DeviceContext, TaggedEvent, EventPayload, MetricType} from './metrics.types';
7
+
8
+ const {getOSVersion, getBrowserName, getBrowserVersion} = BrowserDetection();
9
+
10
+ /**
11
+ * @description top-level abstract class to handle Metrics and common routines.
12
+ * @export
13
+ * @class GenericMetrics
14
+ */
15
+ export default abstract class GenericMetrics extends StatelessWebexPlugin {
16
+ // @ts-ignore
17
+ private clientMetricsBatcher: ClientMetricsBatcher;
18
+ private logger: any; // to avoid adding @ts-ignore everywhere
19
+ private device: any;
20
+ private version: string;
21
+ private deviceId = '';
22
+
23
+ /**
24
+ * Constructor
25
+ * @param {any[]} args
26
+ */
27
+ constructor(...args) {
28
+ super(...args);
29
+ // @ts-ignore
30
+ this.logger = this.webex.logger;
31
+ // @ts-ignore
32
+ this.clientMetricsBatcher = new ClientMetricsBatcher({}, {parent: this.webex});
33
+ // @ts-ignore
34
+ this.device = this.webex.internal.device;
35
+ // @ts-ignore
36
+ this.version = this.webex.version;
37
+ }
38
+
39
+ /**
40
+ * Submit a buisness metric to our metrics endpoint.
41
+ * @param {string} kind of metric for logging
42
+ * @param {string} name of the metric
43
+ * @param {object} event
44
+ * @returns {Promise<any>}
45
+ */
46
+ protected submitEvent({kind, name, event}: {kind: string; name: string; event: object}) {
47
+ this.logger.log(kind, `@submitEvent. Submit event: ${name}`);
48
+
49
+ return this.clientMetricsBatcher.request(event);
50
+ }
51
+
52
+ /**
53
+ * Returns the deviceId from our registration with WDM.
54
+ * @returns {string} deviceId or empty string
55
+ */
56
+ protected getDeviceId(): string {
57
+ if (this.deviceId === '') {
58
+ const {url} = this.device;
59
+ if (url && url.length !== 0) {
60
+ const n = url.lastIndexOf('/');
61
+ if (n !== -1) {
62
+ this.deviceId = url.substring(n + 1);
63
+ }
64
+ }
65
+ }
66
+
67
+ return this.deviceId;
68
+ }
69
+
70
+ /**
71
+ * Returns the context object to be submitted with all metrics.
72
+ * @returns {DeviceContext}
73
+ */
74
+ protected getContext(): DeviceContext {
75
+ return {
76
+ app: {
77
+ version: this.version,
78
+ },
79
+ device: {
80
+ id: this.getDeviceId(),
81
+ },
82
+ locale: window.navigator.language,
83
+ os: {
84
+ name: getOSNameInternal(),
85
+ version: getOSVersion(),
86
+ },
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Returns the browser details to be included with all metrics.
92
+ * @returns {object}
93
+ */
94
+ protected getBrowserDetails(): object {
95
+ return {
96
+ browser: getBrowserName(),
97
+ browserHeight: window.innerHeight,
98
+ browserVersion: getBrowserVersion(),
99
+ browserWidth: window.innerWidth,
100
+ domain: window.location.hostname,
101
+ inIframe: window.self !== window.top,
102
+ locale: window.navigator.language,
103
+ os: getOSNameInternal(),
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Returns true once we have the deviceId we need to submit behavioral/operational/buisness events
109
+ * @returns {boolean}
110
+ */
111
+ public isReadyToSubmitEvents(): boolean {
112
+ const deviceId = this.getDeviceId();
113
+
114
+ return deviceId && deviceId.length !== 0;
115
+ }
116
+
117
+ /**
118
+ * Creates the object to send to our metrics endpoint for a tagged event (i.e. behavoral or operational)
119
+ * @param {[MetricType]} list of event type (i.e. ['behavioral'], ['operational', 'behavioral'])
120
+ * @param {string} metric name
121
+ * @param {EventPayload} user payload
122
+ * @returns {EventPayload}
123
+ */
124
+ protected createTaggedEventObject({
125
+ type,
126
+ name,
127
+ payload,
128
+ }: {
129
+ type: [MetricType];
130
+ name: string;
131
+ payload: EventPayload;
132
+ }): TaggedEvent {
133
+ let allTags: EventPayload = payload;
134
+ allTags = merge(allTags, this.getBrowserDetails());
135
+
136
+ const event = {
137
+ context: this.getContext(),
138
+ metricName: name,
139
+ tags: allTags,
140
+ timestamp: Date.now(),
141
+ type,
142
+ };
143
+
144
+ return event;
145
+ }
146
+ }
package/src/index.ts CHANGED
@@ -22,6 +22,9 @@ import * as CALL_DIAGNOSTIC_CONFIG from './call-diagnostic/config';
22
22
  import * as CallDiagnosticUtils from './call-diagnostic/call-diagnostic-metrics.util';
23
23
  import CallDiagnosticMetrics from './call-diagnostic/call-diagnostic-metrics';
24
24
  import CallDiagnosticLatencies from './call-diagnostic/call-diagnostic-metrics-latencies';
25
+ import BehavioralMetrics from './behavioral-metrics';
26
+ import OperationalMetrics from './operational-metrics';
27
+ import BusinessMetrics from './business-metrics';
25
28
 
26
29
  registerInternalPlugin('metrics', Metrics, {
27
30
  config,
@@ -41,6 +44,9 @@ export {
41
44
  CallDiagnosticUtils,
42
45
  CallDiagnosticLatencies,
43
46
  CallDiagnosticMetrics,
47
+ BehavioralMetrics,
48
+ OperationalMetrics,
49
+ BusinessMetrics,
44
50
  };
45
51
  export type {
46
52
  ClientEvent,
@@ -20,6 +20,42 @@ export type BrowserLaunchMethodType = NonNullable<
20
20
  RawEvent['origin']['clientInfo']
21
21
  >['browserLaunchMethod'];
22
22
 
23
+ export type MetricEventProduct = 'webex' | 'wxcc_desktop';
24
+
25
+ export type MetricEventAgent = 'user' | 'browser' | 'system' | 'sdk' | 'redux' | 'service';
26
+
27
+ export type MetricEventVerb =
28
+ | 'create'
29
+ | 'get'
30
+ | 'fetch'
31
+ | 'update'
32
+ | 'list'
33
+ | 'delete'
34
+ | 'select'
35
+ | 'view'
36
+ | 'set'
37
+ | 'toggle'
38
+ | 'load'
39
+ | 'reload'
40
+ | 'click'
41
+ | 'hover'
42
+ | 'register'
43
+ | 'unregister'
44
+ | 'enable'
45
+ | 'disable'
46
+ | 'use'
47
+ | 'complete'
48
+ | 'submit'
49
+ | 'apply'
50
+ | 'cancel'
51
+ | 'abort'
52
+ | 'sync'
53
+ | 'login'
54
+ | 'logout'
55
+ | 'answer'
56
+ | 'activate'
57
+ | 'deactivate';
58
+
23
59
  export type SubmitClientEventOptions = {
24
60
  meetingId?: string;
25
61
  mediaConnections?: any[];
@@ -66,20 +102,46 @@ export interface ClientEvent {
66
102
  options?: SubmitClientEventOptions;
67
103
  }
68
104
 
69
- export interface BehavioralEvent {
70
- // TODO: not implemented
71
- name: 'host.meeting.participant.admitted' | 'sdk.media-flow.started';
72
- payload?: never;
73
- options?: never;
105
+ export interface DeviceContext {
106
+ app: {version: string};
107
+ device: {id: string};
108
+ locale: string;
109
+ os: {
110
+ name: string;
111
+ version: string;
112
+ };
74
113
  }
75
114
 
76
- export interface OperationalEvent {
77
- // TODO: not implemented
78
- name: never;
79
- payload?: never;
80
- options?: never;
115
+ export type MetricType = 'behavioral' | 'operational' | 'business';
116
+
117
+ type InternalEventPayload = string | number | boolean;
118
+ export type EventPayload = Record<string, InternalEventPayload>;
119
+ export type BehavioralEventPayload = EventPayload; // for compatibilty, can be remove after wxcc-desktop did change their imports.
120
+
121
+ export interface BusinessEventPayload {
122
+ metricName: string;
123
+ timestamp: number;
124
+ context: DeviceContext;
125
+ browserDetails: EventPayload;
126
+ value: EventPayload;
127
+ }
128
+
129
+ export interface BusinessEvent {
130
+ type: string[];
131
+ eventPayload: BusinessEventPayload;
132
+ }
133
+
134
+ export interface TaggedEvent {
135
+ context: DeviceContext;
136
+ metricName: string;
137
+ tags: EventPayload;
138
+ timestamp: number;
139
+ type: [MetricType];
81
140
  }
82
141
 
142
+ export type BehavioralEvent = TaggedEvent;
143
+ export type OperationalEvent = TaggedEvent;
144
+
83
145
  export interface FeatureEvent {
84
146
  // TODO: not implemented
85
147
  name: never;
@@ -104,8 +166,9 @@ export type RecursivePartial<T> = {
104
166
  export type MetricEventNames =
105
167
  | InternalEvent['name']
106
168
  | ClientEvent['name']
107
- | BehavioralEvent['name']
108
- | OperationalEvent['name']
169
+ | BehavioralEvent['metricName']
170
+ | OperationalEvent['metricName']
171
+ | BusinessEvent['eventPayload']['metricName']
109
172
  | FeatureEvent['name']
110
173
  | MediaQualityEvent['name'];
111
174
 
@@ -137,9 +200,11 @@ export type SubmitInternalEvent = (args: {
137
200
  }) => void;
138
201
 
139
202
  export type SubmitBehavioralEvent = (args: {
140
- name: BehavioralEvent['name'];
141
- payload?: RecursivePartial<BehavioralEvent['payload']>;
142
- options?: any;
203
+ product: MetricEventProduct;
204
+ agent: MetricEventAgent;
205
+ target: string;
206
+ verb: MetricEventVerb;
207
+ payload?: EventPayload;
143
208
  }) => void;
144
209
 
145
210
  export type SubmitClientEvent = (args: {
@@ -149,9 +214,8 @@ export type SubmitClientEvent = (args: {
149
214
  }) => Promise<any>;
150
215
 
151
216
  export type SubmitOperationalEvent = (args: {
152
- name: OperationalEvent['name'];
153
- payload?: RecursivePartial<OperationalEvent['payload']>;
154
- options?: any;
217
+ name: OperationalEvent['metricName'];
218
+ payload: EventPayload;
155
219
  }) => void;
156
220
 
157
221
  export type SubmitMQE = (args: {
@@ -6,11 +6,17 @@
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-metrics';
10
+ import OperationalMetrics from './operational-metrics';
11
+ import BusinessMetrics from './business-metrics';
9
12
  import {
10
13
  RecursivePartial,
14
+ MetricEventProduct,
15
+ MetricEventAgent,
16
+ MetricEventVerb,
11
17
  ClientEvent,
12
18
  FeatureEvent,
13
- BehavioralEvent,
19
+ EventPayload,
14
20
  OperationalEvent,
15
21
  MediaQualityEvent,
16
22
  InternalEvent,
@@ -32,6 +38,10 @@ class Metrics extends WebexPlugin {
32
38
  callDiagnosticLatencies: CallDiagnosticLatencies;
33
39
  // Helper classes to handle the different types of metrics
34
40
  callDiagnosticMetrics: CallDiagnosticMetrics;
41
+ behavioralMetrics: BehavioralMetrics;
42
+ operationalMetrics: OperationalMetrics;
43
+ businessMetrics: BusinessMetrics;
44
+ isReady = false;
35
45
 
36
46
  /**
37
47
  * Constructor
@@ -56,6 +66,7 @@ class Metrics extends WebexPlugin {
56
66
  this.webex.once('ready', () => {
57
67
  // @ts-ignore
58
68
  this.callDiagnosticMetrics = new CallDiagnosticMetrics({}, {parent: this.webex});
69
+ this.isReady = true;
59
70
  });
60
71
  }
61
72
 
@@ -79,37 +90,103 @@ class Metrics extends WebexPlugin {
79
90
  }
80
91
  }
81
92
 
93
+ /**
94
+ * @returns true once we have the deviceId we need to submit behavioral events to Amplitude
95
+ */
96
+ isReadyToSubmitBehavioralEvents() {
97
+ return this.behavioralMetrics?.isReadyToSubmitEvents() ?? false;
98
+ }
99
+
100
+ /**
101
+ * @returns true once we have the deviceId we need to submit operational events
102
+ */
103
+ isReadyToSubmitOperationalEvents() {
104
+ return this.operationalMetrics?.isReadyToSubmitEvents() ?? false;
105
+ }
106
+
107
+ /**
108
+ * @returns true once we have the deviceId we need to submit buisness events
109
+ */
110
+ isReadyToSubmitBusinessEvents() {
111
+ return this.businessMetrics?.isReadyToSubmitEvents() ?? false;
112
+ }
113
+
82
114
  /**
83
115
  * Behavioral event
84
116
  * @param args
85
117
  */
86
118
  submitBehavioralEvent({
87
- name,
119
+ product,
120
+ agent,
121
+ target,
122
+ verb,
88
123
  payload,
89
- options,
90
124
  }: {
91
- name: BehavioralEvent['name'];
92
- payload?: RecursivePartial<BehavioralEvent['payload']>;
93
- options?: any;
125
+ product: MetricEventProduct;
126
+ agent: MetricEventAgent;
127
+ target: string;
128
+ verb: MetricEventVerb;
129
+ payload?: EventPayload;
94
130
  }) {
95
- this.callDiagnosticLatencies.saveTimestamp({key: name});
96
- throw new Error('Not implemented.');
131
+ if (!this.isReady) {
132
+ // @ts-ignore
133
+ this.webex.logger.log(
134
+ `NewMetrics: @submitBehavioralEvent. Attempted to submit before webex.ready: ${product}.${agent}.${target}.${verb}`
135
+ );
136
+
137
+ return Promise.resolve();
138
+ }
139
+
140
+ if (!this.behavioralMetrics) {
141
+ // @ts-ignore
142
+ this.behavioralMetrics = new BehavioralMetrics({}, {parent: this.webex});
143
+ }
144
+
145
+ return this.behavioralMetrics.submitBehavioralEvent({product, agent, target, verb, payload});
97
146
  }
98
147
 
99
148
  /**
100
149
  * Operational event
101
150
  * @param args
102
151
  */
103
- submitOperationalEvent({
104
- name,
105
- payload,
106
- options,
107
- }: {
108
- name: OperationalEvent['name'];
109
- payload?: RecursivePartial<OperationalEvent['payload']>;
110
- options?: any;
111
- }) {
112
- throw new Error('Not implemented.');
152
+ submitOperationalEvent({name, payload}: {name: string; payload?: EventPayload}) {
153
+ if (!this.isReady) {
154
+ // @ts-ignore
155
+ this.webex.logger.log(
156
+ `NewMetrics: @submitOperationalEvent. Attempted to submit before webex.ready: ${name}`
157
+ );
158
+
159
+ return Promise.resolve();
160
+ }
161
+
162
+ if (!this.operationalMetrics) {
163
+ // @ts-ignore
164
+ this.operationalMetrics = new OperationalMetrics({}, {parent: this.webex});
165
+ }
166
+
167
+ return this.operationalMetrics.submitOperationalEvent({name, payload});
168
+ }
169
+
170
+ /**
171
+ * Buisness event
172
+ * @param args
173
+ */
174
+ submitBusinessEvent({name, payload}: {name: string; payload: EventPayload}) {
175
+ if (!this.isReady) {
176
+ // @ts-ignore
177
+ this.webex.logger.log(
178
+ `NewMetrics: @submitBusinessEvent. Attempted to submit before webex.ready: ${name}`
179
+ );
180
+
181
+ return Promise.resolve();
182
+ }
183
+
184
+ if (!this.businessMetrics) {
185
+ // @ts-ignore
186
+ this.businessMetrics = new BusinessMetrics({}, {parent: this.webex});
187
+ }
188
+
189
+ return this.businessMetrics.submitBusinessEvent({name, payload});
113
190
  }
114
191
 
115
192
  /**
@@ -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
+ }