Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/practice session - start/stop control API and displayhint #4008

Merged
merged 15 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/@webex/plugin-meetings/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,12 @@ export const DISPLAY_HINTS = {
STAGE_VIEW_INACTIVE: 'STAGE_VIEW_INACTIVE',
ENABLE_STAGE_VIEW: 'ENABLE_STAGE_VIEW',
DISABLE_STAGE_VIEW: 'DISABLE_STAGE_VIEW',

// Practice Session
PRACTICE_SESSION_ON: 'PRACTICE_SESSION_ON',
PRACTICE_SESSION_OFF: 'PRACTICE_SESSION_OFF',
SHOW_PRACTICE_SESSION_START: 'SHOW_PRACTICE_SESSION_START',
SHOW_PRACTICE_SESSION_STOP: 'SHOW_PRACTICE_SESSION_STOP',
};

export const INTERSTITIAL_DISPLAY_HINTS = [DISPLAY_HINTS.VOIP_IS_ENABLED];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ interface IInMeetingActions {
canShowStageView?: boolean;
canEnableStageView?: boolean;
canDisableStageView?: boolean;
isPracticeSessionOn?: boolean;
isPracticeSessionOff?: boolean;
canStartPracticeSession?: boolean;
canStopPracticeSession?: boolean;
}

/**
Expand Down Expand Up @@ -266,6 +270,15 @@ export default class InMeetingActions implements IInMeetingActions {
canEnableStageView = null;

canDisableStageView = null;

isPracticeSessionOn = null;

isPracticeSessionOff = null;

canStartPracticeSession = null;

canStopPracticeSession = null;

/**
* Returns all meeting action options
* @returns {Object}
Expand Down Expand Up @@ -354,6 +367,10 @@ export default class InMeetingActions implements IInMeetingActions {
canShowStageView: this.canShowStageView,
canEnableStageView: this.canEnableStageView,
canDisableStageView: this.canDisableStageView,
isPracticeSessionOn: this.isPracticeSessionOn,
isPracticeSessionOff: this.isPracticeSessionOff,
canStartPracticeSession: this.canStartPracticeSession,
canStopPracticeSession: this.canStopPracticeSession,
});

/**
Expand Down
18 changes: 18 additions & 0 deletions packages/@webex/plugin-meetings/src/meeting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2660,6 +2660,7 @@ export default class Meeting extends StatelessWebexPlugin {
});

this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_PRACTICE_SESSION_STATUS_UPDATED, ({state}) => {
this.webinar.updatePracticeSessionStatus(state);
Trigger.trigger(
this,
{file: 'meeting/index', function: 'setupLocusControlsListener'},
Expand Down Expand Up @@ -3516,6 +3517,7 @@ export default class Meeting extends StatelessWebexPlugin {
emailAddress: string;
email: string;
phoneNumber: string;
roles: Array<string>;
},
alertIfActive = true
) {
Expand Down Expand Up @@ -3915,6 +3917,22 @@ export default class Meeting extends StatelessWebexPlugin {
requiredHints: [DISPLAY_HINTS.DISABLE_STAGE_VIEW],
displayHints: this.userDisplayHints,
}),
isPracticeSessionOn: ControlsOptionsUtil.hasHints({
requiredHints: [DISPLAY_HINTS.PRACTICE_SESSION_ON],
displayHints: this.userDisplayHints,
}),
isPracticeSessionOff: ControlsOptionsUtil.hasHints({
requiredHints: [DISPLAY_HINTS.PRACTICE_SESSION_OFF],
displayHints: this.userDisplayHints,
}),
canStartPracticeSession: ControlsOptionsUtil.hasHints({
requiredHints: [DISPLAY_HINTS.SHOW_PRACTICE_SESSION_START],
displayHints: this.userDisplayHints,
}),
canStopPracticeSession: ControlsOptionsUtil.hasHints({
requiredHints: [DISPLAY_HINTS.SHOW_PRACTICE_SESSION_STOP],
displayHints: this.userDisplayHints,
}),
canShareFile:
(ControlsOptionsUtil.hasHints({
requiredHints: [DISPLAY_HINTS.SHARE_FILE],
Expand Down
1 change: 1 addition & 0 deletions packages/@webex/plugin-meetings/src/members/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const MembersUtil = {
{
address:
options.invitee.emailAddress || options.invitee.email || options.invitee.phoneNumber,
...(options.invitee.roles ? {roles: options.invitee.roles} : {}),
},
],
alertIfActive: options.alertIfActive,
Expand Down
48 changes: 40 additions & 8 deletions packages/@webex/plugin-meetings/src/webinar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
*/
import {WebexPlugin} from '@webex/webex-core';
import {get} from 'lodash';
import {MEETINGS, SELF_ROLES} from '../constants';
import {HTTP_VERBS, MEETINGS, SELF_ROLES} from '../constants';

