@webex/internal-plugin-metrics 3.12.0-llmrefactor.1 → 3.12.0-llmrefactor.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.
- 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 +44 -11
- package/dist/new-metrics.js.map +1 -1
- package/dist/privacy-and-security-permission-enricher/constants.js +18 -0
- package/dist/privacy-and-security-permission-enricher/constants.js.map +1 -0
- package/dist/privacy-and-security-permission-enricher/index.js +143 -0
- package/dist/privacy-and-security-permission-enricher/index.js.map +1 -0
- package/dist/privacy-and-security-permission-enricher/types.js +7 -0
- package/dist/privacy-and-security-permission-enricher/types.js.map +1 -0
- package/dist/privacy-and-security-permission-enricher/utils.js +71 -0
- package/dist/privacy-and-security-permission-enricher/utils.js.map +1 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/metrics.types.d.ts +3 -0
- package/dist/types/new-metrics.d.ts +13 -1
- package/dist/types/privacy-and-security-permission-enricher/constants.d.ts +6 -0
- package/dist/types/privacy-and-security-permission-enricher/index.d.ts +40 -0
- package/dist/types/privacy-and-security-permission-enricher/types.d.ts +14 -0
- package/dist/types/privacy-and-security-permission-enricher/utils.d.ts +5 -0
- package/package.json +7 -7
- package/src/index.ts +2 -0
- package/src/metrics.types.ts +9 -0
- package/src/new-metrics.ts +41 -2
- package/src/privacy-and-security-permission-enricher/constants.ts +33 -0
- package/src/privacy-and-security-permission-enricher/index.ts +142 -0
- package/src/privacy-and-security-permission-enricher/types.ts +21 -0
- package/src/privacy-and-security-permission-enricher/utils.ts +82 -0
- package/test/unit/spec/new-metrics.ts +144 -48
- package/test/unit/spec/privacy-and-security-permission-enricher.ts +318 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type {ClientEventPayload, PrivacyAndSecurityPermission} from '../metrics.types';
|
|
2
|
+
import {
|
|
3
|
+
CAMERA_AND_MICROPHONE_PERMISSION_EVENTS,
|
|
4
|
+
CONTENT_SHARE_PERMISSION_EVENTS,
|
|
5
|
+
FINAL_PERMISSION_EVENTS,
|
|
6
|
+
MEDIA_TX_PERMISSION_EVENTS,
|
|
7
|
+
NO_PERMISSION_ENRICHMENT,
|
|
8
|
+
} from './constants';
|
|
9
|
+
import type {
|
|
10
|
+
PermissionEnrichmentContext,
|
|
11
|
+
PermissionEnrichmentPolicy,
|
|
12
|
+
PermissionEnrichmentRule,
|
|
13
|
+
} from './types';
|
|
14
|
+
import {
|
|
15
|
+
getChangedPermission,
|
|
16
|
+
projectPrivacyAndSecurityPermission,
|
|
17
|
+
resolveContentShareResources,
|
|
18
|
+
resolveMediaResources,
|
|
19
|
+
} from './utils';
|
|
20
|
+
|
|
21
|
+
export const PERMISSION_ENRICHMENT_RULES = [
|
|
22
|
+
{
|
|
23
|
+
events: CAMERA_AND_MICROPHONE_PERMISSION_EVENTS,
|
|
24
|
+
resolve: () => ({resources: ['camera', 'microphone'], terminal: false}),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
events: MEDIA_TX_PERMISSION_EVENTS,
|
|
28
|
+
resolve: (payload) => ({resources: resolveMediaResources(payload), terminal: false}),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
events: CONTENT_SHARE_PERMISSION_EVENTS,
|
|
32
|
+
resolve: (payload) => ({
|
|
33
|
+
resources: resolveContentShareResources(payload),
|
|
34
|
+
terminal: false,
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
events: FINAL_PERMISSION_EVENTS,
|
|
39
|
+
resolve: () => ({resources: ['camera', 'microphone', 'contentShare'], terminal: true}),
|
|
40
|
+
},
|
|
41
|
+
] satisfies readonly PermissionEnrichmentRule[];
|
|
42
|
+
|
|
43
|
+
const resolvePermissionEnrichmentPolicy = (
|
|
44
|
+
name: PermissionEnrichmentContext['name'],
|
|
45
|
+
payload?: ClientEventPayload
|
|
46
|
+
): PermissionEnrichmentPolicy =>
|
|
47
|
+
PERMISSION_ENRICHMENT_RULES.find(({events}) => events.has(name))?.resolve(payload) ??
|
|
48
|
+
NO_PERMISSION_ENRICHMENT;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Enriches eligible client events with relevant browser permission changes.
|
|
52
|
+
*/
|
|
53
|
+
export default class PrivacyAndSecurityPermissionEnricher {
|
|
54
|
+
private permission?: PrivacyAndSecurityPermission;
|
|
55
|
+
|
|
56
|
+
private lastReported = new Map<string, PrivacyAndSecurityPermission>();
|
|
57
|
+
|
|
58
|
+
private readonly onEnrichmentError: (error: unknown) => void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a permission enricher.
|
|
62
|
+
* @param {Function} onEnrichmentError permission enrichment error handler
|
|
63
|
+
*/
|
|
64
|
+
constructor(onEnrichmentError: (error: unknown) => void) {
|
|
65
|
+
this.onEnrichmentError = onEnrichmentError;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Stores the latest normalized browser permission state.
|
|
70
|
+
* @param {PrivacyAndSecurityPermission} permission permission snapshot
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
public setPermission(permission: PrivacyAndSecurityPermission): void {
|
|
74
|
+
this.permission = projectPrivacyAndSecurityPermission(permission, [
|
|
75
|
+
'camera',
|
|
76
|
+
'microphone',
|
|
77
|
+
'contentShare',
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns the original payload or a copy enriched with relevant permission changes.
|
|
83
|
+
* @param {PermissionEnrichmentContext} context event context and permission history scope
|
|
84
|
+
* @returns {ClientEventPayload | undefined}
|
|
85
|
+
*/
|
|
86
|
+
public enrich({
|
|
87
|
+
name,
|
|
88
|
+
payload,
|
|
89
|
+
scope,
|
|
90
|
+
}: PermissionEnrichmentContext): ClientEventPayload | undefined {
|
|
91
|
+
const policy = resolvePermissionEnrichmentPolicy(name, payload);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
if (payload?.privacyAndSecurityPermission !== undefined) {
|
|
95
|
+
// An explicitly supplied permission payload is authoritative for this event.
|
|
96
|
+
if (!policy.terminal) {
|
|
97
|
+
this.lastReported.set(scope, {
|
|
98
|
+
...this.lastReported.get(scope),
|
|
99
|
+
...(payload.privacyAndSecurityPermission as PrivacyAndSecurityPermission),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return payload;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (policy.resources.length === 0) {
|
|
107
|
+
return payload;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const projectedPermission = this.permission
|
|
111
|
+
? projectPrivacyAndSecurityPermission(this.permission, policy.resources)
|
|
112
|
+
: undefined;
|
|
113
|
+
|
|
114
|
+
if (!projectedPermission) {
|
|
115
|
+
return payload;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (policy.terminal) {
|
|
119
|
+
return {...payload, privacyAndSecurityPermission: projectedPermission};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const lastReported = this.lastReported.get(scope) ?? {};
|
|
123
|
+
const changedPermission = getChangedPermission(projectedPermission, lastReported);
|
|
124
|
+
|
|
125
|
+
if (!changedPermission) {
|
|
126
|
+
return payload;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.lastReported.set(scope, {...lastReported, ...changedPermission});
|
|
130
|
+
|
|
131
|
+
return {...payload, privacyAndSecurityPermission: changedPermission};
|
|
132
|
+
} catch (error) {
|
|
133
|
+
this.onEnrichmentError(error);
|
|
134
|
+
|
|
135
|
+
return payload;
|
|
136
|
+
} finally {
|
|
137
|
+
if (policy.terminal) {
|
|
138
|
+
this.lastReported.delete(scope);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ClientEvent,
|
|
3
|
+
ClientEventPayload,
|
|
4
|
+
PrivacyAndSecurityPermissionResource,
|
|
5
|
+
} from '../metrics.types';
|
|
6
|
+
|
|
7
|
+
export type PermissionEnrichmentPolicy = {
|
|
8
|
+
resources: readonly PrivacyAndSecurityPermissionResource[];
|
|
9
|
+
terminal: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type PermissionEnrichmentRule = {
|
|
13
|
+
events: ReadonlySet<ClientEvent['name']>;
|
|
14
|
+
resolve: (payload?: ClientEventPayload) => PermissionEnrichmentPolicy;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PermissionEnrichmentContext = {
|
|
18
|
+
name: ClientEvent['name'];
|
|
19
|
+
payload?: ClientEventPayload;
|
|
20
|
+
scope: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {isEqual} from 'lodash';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ClientEventPayload,
|
|
5
|
+
PrivacyAndSecurityPermission,
|
|
6
|
+
PrivacyAndSecurityPermissionResource,
|
|
7
|
+
} from '../metrics.types';
|
|
8
|
+
|
|
9
|
+
export const resolveMediaResources = (
|
|
10
|
+
payload?: ClientEventPayload
|
|
11
|
+
): PrivacyAndSecurityPermissionResource[] => {
|
|
12
|
+
switch (payload?.mediaType) {
|
|
13
|
+
case 'audio':
|
|
14
|
+
return ['microphone'];
|
|
15
|
+
case 'video':
|
|
16
|
+
return ['camera'];
|
|
17
|
+
case 'share':
|
|
18
|
+
return ['contentShare'];
|
|
19
|
+
default:
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const resolveContentShareResources = (
|
|
25
|
+
payload?: ClientEventPayload
|
|
26
|
+
): PrivacyAndSecurityPermissionResource[] => {
|
|
27
|
+
if (payload?.mediaType !== 'share') {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return ['contentShare'];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const copyPermissionState = (
|
|
35
|
+
source: PrivacyAndSecurityPermission,
|
|
36
|
+
target: PrivacyAndSecurityPermission,
|
|
37
|
+
resource: PrivacyAndSecurityPermissionResource
|
|
38
|
+
): void => {
|
|
39
|
+
switch (resource) {
|
|
40
|
+
case 'camera':
|
|
41
|
+
if (source.camera) {
|
|
42
|
+
target.camera = {...source.camera};
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
case 'microphone':
|
|
46
|
+
if (source.microphone) {
|
|
47
|
+
target.microphone = {...source.microphone};
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
case 'contentShare':
|
|
51
|
+
if (source.contentShare) {
|
|
52
|
+
target.contentShare = {...source.contentShare};
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const projectPrivacyAndSecurityPermission = (
|
|
61
|
+
permission: PrivacyAndSecurityPermission,
|
|
62
|
+
resources: readonly PrivacyAndSecurityPermissionResource[]
|
|
63
|
+
): PrivacyAndSecurityPermission | undefined => {
|
|
64
|
+
const projectedPermission: PrivacyAndSecurityPermission = {};
|
|
65
|
+
|
|
66
|
+
resources.forEach((resource) => {
|
|
67
|
+
copyPermissionState(permission, projectedPermission, resource);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return Object.keys(projectedPermission).length > 0 ? projectedPermission : undefined;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const getChangedPermission = (
|
|
74
|
+
current: PrivacyAndSecurityPermission,
|
|
75
|
+
previous: PrivacyAndSecurityPermission
|
|
76
|
+
): PrivacyAndSecurityPermission | undefined => {
|
|
77
|
+
const changedResources = (Object.keys(current) as PrivacyAndSecurityPermissionResource[]).filter(
|
|
78
|
+
(resource) => !isEqual(current[resource], previous[resource])
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return projectPrivacyAndSecurityPermission(current, changedResources);
|
|
82
|
+
};
|
|
@@ -9,19 +9,23 @@ import MockWebex from '@webex/test-helper-mock-webex';
|
|
|
9
9
|
import sinon from 'sinon';
|
|
10
10
|
|
|
11
11
|
describe('internal-plugin-metrics', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
const mockWebex = () =>
|
|
13
|
+
new MockWebex({
|
|
14
|
+
children: {
|
|
15
|
+
newMetrics: NewMetrics,
|
|
16
|
+
},
|
|
17
|
+
meetings: {
|
|
18
|
+
getBasicMeetingInformation: sinon.stub().callsFake((meetingId) => ({
|
|
19
|
+
id: meetingId,
|
|
20
|
+
correlationId: `correlation-${meetingId}`,
|
|
21
|
+
})),
|
|
22
|
+
},
|
|
23
|
+
request: sinon.stub().resolves({}),
|
|
24
|
+
logger: {
|
|
25
|
+
log: sinon.stub(),
|
|
26
|
+
error: sinon.stub(),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
25
29
|
|
|
26
30
|
describe('check submitClientEvent, submitFeatureEvent when webex is not ready', () => {
|
|
27
31
|
let webex;
|
|
@@ -50,12 +54,14 @@ describe('internal-plugin-metrics', () => {
|
|
|
50
54
|
payload: {
|
|
51
55
|
meetingSummaryInfo: {
|
|
52
56
|
featureName: 'syncSystemMuteStatus',
|
|
53
|
-
featureActions: [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
featureActions: [
|
|
58
|
+
{
|
|
59
|
+
actionName: 'syncMeetingMicUnmuteStatusToSystem',
|
|
60
|
+
actionId: '14200',
|
|
61
|
+
isInitialValue: false,
|
|
62
|
+
clickCount: '1',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
59
65
|
},
|
|
60
66
|
},
|
|
61
67
|
});
|
|
@@ -68,7 +74,6 @@ describe('internal-plugin-metrics', () => {
|
|
|
68
74
|
|
|
69
75
|
describe('new-metrics contstructor', () => {
|
|
70
76
|
it('checks callDiagnosticLatencies is defined before ready emit', () => {
|
|
71
|
-
|
|
72
77
|
const webex = mockWebex();
|
|
73
78
|
|
|
74
79
|
assert.instanceOf(webex.internal.newMetrics.callDiagnosticLatencies, CallDiagnosticLatencies);
|
|
@@ -124,7 +129,7 @@ describe('internal-plugin-metrics', () => {
|
|
|
124
129
|
|
|
125
130
|
afterEach(() => {
|
|
126
131
|
sinon.restore();
|
|
127
|
-
})
|
|
132
|
+
});
|
|
128
133
|
|
|
129
134
|
it('lazy metrics backend initialization when checking if backend ready', () => {
|
|
130
135
|
assert.isUndefined(webex.internal.newMetrics.behavioralMetrics);
|
|
@@ -135,10 +140,10 @@ describe('internal-plugin-metrics', () => {
|
|
|
135
140
|
webex.internal.newMetrics.isReadyToSubmitOperationalEvents();
|
|
136
141
|
assert.isDefined(webex.internal.newMetrics.operationalMetrics);
|
|
137
142
|
|
|
138
|
-
assert.isUndefined(webex.internal.newMetrics.businessMetrics)
|
|
143
|
+
assert.isUndefined(webex.internal.newMetrics.businessMetrics);
|
|
139
144
|
webex.internal.newMetrics.isReadyToSubmitBusinessEvents();
|
|
140
145
|
assert.isDefined(webex.internal.newMetrics.businessMetrics);
|
|
141
|
-
})
|
|
146
|
+
});
|
|
142
147
|
|
|
143
148
|
it('returns the automated user classification', () => {
|
|
144
149
|
assert.strictEqual(
|
|
@@ -148,7 +153,7 @@ describe('internal-plugin-metrics', () => {
|
|
|
148
153
|
});
|
|
149
154
|
|
|
150
155
|
it('passes the table through to the business metrics', () => {
|
|
151
|
-
assert.isUndefined(webex.internal.newMetrics.businessMetrics)
|
|
156
|
+
assert.isUndefined(webex.internal.newMetrics.businessMetrics);
|
|
152
157
|
webex.internal.newMetrics.isReadyToSubmitBusinessEvents();
|
|
153
158
|
assert.isDefined(webex.internal.newMetrics.businessMetrics);
|
|
154
159
|
webex.internal.newMetrics.businessMetrics.submitBusinessEvent = sinon.stub();
|
|
@@ -156,14 +161,14 @@ describe('internal-plugin-metrics', () => {
|
|
|
156
161
|
name: 'foobar',
|
|
157
162
|
payload: {},
|
|
158
163
|
table: 'test',
|
|
159
|
-
metadata: {
|
|
164
|
+
metadata: {foo: 'bar'},
|
|
160
165
|
});
|
|
161
166
|
|
|
162
167
|
assert.calledWith(webex.internal.newMetrics.businessMetrics.submitBusinessEvent, {
|
|
163
168
|
name: 'foobar',
|
|
164
169
|
payload: {},
|
|
165
170
|
table: 'test',
|
|
166
|
-
metadata: {
|
|
171
|
+
metadata: {foo: 'bar'},
|
|
167
172
|
});
|
|
168
173
|
});
|
|
169
174
|
|
|
@@ -187,6 +192,87 @@ describe('internal-plugin-metrics', () => {
|
|
|
187
192
|
});
|
|
188
193
|
});
|
|
189
194
|
|
|
195
|
+
describe('privacy and security permission enrichment', () => {
|
|
196
|
+
const permission = {
|
|
197
|
+
camera: {status: 'GRANTED' as const},
|
|
198
|
+
microphone: {status: 'DENIED' as const, reason: 'DENIED_BY_USER' as const},
|
|
199
|
+
contentShare: {status: 'REQUESTING' as const},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
it('enriches an eligible client event through the public metrics API', () => {
|
|
203
|
+
webex.internal.newMetrics.setPrivacyAndSecurityPermission(permission);
|
|
204
|
+
webex.internal.newMetrics.submitClientEvent({name: 'client.call.initiated'});
|
|
205
|
+
|
|
206
|
+
const submittedPayload =
|
|
207
|
+
webex.internal.newMetrics.callDiagnosticMetrics.submitClientEvent.firstCall.args[0]
|
|
208
|
+
.payload;
|
|
209
|
+
|
|
210
|
+
assert.deepEqual(submittedPayload.privacyAndSecurityPermission, {
|
|
211
|
+
camera: permission.camera,
|
|
212
|
+
microphone: permission.microphone,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('uses the meeting correlation id to preserve history across identifier transitions', () => {
|
|
217
|
+
webex.meetings.getBasicMeetingInformation
|
|
218
|
+
.withArgs('meeting-1')
|
|
219
|
+
.returns({id: 'meeting-1', correlationId: 'correlation-1'});
|
|
220
|
+
webex.internal.newMetrics.setPrivacyAndSecurityPermission(permission);
|
|
221
|
+
|
|
222
|
+
webex.internal.newMetrics.submitClientEvent({
|
|
223
|
+
name: 'client.call.initiated',
|
|
224
|
+
options: {correlationId: 'correlation-1'},
|
|
225
|
+
});
|
|
226
|
+
webex.internal.newMetrics.submitClientEvent({
|
|
227
|
+
name: 'client.ice.end',
|
|
228
|
+
payload: {},
|
|
229
|
+
options: {meetingId: 'meeting-1'},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const submissions = webex.internal.newMetrics.callDiagnosticMetrics.submitClientEvent.args;
|
|
233
|
+
|
|
234
|
+
assert.property(submissions[0][0].payload, 'privacyAndSecurityPermission');
|
|
235
|
+
assert.notProperty(submissions[1][0].payload, 'privacyAndSecurityPermission');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('uses the default scope when no correlation id can be resolved', () => {
|
|
239
|
+
webex.internal.newMetrics.setPrivacyAndSecurityPermission(permission);
|
|
240
|
+
|
|
241
|
+
webex.internal.newMetrics.submitClientEvent({
|
|
242
|
+
name: 'client.call.initiated',
|
|
243
|
+
options: {sessionCorrelationId: 'session-1'},
|
|
244
|
+
});
|
|
245
|
+
webex.internal.newMetrics.submitClientEvent({
|
|
246
|
+
name: 'client.ice.end',
|
|
247
|
+
payload: {},
|
|
248
|
+
options: {sessionCorrelationId: 'session-2'},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const submissions = webex.internal.newMetrics.callDiagnosticMetrics.submitClientEvent.args;
|
|
252
|
+
|
|
253
|
+
assert.property(submissions[0][0].payload, 'privacyAndSecurityPermission');
|
|
254
|
+
assert.notProperty(submissions[1][0].payload, 'privacyAndSecurityPermission');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('captures the permission snapshot before a delayed event is queued', () => {
|
|
258
|
+
webex.internal.newMetrics.setPrivacyAndSecurityPermission(permission);
|
|
259
|
+
webex.internal.newMetrics.setDelaySubmitClientEvents({shouldDelay: true});
|
|
260
|
+
webex.internal.newMetrics.submitClientEvent({name: 'client.call.initiated'});
|
|
261
|
+
webex.internal.newMetrics.setPrivacyAndSecurityPermission({
|
|
262
|
+
camera: {status: 'DENIED' as const},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const submission =
|
|
266
|
+
webex.internal.newMetrics.callDiagnosticMetrics.submitClientEvent.firstCall.args[0];
|
|
267
|
+
|
|
268
|
+
assert.isTrue(submission.delaySubmitEvent);
|
|
269
|
+
assert.deepEqual(submission.payload.privacyAndSecurityPermission, {
|
|
270
|
+
camera: permission.camera,
|
|
271
|
+
microphone: permission.microphone,
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
190
276
|
it('submits feature Event successfully', () => {
|
|
191
277
|
webex.internal.newMetrics.submitFeatureEvent({
|
|
192
278
|
name: 'client.feature.meeting.summary',
|
|
@@ -196,12 +282,14 @@ describe('internal-plugin-metrics', () => {
|
|
|
196
282
|
payload: {
|
|
197
283
|
meetingSummaryInfo: {
|
|
198
284
|
featureName: 'syncSystemMuteStatus',
|
|
199
|
-
featureActions: [
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
285
|
+
featureActions: [
|
|
286
|
+
{
|
|
287
|
+
actionName: 'syncMeetingMicUnmuteStatusToSystem',
|
|
288
|
+
actionId: '14200',
|
|
289
|
+
isInitialValue: false,
|
|
290
|
+
clickCount: '1',
|
|
291
|
+
},
|
|
292
|
+
],
|
|
205
293
|
},
|
|
206
294
|
},
|
|
207
295
|
});
|
|
@@ -215,12 +303,14 @@ describe('internal-plugin-metrics', () => {
|
|
|
215
303
|
payload: {
|
|
216
304
|
meetingSummaryInfo: {
|
|
217
305
|
featureName: 'syncSystemMuteStatus',
|
|
218
|
-
featureActions: [
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
306
|
+
featureActions: [
|
|
307
|
+
{
|
|
308
|
+
actionName: 'syncMeetingMicUnmuteStatusToSystem',
|
|
309
|
+
actionId: '14200',
|
|
310
|
+
isInitialValue: false,
|
|
311
|
+
clickCount: '1',
|
|
312
|
+
},
|
|
313
|
+
],
|
|
224
314
|
},
|
|
225
315
|
},
|
|
226
316
|
options: {meetingId: '123'},
|
|
@@ -281,9 +371,9 @@ describe('internal-plugin-metrics', () => {
|
|
|
281
371
|
method: 'POST',
|
|
282
372
|
api: 'metrics',
|
|
283
373
|
resource: 'clientmetrics',
|
|
284
|
-
headers: {
|
|
374
|
+
headers: {'x-prelogin-userid': 'my-id'},
|
|
285
375
|
body: {},
|
|
286
|
-
qs: {
|
|
376
|
+
qs: {alias: true},
|
|
287
377
|
});
|
|
288
378
|
assert.calledWith(
|
|
289
379
|
webex.logger.log,
|
|
@@ -292,8 +382,8 @@ describe('internal-plugin-metrics', () => {
|
|
|
292
382
|
});
|
|
293
383
|
|
|
294
384
|
it('handles failed request correctly', async () => {
|
|
295
|
-
webex.request.rejects(new Error(
|
|
296
|
-
sinon.stub(Utils, 'generateCommonErrorMetadata').returns('formattedError')
|
|
385
|
+
webex.request.rejects(new Error('test error'));
|
|
386
|
+
sinon.stub(Utils, 'generateCommonErrorMetadata').returns('formattedError');
|
|
297
387
|
try {
|
|
298
388
|
await webex.internal.newMetrics.clientMetricsAliasUser({event: 'test'}, 'my-id');
|
|
299
389
|
} catch (err) {
|
|
@@ -375,19 +465,26 @@ describe('internal-plugin-metrics', () => {
|
|
|
375
465
|
sinon.assert.match(webex.internal.newMetrics.delaySubmitClientEvents, true);
|
|
376
466
|
sinon.assert.match(webex.internal.newMetrics.delayedClientEventsOverrides, {});
|
|
377
467
|
|
|
378
|
-
webex.internal.newMetrics.setDelaySubmitClientEvents({
|
|
468
|
+
webex.internal.newMetrics.setDelaySubmitClientEvents({
|
|
469
|
+
shouldDelay: false,
|
|
470
|
+
overrides: {foo: 'bar'},
|
|
471
|
+
});
|
|
379
472
|
|
|
380
|
-
assert.calledOnce(
|
|
381
|
-
|
|
473
|
+
assert.calledOnce(
|
|
474
|
+
webex.internal.newMetrics.callDiagnosticMetrics.submitDelayedClientEvents
|
|
475
|
+
);
|
|
476
|
+
assert.calledWith(
|
|
477
|
+
webex.internal.newMetrics.callDiagnosticMetrics.submitDelayedClientEvents,
|
|
478
|
+
{foo: 'bar'}
|
|
479
|
+
);
|
|
382
480
|
|
|
383
481
|
sinon.assert.match(webex.internal.newMetrics.delaySubmitClientEvents, false);
|
|
384
482
|
sinon.assert.match(webex.internal.newMetrics.delayedClientEventsOverrides, {foo: 'bar'});
|
|
385
483
|
});
|
|
386
484
|
|
|
387
485
|
it('should not fail when called before webex is ready', () => {
|
|
388
|
-
|
|
389
486
|
// Create mock
|
|
390
|
-
webex = mockWebex()
|
|
487
|
+
webex = mockWebex();
|
|
391
488
|
|
|
392
489
|
webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
|
|
393
490
|
webex.internal.newMetrics.callDiagnosticLatencies.clearTimestamps = sinon.stub();
|
|
@@ -402,7 +499,6 @@ describe('internal-plugin-metrics', () => {
|
|
|
402
499
|
webex.internal.newMetrics.setDelaySubmitClientEvents({shouldDelay: false});
|
|
403
500
|
// Webex is ready
|
|
404
501
|
webex.emit('ready');
|
|
405
|
-
|
|
406
502
|
});
|
|
407
503
|
});
|
|
408
504
|
});
|