@webex/plugin-meetings 3.0.0-beta.67 → 3.0.0-beta.69
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/breakouts/breakout.js +50 -9
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +38 -4
- package/dist/breakouts/index.js.map +1 -1
- package/dist/constants.js +4 -2
- package/dist/constants.js.map +1 -1
- package/dist/locus-info/index.js +8 -0
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +20 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/meeting/index.js +11 -2
- package/dist/meeting/index.js.map +1 -1
- package/dist/metrics/config.js +3 -1
- package/dist/metrics/config.js.map +1 -1
- package/dist/metrics/index.js +5 -0
- package/dist/metrics/index.js.map +1 -1
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/metrics/config.d.ts +2 -0
- package/package.json +18 -18
- package/src/breakouts/README.md +6 -0
- package/src/breakouts/breakout.ts +27 -3
- package/src/breakouts/index.ts +32 -2
- package/src/constants.ts +2 -0
- package/src/locus-info/index.ts +12 -0
- package/src/locus-info/selfUtils.ts +27 -0
- package/src/meeting/index.ts +8 -2
- package/src/metrics/config.ts +2 -0
- package/src/metrics/index.ts +5 -0
- package/test/unit/spec/breakouts/breakout.ts +33 -2
- package/test/unit/spec/breakouts/index.ts +79 -0
- package/test/unit/spec/locus-info/index.js +21 -0
- package/test/unit/spec/locus-info/selfUtils.js +37 -0
- package/test/unit/spec/meeting/index.js +13 -0
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
AUDIO,
|
|
15
15
|
VIDEO,
|
|
16
16
|
MediaContent,
|
|
17
|
+
SELF_ROLES,
|
|
17
18
|
} from '../constants';
|
|
18
19
|
import ParameterError from '../common/errors/parameter';
|
|
19
20
|
|
|
@@ -105,6 +106,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
|
|
|
105
106
|
current
|
|
106
107
|
);
|
|
107
108
|
updates.moderatorChanged = SelfUtils.moderatorChanged(previous, current);
|
|
109
|
+
updates.isUpgradeToModeratorOrCohost = SelfUtils.isUpgradeToModeratorOrCohost(previous, current);
|
|
108
110
|
updates.isMediaInactiveOrReleased = SelfUtils.wasMediaInactiveOrReleased(previous, current);
|
|
109
111
|
updates.isUserObserving = SelfUtils.isDeviceObserving(previous, current);
|
|
110
112
|
updates.layoutChanged = SelfUtils.layoutChanged(previous, current);
|
|
@@ -334,6 +336,31 @@ SelfUtils.moderatorChanged = (oldSelf, changedSelf) => {
|
|
|
334
336
|
return oldSelf.moderator !== changedSelf.moderator;
|
|
335
337
|
};
|
|
336
338
|
|
|
339
|
+
/**
|
|
340
|
+
* @param {Object} oldSelf
|
|
341
|
+
* @param {Object} changedSelf
|
|
342
|
+
* @returns {Boolean}
|
|
343
|
+
* @throws {Error} if changed self was undefined
|
|
344
|
+
*/
|
|
345
|
+
SelfUtils.isUpgradeToModeratorOrCohost = (oldSelf, changedSelf) => {
|
|
346
|
+
if (!oldSelf) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
if (!changedSelf) {
|
|
350
|
+
throw new ParameterError(
|
|
351
|
+
'New self must be defined to determine if self transitioned moderator or cohost status.'
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
const isAttendeeOnly =
|
|
355
|
+
oldSelf.roles.includes(SELF_ROLES.ATTENDEE) &&
|
|
356
|
+
!oldSelf.roles.includes(SELF_ROLES.COHOST) &&
|
|
357
|
+
!oldSelf.roles.includes(SELF_ROLES.MODERATOR);
|
|
358
|
+
const isCohost = changedSelf.roles.includes(SELF_ROLES.COHOST);
|
|
359
|
+
const isModerator = changedSelf.roles.includes(SELF_ROLES.MODERATOR);
|
|
360
|
+
|
|
361
|
+
return isAttendeeOnly && (isCohost || isModerator);
|
|
362
|
+
};
|
|
363
|
+
|
|
337
364
|
/**
|
|
338
365
|
* @param {Object} oldSelf
|
|
339
366
|
* @param {Object} changedSelf
|
package/src/meeting/index.ts
CHANGED
|
@@ -553,7 +553,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
553
553
|
*/
|
|
554
554
|
this.id = uuid.v4();
|
|
555
555
|
/**
|
|
556
|
-
* Correlation ID used for network tracking of meeting
|
|
556
|
+
* Correlation ID used for network tracking of meeting
|
|
557
557
|
* @instance
|
|
558
558
|
* @type {String}
|
|
559
559
|
* @readonly
|
|
@@ -610,7 +610,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
610
610
|
* @memberof Meeting
|
|
611
611
|
*/
|
|
612
612
|
// @ts-ignore
|
|
613
|
-
this.breakouts = new Breakouts({}, {parent: this.webex});
|
|
613
|
+
this.breakouts = new Breakouts({meetingId: this.id}, {parent: this.webex});
|
|
614
614
|
/**
|
|
615
615
|
* helper class for managing receive slots (for multistream media connections)
|
|
616
616
|
*/
|
|
@@ -2581,6 +2581,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2581
2581
|
);
|
|
2582
2582
|
});
|
|
2583
2583
|
|
|
2584
|
+
// We need to reinitialize when user upgrades to host or cohost
|
|
2585
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MODERATOR_OR_COHOST_UPGRADE, (payload) => {
|
|
2586
|
+
this.breakouts.queryPreAssignments(payload);
|
|
2587
|
+
// ...
|
|
2588
|
+
});
|
|
2589
|
+
|
|
2584
2590
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_IS_SHARING_BLOCKED_CHANGE, (payload) => {
|
|
2585
2591
|
Trigger.trigger(
|
|
2586
2592
|
this,
|
package/src/metrics/config.ts
CHANGED
|
@@ -140,6 +140,8 @@ export const eventType = {
|
|
|
140
140
|
PSTN_AUDIO_ATTEMPT_START: 'client.pstnaudio.attempt.start',
|
|
141
141
|
PSTN_AUDIO_ATTEMPT_FINISH: 'client.pstnaudio.attempt.finish',
|
|
142
142
|
PSTN_AUDIO_ATTEMPT_SKIP: 'client.pstnaudio.attempt.skip',
|
|
143
|
+
MOVE_TO_BREAKOUT: 'client.breakout-session.move.request',
|
|
144
|
+
JOIN_BREAKOUT_RESPONSE: 'client.breakout-session.join.response',
|
|
143
145
|
};
|
|
144
146
|
|
|
145
147
|
export const error = {
|
package/src/metrics/index.ts
CHANGED
|
@@ -241,6 +241,11 @@ class Metrics {
|
|
|
241
241
|
if (options.isRoapCallEnabled) {
|
|
242
242
|
payload.event.isRoapCallEnabled = options.isRoapCallEnabled;
|
|
243
243
|
}
|
|
244
|
+
['breakoutMoveId', 'breakoutSessionId', 'breakoutGroupId'].forEach((item) => {
|
|
245
|
+
if (options[item]) {
|
|
246
|
+
payload.event.identifiers[item] = options[item];
|
|
247
|
+
}
|
|
248
|
+
});
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
return payload;
|
|
@@ -3,8 +3,10 @@ import Breakout from '@webex/plugin-meetings/src/breakouts/breakout';
|
|
|
3
3
|
import Breakouts from '@webex/plugin-meetings/src/breakouts';
|
|
4
4
|
import Members from '@webex/plugin-meetings/src/members';
|
|
5
5
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
6
|
+
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
6
7
|
import sinon from 'sinon';
|
|
7
|
-
|
|
8
|
+
import {eventType} from '../../../../src/metrics/config';
|
|
9
|
+
import uuid from 'uuid';
|
|
8
10
|
describe('plugin-meetings', () => {
|
|
9
11
|
describe('breakout', () => {
|
|
10
12
|
let webex;
|
|
@@ -22,6 +24,11 @@ describe('plugin-meetings', () => {
|
|
|
22
24
|
breakout.sessionId = 'sessionId';
|
|
23
25
|
breakout.sessionType = 'BREAKOUT';
|
|
24
26
|
breakout.url = 'url';
|
|
27
|
+
breakout.collection = {
|
|
28
|
+
parent: {
|
|
29
|
+
meetingId: 'activeMeetingId',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
25
32
|
webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
|
|
26
33
|
});
|
|
27
34
|
|
|
@@ -33,8 +40,8 @@ describe('plugin-meetings', () => {
|
|
|
33
40
|
|
|
34
41
|
describe('#join', () => {
|
|
35
42
|
it('makes the request as expected', async () => {
|
|
43
|
+
Metrics.postEvent = sinon.stub();
|
|
36
44
|
const result = await breakout.join();
|
|
37
|
-
|
|
38
45
|
assert.calledOnceWithExactly(webex.request, {
|
|
39
46
|
method: 'POST',
|
|
40
47
|
uri: 'url/move',
|
|
@@ -46,6 +53,30 @@ describe('plugin-meetings', () => {
|
|
|
46
53
|
|
|
47
54
|
assert.equal(result, 'REQUEST_RETURN_VALUE');
|
|
48
55
|
});
|
|
56
|
+
it('send metrics as expected', async () => {
|
|
57
|
+
Metrics.postEvent = sinon.stub();
|
|
58
|
+
uuid.v4 = sinon.stub().returns('breakoutMoveId');
|
|
59
|
+
await breakout.join();
|
|
60
|
+
assert.calledTwice(Metrics.postEvent);
|
|
61
|
+
assert.calledWithMatch(Metrics.postEvent, {
|
|
62
|
+
event: eventType.MOVE_TO_BREAKOUT,
|
|
63
|
+
meetingId: 'activeMeetingId',
|
|
64
|
+
data: {
|
|
65
|
+
breakoutMoveId: 'breakoutMoveId',
|
|
66
|
+
breakoutSessionId: 'sessionId',
|
|
67
|
+
breakoutGroupId: 'groupId',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
assert.calledWithMatch((Metrics.postEvent as any).secondCall, {
|
|
71
|
+
event: eventType.JOIN_BREAKOUT_RESPONSE,
|
|
72
|
+
meetingId: 'activeMeetingId',
|
|
73
|
+
data: {
|
|
74
|
+
breakoutMoveId: 'breakoutMoveId',
|
|
75
|
+
breakoutSessionId: 'sessionId',
|
|
76
|
+
breakoutGroupId: 'groupId',
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
49
80
|
});
|
|
50
81
|
|
|
51
82
|
describe('#leave', () => {
|
|
@@ -1007,6 +1007,85 @@ describe('plugin-meetings', () => {
|
|
|
1007
1007
|
});
|
|
1008
1008
|
});
|
|
1009
1009
|
|
|
1010
|
+
describe('queryPreAssignments', () => {
|
|
1011
|
+
it('makes the expected query', async () => {
|
|
1012
|
+
webex.request.returns(
|
|
1013
|
+
Promise.resolve({
|
|
1014
|
+
body: {
|
|
1015
|
+
"groups": [
|
|
1016
|
+
{
|
|
1017
|
+
"sessions": [
|
|
1018
|
+
{
|
|
1019
|
+
"name": "Breakout session 1",
|
|
1020
|
+
"assignedEmails": [
|
|
1021
|
+
"a@a.com",
|
|
1022
|
+
"b@b.com",
|
|
1023
|
+
"jial2@cisco.com"
|
|
1024
|
+
],
|
|
1025
|
+
"anyoneCanJoin": false
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
"name": "Breakout session 2",
|
|
1029
|
+
"anyoneCanJoin": false
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
"name": "Breakout session 3",
|
|
1033
|
+
"assignedEmails": [
|
|
1034
|
+
"c@c.com"
|
|
1035
|
+
],
|
|
1036
|
+
"anyoneCanJoin": false
|
|
1037
|
+
}
|
|
1038
|
+
],
|
|
1039
|
+
"unassignedInvitees": {
|
|
1040
|
+
"emails": [
|
|
1041
|
+
"d@d.com"
|
|
1042
|
+
]
|
|
1043
|
+
},
|
|
1044
|
+
"type": "BREAKOUT"
|
|
1045
|
+
}
|
|
1046
|
+
]
|
|
1047
|
+
}
|
|
1048
|
+
})
|
|
1049
|
+
);
|
|
1050
|
+
breakouts.shouldFetchPreassignments = false;
|
|
1051
|
+
const result = await breakouts.queryPreAssignments();
|
|
1052
|
+
const arg = webex.request.getCall(0).args[0];
|
|
1053
|
+
assert.equal(arg.uri, 'url/preassignments');
|
|
1054
|
+
assert.equal(breakouts.groups[0].unassignedInvitees.emails[0],'d@d.com');
|
|
1055
|
+
assert.equal(breakouts.groups[0].sessions[0].name,'Breakout session 1');
|
|
1056
|
+
assert.equal(breakouts.groups[0].sessions[0].anyoneCanJoin,false);
|
|
1057
|
+
assert.equal(breakouts.groups[0].sessions[0].assignedEmails.toString(), ["a@a.com", "b@b.com", "jial2@cisco.com"].toString());
|
|
1058
|
+
assert.equal(breakouts.groups[0].sessions[1].name,'Breakout session 2');
|
|
1059
|
+
assert.equal(breakouts.groups[0].sessions[1].anyoneCanJoin,false);
|
|
1060
|
+
assert.equal(breakouts.groups[0].sessions[1].assignedEmails, undefined);
|
|
1061
|
+
assert.equal(breakouts.groups[0].sessions[2].name,'Breakout session 3');
|
|
1062
|
+
assert.equal(breakouts.groups[0].sessions[2].anyoneCanJoin,false);
|
|
1063
|
+
assert.equal(breakouts.groups[0].sessions[2].assignedEmails[0], 'c@c.com');
|
|
1064
|
+
assert.equal(breakouts.groups[0].unassignedInvitees.emails[0],'d@d.com');
|
|
1065
|
+
assert.equal(breakouts.groups[0].type,'BREAKOUT');
|
|
1066
|
+
assert.equal(breakouts.shouldFetchPreassignments, true);
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
it('rejects when no pre-assignments created for this meeting', async () => {
|
|
1070
|
+
const response = {
|
|
1071
|
+
statusCode: 404,
|
|
1072
|
+
body: {
|
|
1073
|
+
errorCode: 201404004,
|
|
1074
|
+
message: 'No pre-assignments created for this meeting'
|
|
1075
|
+
},
|
|
1076
|
+
};
|
|
1077
|
+
webex.request.rejects(response);
|
|
1078
|
+
LoggerProxy.logger.error = sinon.stub();
|
|
1079
|
+
const result = await breakouts.queryPreAssignments();
|
|
1080
|
+
await testUtils.flushPromises();
|
|
1081
|
+
assert.calledOnceWithExactly(
|
|
1082
|
+
LoggerProxy.logger.error,
|
|
1083
|
+
'Meeting:breakouts#queryPreAssignments failed',
|
|
1084
|
+
response
|
|
1085
|
+
);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1010
1089
|
describe('#dynamicAssign', () => {
|
|
1011
1090
|
it('should make a PUT request with correct body and return the result', async () => {
|
|
1012
1091
|
breakouts.dynamicAssign = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
|
|
@@ -871,6 +871,27 @@ describe('plugin-meetings', () => {
|
|
|
871
871
|
);
|
|
872
872
|
});
|
|
873
873
|
|
|
874
|
+
it('should trigger upgradeToModeratorOrCohost for breakouts', () => {
|
|
875
|
+
|
|
876
|
+
locusInfo.self = self;
|
|
877
|
+
const upgradeToModeratorOrCohost = cloneDeep(self);
|
|
878
|
+
upgradeToModeratorOrCohost.roles = ['ATTENDEE','COHOST'];
|
|
879
|
+
|
|
880
|
+
locusInfo.webex.internal.device.url = self.deviceUrl;
|
|
881
|
+
locusInfo.emitScoped = sinon.stub();
|
|
882
|
+
locusInfo.updateSelf(upgradeToModeratorOrCohost, []);
|
|
883
|
+
|
|
884
|
+
assert.neverCalledWith(
|
|
885
|
+
locusInfo.emitScoped,
|
|
886
|
+
{
|
|
887
|
+
file: 'locus-info',
|
|
888
|
+
function: 'updateSelf',
|
|
889
|
+
},
|
|
890
|
+
LOCUSINFO.EVENTS.SELF_MODERATOR_OR_COHOST_UPGRADE,
|
|
891
|
+
self
|
|
892
|
+
);
|
|
893
|
+
});
|
|
894
|
+
|
|
874
895
|
it('should trigger SELF_REMOTE_MUTE_STATUS_UPDATED if muted and disallowUnmute changed', () => {
|
|
875
896
|
locusInfo.self = self;
|
|
876
897
|
const selfWithMutedByOthersAndDissalowUnmute = cloneDeep(self);
|
|
@@ -289,4 +289,41 @@ describe('plugin-meetings', () => {
|
|
|
289
289
|
);
|
|
290
290
|
});
|
|
291
291
|
});
|
|
292
|
+
|
|
293
|
+
describe('isUpgradeToModeratorOrCohost', () => {
|
|
294
|
+
it('returns true if changed', () => {
|
|
295
|
+
assert.equal(
|
|
296
|
+
SelfUtils.isUpgradeToModeratorOrCohost({roles: ['ATTENDEE']}, {roles: ['ATTENDEE','MODERATOR']}),
|
|
297
|
+
true
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('returns true if changed', () => {
|
|
302
|
+
assert.equal(
|
|
303
|
+
SelfUtils.isUpgradeToModeratorOrCohost({roles: ['ATTENDEE']}, {roles: ['ATTENDEE','COHOST']}),
|
|
304
|
+
true
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('returns false if changed', () => {
|
|
309
|
+
assert.equal(
|
|
310
|
+
SelfUtils.isUpgradeToModeratorOrCohost({roles: ['ATTENDEE','MODERATOR']}, {roles: ['ATTENDEE']}),
|
|
311
|
+
false
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('returns false if changed', () => {
|
|
316
|
+
assert.equal(
|
|
317
|
+
SelfUtils.isUpgradeToModeratorOrCohost({roles: ['ATTENDEE','COHOST']}, {roles: ['ATTENDEE']}),
|
|
318
|
+
false
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('returns false if changed', () => {
|
|
323
|
+
assert.equal(
|
|
324
|
+
SelfUtils.isUpgradeToModeratorOrCohost({roles: ['ATTENDEE','HOST','MODERATOR']}, {roles: ['ATTENDEE']}),
|
|
325
|
+
false
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
292
329
|
});
|
|
@@ -4518,6 +4518,19 @@ describe('plugin-meetings', () => {
|
|
|
4518
4518
|
});
|
|
4519
4519
|
});
|
|
4520
4520
|
|
|
4521
|
+
describe('#setUpBreakoutsPreAssignmentsListener', () => {
|
|
4522
|
+
it('listens to the self moderator or cohost upgrade event', () => {
|
|
4523
|
+
meeting.breakouts.queryPreAssignments = sinon.stub();
|
|
4524
|
+
const payload = 'payload';
|
|
4525
|
+
meeting.locusInfo.emit(
|
|
4526
|
+
{function: 'test', file: 'test'},
|
|
4527
|
+
'SELF_MODERATOR_OR_COHOST_UPGRADE',
|
|
4528
|
+
payload,
|
|
4529
|
+
);
|
|
4530
|
+
assert.calledOnceWithExactly(meeting.breakouts.queryPreAssignments, payload);
|
|
4531
|
+
});
|
|
4532
|
+
});
|
|
4533
|
+
|
|
4521
4534
|
describe('#setUpBreakoutsListener', () => {
|
|
4522
4535
|
it('listens to the closing event from breakouts and triggers the closing event', () => {
|
|
4523
4536
|
TriggerProxy.trigger.reset();
|