@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.
@@ -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
@@ -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 join
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,
@@ -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 = {
@@ -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();