import WebinarCollection from './collection';
import LoggerProxy from '../common/logs/logger-proxy';

/**
* @class Webinar
Expand All @@ -22,6 +23,7 @@ const Webinar = WebexPlugin.extend({
canManageWebcast: 'boolean', // appears the ability to manage webcast
selfIsPanelist: 'boolean', // self is panelist
selfIsAttendee: 'boolean', // self is attendee
practiceSessionEnabled: 'boolean', // practice session enabled
},

/**
Expand Down Expand Up @@ -59,18 +61,48 @@ const Webinar = WebexPlugin.extend({
* @returns {{isPromoted: boolean, isDemoted: boolean}} Role transition states
*/
updateRoleChanged(payload) {
const oldRoles = get(payload, 'oldRoles', []);
const newRoles = get(payload, 'newRoles', []);

const isPromoted =
get(payload, 'oldRoles', []).includes(SELF_ROLES.ATTENDEE) &&
get(payload, 'newRoles', []).includes(SELF_ROLES.PANELIST);
oldRoles.includes(SELF_ROLES.ATTENDEE) && newRoles.includes(SELF_ROLES.PANELIST);
const isDemoted =
get(payload, 'oldRoles', []).includes(SELF_ROLES.PANELIST) &&
get(payload, 'newRoles', []).includes(SELF_ROLES.ATTENDEE);
this.set('selfIsPanelist', get(payload, 'newRoles', []).includes(SELF_ROLES.PANELIST));
this.set('selfIsAttendee', get(payload, 'newRoles', []).includes(SELF_ROLES.ATTENDEE));
this.updateCanManageWebcast(get(payload, 'newRoles', []).includes(SELF_ROLES.MODERATOR));
oldRoles.includes(SELF_ROLES.PANELIST) && newRoles.includes(SELF_ROLES.ATTENDEE);
this.set('selfIsPanelist', newRoles.includes(SELF_ROLES.PANELIST));
this.set('selfIsAttendee', newRoles.includes(SELF_ROLES.ATTENDEE));
this.updateCanManageWebcast(newRoles.includes(SELF_ROLES.MODERATOR));

return {isPromoted, isDemoted};
},

/**
* start or stop practice session for webinar
* @param {boolean} enabled
* @returns {Promise}
*/
setPracticeSessionState(enabled) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: can you point me to where we are using this method, I was unable to find it

return this.request({
method: HTTP_VERBS.PATCH,
uri: `${this.locusUrl}/controls`,
body: {
practiceSession: {
enabled,
},
},
}).catch((error) => {
LoggerProxy.logger.error('Meeting:webinar#setPracticeSessionState failed', error);
throw error;
});
},

/**
* update practice session status
* @param {object} payload
* @returns {void}
*/
updatePracticeSessionStatus(payload) {
this.set('practiceSessionEnabled', payload.enabled);
},
});

