Merge remote-tracking branch 'upstream/v2.4.x-release' into improve-shortcut-test
This commit is contained in:
commit
cac21564a1
@ -15,7 +15,7 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handleCreateBreakoutRoomsCmdMsg(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to create breakout room for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId,
|
||||
|
@ -15,7 +15,7 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handleSetLockSettings(msg: ChangeLockSettingsInMeetingCmdMsg): Unit = {
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to change lock settings"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -12,7 +12,7 @@ trait ChangeUserRoleCmdMsgHdlr extends RightsManagementTrait {
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleChangeUserRoleCmdMsg(msg: ChangeUserRoleCmdMsg) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to change user role in meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -23,7 +23,7 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.VIEWER_LEVEL,
|
||||
liveMeeting.users2x,
|
||||
msg.header.userId
|
||||
)) {
|
||||
) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
|
||||
val reason = "No permission to eject user from meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -15,7 +15,7 @@ trait LogoutAndEndMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
val eventBus: InternalEventBus
|
||||
|
||||
def handleLogoutAndEndMeetingCmdMsg(msg: LogoutAndEndMeetingCmdMsg, state: MeetingState2x): Unit = {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to end meeting on logout."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -13,7 +13,7 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handleUpdateWebcamsOnlyForModeratorCmdMsg(msg: UpdateWebcamsOnlyForModeratorCmdMsg) {
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to change lock settings"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -12,7 +12,7 @@ trait MuteAllExceptPresentersCmdMsgHdlr extends RightsManagementTrait {
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleMuteAllExceptPresentersCmdMsg(msg: MuteAllExceptPresentersCmdMsg) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to mute all except presenters."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -13,7 +13,7 @@ trait MuteMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handleMuteMeetingCmdMsg(msg: MuteMeetingCmdMsg): Unit = {
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to mute meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -14,7 +14,7 @@ trait SetGuestPolicyMsgHdlr extends RightsManagementTrait {
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleSetGuestPolicyMsg(msg: SetGuestPolicyCmdMsg): Unit = {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to set guest policy in meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
|
@ -28,6 +28,14 @@ public class LearningDashboardService {
|
||||
private static Logger log = LoggerFactory.getLogger(LearningDashboardService.class);
|
||||
private static String learningDashboardFilesDir = "/var/bigbluebutton/learning-dashboard";
|
||||
|
||||
public File getJsonDataFile(String meetingId, String learningDashboardAccessToken) {
|
||||
File baseDir = new File(this.getDestinationBaseDirectoryName(meetingId,learningDashboardAccessToken));
|
||||
if (!baseDir.exists()) baseDir.mkdirs();
|
||||
|
||||
File jsonFile = new File(baseDir.getAbsolutePath() + File.separatorChar + "learning_dashboard_data.json");
|
||||
return jsonFile;
|
||||
}
|
||||
|
||||
public void writeJsonDataFile(String meetingId, String learningDashboardAccessToken, String activityJson) {
|
||||
|
||||
try {
|
||||
@ -36,10 +44,7 @@ public class LearningDashboardService {
|
||||
return;
|
||||
}
|
||||
|
||||
File baseDir = new File(this.getDestinationBaseDirectoryName(meetingId,learningDashboardAccessToken));
|
||||
if (!baseDir.exists()) baseDir.mkdirs();
|
||||
|
||||
File jsonFile = new File(baseDir.getAbsolutePath() + File.separatorChar + "learning_dashboard_data.json");
|
||||
File jsonFile = this.getJsonDataFile(meetingId,learningDashboardAccessToken);
|
||||
|
||||
FileOutputStream fileOutput = new FileOutputStream(jsonFile);
|
||||
fileOutput.write(activityJson.getBytes());
|
||||
|
@ -17,6 +17,7 @@ class App extends React.Component {
|
||||
tab: 'overview',
|
||||
meetingId: '',
|
||||
learningDashboardAccessToken: '',
|
||||
sessionToken: '',
|
||||
};
|
||||
}
|
||||
|
||||
@ -30,6 +31,7 @@ class App extends React.Component {
|
||||
setDashboardParams() {
|
||||
let learningDashboardAccessToken = '';
|
||||
let meetingId = '';
|
||||
let sessionToken = '';
|
||||
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
@ -38,6 +40,10 @@ class App extends React.Component {
|
||||
meetingId = params.meeting;
|
||||
}
|
||||
|
||||
if (typeof params.sessionToken !== 'undefined') {
|
||||
sessionToken = params.sessionToken;
|
||||
}
|
||||
|
||||
if (typeof params.report !== 'undefined') {
|
||||
learningDashboardAccessToken = params.report;
|
||||
} else {
|
||||
@ -56,11 +62,12 @@ class App extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ learningDashboardAccessToken, meetingId }, this.fetchActivitiesJson);
|
||||
this.setState({ learningDashboardAccessToken, meetingId, sessionToken },
|
||||
this.fetchActivitiesJson);
|
||||
}
|
||||
|
||||
fetchActivitiesJson() {
|
||||
const { learningDashboardAccessToken, meetingId } = this.state;
|
||||
const { learningDashboardAccessToken, meetingId, sessionToken } = this.state;
|
||||
|
||||
if (learningDashboardAccessToken !== '') {
|
||||
fetch(`${meetingId}/${learningDashboardAccessToken}/learning_dashboard_data.json`)
|
||||
@ -71,6 +78,24 @@ class App extends React.Component {
|
||||
}).catch(() => {
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
} else if (sessionToken !== '') {
|
||||
const url = new URL('/bigbluebutton/api/learningDashboard', window.location);
|
||||
fetch(`${url}?sessionToken=${sessionToken}`)
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
if (json.response.returncode === 'SUCCESS') {
|
||||
const jsonData = JSON.parse(json.response.data);
|
||||
this.setState({ activitiesJson: jsonData, loading: false });
|
||||
document.title = `Learning Dashboard - ${jsonData.name}`;
|
||||
} else {
|
||||
// When meeting is ended the sessionToken stop working, check for new cookies
|
||||
this.setDashboardParams();
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
} else {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
@ -78,7 +103,7 @@ class App extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
activitiesJson, tab, learningDashboardAccessToken, loading,
|
||||
activitiesJson, tab, meetingId, learningDashboardAccessToken, sessionToken, loading,
|
||||
} = this.state;
|
||||
const { intl } = this.props;
|
||||
|
||||
@ -162,19 +187,59 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
function getErrorMessage() {
|
||||
if (learningDashboardAccessToken === '') {
|
||||
if (learningDashboardAccessToken === '' && sessionToken === '') {
|
||||
return intl.formatMessage({ id: 'app.learningDashboard.errors.invalidToken', defaultMessage: 'Invalid session token' });
|
||||
}
|
||||
return intl.formatMessage({ id: 'app.learningDashboard.errors.dataUnavailable', defaultMessage: 'Data is no longer available' });
|
||||
|
||||
if (activitiesJson === {} || typeof activitiesJson.name === 'undefined') {
|
||||
return intl.formatMessage({ id: 'app.learningDashboard.errors.dataUnavailable', defaultMessage: 'Data is no longer available' });
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
if (loading === false && typeof activitiesJson.name === 'undefined') return <ErrorMessage message={getErrorMessage()} />;
|
||||
function copyPublicLink() {
|
||||
let url = window.location.href.split('?')[0];
|
||||
url += `?meeting=${meetingId}&report=${learningDashboardAccessToken}&lang=${intl.locale}`;
|
||||
navigator.clipboard.writeText(url);
|
||||
const copiedMessage = intl.formatMessage({ id: 'app.learningDashboard.linkCopied', defaultMessage: 'Link successfully copied' });
|
||||
alert(copiedMessage);
|
||||
}
|
||||
|
||||
if (loading === false && getErrorMessage() !== '') return <ErrorMessage message={getErrorMessage()} />;
|
||||
|
||||
return (
|
||||
<div className="mx-10">
|
||||
<div className="flex items-start justify-between pb-3">
|
||||
<h1 className="mt-3 text-2xl font-semibold whitespace-nowrap inline-block">
|
||||
<FormattedMessage id="app.learningDashboard.dashboardTitle" defaultMessage="Learning Dashboard" />
|
||||
|
||||
{
|
||||
learningDashboardAccessToken !== ''
|
||||
? (
|
||||
<button type="button" onClick={() => { copyPublicLink(); }} className="text-sm font-medium text-blue-500 ease-out" name="teste">
|
||||
(
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4 inline"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<FormattedMessage id="app.learningDashboard.shareButton" defaultMessage="Share with others" />
|
||||
)
|
||||
</button>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<br />
|
||||
<span className="text-sm font-medium">{activitiesJson.name || ''}</span>
|
||||
</h1>
|
||||
|
@ -60,7 +60,7 @@ class Dashboard extends React.Component {
|
||||
setRtl() {
|
||||
const { intlLocale } = this.state;
|
||||
|
||||
if (RTL_LANGUAGES.includes(intlLocale)) {
|
||||
if (RTL_LANGUAGES.includes(intlLocale.substring(0, 2))) {
|
||||
document.body.parentNode.setAttribute('dir', 'rtl');
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
git clone --branch v2.5.2 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
|
||||
git clone --branch v2.6.0-beta.5 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
|
||||
|
@ -1 +1 @@
|
||||
BIGBLUEBUTTON_RELEASE=2.4-rc-2
|
||||
BIGBLUEBUTTON_RELEASE=2.4-rc-3
|
||||
|
@ -34,15 +34,23 @@ log_history=28
|
||||
find /var/bigbluebutton/ -maxdepth 1 -type d -name "*-*" -mtime +$history -exec rm -rf '{}' +
|
||||
|
||||
#
|
||||
# Delete streams in kurento older than N days
|
||||
# Delete streams from Kurento and mediasoup older than N days
|
||||
#
|
||||
for app in recordings screenshare; do
|
||||
app_dir=/var/kurento/$app
|
||||
if [[ -d $app_dir ]]; then
|
||||
find $app_dir -name "*.mkv" -o -name "*.webm" -mtime +$history -delete
|
||||
find $app_dir -type d -empty -mtime +$history -exec rmdir '{}' +
|
||||
fi
|
||||
done
|
||||
kurento_dir=/var/kurento/
|
||||
mediasoup_dir=/var/mediasoup/
|
||||
|
||||
remove_stale_sfu_raw_files() {
|
||||
for app in recordings screenshare; do
|
||||
app_dir="${1}${app}"
|
||||
if [[ -d $app_dir ]]; then
|
||||
find "$app_dir" -name "*.mkv" -o -name "*.webm" -mtime +"$history" -delete
|
||||
find "$app_dir" -type d -empty -mtime +"$history" -exec rmdir '{}' +
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
remove_stale_sfu_raw_files "$kurento_dir"
|
||||
remove_stale_sfu_raw_files "$mediasoup_dir"
|
||||
|
||||
#
|
||||
# Delete FreeSWITCH wav/opus recordings older than N days
|
||||
|
@ -75,8 +75,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
textarea::-webkit-input-placeholder,
|
||||
input::-webkit-input-placeholder {
|
||||
::-webkit-input-placeholder {
|
||||
color: var(--palette-placeholder-text);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:-moz-placeholder, /* Firefox 4 to 18 */
|
||||
::-moz-placeholder { /* Firefox 19+ */
|
||||
color: var(--palette-placeholder-text);
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -23,9 +23,18 @@ export default function handleMeetingEnd({ header, body }) {
|
||||
}
|
||||
};
|
||||
|
||||
Meetings.update({ meetingId },
|
||||
{ $set: { meetingEnded: true, meetingEndedBy: userId, meetingEndedReason: reason } },
|
||||
(err, num) => { cb(err, num, 'Meeting'); });
|
||||
Meetings.find({ meetingId }).forEach((doc) => {
|
||||
Meetings.update({ meetingId },
|
||||
{
|
||||
$set: {
|
||||
meetingEnded: true,
|
||||
meetingEndedBy: userId,
|
||||
meetingEndedReason: reason,
|
||||
learningDashboardAccessToken: doc.password.learningDashboardAccessToken,
|
||||
},
|
||||
},
|
||||
(err, num) => { cb(err, num, 'Meeting'); });
|
||||
});
|
||||
|
||||
Breakouts.update({ parentMeetingId: meetingId },
|
||||
{ $set: { meetingEnded: true } },
|
||||
|
@ -43,10 +43,8 @@ function meetings(role) {
|
||||
},
|
||||
};
|
||||
|
||||
if (User.role === ROLE_MODERATOR) {
|
||||
delete options.fields.password;
|
||||
options.fields['password.viewerPass'] = false;
|
||||
options.fields['password.moderatorPass'] = false;
|
||||
if (User.role !== ROLE_MODERATOR) {
|
||||
options.fields.learningDashboardAccessToken = false;
|
||||
}
|
||||
|
||||
return Meetings.find(selector, options);
|
||||
|
@ -323,30 +323,6 @@ WebApp.connectHandlers.use('/guestWait', (req, res) => {
|
||||
res.end(guestWaitHtml);
|
||||
});
|
||||
|
||||
// WASM endpoint to be used to fetch the .wasm models for camera effects
|
||||
// (blur, virtual background).
|
||||
// See: /imports/ui/services/virtual-backgrounds/
|
||||
WebApp.connectHandlers.use('/wasm', (req, res) => {
|
||||
const pathname = req._parsedUrl.pathname;
|
||||
let file = "";
|
||||
let hasError = false;
|
||||
try {
|
||||
file = Assets.getBinary(pathname.substr(1, pathname.length-1));
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
Logger.warn(`Could not find WASM file: ${error}`);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/wasm');
|
||||
if (hasError) {
|
||||
res.writeHead(404);
|
||||
} else {
|
||||
res.writeHead(200);
|
||||
}
|
||||
res.end(file);
|
||||
});
|
||||
|
||||
|
||||
export const eventEmitter = Redis.emitter;
|
||||
|
||||
export const redisPubSub = Redis;
|
||||
|
@ -227,7 +227,8 @@ class BreakoutRoom extends PureComponent {
|
||||
componentDidUpdate(prevProps, prevstate) {
|
||||
if (this.listOfUsers) {
|
||||
for (let i = 0; i < this.listOfUsers.children.length; i += 1) {
|
||||
const roomList = this.listOfUsers.children[i].getElementsByTagName('div')[0];
|
||||
const roomWrapperChildren = this.listOfUsers.children[i].getElementsByTagName('div');
|
||||
const roomList = roomWrapperChildren[roomWrapperChildren.length > 1 ? 1 : 0];
|
||||
roomList.addEventListener('keydown', this.handleMoveEvent, true);
|
||||
}
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ class BreakoutJoinConfirmation extends Component {
|
||||
))
|
||||
}
|
||||
</select>
|
||||
{ waiting ? <span>{intl.formatMessage(intlMessages.generatingURL)}</span> : null}
|
||||
{ waiting ? <span data-test="labelGeneratingURL">{intl.formatMessage(intlMessages.generatingURL)}</span> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -536,6 +536,7 @@ class BreakoutRoom extends PureComponent {
|
||||
size="lg"
|
||||
label={intl.formatMessage(intlMessages.endAllBreakouts)}
|
||||
className={styles.endButton}
|
||||
data-test="endBreakoutRoomsButton"
|
||||
onClick={() => {
|
||||
this.closePanel();
|
||||
endAllBreakouts();
|
||||
|
@ -20,23 +20,45 @@ const isModerator = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const getLearningDashboardAccessToken = () => ((
|
||||
const isLearningDashboardEnabled = () => (((
|
||||
Meetings.findOne(
|
||||
{ meetingId: Auth.meetingID },
|
||||
{
|
||||
fields: { 'password.learningDashboardAccessToken': 1 },
|
||||
fields: { 'meetingProp.learningDashboardEnabled': 1 },
|
||||
},
|
||||
) || {}).password || {}).learningDashboardAccessToken || null;
|
||||
) || {}).meetingProp || {}).learningDashboardEnabled || false);
|
||||
|
||||
const getLearningDashboardAccessToken = () => ((
|
||||
Meetings.findOne(
|
||||
{ meetingId: Auth.meetingID, learningDashboardAccessToken: { $exists: true } },
|
||||
{
|
||||
fields: { learningDashboardAccessToken: 1 },
|
||||
},
|
||||
) || {}).learningDashboardAccessToken || null);
|
||||
|
||||
const setLearningDashboardCookie = () => {
|
||||
const learningDashboardAccessToken = getLearningDashboardAccessToken();
|
||||
if (learningDashboardAccessToken !== null) {
|
||||
const cookieExpiresDate = new Date();
|
||||
cookieExpiresDate.setTime(cookieExpiresDate.getTime() + (3600000 * 24 * 30)); // keep cookie 30d
|
||||
document.cookie = `learningDashboardAccessToken-${Auth.meetingID}=${getLearningDashboardAccessToken()}; expires=${cookieExpiresDate.toGMTString()}; path=/`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const openLearningDashboardUrl = (lang) => {
|
||||
const cookieExpiresDate = new Date();
|
||||
cookieExpiresDate.setTime(cookieExpiresDate.getTime() + (3600000 * 24 * 30)); // keep cookie 30d
|
||||
document.cookie = `learningDashboardAccessToken-${Auth.meetingID}=${getLearningDashboardAccessToken()}; expires=${cookieExpiresDate.toGMTString()}; path=/`;
|
||||
window.open(`/learning-dashboard/?meeting=${Auth.meetingID}&lang=${lang}`, '_blank');
|
||||
if (getLearningDashboardAccessToken() && setLearningDashboardCookie()) {
|
||||
window.open(`/learning-dashboard/?meeting=${Auth.meetingID}&lang=${lang}`, '_blank');
|
||||
} else {
|
||||
window.open(`/learning-dashboard/?meeting=${Auth.meetingID}&sessionToken=${Auth.sessionToken}&lang=${lang}`, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
isModerator,
|
||||
isLearningDashboardEnabled,
|
||||
getLearningDashboardAccessToken,
|
||||
setLearningDashboardCookie,
|
||||
openLearningDashboardUrl,
|
||||
};
|
||||
|
@ -266,7 +266,9 @@ class MeetingEnded extends PureComponent {
|
||||
<div>
|
||||
{
|
||||
LearningDashboardService.isModerator()
|
||||
&& LearningDashboardService.getLearningDashboardAccessToken() != null
|
||||
&& LearningDashboardService.isLearningDashboardEnabled() === true
|
||||
// Always set cookie in case Dashboard is already opened
|
||||
&& LearningDashboardService.setLearningDashboardCookie() === true
|
||||
? (
|
||||
<div className={styles.text}>
|
||||
<Button
|
||||
|
@ -4,6 +4,7 @@ import _ from 'lodash';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import { styles } from './styles';
|
||||
import Service from './service';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
wasTalking: {
|
||||
@ -22,6 +23,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.actionsBar.muteLabel',
|
||||
description: 'indicator mute label for moderators',
|
||||
},
|
||||
moreThanMaxIndicatorsTalking: {
|
||||
id: 'app.talkingIndicator.moreThanMaxIndicatorsTalking',
|
||||
description: 'indicator label for all users who is talking but not visible',
|
||||
},
|
||||
moreThanMaxIndicatorsWereTalking: {
|
||||
id: 'app.talkingIndicator.moreThanMaxIndicatorsWereTalking',
|
||||
description: 'indicator label for all users who is not talking but not visible',
|
||||
},
|
||||
});
|
||||
|
||||
class TalkingIndicator extends PureComponent {
|
||||
@ -39,6 +48,7 @@ class TalkingIndicator extends PureComponent {
|
||||
amIModerator,
|
||||
sidebarNavigationIsOpen,
|
||||
sidebarContentIsOpen,
|
||||
moreThanMaxIndicators,
|
||||
} = this.props;
|
||||
if (!talkers) return null;
|
||||
|
||||
@ -76,8 +86,7 @@ class TalkingIndicator extends PureComponent {
|
||||
label={callerName}
|
||||
tooltipLabel={!muted && amIModerator
|
||||
? `${intl.formatMessage(intlMessages.muteLabel)} ${callerName}`
|
||||
: null
|
||||
}
|
||||
: null}
|
||||
data-test={talking ? 'isTalking' : 'wasTalking'}
|
||||
aria-label={ariaLabel}
|
||||
aria-describedby={talking ? 'description' : null}
|
||||
@ -93,16 +102,55 @@ class TalkingIndicator extends PureComponent {
|
||||
<div id="description" className={styles.hidden}>
|
||||
{`${intl.formatMessage(intlMessages.ariaMuteDesc)}`}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
) : null}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
const maxIndicator = () => {
|
||||
if (!moreThanMaxIndicators) return null;
|
||||
|
||||
const nobodyTalking = Service.nobodyTalking(talkers);
|
||||
|
||||
const style = {
|
||||
[styles.talker]: true,
|
||||
[styles.spoke]: nobodyTalking,
|
||||
// [styles.muted]: false,
|
||||
[styles.mobileHide]: sidebarNavigationIsOpen
|
||||
&& sidebarContentIsOpen,
|
||||
};
|
||||
|
||||
const { moreThanMaxIndicatorsTalking, moreThanMaxIndicatorsWereTalking } = intlMessages;
|
||||
|
||||
const ariaLabel = intl.formatMessage(nobodyTalking
|
||||
? moreThanMaxIndicatorsWereTalking : moreThanMaxIndicatorsTalking, {
|
||||
0: Object.keys(talkers).length,
|
||||
});
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={_.uniqueId('_has__More_')}
|
||||
className={cx(style)}
|
||||
onClick={() => {}} // maybe add a dropdown to show the rest of the users
|
||||
label="..."
|
||||
tooltipLabel={ariaLabel}
|
||||
aria-label={ariaLabel}
|
||||
color="primary"
|
||||
size="sm"
|
||||
style={{
|
||||
backgroundColor: '#4a148c',
|
||||
border: 'solid 2px #4a148c',
|
||||
cursor: 'default',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.isTalkingWrapper}>
|
||||
<div className={styles.speaking}>
|
||||
{talkingUserElements}
|
||||
{maxIndicator()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -6,12 +6,12 @@ import { debounce } from 'lodash';
|
||||
import TalkingIndicator from './component';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import { meetingIsBreakout } from '/imports/ui/components/app/service';
|
||||
import Service from './service';
|
||||
import LayoutContext from '../../layout/context';
|
||||
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const { enableTalkingIndicator } = APP_CONFIG;
|
||||
const TALKING_INDICATOR_MUTE_INTERVAL = 500;
|
||||
const TALKING_INDICATORS_MAX = 8;
|
||||
|
||||
const TalkingIndicatorContainer = (props) => {
|
||||
if (!enableTalkingIndicator) return null;
|
||||
@ -50,10 +50,18 @@ export default withTracker(() => {
|
||||
muted: 1,
|
||||
intId: 1,
|
||||
},
|
||||
}).fetch().sort(Service.sortVoiceUsers);
|
||||
sort: {
|
||||
startTime: 1,
|
||||
},
|
||||
limit: TALKING_INDICATORS_MAX + 1,
|
||||
}).fetch();
|
||||
|
||||
if (usersTalking) {
|
||||
for (let i = 0; i < usersTalking.length; i += 1) {
|
||||
const maxNumberVoiceUsersNotification = usersTalking.length < TALKING_INDICATORS_MAX
|
||||
? usersTalking.length
|
||||
: TALKING_INDICATORS_MAX;
|
||||
|
||||
for (let i = 0; i < maxNumberVoiceUsersNotification; i += 1) {
|
||||
const {
|
||||
callerName, talking, color, voiceUserId, muted, intId,
|
||||
} = usersTalking[i];
|
||||
@ -82,5 +90,6 @@ export default withTracker(() => {
|
||||
talkers,
|
||||
muteUser,
|
||||
isBreakoutRoom: meetingIsBreakout(),
|
||||
moreThanMaxIndicators: usersTalking.length > TALKING_INDICATORS_MAX,
|
||||
};
|
||||
})(TalkingIndicatorContainer);
|
||||
|
@ -1,14 +1,8 @@
|
||||
const sortByStartTime = (a, b) => {
|
||||
if (a.startTime < b.startTime) return -1;
|
||||
if (a.startTime > b.startTime) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
const sortVoiceUsers = (a, b) => {
|
||||
const sort = sortByStartTime(a, b);
|
||||
return sort;
|
||||
const nobodyTalking = (talkers) => {
|
||||
const values = Object.values(talkers);
|
||||
return values.every(({ talking }) => talking === false);
|
||||
};
|
||||
|
||||
export default {
|
||||
sortVoiceUsers,
|
||||
nobodyTalking,
|
||||
};
|
||||
|
@ -36,6 +36,8 @@
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
max-height: var(--talker-padding-xl);
|
||||
scrollbar-width: 0; // firefox
|
||||
scrollbar-color: transparent;
|
||||
}
|
||||
|
||||
.speaking::-webkit-scrollbar {
|
||||
|
@ -326,7 +326,7 @@ class ApplicationMenu extends BaseMenu {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label htmlFor="layoutList" className={styles.label}>
|
||||
{intl.formatMessage(intlMessages.layoutOptionLabel)}
|
||||
@ -412,7 +412,7 @@ class ApplicationMenu extends BaseMenu {
|
||||
{this.renderPaginationToggle()}
|
||||
|
||||
<div className={styles.row}>
|
||||
<div className={styles.col} aria-hidden="true">
|
||||
<div className={styles.col}>
|
||||
<div className={styles.formElement}>
|
||||
<label
|
||||
className={styles.label}
|
||||
|
@ -66,6 +66,8 @@ class UserParticipants extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.getElementById('user-list-virtualized-scroll')?.getElementsByTagName('div')[0]?.firstElementChild?.setAttribute('aria-label', 'Users list');
|
||||
|
||||
const { compact } = this.props;
|
||||
if (!compact) {
|
||||
this.refScrollContainer.addEventListener(
|
||||
@ -220,6 +222,8 @@ class UserParticipants extends Component {
|
||||
}
|
||||
<div
|
||||
id={'user-list-virtualized-scroll'}
|
||||
aria-label="Users list"
|
||||
role="region"
|
||||
className={styles.virtulizedScrollableList}
|
||||
tabIndex={0}
|
||||
ref={(ref) => {
|
||||
|
@ -214,7 +214,7 @@ class UserOptions extends PureComponent {
|
||||
hasBreakoutRoom,
|
||||
isBreakoutEnabled,
|
||||
getUsersNotAssigned,
|
||||
learningDashboardAccessToken,
|
||||
learningDashboardEnabled,
|
||||
openLearningDashboardUrl,
|
||||
amIModerator,
|
||||
users,
|
||||
@ -326,7 +326,7 @@ class UserOptions extends PureComponent {
|
||||
});
|
||||
}
|
||||
if (amIModerator) {
|
||||
if (learningDashboardAccessToken != null) {
|
||||
if (learningDashboardEnabled === true) {
|
||||
this.menuItems.push({
|
||||
icon: 'multi_whiteboard',
|
||||
iconRight: 'popout_window',
|
||||
@ -336,7 +336,7 @@ class UserOptions extends PureComponent {
|
||||
onClick: () => { openLearningDashboardUrl(locale); },
|
||||
dividerTop: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ const UserOptionsContainer = withTracker((props) => {
|
||||
guestPolicy: WaitingUsersService.getGuestPolicy(),
|
||||
isMeteorConnected: Meteor.status().connected,
|
||||
meetingName: getMeetingName(),
|
||||
learningDashboardAccessToken: LearningDashboardService.getLearningDashboardAccessToken(),
|
||||
learningDashboardEnabled: LearningDashboardService.isLearningDashboardEnabled(),
|
||||
openLearningDashboardUrl: LearningDashboardService.openLearningDashboardUrl,
|
||||
dynamicGuestPolicy,
|
||||
};
|
||||
|
@ -98,8 +98,8 @@
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
height: 14rem;
|
||||
max-height: 40vh;
|
||||
min-height: 14rem;
|
||||
max-height: 50vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
|
@ -2,8 +2,7 @@ import React, { useContext } from 'react';
|
||||
|
||||
import { withModalMounter } from '/imports/ui/components/modal/service';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import MediaService, { getSwapLayout, } from '/imports/ui/components/media/service';
|
||||
import MediaService, { getSwapLayout } from '/imports/ui/components/media/service';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import breakoutService from '/imports/ui/components/breakout-room/service';
|
||||
import VideoService from '/imports/ui/components/video-provider/service';
|
||||
@ -16,11 +15,12 @@ const WebcamContainer = ({
|
||||
audioModalIsOpen,
|
||||
swapLayout,
|
||||
usersVideo,
|
||||
disableVideo,
|
||||
}) => {
|
||||
const layoutContext = useContext(LayoutContext);
|
||||
const { layoutContextState, layoutContextDispatch } = layoutContext;
|
||||
const { fullscreen, output, input, isRTL } = layoutContextState;
|
||||
const {
|
||||
fullscreen, output, input, isRTL,
|
||||
} = layoutContextState;
|
||||
const { cameraDock, presentation } = output;
|
||||
const { cameraDock: cameraDockInput } = input;
|
||||
const { cameraOptimalGridSize } = cameraDockInput;
|
||||
@ -30,8 +30,7 @@ const WebcamContainer = ({
|
||||
const { users } = usingUsersContext;
|
||||
const currentUser = users[Auth.meetingID][Auth.userID];
|
||||
|
||||
return !disableVideo
|
||||
&& !audioModalIsOpen
|
||||
return !audioModalIsOpen
|
||||
&& usersVideo.length > 0
|
||||
? (
|
||||
<WebcamComponent
|
||||
@ -54,8 +53,6 @@ const WebcamContainer = ({
|
||||
let userWasInBreakout = false;
|
||||
|
||||
export default withModalMounter(withTracker(() => {
|
||||
const { dataSaving } = Settings;
|
||||
const { viewParticipantsWebcams } = dataSaving;
|
||||
const { current_presentation: hasPresentation } = MediaService.getPresentationInfo();
|
||||
const data = {
|
||||
audioModalIsOpen: Session.get('audioModalIsOpen'),
|
||||
@ -90,7 +87,6 @@ export default withModalMounter(withTracker(() => {
|
||||
const { streams: usersVideo } = VideoService.getVideoStreams();
|
||||
data.usersVideo = usersVideo;
|
||||
data.swapLayout = getSwapLayout() || !hasPresentation;
|
||||
data.disableVideo = !viewParticipantsWebcams;
|
||||
|
||||
if (data.swapLayout) {
|
||||
data.floatingOverlay = true;
|
||||
|
@ -335,7 +335,7 @@
|
||||
|
||||
<body>
|
||||
<div id="content">
|
||||
<h1 id="heading">Guest Lobby</h1>
|
||||
<h1 id="heading">BigBlueButton - Guest Lobby</h1>
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
|
@ -411,6 +411,8 @@
|
||||
"app.switch.offLabel": "OFF",
|
||||
"app.talkingIndicator.ariaMuteDesc" : "Select to mute user",
|
||||
"app.talkingIndicator.isTalking" : "{0} is talking",
|
||||
"app.talkingIndicator.moreThanMaxIndicatorsTalking" : "{0}+ are talking",
|
||||
"app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ were talking",
|
||||
"app.talkingIndicator.wasTalking" : "{0} stopped talking",
|
||||
"app.actionsBar.actionsDropdown.actionsLabel": "Actions",
|
||||
"app.actionsBar.actionsDropdown.presentationLabel": "Manage presentations",
|
||||
@ -910,6 +912,8 @@
|
||||
"playback.player.video.wrapper.aria": "Video area",
|
||||
"app.learningDashboard.dashboardTitle": "Learning Dashboard",
|
||||
"app.learningDashboard.user": "User",
|
||||
"app.learningDashboard.shareButton": "Share with others",
|
||||
"app.learningDashboard.shareLinkCopied": "Link successfully copied",
|
||||
"app.learningDashboard.indicators.meetingStatusEnded": "Ended",
|
||||
"app.learningDashboard.indicators.meetingStatusActive": "Active",
|
||||
"app.learningDashboard.indicators.usersOnline": "Active Users",
|
||||
|
@ -1,6 +1,5 @@
|
||||
const Page = require('../core/page');
|
||||
const e = require('../core/elements');
|
||||
const { checkElement } = require('../core/util');
|
||||
const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
|
||||
|
||||
class Create {
|
||||
@ -62,7 +61,7 @@ class Create {
|
||||
// Check if Breakoutrooms have been created
|
||||
async testCreatedBreakout(testName) {
|
||||
try {
|
||||
const resp = await this.modPage1.page.evaluate(checkElement, e.breakoutRoomsItem);
|
||||
const resp = await this.modPage1.hasElement(e.breakoutRoomsItem);
|
||||
if (resp === true) {
|
||||
await this.modPage1.screenshot(`${testName}`, `05-page01-success-${testName}`);
|
||||
|
||||
@ -88,8 +87,7 @@ class Create {
|
||||
await this.modPage2.waitAndClick(e.chatButton);
|
||||
await this.modPage2.waitAndClick(e.breakoutRoomsItem);
|
||||
|
||||
await this.modPage2.waitAndClick(e.generateRoom1);
|
||||
await this.modPage2.waitAndClick(e.joinGeneratedRoom1);
|
||||
await this.modPage2.waitAndClick(e.askJoinRoom1);
|
||||
await this.modPage2.waitForSelector(e.alreadyConnected, ELEMENT_WAIT_LONGER_TIME);
|
||||
|
||||
const breakoutModPage2 = await this.modPage2.getLastTargetPage();
|
||||
@ -107,8 +105,7 @@ class Create {
|
||||
} else if (testName === 'joinBreakoutroomsWithVideo') {
|
||||
await this.modPage2.init(true, true, testName, 'Moderator2', this.modPage1.meetingId);
|
||||
await this.modPage2.waitAndClick(e.breakoutRoomsButton);
|
||||
await this.modPage2.waitAndClick(e.generateRoom1);
|
||||
await this.modPage2.waitAndClick(e.joinGeneratedRoom1);
|
||||
await this.modPage2.waitAndClick(e.askJoinRoom1);
|
||||
await this.modPage2.waitForSelector(e.alreadyConnected);
|
||||
|
||||
const breakoutModPage2 = await this.modPage2.getLastTargetPage();
|
||||
@ -116,18 +113,15 @@ class Create {
|
||||
|
||||
await breakoutModPage2.bringToFront();
|
||||
await breakoutModPage2.closeAudioModal();
|
||||
await breakoutModPage2.waitAndClick(e.joinVideo);
|
||||
const parsedSettings = await this.modPage2.getSettingsYaml();
|
||||
const videoPreviewTimeout = parseInt(parsedSettings.public.kurento.gUMTimeout);
|
||||
await breakoutModPage2.waitAndClick(e.videoPreview, videoPreviewTimeout);
|
||||
await breakoutModPage2.waitAndClick(e.startSharingWebcam);
|
||||
await breakoutModPage2.shareWebcam(true, videoPreviewTimeout);
|
||||
|
||||
await breakoutModPage2.screenshot(testName, '00-breakout-page03-user-joined-with-webcam-before-check');
|
||||
} else if (testName === 'joinBreakoutroomsAndShareScreen') {
|
||||
await this.modPage2.init(true, true, testName, 'Moderator2', this.modPage1.meetingId);
|
||||
await this.modPage2.waitAndClick(e.breakoutRoomsButton);
|
||||
await this.modPage2.waitAndClick(e.generateRoom1);
|
||||
await this.modPage2.waitAndClick(e.joinGeneratedRoom1);
|
||||
await this.modPage2.waitAndClick(e.askJoinRoom1);
|
||||
await this.modPage2.waitForSelector(e.alreadyConnected);
|
||||
const breakoutModPage2 = await this.modPage2.getLastTargetPage();
|
||||
|
||||
|
@ -10,6 +10,7 @@ exports.CLIENT_RECONNECTION_TIMEOUT = 120000;
|
||||
// STRESS TESTS VARS
|
||||
exports.JOIN_AS_MODERATOR_TEST_ROUNDS = 100;
|
||||
exports.MAX_JOIN_AS_MODERATOR_FAIL_RATE = 0.05;
|
||||
exports.BREAKOUT_ROOM_INVITATION_TEST_ROUNDS = 20;
|
||||
|
||||
// MEDIA CONNECTION TIMEOUTS
|
||||
exports.VIDEO_LOADING_WAIT_TIME = 15000;
|
||||
|
@ -49,9 +49,11 @@ exports.alreadyConnected = 'span[class^="alreadyConnected--"]';
|
||||
exports.breakoutJoin = '[data-test="breakoutJoin"]';
|
||||
exports.userJoined = 'div[aria-label^="Moderator3"]';
|
||||
exports.breakoutRoomsButton = 'div[aria-label="Breakout Rooms"]';
|
||||
exports.generateRoom1 = 'button[aria-label="Generate URL Room 1"]';
|
||||
exports.joinGeneratedRoom1 = 'button[aria-label="Generated Room 1"]';
|
||||
exports.askJoinRoom1 = 'button[aria-label="Ask to join Room 1"]';
|
||||
exports.joinRoom1 = 'button[aria-label="Join room Room 1"]';
|
||||
exports.allowChoiceRoom = 'input[id="freeJoinCheckbox"]';
|
||||
exports.labelGeneratingURL = 'span[data-test="labelGeneratingURL"]';
|
||||
exports.endBreakoutRoomsButton = 'button[data-test="endBreakoutRoomsButton"]';
|
||||
|
||||
// Chat
|
||||
exports.chatButton = 'div[data-test="chatButton"]';
|
||||
@ -204,6 +206,8 @@ exports.chatPanel = 'section[data-test="chatPanel"]';
|
||||
exports.userListPanel = 'div[data-test="userListPanel"]';
|
||||
exports.multiWhiteboardTool = 'span[data-test="multiWhiteboardTool"]'
|
||||
exports.connectionStatusBtn = 'button[data-test="connectionStatusButton"]';
|
||||
exports.connectionDataContainer = '[class^=networkDataContainer--]';
|
||||
exports.connectionNetwordData = '[class^=networkData--]';
|
||||
|
||||
// Webcam
|
||||
exports.joinVideo = 'button[data-test="joinVideo"]';
|
||||
|
@ -8,7 +8,7 @@ const path = require('path');
|
||||
const PuppeteerVideoRecorder = require('puppeteer-video-recorder');
|
||||
const helper = require('./helper');
|
||||
const params = require('./params');
|
||||
const { ELEMENT_WAIT_TIME } = require('./constants');
|
||||
const { ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME } = require('./constants');
|
||||
const { getElementLength } = require('./util');
|
||||
const e = require('./elements');
|
||||
const { NETWORK_PRESETS } = require('./profiles');
|
||||
@ -104,6 +104,17 @@ class Page {
|
||||
await this.waitForSelector(e.isTalking);
|
||||
}
|
||||
|
||||
async shareWebcam(shouldConfirmSharing, videoPreviewTimeout = ELEMENT_WAIT_TIME) {
|
||||
await this.waitAndClick(e.joinVideo);
|
||||
if (shouldConfirmSharing) {
|
||||
await this.waitForSelector(e.videoPreview, videoPreviewTimeout);
|
||||
await this.waitAndClick(e.startSharingWebcam);
|
||||
}
|
||||
await this.waitForSelector(e.webcamConnecting);
|
||||
await this.waitForSelector(e.webcamVideo, VIDEO_LOADING_WAIT_TIME);
|
||||
await this.waitForSelector(e.leaveVideo, VIDEO_LOADING_WAIT_TIME);
|
||||
}
|
||||
|
||||
// Joining audio with microphone
|
||||
async joinMicrophoneWithoutEchoTest() {
|
||||
await this.waitAndClick(e.joinAudio);
|
||||
@ -214,16 +225,6 @@ class Page {
|
||||
}
|
||||
}
|
||||
|
||||
async isNotVisible(element, timeout = ELEMENT_WAIT_TIME) {
|
||||
try {
|
||||
await this.hasElement(element, false, timeout);
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// async emulateMobile(userAgent) {
|
||||
// await this.page.setUserAgent(userAgent);
|
||||
// }
|
||||
@ -243,7 +244,7 @@ class Page {
|
||||
}
|
||||
}
|
||||
|
||||
async hasElement(element, visible = false, timeout = ELEMENT_WAIT_TIME) {
|
||||
async hasElement(element, visible = true, timeout = ELEMENT_WAIT_TIME) {
|
||||
try {
|
||||
await this.page.waitForSelector(element, { visible, timeout });
|
||||
return true;
|
||||
|
@ -3,9 +3,8 @@ exports.listenOnlyMode = 'userdata-bbb_listen_only_mode=false';
|
||||
exports.forceListenOnly = 'userdata-bbb_force_listen_only=true';
|
||||
exports.skipCheck = 'userdata-bbb_skip_check_audio=true';
|
||||
exports.skipCheckOnFirstJoin = 'userdata-bbb_skip_check_audio_on_first_join=true';
|
||||
const docTitle = 'puppeteer';
|
||||
exports.docTitle = docTitle;
|
||||
exports.clientTitle = `userdata-bbb_client_title=${docTitle}`;
|
||||
exports.docTitle = 'puppeteer';
|
||||
exports.clientTitle = `userdata-bbb_client_title=${this.docTitle}`;
|
||||
exports.askForFeedbackOnLogout = 'userdata-bbb_ask_for_feedback_on_logout=true';
|
||||
exports.displayBrandingArea = 'userdata-bbb_display_branding_area=true';
|
||||
exports.logo = 'logo=https://bigbluebutton.org/wp-content/themes/bigbluebutton/library/images/bigbluebutton-logo.png';
|
||||
|
@ -3,7 +3,7 @@ const e = require('../core/elements');
|
||||
const c = require('./constants');
|
||||
const util = require('./util');
|
||||
const { VIDEO_LOADING_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
|
||||
const { checkElementLengthEqualTo, checkElementLengthDifferentTo } = require('../core/util');
|
||||
const { checkElementLengthEqualTo, checkElement } = require('../core/util');
|
||||
|
||||
class CustomParameters {
|
||||
constructor() {
|
||||
@ -25,7 +25,7 @@ class CustomParameters {
|
||||
await this.page1.startRecording(testName);
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
await this.page1.waitForSelector(e.chatMessages);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, e.audioModal, 0);
|
||||
const resp = await this.page1.wasRemoved(e.audioModal);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -96,7 +96,7 @@ class CustomParameters {
|
||||
await this.page1.screenshot(`${testName}`, `02-${testName}`);
|
||||
await this.page1.waitForElementHandleToBeRemoved(e.connectingStatus, ELEMENT_WAIT_LONGER_TIME);
|
||||
await this.page1.screenshot(`${testName}`, `03-${testName}`);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, e.echoYesButton, 0);
|
||||
const resp = await this.page1.wasRemoved(e.echoYesButton);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `04-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -118,13 +118,13 @@ class CustomParameters {
|
||||
await this.page1.startRecording(testName);
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
await this.page1.waitAndClick(e.microphoneButton);
|
||||
const firstCheck = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.connecting, 0);
|
||||
const firstCheck = await this.page1.hasElement(e.connecting);
|
||||
await this.page1.screenshot(`${testName}`, `02-${testName}`);
|
||||
await this.page1.leaveAudio();
|
||||
await this.page1.screenshot(`${testName}`, `03-${testName}`);
|
||||
await this.page1.waitAndClick(e.joinAudio);
|
||||
await this.page1.waitAndClick(e.microphoneButton);
|
||||
const secondCheck = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.connectingToEchoTest, 0);
|
||||
const secondCheck = await this.page1.hasElement(e.connectingToEchoTest);
|
||||
|
||||
if (firstCheck !== secondCheck) {
|
||||
await this.page1.screenshot(`${testName}`, `04-fail-${testName}`);
|
||||
@ -175,7 +175,7 @@ class CustomParameters {
|
||||
await this.page1.waitForSelector(e.meetingEndedModal);
|
||||
await this.page1.screenshot(`${testName}`, `04-${testName}`);
|
||||
await this.page1.logger('audio modal closed');
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.rating, 0);
|
||||
const resp = await this.page1.hasElement(e.rating);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `05-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -199,7 +199,7 @@ class CustomParameters {
|
||||
await this.page1.screenshot(`${testName}`, `02-${testName}`);
|
||||
await this.page1.logger('audio modal closed');
|
||||
await this.page1.waitForSelector(e.userListContent);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.brandingAreaLogo, 0);
|
||||
const resp = await this.page1.hasElement(e.brandingAreaLogo);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -253,7 +253,7 @@ class CustomParameters {
|
||||
await this.page1.init(true, true, testName, 'Moderator', undefined, customParameter);
|
||||
await this.page1.startRecording(testName);
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, e.startScreenSharing, 0);
|
||||
const resp = await this.page1.wasRemoved(e.startScreenSharing);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -274,7 +274,7 @@ class CustomParameters {
|
||||
await this.page1.init(true, true, testName, 'Moderator', undefined, customParameter);
|
||||
await this.page1.startRecording(testName);
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, e.joinVideo, 0);
|
||||
const resp = await this.page1.wasRemoved(e.joinVideo);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -457,7 +457,10 @@ class CustomParameters {
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
await this.page1.waitForSelector(e.actions);
|
||||
await this.page1.screenshot(`${testName}`, `02-${testName}`);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.defaultContent, 0);
|
||||
const checkPresentationButton = await this.page1.page.evaluate(checkElement, e.restorePresentation);
|
||||
const checkPresentationPlaceholder = await this.page1.page.evaluate(checkElement, e.presentationPlaceholder);
|
||||
const resp = !(checkPresentationButton || checkPresentationPlaceholder);
|
||||
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -480,7 +483,7 @@ class CustomParameters {
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
await this.page1.waitForSelector(e.actions);
|
||||
await this.page1.screenshot(`${testName}`, `02-${testName}`);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.notificationBar, 0);
|
||||
const resp = await this.page1.hasElement(e.notificationBar);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -525,7 +528,7 @@ class CustomParameters {
|
||||
await this.page1.startRecording(testName);
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
await this.page1.waitForSelector(e.actions);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.restorePresentation, 0) && await this.page1.page.evaluate(checkElementLengthDifferentTo, e.defaultContent, 0);
|
||||
const resp = await this.page1.hasElement(e.restorePresentation) && await this.page1.hasElement(e.defaultContent);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -592,14 +595,13 @@ class CustomParameters {
|
||||
await this.page1.screenshot(`${testName}`, `05-page1-${testName}`);
|
||||
await this.page2.screenshot(`${testName}`, `06-page2-${testName}`);
|
||||
|
||||
const test = await this.page2.page.evaluate(checkElementLengthDifferentTo, e.restorePresentation, 0);
|
||||
const test = await this.page2.page.evaluate(checkElement, e.restorePresentation);
|
||||
const resp = (zoomInCase && zoomOutCase && pollCase && previousSlideCase && nextSlideCase && annotationCase && test);
|
||||
if (resp) {
|
||||
await this.page2.screenshot(`${testName}`, `07-page2-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
return false;
|
||||
}
|
||||
await this.page2.page.evaluate(checkElementLengthEqualTo, e.restorePresentation, 0);
|
||||
await this.page2.screenshot(`${testName}`, `07-page2-success-${testName}`);
|
||||
await this.page1.logger(testName, ' passed');
|
||||
|
||||
@ -624,7 +626,7 @@ class CustomParameters {
|
||||
await this.page1.screenshot(`${testName}`, `02-page1-${testName}`);
|
||||
await this.page2.screenshot(`${testName}`, `03-page2-${testName}`);
|
||||
|
||||
const test = await this.page2.page.evaluate(checkElementLengthDifferentTo, e.restorePresentation, 0);
|
||||
const test = await this.page2.page.evaluate(checkElement, e.restorePresentation);
|
||||
if (pollCase && test) {
|
||||
await this.page2.screenshot(`${testName}`, `04-page2-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
@ -688,31 +690,22 @@ class CustomParameters {
|
||||
await this.page1.init(true, true, testName, 'Moderator1', undefined, customParameter);
|
||||
await this.page1.startRecording(testName);
|
||||
await this.page1.screenshot(`${testName}`, `01-${testName}`);
|
||||
await this.page1.waitAndClick(e.joinVideo);
|
||||
const firstCheck = await this.page1.page.evaluate(checkElementLengthEqualTo, e.webcamSettingsModal, 0);
|
||||
await this.page1.shareWebcam(false);
|
||||
|
||||
await this.page1.waitAndClick(e.leaveVideo, VIDEO_LOADING_WAIT_TIME);
|
||||
await this.page1.waitForElementHandleToBeRemoved(e.webcamVideo, ELEMENT_WAIT_LONGER_TIME);
|
||||
await this.page1.waitForElementHandleToBeRemoved(e.leaveVideo, ELEMENT_WAIT_LONGER_TIME);
|
||||
|
||||
await this.page1.waitAndClick(e.joinVideo);
|
||||
const parsedSettings = await this.page1.getSettingsYaml();
|
||||
const videoPreviewTimeout = parseInt(parsedSettings.public.kurento.gUMTimeout);
|
||||
await this.page1.waitForSelector(e.videoPreview, videoPreviewTimeout);
|
||||
await this.page1.waitForSelector(e.startSharingWebcam);
|
||||
const secondCheck = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.webcamSettingsModal, 0);
|
||||
await this.page1.waitAndClick(e.startSharingWebcam);
|
||||
await this.page1.waitForSelector(e.webcamConnecting);
|
||||
await this.page1.shareWebcam(true, videoPreviewTimeout);
|
||||
|
||||
if (firstCheck !== secondCheck) {
|
||||
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
return false;
|
||||
}
|
||||
await this.page1.screenshot(`${testName}`, `02-success-${testName}`);
|
||||
await this.page1.logger(testName, ' passed');
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
|
||||
await this.page1.logger(err);
|
||||
return false;
|
||||
}
|
||||
@ -726,7 +719,7 @@ class CustomParameters {
|
||||
await this.page1.waitAndClick(e.joinVideo);
|
||||
await this.page1.waitForSelector(e.webcamMirroredVideoPreview);
|
||||
await this.page1.waitAndClick(e.startSharingWebcam);
|
||||
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, e.webcamMirroredVideoContainer, 0);
|
||||
const resp = await this.page1.hasElement(e.webcamMirroredVideoContainer);
|
||||
if (!resp) {
|
||||
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
|
||||
await this.page1.logger(testName, ' failed');
|
||||
|
@ -116,7 +116,7 @@ class Presentation {
|
||||
await this.userPage.screenshot(testName, `3-userPage-after-allow-download-and-save-[${this.modPage.meetingId}]`);
|
||||
await this.userPage.waitForSelector(e.toastDownload);
|
||||
// check download button in presentation after ALLOW it - should be true
|
||||
const hasPresentationDownloadBtnAfterAllow = await this.userPage.page.evaluate(checkElement, e.presentationDownloadBtn);
|
||||
const hasPresentationDownloadBtnAfterAllow = await this.userPage.hasElement(e.presentationDownloadBtn);
|
||||
|
||||
// disallow the presentation download
|
||||
await this.modPage.waitAndClick(e.actions);
|
||||
|
@ -2,11 +2,11 @@ const Page = require('../core/page');
|
||||
const e = require('../core/elements');
|
||||
const c = require('../core/constants');
|
||||
const util = require('./util');
|
||||
const { checkElementLengthEqualTo } = require('../core/util');
|
||||
|
||||
class Stress extends Page {
|
||||
class Stress {
|
||||
constructor() {
|
||||
super();
|
||||
this.modPage = new Page();
|
||||
this.userPages = [];
|
||||
}
|
||||
|
||||
async moderatorAsPresenter(testName) {
|
||||
@ -14,26 +14,97 @@ class Stress extends Page {
|
||||
const maxFailRate = c.JOIN_AS_MODERATOR_TEST_ROUNDS * c.MAX_JOIN_AS_MODERATOR_FAIL_RATE;
|
||||
let failureCount = 0;
|
||||
for (let i = 1; i <= c.JOIN_AS_MODERATOR_TEST_ROUNDS; i++) {
|
||||
await this.init(true, true, testName, `Moderator-${i}`);
|
||||
await this.waitForSelector(e.userAvatar);
|
||||
const hasPresenterClass = await this.page.evaluate(util.checkIncludeClass, e.userAvatar, e.presenterClassName);
|
||||
await this.waitAndClick(e.actions);
|
||||
const canStartPoll = await this.page.evaluate(checkElementLengthEqualTo, e.polling, 1);
|
||||
await this.modPage.init(true, true, testName, `Moderator-${i}`);
|
||||
await this.modPage.waitForSelector(e.userAvatar);
|
||||
const hasPresenterClass = await this.modPage.page.evaluate(util.checkIncludeClass, e.userAvatar, e.presenterClassName);
|
||||
await this.modPage.waitAndClick(e.actions);
|
||||
const canStartPoll = await this.modPage.hasElement(e.polling);
|
||||
if (!hasPresenterClass || !canStartPoll) {
|
||||
failureCount++;
|
||||
await this.screenshot(testName, `loop-${i}-failure-${testName}`);
|
||||
await this.modPage.screenshot(testName, `loop-${i}-failure-${testName}`);
|
||||
}
|
||||
await this.close();
|
||||
await this.logger(`Loop ${i} of ${c.JOIN_AS_MODERATOR_TEST_ROUNDS} completed`);
|
||||
await this.modPage.close();
|
||||
await this.modPage.logger(`Loop ${i} of ${c.JOIN_AS_MODERATOR_TEST_ROUNDS} completed`);
|
||||
if (failureCount > maxFailRate) return false;
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.close();
|
||||
this.logger(err);
|
||||
await this.modPage.logger(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async breakoutRoomInvitation(testName) {
|
||||
try {
|
||||
await this.modPage.init(true, true, testName, 'Moderator');
|
||||
for (let i = 1; i <= c.BREAKOUT_ROOM_INVITATION_TEST_ROUNDS; i++) {
|
||||
const userName = `User-${i}`;
|
||||
const userPage = new Page();
|
||||
await userPage.init(false, true, testName, userName, this.modPage.meetingId);
|
||||
await userPage.logger(`${userName} joined`);
|
||||
this.userPages.push(userPage);
|
||||
}
|
||||
|
||||
// Create breakout rooms with the allow choice option enabled
|
||||
await this.modPage.bringToFront();
|
||||
await this.modPage.waitAndClick(e.manageUsers);
|
||||
await this.modPage.waitAndClick(e.createBreakoutRooms);
|
||||
await this.modPage.waitAndClick(e.allowChoiceRoom);
|
||||
await this.modPage.screenshot(testName, '01-modPage-before-create-breakout-rooms-allowing-choice');
|
||||
await this.modPage.waitAndClick(e.modalConfirmButton);
|
||||
|
||||
for (const page of this.userPages) {
|
||||
await page.bringToFront();
|
||||
const firstCheck = await page.hasElement(e.modalConfirmButton, c.ELEMENT_WAIT_LONGER_TIME);
|
||||
const secondCheck = await page.wasRemoved(e.labelGeneratingURL, c.ELEMENT_WAIT_LONGER_TIME);
|
||||
|
||||
if (!firstCheck || !secondCheck) {
|
||||
await page.screenshot(testName, `${page.effectiveParams.fullName}-breakout-modal-failed`);
|
||||
return false;
|
||||
}
|
||||
await page.screenshot(testName, `${page.effectiveParams.fullName}-breakout-modal-allowing-choice-success`);
|
||||
}
|
||||
|
||||
// End breakout rooms
|
||||
await this.modPage.bringToFront();
|
||||
await this.modPage.waitAndClick(e.breakoutRoomsItem);
|
||||
await this.modPage.waitAndClick(e.endBreakoutRoomsButton);
|
||||
await this.modPage.closeAudioModal();
|
||||
|
||||
// Create breakout rooms with the allow choice option NOT enabled (randomly assign)
|
||||
await this.modPage.waitAndClick(e.manageUsers);
|
||||
await this.modPage.waitAndClick(e.createBreakoutRooms);
|
||||
await this.modPage.waitAndClick(e.randomlyAssign);
|
||||
await this.modPage.screenshot(testName, '02-modPage-before-create-breakout-rooms-not-allowing-choice');
|
||||
await this.modPage.waitAndClick(e.modalConfirmButton);
|
||||
|
||||
for (const page of this.userPages) {
|
||||
await page.bringToFront();
|
||||
const check = await page.hasElement(e.modalConfirmButton);
|
||||
|
||||
if (!check) {
|
||||
await page.screenshot(testName, `${page.effectiveParams.fullName}-breakout-modal-not-allowing-choose-failed`);
|
||||
return false;
|
||||
}
|
||||
await page.screenshot(testName, `${page.effectiveParams.fullName}-breakout-modal-not-allowing-choose-success`);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.modPage.logger(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async closeUserPages() {
|
||||
for (const page of this.userPages) {
|
||||
try {
|
||||
await page.close();
|
||||
} catch (err) {
|
||||
await this.modPage.logger(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = Stress;
|
@ -7,11 +7,31 @@ const stressTest = () => {
|
||||
let response;
|
||||
try {
|
||||
const testName = 'firstModeratorAsPresenter';
|
||||
await test.logger('begin of ', testName);
|
||||
await test.modPage.logger('begin of ', testName);
|
||||
response = await test.moderatorAsPresenter(testName);
|
||||
await test.logger('end of ', testName);
|
||||
await test.modPage.logger('end of ', testName);
|
||||
} catch (err) {
|
||||
await test.logger(err);
|
||||
await test.modPage.logger(err);
|
||||
} finally {
|
||||
await test.modPage.close();
|
||||
}
|
||||
expect(response).toBe(true);
|
||||
});
|
||||
|
||||
// Check that all users invited to a breakout room can join it
|
||||
test('All users must receive breakout room invitations', async () => {
|
||||
const test = new Stress();
|
||||
let response;
|
||||
try {
|
||||
const testName = 'breakoutRoomInvitation';
|
||||
await test.modPage.logger('begin of ', testName);
|
||||
response = await test.breakoutRoomInvitation(testName);
|
||||
await test.modPage.logger('end of ', testName);
|
||||
} catch (err) {
|
||||
await test.modPage.logger(err);
|
||||
} finally {
|
||||
await test.modPage.close();
|
||||
await test.closeUserPages();
|
||||
}
|
||||
expect(response).toBe(true);
|
||||
});
|
||||
|
@ -3,7 +3,6 @@ const { exec } = require("child_process");
|
||||
const { CLIENT_RECONNECTION_TIMEOUT } = require('../core/constants'); // core constants (Timeouts vars imported)
|
||||
const { sleep } = require('../core/helper');
|
||||
const e = require('../core/elements');
|
||||
const { checkElementLengthDifferentTo } = require('../core/util');
|
||||
|
||||
class Trigger extends Page {
|
||||
constructor() {
|
||||
@ -30,7 +29,7 @@ class Trigger extends Page {
|
||||
await sleep(3000);
|
||||
await this.screenshot(`${testName}`, `03-after-meteor-reconnection-[${this.meetingId}]`);
|
||||
|
||||
const findUnauthorized = await this.page.evaluate(checkElementLengthDifferentTo, e.unauthorized, 0) === true;
|
||||
const findUnauthorized = await this.hasElement(e.unauthorized);
|
||||
await this.logger('Check if Unauthorized message appears => ', findUnauthorized);
|
||||
return meteorStatusConfirm && getAudioButton && findUnauthorized;
|
||||
} catch (err) {
|
||||
@ -84,7 +83,7 @@ class Trigger extends Page {
|
||||
}, e.joinAudio)
|
||||
await this.logger('Check if Connections Buttons are disabled => ', getAudioButton);
|
||||
await sleep(3000);
|
||||
const findUnauthorized = await this.page.evaluate(checkElementLengthDifferentTo, e.unauthorized, 0) === true;
|
||||
const findUnauthorized = await this.hasElement(e.unauthorized);
|
||||
await this.logger('Check if Unauthorized message appears => ', findUnauthorized);
|
||||
return meteorStatusConfirm && getAudioButton && findUnauthorized;
|
||||
} catch (err) {
|
||||
|
@ -2,8 +2,8 @@ const Page = require('../core/page');
|
||||
const util = require('../chat/util');
|
||||
const utilUser = require('./util');
|
||||
const e = require('../core/elements');
|
||||
const { ELEMENT_WAIT_TIME } = require('../core/constants');
|
||||
const { getElementLength, checkElementLengthEqualTo, checkElementLengthDifferentTo } = require('../core/util');
|
||||
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
|
||||
const { getElementLength, checkElementLengthEqualTo } = require('../core/util');
|
||||
|
||||
class MultiUsers {
|
||||
constructor() {
|
||||
@ -293,7 +293,7 @@ class MultiUsers {
|
||||
await this.page2.close();
|
||||
await utilUser.connectionStatus(this.page1);
|
||||
const connectionStatusItemEmpty = await this.page1.wasRemoved(e.connectionStatusItemEmpty);
|
||||
const connectionStatusOfflineUser = await this.page1.hasElement(e.connectionStatusOfflineUser, true);
|
||||
const connectionStatusOfflineUser = await this.page1.hasElement(e.connectionStatusOfflineUser, true, ELEMENT_WAIT_LONGER_TIME);
|
||||
|
||||
return connectionStatusItemEmpty && connectionStatusOfflineUser;
|
||||
} catch (err) {
|
||||
@ -313,13 +313,58 @@ class MultiUsers {
|
||||
}
|
||||
}
|
||||
|
||||
async usersConnectionStatus(testName) {
|
||||
try {
|
||||
await this.page1.shareWebcam(true);
|
||||
await this.page1.screenshot(testName, '01-page1-after-share-webcam');
|
||||
await this.initUserPage(false, testName);
|
||||
await this.userPage.joinMicrophone();
|
||||
await this.userPage.screenshot(testName, '02-userPage-after-join-microhpone');
|
||||
await this.userPage.shareWebcam(true);
|
||||
await this.userPage.screenshot(testName, '03-userPage-after-share-webcam');
|
||||
await this.userPage.waitAndClick(e.connectionStatusBtn);
|
||||
try {
|
||||
await this.userPage.page.waitForFunction(utilUser.checkNetworkStatus, { timeout: ELEMENT_WAIT_TIME },
|
||||
e.connectionDataContainer, e.connectionNetwordData
|
||||
);
|
||||
await this.userPage.screenshot(testName, '04-connection-network-success');
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.userPage.screenshot(testName, '04-connection-network-failed');
|
||||
this.userPage.logger(err);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
this.page1.logger(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async disableWebcamsFromConnectionStatus() {
|
||||
try {
|
||||
await this.page1.shareWebcam(true, ELEMENT_WAIT_LONGER_TIME);
|
||||
await this.page2.shareWebcam(true, ELEMENT_WAIT_LONGER_TIME);
|
||||
await utilUser.connectionStatus(this.page1);
|
||||
await this.page1.waitAndClickElement(e.dataSavingWebcams);
|
||||
await this.page1.waitAndClickElement(e.closeConnectionStatusModal);
|
||||
await this.page1.waitForSelector(e.smallToastMsg);
|
||||
const checkUserWhoHasDisabled = await this.page1.page.evaluate(checkElementLengthEqualTo, e.videoContainer, 1);
|
||||
const checkSecondUser = await this.page2.page.evaluate(checkElementLengthEqualTo, e.videoContainer, 2);
|
||||
|
||||
return checkUserWhoHasDisabled && checkSecondUser;
|
||||
} catch (err) {
|
||||
await this.page1.logger(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async whiteboardNotAppearOnMobile() {
|
||||
try {
|
||||
await this.page1.waitAndClick(e.userListButton);
|
||||
await this.page2.waitAndClick(e.userListButton);
|
||||
await this.page2.waitAndClick(e.chatButtonKey);
|
||||
const onUserListPanel = await this.page1.isNotVisible(e.hidePresentation);
|
||||
const onChatPanel = await this.page2.isNotVisible(e.hidePresentation);
|
||||
const onUserListPanel = await this.page1.wasRemoved(e.hidePresentation);
|
||||
const onChatPanel = await this.page2.wasRemoved(e.hidePresentation);
|
||||
|
||||
return onUserListPanel && onChatPanel;
|
||||
} catch (err) {
|
||||
@ -333,7 +378,7 @@ class MultiUsers {
|
||||
await this.page2.waitAndClick(e.userListButton);
|
||||
await this.page2.waitAndClick(e.chatButtonKey);
|
||||
const whiteboard = await this.page1.page.evaluate(checkElementLengthEqualTo, e.chatButtonKey, 0);
|
||||
const onChatPanel = await this.page2.isNotVisible(e.chatButtonKey);
|
||||
const onChatPanel = await this.page2.hasElement(e.chatButtonKey, false);
|
||||
|
||||
return whiteboard && onChatPanel;
|
||||
} catch (err) {
|
||||
|
@ -2,10 +2,9 @@ const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
|
||||
const Page = require('../core/page');
|
||||
const e = require('../core/elements');
|
||||
const util = require('./util');
|
||||
const utilWebcam = require('../webcam/util');
|
||||
const utilScreenshare = require('../screenshare/util');
|
||||
const { sleep } = require('../core/helper');
|
||||
const { checkElementLengthEqualTo, checkElementLengthDifferentTo } = require('../core/util');
|
||||
const { checkElementLengthEqualTo } = require('../core/util');
|
||||
|
||||
class Status extends Page {
|
||||
constructor() {
|
||||
@ -15,12 +14,11 @@ class Status extends Page {
|
||||
async test() {
|
||||
try {
|
||||
await util.setStatus(this, e.applaud);
|
||||
const resp1 = await this.page.evaluate(checkElementLengthDifferentTo, e.applauseIcon, 0);
|
||||
const resp1 = await this.hasElement(e.applauseIcon);
|
||||
await sleep(1000);
|
||||
await util.setStatus(this, e.away);
|
||||
const resp2 = await this.page.evaluate(checkElementLengthDifferentTo, e.awayIcon, 0);
|
||||
const resp2 = await this.hasElement(e.awayIcon);
|
||||
|
||||
await this.waitAndClick(e.firstUser);
|
||||
await this.waitAndClick(e.clearStatus);
|
||||
return resp1 === resp2;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
@ -33,7 +31,7 @@ class Status extends Page {
|
||||
await this.waitAndClick(e.userList);
|
||||
await this.waitForSelector(e.firstUser);
|
||||
|
||||
const response = await this.page.evaluate(checkElementLengthDifferentTo, e.mobileUser, 0);
|
||||
const response = await this.hasElement(e.mobileUser);
|
||||
return response === true;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
@ -44,7 +42,7 @@ class Status extends Page {
|
||||
async findConnectionStatusModal() {
|
||||
try {
|
||||
await util.connectionStatus(this);
|
||||
const resp = await this.page.evaluate(checkElementLengthDifferentTo, e.connectionStatusModal, 0);
|
||||
const resp = await this.hasElement(e.connectionStatusModal);
|
||||
return resp === true;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
@ -52,21 +50,6 @@ class Status extends Page {
|
||||
}
|
||||
}
|
||||
|
||||
async disableWebcamsFromConnectionStatus() {
|
||||
try {
|
||||
await utilWebcam.enableWebcam(this, ELEMENT_WAIT_LONGER_TIME);
|
||||
await util.connectionStatus(this);
|
||||
await this.waitAndClickElement(e.dataSavingWebcams);
|
||||
await this.waitAndClickElement(e.closeConnectionStatusModal);
|
||||
await sleep(2000);
|
||||
const webcamsIsDisabledInDataSaving = await this.page.evaluate(checkElementLengthDifferentTo, e.webcamsIsDisabledInDataSaving, 0);
|
||||
return webcamsIsDisabledInDataSaving === true;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async disableScreenshareFromConnectionStatus() {
|
||||
try {
|
||||
await utilScreenshare.startScreenshare(this);
|
||||
@ -74,8 +57,8 @@ class Status extends Page {
|
||||
await util.connectionStatus(this);
|
||||
await this.waitAndClickElement(e.dataSavingScreenshare);
|
||||
await this.waitAndClickElement(e.closeConnectionStatusModal);
|
||||
await sleep(2000);
|
||||
const webcamsIsDisabledInDataSaving = await this.page.evaluate(checkElementLengthEqualTo, e.screenshareLocked, 0);
|
||||
|
||||
const webcamsIsDisabledInDataSaving = await this.hasElement(e.screenshareLocked);
|
||||
return webcamsIsDisabledInDataSaving === true;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
@ -87,13 +70,13 @@ class Status extends Page {
|
||||
try {
|
||||
await this.page.evaluate(() => window.dispatchEvent(new CustomEvent('socketstats', { detail: { rtt: 2000 } })));
|
||||
await this.joinMicrophone();
|
||||
await utilWebcam.enableWebcam(this, ELEMENT_WAIT_LONGER_TIME);
|
||||
await this.shareWebcam(true, ELEMENT_WAIT_LONGER_TIME);
|
||||
await utilScreenshare.startScreenshare(this);
|
||||
await utilScreenshare.waitForScreenshareContainer(this);
|
||||
await util.connectionStatus(this);
|
||||
await sleep(5000);
|
||||
const connectionStatusItemEmpty = await this.page.evaluate(checkElementLengthEqualTo, e.connectionStatusItemEmpty, 0);
|
||||
const connectionStatusItemUser = await this.page.evaluate(checkElementLengthDifferentTo, e.connectionStatusItemUser, 0);
|
||||
const connectionStatusItemUser = await this.hasElement(e.connectionStatusItemUser);
|
||||
return connectionStatusItemUser && connectionStatusItemEmpty;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
|
@ -112,22 +112,22 @@ const userTest = () => {
|
||||
// Open Connection Status Modal, start Webcam Share, disable Webcams in
|
||||
// Connection Status Modal and check if webcam sharing is still available
|
||||
test('Disable Webcams From Connection Status Modal', async () => {
|
||||
const test = new Status();
|
||||
const test = new MultiUsers();
|
||||
let response;
|
||||
let screenshot;
|
||||
try {
|
||||
const testName = 'disableWebcamsFromConnectionStatus';
|
||||
await test.logger('begin of ', testName);
|
||||
await test.init(true, true, testName);
|
||||
await test.startRecording(testName);
|
||||
await test.page1.logger('begin of ', testName);
|
||||
await test.init(testName);
|
||||
await test.page1.startRecording(testName);
|
||||
response = await test.disableWebcamsFromConnectionStatus();
|
||||
await test.stopRecording();
|
||||
screenshot = await test.page.screenshot();
|
||||
await test.logger('end of ', testName);
|
||||
await test.page1.stopRecording();
|
||||
screenshot = await test.page1.screenshot();
|
||||
await test.page1.logger('end of ', testName);
|
||||
} catch (err) {
|
||||
await test.logger(err);
|
||||
await test.page1.logger(err);
|
||||
} finally {
|
||||
await test.close();
|
||||
await test.close(test.page1, test.page2);
|
||||
}
|
||||
expect(response).toBe(true);
|
||||
Page.checkRegression(2.0, screenshot);
|
||||
@ -205,6 +205,29 @@ const userTest = () => {
|
||||
Page.checkRegression(2.0, screenshot);
|
||||
}, TEST_DURATION_TIME);
|
||||
|
||||
test('Show network data in Connection Status', async () => {
|
||||
const test = new MultiUsers();
|
||||
let response;
|
||||
let screenshot;
|
||||
try {
|
||||
const testName = 'connectionNetworkStatus';
|
||||
await test.page1.logger('begin of ', testName);
|
||||
await test.initMod1(testName);
|
||||
await test.page1.startRecording(testName);
|
||||
response = await test.usersConnectionStatus(testName);
|
||||
await test.page1.stopRecording();
|
||||
screenshot = await test.page1.page.screenshot();
|
||||
await test.page1.logger('end of ', testName);
|
||||
} catch (err) {
|
||||
await test.page1.logger(err);
|
||||
} finally {
|
||||
await test.close(test.page1, test.userPage);
|
||||
}
|
||||
expect(response).toBe(true);
|
||||
Page.checkRegression(2.0, screenshot);
|
||||
});
|
||||
|
||||
|
||||
// Raise and Lower Hand and make sure that the User2 Avatar color
|
||||
// and its avatar in raised hand toast are the same
|
||||
test('Raise Hand Toast', async () => {
|
||||
|
@ -11,5 +11,15 @@ async function connectionStatus(test) {
|
||||
await test.waitForSelector(e.connectionStatusModal);
|
||||
}
|
||||
|
||||
function checkNetworkStatus(dataContainer, networdData) {
|
||||
const values = Array.from(document.querySelectorAll(`${dataContainer} > ${networdData}`));
|
||||
values.splice(4, values.length - 4);
|
||||
const check = values.filter(e => e.textContent.includes(' 0 k'))[0];
|
||||
|
||||
if (!check) return true;
|
||||
}
|
||||
|
||||
|
||||
exports.setStatus = setStatus;
|
||||
exports.connectionStatus = connectionStatus;
|
||||
exports.checkNetworkStatus = checkNetworkStatus;
|
||||
|
@ -22,7 +22,7 @@ class Check extends Share {
|
||||
const parsedSettings = await this.getSettingsYaml();
|
||||
const videoPreviewTimeout = parseInt(parsedSettings.public.kurento.gUMTimeout);
|
||||
|
||||
await util.enableWebcam(this, videoPreviewTimeout);
|
||||
await this.shareWebcam(true, videoPreviewTimeout);
|
||||
const respUser = await util.webcamContentCheck(this);
|
||||
return respUser === true;
|
||||
} catch (err) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
const Page = require('../core/page');
|
||||
const util = require('./util');
|
||||
const e = require('../core/elements');
|
||||
const { checkElementLengthDifferentTo } = require('../core/util');
|
||||
const { VIDEO_LOADING_WAIT_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
|
||||
|
||||
class Share extends Page {
|
||||
@ -13,8 +11,9 @@ class Share extends Page {
|
||||
try {
|
||||
const parsedSettings = await this.getSettingsYaml();
|
||||
const videoPreviewTimeout = parseInt(parsedSettings.public.kurento.gUMTimeout);
|
||||
const response = await util.enableWebcam(this, videoPreviewTimeout);
|
||||
return response;
|
||||
await this.shareWebcam(true, videoPreviewTimeout);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
return false;
|
||||
@ -26,7 +25,7 @@ class Share extends Page {
|
||||
await this.joinMicrophone();
|
||||
const parsedSettings = await this.getSettingsYaml();
|
||||
const videoPreviewTimeout = parseInt(parsedSettings.public.kurento.gUMTimeout);
|
||||
await util.enableWebcam(this, videoPreviewTimeout);
|
||||
await this.shareWebcam(true, videoPreviewTimeout);
|
||||
} catch (err) {
|
||||
await this.logger(err);
|
||||
}
|
||||
@ -37,7 +36,7 @@ class Share extends Page {
|
||||
await this.waitForSelector(e.webcamVideo, VIDEO_LOADING_WAIT_TIME);
|
||||
await this.waitForSelector(e.leaveVideo, VIDEO_LOADING_WAIT_TIME);
|
||||
await this.waitForSelector(e.isTalking);
|
||||
const foundTestElement = await this.page.evaluate(checkElementLengthDifferentTo, e.webcamItemTalkingUser, 0);
|
||||
const foundTestElement = await this.hasElement(e.webcamItemTalkingUser);
|
||||
if (foundTestElement === true) {
|
||||
await this.screenshot(`${testName}`, `success-${testName}`);
|
||||
this.logger(testName, ' passed');
|
||||
|
@ -1,23 +1,11 @@
|
||||
const e = require('../core/elements');
|
||||
const { sleep } = require('../core/helper');
|
||||
const { checkElement, checkElementLengthDifferentTo } = require('../core/util');
|
||||
const { checkElement } = require('../core/util');
|
||||
const {
|
||||
LOOP_INTERVAL,
|
||||
VIDEO_LOADING_WAIT_TIME,
|
||||
ELEMENT_WAIT_LONGER_TIME,
|
||||
} = require('../core/constants');
|
||||
|
||||
async function enableWebcam(test, videoPreviewTimeout) {
|
||||
// Enabling webcam
|
||||
await test.waitAndClick(e.joinVideo);
|
||||
await test.waitForSelector(e.videoPreview, videoPreviewTimeout);
|
||||
await test.waitAndClick(e.startSharingWebcam);
|
||||
await test.waitForSelector(e.webcamConnecting);
|
||||
await test.waitForSelector(e.webcamVideo, VIDEO_LOADING_WAIT_TIME);
|
||||
await test.waitForSelector(e.leaveVideo, VIDEO_LOADING_WAIT_TIME);
|
||||
return test.page.evaluate(checkElementLengthDifferentTo, e.webcamVideo, 0);
|
||||
}
|
||||
|
||||
async function evaluateCheck(test) {
|
||||
await test.waitForSelector(e.videoContainer);
|
||||
return test.page.evaluate(checkElement, e.presentationFullscreenButton, 1);
|
||||
@ -69,4 +57,3 @@ async function webcamContentCheck(test) {
|
||||
exports.startAndCheckForWebcams = startAndCheckForWebcams;
|
||||
exports.webcamContentCheck = webcamContentCheck;
|
||||
exports.evaluateCheck = evaluateCheck;
|
||||
exports.enableWebcam = enableWebcam;
|
||||
|
@ -19,7 +19,6 @@ const webcamTest = () => {
|
||||
const testName = 'shareWebcam';
|
||||
await test.logger('begin of ', testName);
|
||||
await test.init(true, true, testName);
|
||||
await test.closeAudioModal();
|
||||
await test.startRecording(testName);
|
||||
response = await test.test();
|
||||
await test.stopRecording();
|
||||
@ -42,7 +41,6 @@ const webcamTest = () => {
|
||||
const testName = 'checkWebcamContent';
|
||||
await test.logger('begin of ', testName);
|
||||
await test.init(true, true, testName);
|
||||
await test.closeAudioModal();
|
||||
await test.startRecording(testName);
|
||||
response = await test.test();
|
||||
await test.stopRecording();
|
||||
|
@ -1056,6 +1056,120 @@ class ApiController {
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************
|
||||
* LEARNING DASHBOARD DATA
|
||||
***********************************************/
|
||||
def learningDashboard = {
|
||||
String API_CALL = 'learningDashboard'
|
||||
log.debug CONTROLLER_NAME + "#${API_CALL}"
|
||||
|
||||
String respMessage = ""
|
||||
boolean reject = false
|
||||
|
||||
String sessionToken
|
||||
UserSession us
|
||||
Meeting meeting
|
||||
|
||||
String validationResponse = validateRequest(
|
||||
ValidationService.ApiCall.ENTER,
|
||||
request.getParameterMap(),
|
||||
request.getQueryString(),
|
||||
)
|
||||
|
||||
//Validate Session
|
||||
if(!validationResponse.isEmpty()) {
|
||||
respMessage = validationResponse
|
||||
reject = true
|
||||
} else {
|
||||
sessionToken = sanitizeSessionToken(params.sessionToken)
|
||||
if (!hasValidSession(sessionToken)) {
|
||||
reject = true
|
||||
respMessage = "Invalid Session"
|
||||
}
|
||||
}
|
||||
|
||||
//Validate User
|
||||
if(reject == false) {
|
||||
us = getUserSession(sessionToken)
|
||||
|
||||
if(us == null) {
|
||||
reject = true;
|
||||
respMessage = "Access denied"
|
||||
} else if(!us.role.equals(ROLE_MODERATOR)) {
|
||||
reject = true
|
||||
respMessage = "Access denied"
|
||||
}
|
||||
}
|
||||
|
||||
//Validate Meeting
|
||||
if(reject == false) {
|
||||
meeting = meetingService.getMeeting(us.meetingID)
|
||||
boolean isRunning = meeting != null && meeting.isRunning();
|
||||
if(!isRunning) {
|
||||
reject = true
|
||||
respMessage = "Meeting not found"
|
||||
}
|
||||
|
||||
if(meeting.getLearningDashboardEnabled() == false) {
|
||||
reject = true
|
||||
respMessage = "Learning Dashboard disabled for this meeting"
|
||||
}
|
||||
}
|
||||
|
||||
//Validate File
|
||||
File jsonDataFile
|
||||
if(reject == false) {
|
||||
jsonDataFile = meetingService.learningDashboardService.getJsonDataFile(us.meetingID,meeting.getLearningDashboardAccessToken());
|
||||
if (!jsonDataFile.exists()) {
|
||||
reject = true
|
||||
respMessage = "Learning Dashboard data not found"
|
||||
}
|
||||
}
|
||||
|
||||
if (reject) {
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
withFormat {
|
||||
json {
|
||||
def builder = new JsonBuilder()
|
||||
builder.response {
|
||||
returncode RESP_CODE_FAILED
|
||||
message respMessage
|
||||
sessionToken
|
||||
}
|
||||
render(contentType: "application/json", text: builder.toPrettyString())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Map<String, Object> logData = new HashMap<String, Object>();
|
||||
logData.put("meetingid", us.meetingID);
|
||||
logData.put("extMeetingid", us.externMeetingID);
|
||||
logData.put("name", us.fullname);
|
||||
logData.put("userid", us.internalUserId);
|
||||
logData.put("sessionToken", sessionToken);
|
||||
logData.put("logCode", "learningDashboard");
|
||||
logData.put("description", "Request Learning Dashboard data.");
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
|
||||
log.info(" --analytics-- data=" + logStr);
|
||||
|
||||
response.addHeader("Cache-Control", "no-cache")
|
||||
|
||||
withFormat {
|
||||
json {
|
||||
def builder = new JsonBuilder()
|
||||
builder.response {
|
||||
returncode RESP_CODE_SUCCESS
|
||||
data jsonDataFile.getText()
|
||||
sessionToken
|
||||
}
|
||||
render(contentType: "application/json", text: builder.toPrettyString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def uploadDocuments(conf) { //
|
||||
log.debug("ApiController#uploadDocuments(${conf.getInternalId()})");
|
||||
|
||||
|
@ -151,6 +151,30 @@
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "learningDashboard",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/{{path}}/learningDashboard?{{param_session_token}}=",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"{{path}}",
|
||||
"learningDashboard"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "{{param_session_token}}",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
@ -108,6 +108,20 @@ if [ -f /usr/lib/systemd/system/red5.service ]; then
|
||||
chown root:root /usr/lib/systemd/system/red5.service
|
||||
fi
|
||||
|
||||
# Verify mediasoup raw media directories ownership and perms
|
||||
if [ -d /var/mediasoup ]; then
|
||||
chown bigbluebutton:bigbluebutton /var/mediasoup
|
||||
chmod 0700 /var/mediasoup
|
||||
fi
|
||||
|
||||
if [ -d /var/mediasoup/recordings ]; then
|
||||
chmod 0700 /var/mediasoup/recordings
|
||||
fi
|
||||
|
||||
if [ -d /var/mediasoup/screenshare ]; then
|
||||
chmod 0700 /var/mediasoup/screenshare
|
||||
fi
|
||||
|
||||
sed -i 's/worker_connections 768/worker_connections 4000/g' /etc/nginx/nginx.conf
|
||||
|
||||
if ! grep "worker_rlimit_nofile 10000;" /etc/nginx/nginx.conf; then
|
||||
|
@ -32,7 +32,9 @@ git clone https://github.com/mconf/ep_redis_publisher.git
|
||||
npm pack ./ep_redis_publisher
|
||||
npm install ./ep_redis_publisher-*.tgz
|
||||
|
||||
npm install ep_cursortrace
|
||||
# npm install ep_cursortrace
|
||||
# using mconf's fork due to https://github.com/ether/ep_cursortrace/pull/25 not being accepted upstream
|
||||
npm install git+https://github.com/mconf/ep_cursortrace.git
|
||||
npm install ep_disable_chat
|
||||
|
||||
# For some reason installing from github using npm 7.5.2 gives
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
/opt/freeswitch/bin/fs_cli -p $(xmlstarlet sel -t -m 'configuration/settings/param[@name="password"]' -v @value /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml)
|
||||
|
||||
# "$@" is placed at the end of the command so it can be used as "fs_clibbb -x 'show channels as json'"
|
||||
# @ will be replaced by the arguments you pass in the command line.
|
||||
/opt/freeswitch/bin/fs_cli -p $(xmlstarlet sel -t -m 'configuration/settings/param[@name="password"]' -v @value /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml) "$@"
|
||||
|
@ -18,6 +18,14 @@ location /html5client/fonts {
|
||||
alias /usr/share/meteor/bundle/programs/web.browser/app/fonts;
|
||||
}
|
||||
|
||||
location /html5client/wasm {
|
||||
types {
|
||||
application/wasm wasm;
|
||||
}
|
||||
gzip_static on;
|
||||
alias /usr/share/meteor/bundle/programs/web.browser/app/wasm;
|
||||
}
|
||||
|
||||
location ~ ^/html5client/ {
|
||||
# proxy_pass http://127.0.0.1:4100; # use for development
|
||||
proxy_pass http://poolhtml5servers; # use for production
|
||||
|
@ -84,6 +84,16 @@ if [ -f staging/usr/share/meteor/bundle/programs/web.browser/head.html ]; then
|
||||
sed -i "s/VERSION/$(($BUILD))/" staging/usr/share/meteor/bundle/programs/web.browser/head.html
|
||||
fi
|
||||
|
||||
# Compress tensorflow WASM binaries used for virtual backgrounds. Keep the
|
||||
# uncompressed versions as well so it works with mismatched nginx location blocks
|
||||
if [ -f staging/usr/share/meteor/bundle/programs/web.browser/app/wasm/tflite-simd.wasm ]; then
|
||||
gzip -k -f -9 staging/usr/share/meteor/bundle/programs/web.browser/app/wasm/tflite-simd.wasm
|
||||
fi
|
||||
|
||||
if [ -f staging/usr/share/meteor/bundle/programs/web.browser/app/wasm/tflite.wasm ]; then
|
||||
gzip -k -f -9 staging/usr/share/meteor/bundle/programs/web.browser/app/wasm/tflite.wasm
|
||||
fi
|
||||
|
||||
mkdir -p staging/etc/nginx/sites-available
|
||||
cp bigbluebutton.nginx staging/etc/nginx/sites-available/bigbluebutton
|
||||
|
||||
|
@ -101,6 +101,8 @@ bbb_config() {
|
||||
touch /var/log/bigbluebutton/bbb-web.log
|
||||
chown bigbluebutton:bigbluebutton /var/log/bigbluebutton/bbb-web.log
|
||||
|
||||
update-java-alternatives -s java-1.8.0-openjdk-amd64
|
||||
|
||||
# Restart bbb-web to deploy new
|
||||
startService bbb-web.service || echo "bbb-web.service could not be registered or started"
|
||||
# sed -i 's/8080/8090/g' /etc/bigbluebutton/nginx/web
|
||||
|
@ -1,3 +1,3 @@
|
||||
. ./opts-global.sh
|
||||
|
||||
OPTS="$OPTS -t deb -d zip,unzip,imagemagick,redis-server,xpdf-utils,bbb-libreoffice-docker,psmisc,fonts-crosextra-carlito,fonts-crosextra-caladea,fonts-noto --deb-user bigbluebutton --deb-group bigbluebutton --deb-use-file-permissions"
|
||||
OPTS="$OPTS -t deb -d zip,unzip,imagemagick,redis-server,xpdf-utils,bbb-libreoffice-docker,psmisc,fonts-crosextra-carlito,fonts-crosextra-caladea,fonts-noto,openjdk-8-jdk --deb-user bigbluebutton --deb-group bigbluebutton --deb-use-file-permissions"
|
||||
|
@ -16,6 +16,10 @@ case "$1" in
|
||||
# https://github.com/bigbluebutton/bbb-webrtc-sfu/pull/37
|
||||
# yq w -i $TARGET kurento[0].url "ws://$SERVER_URL:8888/kurento"
|
||||
|
||||
# Set mediasoup IPs
|
||||
yq w -i $TARGET mediasoup.webrtc.listenIps[0].announcedIp "$IP"
|
||||
yq w -i $TARGET mediasoup.plainRtp.listenIp.announcedIp "$IP"
|
||||
|
||||
FREESWITCH_IP=$(xmlstarlet sel -t -v '//X-PRE-PROCESS[@cmd="set" and starts-with(@data, "local_ip_v4=")]/@data' /opt/freeswitch/conf/vars.xml | sed 's/local_ip_v4=//g')
|
||||
if [ "$FREESWITCH_IP" != "" ]; then
|
||||
yq w -i $TARGET freeswitch.ip $FREESWITCH_IP
|
||||
@ -35,8 +39,8 @@ case "$1" in
|
||||
|
||||
# there's a problem rebuilding bufferutil
|
||||
# do not abort in case npm rebuild return something different than 0
|
||||
npm config set unsafe-perm true
|
||||
npm rebuild || true
|
||||
#npm config set unsafe-perm true
|
||||
#npm rebuild || true
|
||||
|
||||
mkdir -p /var/log/bbb-webrtc-sfu/
|
||||
touch /var/log/bbb-webrtc-sfu/bbb-webrtc-sfu.log
|
||||
@ -77,6 +81,10 @@ case "$1" in
|
||||
# echo "#"
|
||||
# fi
|
||||
|
||||
# Creates the mediasoup raw media file dir if needed
|
||||
if [ ! -d /var/mediasoup ]; then
|
||||
mkdir -p /var/mediasoup
|
||||
fi
|
||||
|
||||
# Create a symbolic link from /var/kurento -> /var/lib/kurento if needed
|
||||
if [ ! -d /var/kurento ]; then
|
||||
|
@ -15,6 +15,9 @@ case "$1" in
|
||||
if [ -f /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml ]; then
|
||||
cp /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml /tmp/bbb-webrtc-sfu-default.yml
|
||||
fi
|
||||
# there might be remaining files from older BBB versions
|
||||
# BBB 2.3 and earlier did an npm rebuild in the after-install script.
|
||||
rm -rf /usr/local/bigbluebutton/bbb-webrtc-sfu/node_modules
|
||||
;;
|
||||
|
||||
abort-upgrade)
|
||||
|
@ -40,6 +40,14 @@ else
|
||||
npm install --unsafe-perm --production
|
||||
fi
|
||||
|
||||
# clean out stuff that is not required in the final package
|
||||
rm -rf node_modules/mediasoup/{rust,.github,test}
|
||||
rm -rf node_modules/mediasoup/worker/{deps,src,test,include,fuzzer}
|
||||
rm -rf node_modules/mediasoup/worker/out/Release/*.a
|
||||
rm -rf node_modules/mediasoup/worker/out/Release/.deps
|
||||
rm -rf node_modules/mediasoup/worker/out/Release/obj.target
|
||||
rm -rf node_modules/mediasoup/worker/out/deps
|
||||
|
||||
popd
|
||||
|
||||
cp webrtc-sfu.nginx staging/etc/bigbluebutton/nginx
|
||||
|
Loading…
Reference in New Issue
Block a user