export default Webinar;
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ describe('plugin-meetings', () => {
canShowStageView: null,
canEnableStageView: null,
canDisableStageView: null,
isPracticeSessionOn : null,
isPracticeSessionOff : null,
canStartPracticeSession: null,
canStopPracticeSession: null,

...expected,
};

Expand Down Expand Up @@ -181,7 +186,12 @@ describe('plugin-meetings', () => {
'canShowStageView',
'canEnableStageView',
'canDisableStageView',
].forEach((key) => {
'isPracticeSessionOn',
'isPracticeSessionOff',
'canStartPracticeSession',
'canStopPracticeSession',

].forEach((key) => {
it(`get and set for ${key} work as expected`, () => {
const inMeetingActions = new InMeetingActions();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9044,6 +9044,8 @@ describe('plugin-meetings', () => {
});

it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
meeting.webinar.updatePracticeSessionStatus = sinon.stub();

const state = {example: 'value'};

await meeting.locusInfo.emitScoped(
Expand All @@ -9052,6 +9054,7 @@ describe('plugin-meetings', () => {
{state}
);

assert.calledOnceWithExactly( meeting.webinar.updatePracticeSessionStatus, state);
assert.calledWith(
TriggerProxy.trigger,
meeting,
Expand Down
95 changes: 95 additions & 0 deletions packages/@webex/plugin-meetings/test/unit/spec/members/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,5 +262,100 @@ describe('plugin-meetings', () => {
testParams(false);
});
});

describe('#getAddMemberBody', () => {
it('returns the correct body with email address and roles', () => {
const options = {
invitee: {
emailAddress: '[email protected]',
roles: ['role1', 'role2'],
},
alertIfActive: true,
};

assert.deepEqual(MembersUtil.getAddMemberBody(options), {
invitees: [
{
address: '[email protected]',
roles: ['role1', 'role2'],
},
],
alertIfActive: true,
});
});

it('returns the correct body with phone number and no roles', () => {
const options = {
invitee: {
phoneNumber: '1234567890',
},
alertIfActive: false,
};

assert.deepEqual(MembersUtil.getAddMemberBody(options), {
invitees: [
{
address: '1234567890',
},
],
alertIfActive: false,
});
});

it('returns the correct body with fallback to email', () => {
const options = {
invitee: {
email: '[email protected]',
},
alertIfActive: true,
};

assert.deepEqual(MembersUtil.getAddMemberBody(options), {
invitees: [
{
address: '[email protected]',
},
],
alertIfActive: true,
});
});

it('handles missing `alertIfActive` gracefully', () => {
const options = {
invitee: {
emailAddress: '[email protected]',
roles: ['role1'],
},
};

assert.deepEqual(MembersUtil.getAddMemberBody(options), {
invitees: [
{
address: '[email protected]',
roles: ['role1'],
},
],
alertIfActive: undefined,
});
});

it('ignores roles if not provided', () => {
const options = {
invitee: {
emailAddress: '[email protected]',
},
alertIfActive: false,
};

assert.deepEqual(MembersUtil.getAddMemberBody(options), {
invitees: [
{
address: '[email protected]',
},
],
alertIfActive: false,
});
});
});
});
});
47 changes: 47 additions & 0 deletions packages/@webex/plugin-meetings/test/unit/spec/webinar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,52 @@ describe('plugin-meetings', () => {
});
});

describe("#setPracticeSessionState", () => {
[true, false].forEach((enabled) => {
it(`sends a PATCH request to ${enabled ? "enable" : "disable"} the practice session`, async () => {
const result = await webinar.setPracticeSessionState(enabled);
assert.calledOnce(webex.request);
assert.calledWith(webex.request, {
method: "PATCH",
uri: `${webinar.locusUrl}/controls`,
body: {
practiceSession: { enabled }
}
});
assert.equal(result, "REQUEST_RETURN_VALUE", "should return the resolved value from the request");
});
});

it('handles API call failures gracefully', async () => {
webex.request.rejects(new Error('API_ERROR'));
const errorLogger = sinon.stub(LoggerProxy.logger, 'error');

try {
await webinar.setPracticeSessionState(true);
assert.fail('setPracticeSessionState should throw an error');
} catch (error) {
assert.equal(error.message, 'API_ERROR', 'should throw the correct error');
assert.calledOnce(errorLogger);
assert.calledWith(errorLogger, 'Meeting:webinar#setPracticeSessionState failed', sinon.match.instanceOf(Error));
}

errorLogger.restore();
});
});

describe('#updatePracticeSessionStatus', () => {
it('sets PS state true', () => {
webinar.updatePracticeSessionStatus({enabled: true});

assert.equal(webinar.practiceSessionEnabled, true);
});
it('sets PS state true', () => {
webinar.updatePracticeSessionStatus({enabled: false});

assert.equal(webinar.practiceSessionEnabled, false);
});
});


Shreyas281299 marked this conversation as resolved.
Show resolved Hide resolved
})
})
Loading