Merge pull request #14451 from ramonlsouza/24-25-feb24

chore: Merge 2.4 into 2.5
This commit is contained in:
Anton Georgiev 2022-02-24 10:46:14 -05:00 committed by GitHub
commit 8e0fc7ddc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 3851 additions and 562 deletions

View File

@ -0,0 +1,49 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{
Users2x,
VoiceUsers,
Webcams
}
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
trait SyncGetWebcamInfoRespMsgHdlr {
this: UsersApp =>
def handleSyncGetWebcamInfoRespMsg(liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.systemProps.html5InstanceId.toString
)
val envelope = BbbCoreEnvelope(SyncGetWebcamInfoRespMsg.NAME, routing)
val header = BbbClientMsgHeader(
SyncGetWebcamInfoRespMsg.NAME,
liveMeeting.props.meetingProp.intId,
"nodeJSapp"
)
val webcamsList = Webcams.findAll(liveMeeting.webcams) flatMap { webcam =>
val stream = webcam.stream.id
val userId = webcam.stream.userId
val pin = Users2x.isPin(userId, liveMeeting.users2x)
for {
u <- Users2x.findWithIntId(liveMeeting.users2x, userId)
} yield {
VoiceUsers.findWIthIntId(liveMeeting.voiceUsers, userId) match {
case Some(vu) => WebcamStreamInfo(stream, userId, u.name, pin, vu.floor, vu.lastFloorTime)
case _ => WebcamStreamInfo(stream, userId, u.name, pin, false, "0")
}
}
}
val body = SyncGetWebcamInfoRespMsgBody(
webcamsList
)
val event = SyncGetWebcamInfoRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
}

View File

@ -170,6 +170,7 @@ class UsersApp(
with EjectDuplicateUserReqMsgHdlr
with EjectUserFromMeetingCmdMsgHdlr
with EjectUserFromMeetingSysMsgHdlr
with SyncGetWebcamInfoRespMsgHdlr
with MuteUserCmdMsgHdlr {
val log = Logging(context.system, getClass)

View File

@ -708,6 +708,9 @@ class MeetingActor(
// send all screen sharing info
screenshareApp2x.handleSyncGetScreenshareInfoRespMsg(liveMeeting, msgBus)
// send all webcam info
usersApp.handleSyncGetWebcamInfoRespMsg(liveMeeting, msgBus)
}
def handleGetAllMeetingsReqMsg(msg: GetAllMeetingsReqMsg): Unit = {

View File

@ -359,16 +359,14 @@ class LearningDashboardActor(
}
private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) {
if(msg.body.role == Roles.MODERATOR_ROLE) {
for {
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
user <- findUserByIntId(meeting, msg.body.userId)
} yield {
val updatedUser = user.copy(isModerator = true)
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
for {
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
user <- findUserByIntId(meeting, msg.body.userId)
} yield {
val updatedUser = user.copy(isModerator = (msg.body.role == Roles.MODERATOR_ROLE))
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
meetings += (updatedMeeting.intId -> updatedMeeting)
}
meetings += (updatedMeeting.intId -> updatedMeeting)
}
}

View File

@ -195,3 +195,17 @@ case class CamStreamSubscribedInSfuEvtMsgBody(
subscriberStreamId: String,
sfuSessionId: String // Subscriber's SFU session ID
)
/**
* Sync webcam state with bbb-html5
*/
object SyncGetWebcamInfoRespMsg { val NAME = "SyncGetWebcamInfoRespMsg" }
case class SyncGetWebcamInfoRespMsg(
header: BbbClientMsgHeader,
body: SyncGetWebcamInfoRespMsgBody
) extends BbbCoreMsg
case class SyncGetWebcamInfoRespMsgBody(
webcamListSync: Vector[WebcamStreamInfo]
)
case class WebcamStreamInfo(stream: String, userId: String, name: String, pin: Boolean, floor: Boolean, lastFloorTime: String)

View File

@ -105,6 +105,7 @@ public class MeetingService implements MessageListener {
private SwfSlidesGenerationProgressNotifier notifier;
private long usersTimeout;
private long waitingGuestUsersTimeout;
private long enteredUsersTimeout;
private ParamsProcessorUtil paramsProcessorUtil;
@ -139,13 +140,13 @@ public class MeetingService implements MessageListener {
public void registerUser(String meetingID, String internalUserId,
String fullname, String role, String externUserID,
String authToken, String avatarURL, Boolean guest,
Boolean authed, String guestStatus, Boolean excludeFromDashboard) {
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
handle(new RegisterUser(meetingID, internalUserId, fullname, role,
externUserID, authToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard));
externUserID, authToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard, leftGuestLobby));
Meeting m = getMeeting(meetingID);
if (m != null) {
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus, excludeFromDashboard);
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus, excludeFromDashboard, leftGuestLobby);
m.userRegistered(ruser);
}
}
@ -260,17 +261,16 @@ public class MeetingService implements MessageListener {
for (AbstractMap.Entry<String, Meeting> entry : this.meetings.entrySet()) {
Long now = System.currentTimeMillis();
Meeting meeting = entry.getValue();
ConcurrentMap<String, User> users = meeting.getUsersMap();
for (AbstractMap.Entry<String, RegisteredUser> registeredUser : meeting.getRegisteredUsers().entrySet()) {
String registeredUserID = registeredUser.getKey();
RegisteredUser ru = registeredUser.getValue();
long elapsedTime = now - ru.getGuestWaitedOn();
if (elapsedTime >= 15000 && ru.getGuestStatus() == GuestPolicy.WAIT) {
if (elapsedTime >= waitingGuestUsersTimeout && ru.getGuestStatus() == GuestPolicy.WAIT) {
if (meeting.userUnregistered(registeredUserID) != null) {
gw.guestWaitingLeft(meeting.getInternalId(), registeredUserID);
meeting.setLeftGuestLobby(registeredUserID, true);
};
}
}
@ -1304,6 +1304,10 @@ public class MeetingService implements MessageListener {
usersTimeout = value;
}
public void setWaitingGuestUsersTimeout(long value) {
waitingGuestUsersTimeout = value;
}
public void setEnteredUsersTimeout(long value) {
enteredUsersTimeout = value;
}

View File

@ -188,6 +188,22 @@ public class Meeting {
}
}
public void setLeftGuestLobby(String userId, Boolean bool) {
RegisteredUser ruser = registeredUsers.get(userId);
if (ruser != null) {
ruser.setLeftGuestLobby(bool);
}
}
public Boolean didGuestUserLeaveGuestLobby(String userId) {
RegisteredUser ruser = registeredUsers.get(userId);
if (ruser != null) {
return ruser.getLeftGuestLobby();
}
return true;
}
public void setGuestStatusWithId(String userId, String guestStatus) {
User user = getUserById(userId);
if (user != null) {

View File

@ -8,12 +8,14 @@ public class RegisteredUser {
private String guestStatus;
private Boolean excludeFromDashboard;
private Long guestWaitedOn;
private Boolean leftGuestLobby;
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard) {
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
this.authToken = authToken;
this.userId = userId;
this.guestStatus = guestStatus;
this.excludeFromDashboard = excludeFromDashboard;
this.leftGuestLobby = leftGuestLobby;
Long currentTimeMillis = System.currentTimeMillis();
this.registeredOn = currentTimeMillis;
@ -28,6 +30,10 @@ public class RegisteredUser {
return guestStatus;
}
public Boolean getLeftGuestLobby() {
return leftGuestLobby;
}
public void setExcludeFromDashboard(Boolean excludeFromDashboard) {
this.excludeFromDashboard = excludeFromDashboard;
}
@ -40,6 +46,9 @@ public class RegisteredUser {
this.guestWaitedOn = System.currentTimeMillis();
}
public void setLeftGuestLobby(boolean bool) {
this.leftGuestLobby = bool;
}
public Long getGuestWaitedOn() {
return this.guestWaitedOn;
}

View File

@ -45,6 +45,7 @@ public class UserSession {
public String guestStatus = GuestPolicy.ALLOW;
public String clientUrl = null;
public Boolean excludeFromDashboard = false;
public Boolean leftGuestLobby = false;
private AtomicInteger connections = new AtomicInteger(0);

View File

@ -14,10 +14,11 @@ public class RegisterUser implements IMessage {
public final Boolean authed;
public final String guestStatus;
public final Boolean excludeFromDashboard;
public final Boolean leftGuestLobby;
public RegisterUser(String meetingID, String internalUserId, String fullname, String role, String externUserID,
String authToken, String avatarURL, Boolean guest,
Boolean authed, String guestStatus, Boolean excludeFromDashboard) {
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
this.meetingID = meetingID;
this.internalUserId = internalUserId;
this.fullname = fullname;
@ -29,5 +30,6 @@ public class RegisterUser implements IMessage {
this.authed = authed;
this.guestStatus = guestStatus;
this.excludeFromDashboard = excludeFromDashboard;
this.leftGuestLobby = leftGuestLobby;
}
}

View File

@ -3,5 +3,13 @@ ENV DEBIAN_FRONTEND noninteractive
RUN echo "deb http://deb.debian.org/debian bullseye-backports main" >> /etc/apt/sources.list
RUN apt update && apt -y install locales-all fontconfig libxt6 libxrender1
RUN apt update && apt -y install -t bullseye-backports libreoffice && rm /usr/share/java/ant-apache-log4j-1.10.9.jar && rm /usr/share/maven-repo/org/apache/ant/ant-apache-log4j/1.10.9/ant-apache-log4j-1.10.9.jar
RUN apt update && apt -y install -t \
bullseye-backports \
libreoffice \
&& rm -f \
/usr/share/java/ant-apache-log4j-1.10.9.jar \
/usr/share/java/log4j-1.2-1.2.17.jar /usr/share/java/log4j-1.2.jar \
/usr/share/maven-repo/log4j/log4j/1.2.17/log4j-1.2.17.jar \
/usr/share/maven-repo/log4j/log4j/1.2.x/log4j-1.2.x.jar \
/usr/share/maven-repo/org/apache/ant/ant-apache-log4j/1.10.9/ant-apache-log4j-1.10.9.jar

View File

@ -105,7 +105,7 @@ enableUFWRules() {
enableMultipleKurentos() {
echo " - Configuring three Kurento Media Servers (listen only, webcam, and screeshare)"
echo " - Configuring three Kurento Media Servers (listen only, webcam, and screenshare)"
# Step 1. Setup shared certificate between FreeSWITCH and Kurento

View File

@ -115,9 +115,9 @@
}
.react-toggle--focus .react-toggle-thumb {
box-shadow: 0px 0px 2px 3px #0F70D7;
box-shadow: 0px 0px 2px 3px var(--color-primary);
}
.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
box-shadow: 0px 0px 5px 5px #0F70D7;
box-shadow: 0px 0px 5px 5px var(--color-primary);
}

View File

@ -3,8 +3,10 @@ import handleUserSharedHtml5Webcam from './handlers/userSharedHtml5Webcam';
import handleUserUnsharedHtml5Webcam from './handlers/userUnsharedHtml5Webcam';
import handleFloorChanged from './handlers/floorChanged';
import handlePinnedChanged from './handlers/userPinChanged';
import handleWebcamSync from './handlers/webcamSync';
RedisPubSub.on('UserBroadcastCamStartedEvtMsg', handleUserSharedHtml5Webcam);
RedisPubSub.on('UserBroadcastCamStoppedEvtMsg', handleUserUnsharedHtml5Webcam);
RedisPubSub.on('AudioFloorChangedEvtMsg', handleFloorChanged);
RedisPubSub.on('UserPinStateChangedEvtMsg', handlePinnedChanged);
RedisPubSub.on('SyncGetWebcamInfoRespMsg', handleWebcamSync);

View File

@ -0,0 +1,48 @@
import { check } from 'meteor/check';
import { addWebcamSync } from '../modifiers/sharedWebcam';
import VideoStreams from '/imports/api/video-streams/';
import updatedVideoStream from '../modifiers/updatedVideoStream';
import unsharedWebcam from '../modifiers/unsharedWebcam';
export default function handleWebcamSync({ body }, meetingId) {
check(meetingId, String);
check(body, Object);
const { webcamListSync } = body;
check(webcamListSync, Array);
const streamsIds = webcamListSync.map((webcam) => webcam.stream);
const webcamStreamsToUpdate = VideoStreams.find({
meetingId,
stream: { $in: streamsIds },
}, {
fields: {
stream: 1,
},
}).fetch()
.map((m) => m.stream);
webcamListSync.forEach((webcam) => {
if (webcamStreamsToUpdate.indexOf(webcam.stream) >= 0) {
// stream already exist, then update
updatedVideoStream(meetingId, webcam);
} else {
// stream doesn't exist yet, then add it
addWebcamSync(meetingId, webcam);
}
});
// removing extra video streams already existing in Mongo
const videoStreamsToRemove = VideoStreams.find({
meetingId,
stream: { $nin: streamsIds },
}, {
fields: {
stream: 1,
userId: 1,
},
}).fetch();
videoStreamsToRemove
.forEach((videoStream) => unsharedWebcam(meetingId, videoStream.userId, videoStream.stream));
}

View File

@ -56,3 +56,46 @@ export default function sharedWebcam(meetingId, userId, stream) {
Logger.error(`Error setting stream: ${err}`);
}
}
export function addWebcamSync(meetingId, videoStream) {
check(videoStream, {
userId: String,
stream: String,
name: String,
pin: Boolean,
floor: Boolean,
lastFloorTime: String,
});
const {
stream, userId, name, pin, floor, lastFloorTime,
} = videoStream;
const deviceId = getDeviceId(stream);
const selector = {
meetingId,
userId,
deviceId,
};
const modifier = {
$set: {
stream,
name,
lastFloorTime,
floor,
pin,
},
};
try {
const { insertedId } = VideoStreams.upsert(selector, modifier);
if (insertedId) {
Logger.info(`Synced stream=${stream} meeting=${meetingId}`);
}
} catch (err) {
Logger.error(`Error setting sync stream: ${err}`);
}
}

View File

@ -0,0 +1,39 @@
import { check } from 'meteor/check';
import Logger from '/imports/startup/server/logger';
import VideoStreams from '/imports/api/video-streams';
export default function updateVideoStream(meetingId, videoStream) {
check(meetingId, String);
check(videoStream, {
userId: String,
stream: String,
name: String,
pin: Boolean,
floor: Boolean,
lastFloorTime: String,
});
const { userId, stream } = videoStream;
const selector = {
meetingId,
stream,
userId,
};
const modifier = {
$set: Object.assign(
...videoStream,
),
};
try {
const numberAffected = VideoStreams.update(selector, modifier);
if (numberAffected) {
Logger.debug(`Update videoStream ${stream} for user ${userId} in ${meetingId}`);
}
} catch (err) {
Logger.error(`Error update videoStream ${stream} for user=${userId}: ${err}`);
}
}

View File

@ -199,6 +199,7 @@ class ActionsDropdown extends PureComponent {
label: intl.formatMessage(intlMessages.selectRandUserLabel),
key: this.selectUserRandId,
onClick: () => mountModal(<RandomUserSelectContainer isSelectedUser={false} />),
dataTest: "selectRandomUser",
})
}

View File

@ -125,7 +125,7 @@ const ChatAlert = (props) => {
{ autoClose: 3000 },
<div>
<div style={{ fontWeight: 700 }}>{chatsTracker[key].lastSender}</div>
<div>{chatsTracker[key].content}</div>
<div dangerouslySetInnerHTML={{ __html: chatsTracker[key].content }} />
</div>,
true,
);

View File

@ -73,12 +73,13 @@ const ChatAlertContainer = (props) => {
try {
if (c[0] === idChat || (c[0] === 'MAIN-PUBLIC-GROUP-CHAT' && idChat === 'public')) {
chatsTracker[c[0]] = {};
chatsTracker[c[0]].lastSender = users[Auth.meetingID][c[1]?.lastSender]?.name;
if (c[1]?.posJoinMessages || c[1]?.messageGroups) {
const m = Object.entries(c[1]?.posJoinMessages || c[1]?.messageGroups);
chatsTracker[c[0]].count = m?.length;
if (m[m.length - 1]) {
const sameUserCount = m.filter((message) => message[1]?.sender === Auth.userID).length;
if (m[m.length - 1] && m[m.length - 1][1]?.sender !== Auth.userID) {
chatsTracker[c[0]].lastSender = users[Auth.meetingID][c[1]?.lastSender]?.name;
chatsTracker[c[0]].content = m[m.length - 1][1]?.message;
chatsTracker[c[0]].count = m?.length - sameUserCount;
}
}
}
@ -94,7 +95,7 @@ const ChatAlertContainer = (props) => {
if (prevTracker) {
const keys = Object.keys(prevTracker);
keys.forEach((key) => {
if (chatsTracker[key]?.count > prevTracker[key]?.count) {
if (chatsTracker[key]?.count > (prevTracker[key]?.count || 0)) {
chatsTracker[key].shouldNotify = true;
}
});

View File

@ -118,6 +118,7 @@ const Chat = (props) => {
aria-label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
label={intl.formatMessage(intlMessages.closeChatLabel, { 0: title })}
accessKey={CLOSE_CHAT_AK}
data-test="closePrivateChat"
/>
)
: (

View File

@ -1,7 +1,7 @@
import styled from 'styled-components';
import {
colorWhite,
colorGrayDark,
colorBackground,
colorGrayLighter,
} from '/imports/ui/stylesheets/styled-components/palette';
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
@ -13,7 +13,7 @@ const Background = styled.div`
justify-content: center;
width: 100%;
height: 100%;
background-color: ${colorGrayDark};
background-color: ${colorBackground};
color: ${colorWhite};
text-align: center;
`;

View File

@ -80,7 +80,7 @@ class BBBMenu extends React.Component {
<Styled.BBBMenuItem
emoji={emojiSelected ? 'yes' : 'no'}
key={label}
data-test={dataTest || key}
data-test={dataTest}
disableRipple={true}
disableGutters={true}
disabled={disabled}

View File

@ -144,7 +144,9 @@ class RandomUserSelect extends Component {
<Styled.ModalViewTitle>
{intl.formatMessage(messages.randUserTitle)}
</Styled.ModalViewTitle>
<div>{intl.formatMessage(messages.noViewers)}</div>
<div data-test="noViewersSelectedMessage">
{intl.formatMessage(messages.noViewers)}
</div>
</Styled.ModalViewContainer>
);
} else { // viewers are available
@ -165,7 +167,7 @@ class RandomUserSelect extends Component {
<Styled.ModalAvatar aria-hidden style={{ backgroundColor: `${selectedUser.color}` }}>
{selectedUser.name.slice(0, 2)}
</Styled.ModalAvatar>
<Styled.SelectedUserName>
<Styled.SelectedUserName data-test="selectedUserName">
{selectedUser.name}
</Styled.SelectedUserName>
{currentUser.presenter
@ -176,6 +178,7 @@ class RandomUserSelect extends Component {
color="primary"
size="md"
onClick={() => this.reselect()}
data-test="selectAgainRadomUser"
/>
)}
</Styled.ModalViewContainer>

View File

@ -3,7 +3,11 @@ import {
borderRadius,
lgPaddingX,
} from '/imports/ui/stylesheets/styled-components/general';
import { colorWhite, colorText } from '/imports/ui/stylesheets/styled-components/palette';
import {
colorWhite,
colorText,
colorBackground,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
fontSizeLarge,
headingsFontWeight,
@ -20,6 +24,7 @@ const Parent = styled.div`
display: flex;
justify-content: center;
align-items: center;
background-color: ${colorBackground};
`;
const Modal = styled.div`

View File

@ -98,6 +98,10 @@ const intlMessages = defineMessages({
id: 'app.presentationUploder.rejectedError',
description: 'some files rejected, please check the file mime types',
},
badConnectionError: {
id: 'app.presentationUploder.connectionClosedError',
description: 'message indicating that the connection was closed',
},
uploadProcess: {
id: 'app.presentationUploder.upload.progress',
description: 'message that indicates the percentage of the upload',
@ -613,9 +617,9 @@ class PresentationUploader extends Component {
}}
>
<Styled.FileLine>
<Styled.FileIcon>
<span>
<Icon iconName="file" />
</Styled.FileIcon>
</span>
<Styled.ToastFileName>
<span>{item.filename}</span>
</Styled.ToastFileName>
@ -979,6 +983,11 @@ class PresentationUploader extends Component {
constraint['0'] = ((item.conversion.maxFileSize) / 1000 / 1000).toFixed(2);
}
if (item.upload.progress < 100) {
const errorMessage = intlMessages.badConnectionError;
return intl.formatMessage(errorMessage);
}
const errorMessage = intlMessages[item.upload.status] || intlMessages.genericError;
return intl.formatMessage(errorMessage, constraint);
}

View File

@ -60,16 +60,9 @@ const UploadRow = styled.div`
const FileLine = styled.div`
display: flex;
flex-direction: row;
width: ${fileLineWidth};
`;
const FileIcon = styled.span`
width: 1%;
align-items: center;
padding-bottom: ${iconPaddingMd};
& > i {
position: relative;
top: ${borderSizeLarge};
}
width: ${fileLineWidth};
`;
const ToastFileName = styled.span`
@ -77,31 +70,21 @@ const ToastFileName = styled.span`
overflow: hidden;
white-space: nowrap;
height: 1.25rem !important;
margin-left: ${lgPaddingX};
top: ${borderSizeLarge};
margin-left: ${mdPaddingY};
height: 1rem;
width: auto;
position: relative;
text-align: left;
font-weight: ${headingsFontWeight};
[dir="rtl"] & {
margin-right: ${lgPaddingX};
margin-right: ${mdPaddingY};
margin-left: 0;
text-align: right;
}
`;
const StatusIcon = styled.span`
margin-left: auto;
[dir="rtl"] & {
margin-right: auto;
margin-left: 0;
}
& > i {
position: relative;
top: 1px;
height: ${statusIconSize};
width: ${statusIconSize};
}
@ -162,9 +145,6 @@ const Table = styled.table`
font-weight: bold;
color: ${colorGrayDark};
}
td {
}
}
}
`;
@ -219,6 +199,11 @@ const InnerToast = styled(ScrollboxVertical)`
padding-right: 1.5rem;
box-sizing: content-box;
background: none;
[dir="rtl"] & {
padding-right: 0;
padding-left: 1.5rem;
}
`;
const TableItemIcon = styled.td`
@ -455,9 +440,13 @@ const ToastItemIcon = styled(Icon)`
width: ${statusIconSize};
height: ${statusIconSize};
font-size: 117%;
bottom: ${borderSize};
left: ${statusInfoHeight};
[dir="rtl"] & {
left: unset;
right: ${statusInfoHeight};
}
${({ done }) => done && `
color: ${colorSuccess};
`}
@ -589,7 +578,6 @@ const ExtraHint = styled.div`
export default {
UploadRow,
FileLine,
FileIcon,
ToastFileName,
StatusIcon,
StatusInfo,

View File

@ -49,7 +49,7 @@ const UserAvatar = ({
}) => (
<Styled.Avatar
aria-hidden="true"
data-test="userAvatar"
data-test={moderator ? 'moderatorAvatar' : 'viewerAvatar'}
moderator={moderator}
presenter={presenter}
whiteboardAccess={whiteboardAccess && !presenter}

View File

@ -1,6 +1,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
import _ from 'lodash';
import { Session } from 'meteor/session';
import { findDOMNode } from 'react-dom';
@ -328,6 +329,7 @@ class UserListItem extends PureComponent {
onClick: () => this.setState({ showNestedOptions: true }),
icon: 'user',
iconRight: 'right_arrow',
dataTest: 'setStatus',
},
{
allowed: showNestedOptions && isMeteorConnected && allowedToChangeStatus,
@ -447,6 +449,7 @@ class UserListItem extends PureComponent {
this.handleClose();
},
icon: 'presentation',
dataTest: isMe(user.userId) ? 'takePresenter' : 'makePresenter',
},
{
allowed: allowedToPromote && isMeteorConnected && !showNestedOptions,
@ -457,6 +460,7 @@ class UserListItem extends PureComponent {
this.handleClose();
},
icon: 'promote',
dataTest: 'promoteToModerator',
},
{
allowed: allowedToDemote && isMeteorConnected && !showNestedOptions,
@ -467,6 +471,7 @@ class UserListItem extends PureComponent {
this.handleClose();
},
icon: 'user',
dataTest: 'demoteToViewer',
},
{
allowed: allowedToChangeUserLockStatus && isMeteorConnected && !showNestedOptions,
@ -536,6 +541,7 @@ class UserListItem extends PureComponent {
this.handleClose();
},
icon: getEmojiList[s],
dataTest: s,
})
});
@ -707,7 +713,7 @@ class UserListItem extends PureComponent {
const innerContents = (
<Styled.UserItemInnerContents>
<Styled.UserAvatar>
<Styled.UserAvatar data-test="userAvatar">
{this.renderUserAvatar()}
</Styled.UserAvatar>
{!compact
@ -718,10 +724,12 @@ class UserListItem extends PureComponent {
aria-expanded={isActionsOpen}
>
<Styled.UserNameMain>
<span>
{user.name}
&nbsp;
</span>
<TooltipContainer title={user.name}>
<span>
{user.name}
&nbsp;
</span>
</TooltipContainer>
<i>{(isMe(user.userId)) ? `(${intl.formatMessage(messages.you)})` : ''}</i>
</Styled.UserNameMain>
{

View File

@ -46,7 +46,7 @@ const toolbarItemTrianglePadding = '2px';
const toolbarMargin = '.8rem';
const fileLineWidth = '16.75rem';
const iconPaddingMd = '.65rem';
const iconPaddingMd = '.4rem';
const statusIconSize = '16px';
const toastMdMargin = '.5rem';
const uploadListHeight = '30vh';

View File

@ -11,6 +11,7 @@ import {
colorText,
colorWhite,
colorGrayLighter,
colorOverlay,
} from '/imports/ui/stylesheets/styled-components/palette';
const GlobalStyle = createGlobalStyle`
@ -64,7 +65,7 @@ const GlobalStyle = createGlobalStyle`
left: 0;
right: 0;
bottom: 0;
background-color: rgba(6, 23, 42, 0.75);
background-color: ${colorOverlay};
}
.fullscreenModalOverlay {

View File

@ -24,6 +24,8 @@ const colorMuted = 'var(--color-muted, #586571)';
const colorMutedBackground = 'var(--color-muted-background, #F3F6F9)';
const colorBackground = `var(--color-background, ${colorGrayDark})`;
const colorOverlay = `var(--color-overlay, rgba(6, 23, 42, 0.75))`;
const userListBg = `var(--user-list-bg, ${colorOffWhite})`;
const userListText = `var(--user-list-text, ${colorGray})`;
const unreadMessagesBg = `var(--unread-messages-bg, ${colorDanger})`;
@ -91,7 +93,7 @@ const colorContentBackground = 'var(--color-content-background, #1B2A3A)';
const dropdownBg = `var(--dropdown-bg, ${colorWhite})`;
const pollStatsBorderColor = 'var(--poll-stats-border-color, #D4D9DF)';
const pollBlue = 'var(--poll-blue, #1A73D4)';
const pollBlue = `var(--poll-blue, ${colorPrimary})`;
const toastDefaultColor = `var(--toast-default-color, ${colorWhite})`;
const toastDefaultBg = `var(--toast-default-bg, ${colorGray})`;
@ -126,6 +128,7 @@ export {
colorSuccess,
colorWarning,
colorBackground,
colorOverlay,
userListBg,
userListText,
unreadMessagesBg,

View File

@ -0,0 +1,6 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "/*": ["*"] }
}
}

View File

@ -272,7 +272,7 @@
return;
}
pollGuestStatus(sessionToken, 0, ATTEMPT_LIMIT, ATTEMPT_EVERY_MS);
pollGuestStatus(sessionToken, ATTEMPT_EVERY_MS);
} catch (e) {
disableAnimation();
console.error(e);
@ -302,15 +302,9 @@
}, REDIRECT_TIMEOUT);
};
function pollGuestStatus(token, attempt, limit, everyMs) {
function pollGuestStatus(token, everyMs) {
setTimeout(function () {
if (attempt >= limit) {
disableAnimation();
updateMessage(_('app.guest.noModeratorResponse'));
return;
}
fetchGuestWait(token)
.then(async (resp) => await resp.json())
.then((data) => {
@ -342,7 +336,7 @@
updatePositionInWaitingQueue(data.response.positionInWaitingQueue);
updateLobbyMessage(data.response.lobbyMessage);
return pollGuestStatus(token, attempt + 1, limit, everyMs);
return pollGuestStatus(token, everyMs);
});
}, everyMs);
};

View File

@ -207,6 +207,7 @@
"app.presentationUploder.fileToUpload": "To be uploaded ...",
"app.presentationUploder.currentBadge": "Current",
"app.presentationUploder.rejectedError": "The selected file(s) have been rejected. Please check the file type(s).",
"app.presentationUploder.connectionClosedError": "Interrupted by poor connectivity. Please try again.",
"app.presentationUploder.upload.progress": "Uploading ({0}%)",
"app.presentationUploder.upload.413": "File is too large, exceeded the maximum of {0} MB",
"app.presentationUploder.genericError": "Oops, Something went wrong ...",
@ -607,6 +608,8 @@
"app.guest.allow": "Guest approved and redirecting to meeting.",
"app.guest.firstPositionInWaitingQueue": "You are the first in line!",
"app.guest.positionInWaitingQueue": "Your current position in waiting queue: ",
"app.guest.guestInvalid": "Guest user is invalid",
"app.guest.meetingForciblyEnded": "You cannot join a meeting that has already been forcibly ended",
"app.userList.guest.waitingUsers": "Waiting Users",
"app.userList.guest.waitingUsersTitle": "User Management",
"app.userList.guest.optionTitle": "Review Pending Users",
@ -938,6 +941,17 @@
"playback.error.wrapper.aria": "Error area",
"playback.loader.wrapper.aria": "Loader area",
"playback.player.wrapper.aria": "Player area",
"playback.player.about.modal.shortcuts.title": "Shortcuts",
"playback.player.about.modal.shortcuts.alt": "Alt",
"playback.player.about.modal.shortcuts.shift": "Shift",
"playback.player.about.modal.shortcuts.fullscreen": "Toggle fullscreen",
"playback.player.about.modal.shortcuts.play": "Play/Pause",
"playback.player.about.modal.shortcuts.section": "Toggle side section",
"playback.player.about.modal.shortcuts.seek.backward": "Seek backwards",
"playback.player.about.modal.shortcuts.seek.forward": "Seek forwards",
"playback.player.about.modal.shortcuts.skip.next": "Next slide",
"playback.player.about.modal.shortcuts.skip.previous": "Previous Slide",
"playback.player.about.modal.shortcuts.swap": "Swap content",
"playback.player.chat.message.poll.name": "Poll result",
"playback.player.chat.message.poll.question": "Question",
"playback.player.chat.message.poll.options": "Options",

View File

@ -46,4 +46,16 @@ test.describe.parallel('Chat', () => {
await chat.init(true, true);
await chat.emptyMessage();
});
test('Close private chat', async ({ browser, context, page }) => {
const privateChat = new PrivateChat(browser, context);
await privateChat.initPages(page);
await privateChat.closeChat();
});
test('Private chat disabled when user leaves meeting', async ({ browser, context, page }) => {
const privateChat = new PrivateChat(browser, context);
await privateChat.initPages(page);
await privateChat.chatDisabledUserLeaves();
});
});

View File

@ -1,8 +1,7 @@
const { MultiUsers } = require('../user/multiusers');
const e = require('../core/elements');
const { sleep } = require('../core/helpers');
const { checkElementLengthEqualTo } = require('../core/util');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
const { expect } = require('@playwright/test');
class PrivateChat extends MultiUsers {
constructor(browser, context) {
@ -17,11 +16,7 @@ class PrivateChat extends MultiUsers {
// modPage send message
await this.modPage.type(e.chatBox, e.message1);
await this.modPage.waitAndClick(e.sendButton);
await this.userPage.page.waitForFunction(
checkElementLengthEqualTo,
[e.chatButton, 2],
{ timeout: ELEMENT_WAIT_TIME },
);
await this.userPage.waitUntilHaveCountSelector(e.chatButton, 2);
await this.userPage.waitAndClickElement(e.chatButton, 1);
await this.userPage.waitForSelector(e.hidePrivateChat);
// check sent messages
@ -34,6 +29,33 @@ class PrivateChat extends MultiUsers {
await this.modPage.hasText(e.privateChat, e.message2);
await this.userPage.hasText(e.privateChat, e.message2);
}
async closeChat() {
await this.modPage.waitAndClick(e.userListItem);
await this.modPage.waitAndClick(e.startPrivateChat);
await this.modPage.waitUntilHaveCountSelector(e.chatButton, 2);
const privateChatLocator = this.modPage.getLocatorByIndex(e.chatButton, -1);
expect(privateChatLocator).toContainText(this.userPage.username);
await this.modPage.type(e.chatBox, e.message1);
await this.modPage.waitAndClick(e.sendButton);
await this.userPage.waitUntilHaveCountSelector(e.chatButton, 2);
await this.modPage.waitAndClick(e.closePrivateChat);
await this.modPage.wasRemoved(e.hidePrivateChat);
await this.modPage.waitUntilHaveCountSelector(e.chatButton, 1);
const userChatCount = await this.userPage.getSelectorCount(e.chatButton);
expect(userChatCount).toBe(2);
}
async chatDisabledUserLeaves() {
await this.modPage.waitAndClick(e.userListItem);
await this.modPage.waitAndClick(e.startPrivateChat);
await this.modPage.waitForSelector(e.sendButton);
await this.userPage.waitAndClick(e.optionsButton);
await this.userPage.waitAndClick(e.logout);
await this.modPage.hasElementDisabled(e.chatBox);
await this.modPage.hasElementDisabled(e.sendButton);
}
}
exports.PrivateChat = PrivateChat;

View File

@ -59,9 +59,11 @@ exports.publicChat = 'div[data-test="publicChat"]';
exports.privateChat = 'div[data-test="privateChat"]';
exports.hidePublicChat = 'button[data-test="hidePublicChat"]';
exports.hidePrivateChat = 'button[data-test="hidePrivateChat"]';
exports.closePrivateChat = 'button[data-test="closePrivateChat"]';
exports.typingIndicator = 'span[data-test="typingIndicator"]';
exports.chatUserMessageText = 'p[data-test="chatUserMessageText"]';
exports.chatClearMessageText = 'p[data-test="chatClearMessageText"]';
exports.chatWelcomeMessageText = 'p[data-test="chatWelcomeMessageText"]';
// Messages
exports.message = 'Hello World!';
exports.testMessage = 'Just a test';
@ -187,9 +189,11 @@ exports.notesTitle = 'h2[data-test="notesTitle"]';
// User
exports.userAvatar = 'div[data-test="userAvatar"]';
exports.moderatorAvatar = 'div[data-test="moderatorAvatar"]';
exports.viewerAvatar = 'div[data-test="viewerAvatar"]';
exports.applauseIcon = `${this.userAvatar} > div > i[class="icon-bbb-applause"]`;
exports.awayIcon = `${this.userAvatar} > div > i[class="icon-bbb-time"]`;
exports.setStatus = 'li[data-test="setstatus"]';
exports.setStatus = 'li[data-test="setStatus"]';
exports.away = 'li[data-test="away"]';
exports.applaud = 'li[data-test="applause"]';
exports.userListItem = 'div[data-test="userListItem"]';
@ -198,7 +202,7 @@ exports.multiWhiteboardTool = 'span[data-test="multiWhiteboardTool"]';
exports.manageUsers = 'button[data-test="manageUsers"]';
exports.presenterClassName = 'presenter--';
exports.anyUser = 'div[data-test="userListItem"]';
exports.userList = 'button[data-test="toggleUserList"]';
exports.userListToggleBtn = 'button[data-test="toggleUserList"]';
exports.mobileUser = 'span[data-test="mobileUser"]';
exports.connectionStatusBtn = 'button[data-test="connectionStatusButton"]';
exports.connectionStatusModal = 'div[aria-label="Connection status modal"]';
@ -217,6 +221,14 @@ exports.joinMeetingDemoPage = 'div[class^="join-meeting"]';
exports.askModerator = 'button[data-test="askModerator"]';
exports.alwaysAccept = 'button[data-test="alwaysAccept"]';
exports.alwaysDeny = 'button[data-test="alwaysDeny"]';
exports.selectRandomUser = 'li[data-test="selectRandomUser"]';
exports.noViewersSelectedMessage = 'div[data-test="noViewersSelectedMessage"]';
exports.selectedUserName = 'div[data-test="selectedUserName"]';
exports.selectAgainRadomUser = 'button[data-test="selectAgainRadomUser"]';
exports.promoteToModerator = 'li[data-test="promoteToModerator"]';
exports.demoteToViewer = 'li[data-test="demoteToViewer"]';
exports.makePresenter = 'li[data-test="makePresenter"]';
exports.takePresenter = 'li[data-test="takePresenter"]';
// Lock Viewers
exports.lockViewersButton = 'li[data-test="lockViewersButton"]';
exports.unlockUserButton = 'li[data-test="unlockUserButton"]';

View File

@ -7,7 +7,7 @@ const parameters = require('./parameters');
const helpers = require('./helpers');
const e = require('./elements');
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME, VIDEO_LOADING_WAIT_TIME } = require('./constants');
const { checkElement } = require('./util');
const { checkElement, checkElementLengthEqualTo } = require('./util');
class Page {
constructor(browser, page) {
@ -81,6 +81,10 @@ class Page {
return this.page.locator(selector);
}
getLocatorByIndex(selector, index) {
return this.page.locator(selector).nth(index);
}
async getSelectorCount(selector) {
const locator = this.getLocator(selector);
return locator.count();
@ -95,6 +99,14 @@ class Page {
await this.page.waitForSelector(selector, { timeout });
}
async waitUntilHaveCountSelector(selector, count, timeout = ELEMENT_WAIT_TIME) {
await this.page.waitForFunction(
checkElementLengthEqualTo,
[selector, count],
{ timeout },
);
}
async type(selector, text) {
const handle = this.getLocator(selector);
await handle.focus();

View File

@ -9,7 +9,7 @@ function checkElementLengthEqualTo([element, count]) {
}
function checkIncludeClass([selector, className]) {
return document.querySelectorAll(selector)[0].className.includes(className);
return document.querySelectorAll(`${selector} > div`)[0].className.includes(className);
}
exports.checkElement = checkElement;

View File

@ -1,7 +1,5 @@
const { expect } = require('@playwright/test');
const e = require('../core/elements');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
const { checkElementLengthEqualTo } = require('../core/util');
async function enableChatPopup(test) {
await test.waitAndClick(e.notificationsTab);
@ -40,11 +38,7 @@ async function privateChatMessageToast(page2) {
await page2.waitAndClick(e.userListItem);
await page2.waitAndClick(e.startPrivateChat);
// wait for the private chat to be ready
await page2.page.waitForFunction(
checkElementLengthEqualTo,
[e.chatButton, 2],
{ timeout: ELEMENT_WAIT_TIME },
);
await page2.waitUntilHaveCountSelector(e.chatButton, 2);
// send a private message
await page2.type(e.chatBox, e.message1);
await page2.waitAndClick(e.sendButton);

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,11 @@
"test:debug": "npx playwright test --debug -g"
},
"devDependencies": {
"@playwright/test": "^1.16.3",
"axios": "^0.24.0",
"dotenv": "^10.0.0",
"@playwright/test": "^1.18.1",
"axios": "^0.25.0",
"dotenv": "^16.0.0",
"js-yaml": "^4.1.0",
"playwright": "^1.16.3",
"playwright": "^1.18.1",
"sha1": "^1.1.1"
}
}

View File

@ -133,17 +133,15 @@ class Polling extends MultiUsers {
}
async typeOnLastChoiceInput() {
const allInputs = this.modPage.getLocator(e.pollOptionItem);
const lastInput = allInputs.last();
const lastInput = this.modPage.getLocatorByIndex(e.pollOptionItem, -1);
await lastInput.fill(this.newInputText);
}
async checkLastOptionText() {
await this.userPage.waitForSelector(e.pollingContainer);
const answerOptions = this.userPage.getLocator(e.pollAnswerOptionBtn);
const lastOptionText = await answerOptions.last().textContent();
await expect(lastOptionText).toEqual(this.newInputText);
const lastOptionText = this.userPage.getLocatorByIndex(e.pollAnswerOptionBtn, -1);
await expect(lastOptionText).toHaveText(this.newInputText);
}
}

View File

@ -2,7 +2,7 @@ const { expect } = require('@playwright/test');
const Page = require('../core/page');
const e = require('../core/elements');
const c = require('../core/constants');
const { checkIncludeClass } = require('../core/util');
const { checkPresenterClass } = require('../user/util');
class Stress {
constructor(browser, context, page) {
@ -18,7 +18,7 @@ class Stress {
for (let i = 1; i <= c.JOIN_AS_MODERATOR_TEST_ROUNDS; i++) {
await this.modPage.init(true, true, { fullName: `Moderator-${i}` });
await this.modPage.waitForSelector(e.userAvatar);
const hasPresenterClass = await this.modPage.page.evaluate(checkIncludeClass, [e.userAvatar, e.presenterClassName]);
const hasPresenterClass = await checkPresenterClass(this.modPage);
await this.modPage.waitAndClick(e.actions);
const canStartPoll = await this.modPage.checkElement(e.polling);
if (!hasPresenterClass || !canStartPoll) {

View File

@ -1,6 +1,7 @@
const { MultiUsers } = require("./multiusers");
const e = require('../core/elements');
const { sleep } = require('../core/helpers');
const { setGuestPolicyOption } = require("./util");
class GuestPolicy extends MultiUsers {
constructor(browser, context) {
@ -8,25 +9,22 @@ class GuestPolicy extends MultiUsers {
}
async askModerator() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.guestPolicyLabel);
await this.modPage.waitAndClick(e.askModerator);
await setGuestPolicyOption(this.modPage, e.askModerator);
await sleep(500);
await this.initUserPage(false);
await this.modPage.hasElement(e.waitingUsersBtn);
}
async alwaysAccept() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.guestPolicyLabel);
await this.modPage.waitAndClick(e.alwaysAccept);
await setGuestPolicyOption(this.modPage, e.askModerator);
await setGuestPolicyOption(this.modPage, e.alwaysAccept);
await sleep(500);
await this.initUserPage(false);
await this.userPage.hasElement(e.audioModal);
}
async alwaysDeny() {
await this.modPage.waitAndClick(e.manageUsers);
await this.modPage.waitAndClick(e.guestPolicyLabel);
await this.modPage.waitAndClick(e.alwaysDeny);
await setGuestPolicyOption(this.modPage, e.alwaysDeny);
await sleep(1500);
await this.initUserPage(false);
await this.userPage.hasElement(e.joinMeetingDemoPage);

View File

@ -5,6 +5,7 @@ const { expect } = require("@playwright/test");
const { ELEMENT_WAIT_LONGER_TIME, ELEMENT_WAIT_TIME } = require("../core/constants");
const { getNotesLocator } = require("../sharednotes/util");
const { waitAndClearNotification } = require("../notifications/util");
const { sleep } = require("../core/helpers");
class LockViewers extends MultiUsers {
constructor(browser, page) {
@ -35,6 +36,7 @@ class LockViewers extends MultiUsers {
await openLockViewers(this.modPage);
await this.modPage.waitAndClickElement(e.lockSeeOtherViewersWebcam);
await this.modPage.waitAndClick(e.applyLockSettings);
await sleep(500);
const videoContainersCount = [
await this.modPage.getSelectorCount(e.webcamVideoItem),
await this.userPage.getSelectorCount(e.webcamVideoItem),
@ -69,9 +71,9 @@ class LockViewers extends MultiUsers {
}
async lockSendPrivateChatMessages() {
const lastUserItemLocator = this.userPage.getLocator(e.userListItem).last();
const lastUserItemLocator = this.userPage.getLocatorByIndex(e.userListItem, -1);
await this.userPage.clickOnLocator(lastUserItemLocator);
const startPrivateChatButton = this.userPage.getLocator(e.startPrivateChat).last();
const startPrivateChatButton = this.userPage.getLocatorByIndex(e.startPrivateChat, -1);
await this.userPage.clickOnLocator(startPrivateChatButton);
await openLockViewers(this.modPage);
await this.modPage.waitAndClickElement(e.lockPrivateChat);
@ -115,9 +117,9 @@ class LockViewers extends MultiUsers {
await this.userPage.hasElementDisabled(e.joinVideo);
await this.userPage2.hasElementDisabled(e.joinVideo);
const lastUserItemLocator = this.modPage.getLocator(e.userListItem).last();
const lastUserItemLocator = this.modPage.getLocatorByIndex(e.userListItem, -1);
await this.modPage.clickOnLocator(lastUserItemLocator);
const unlockUserButton = this.modPage.getLocator(e.unlockUserButton).last();
const unlockUserButton = this.modPage.getLocatorByIndex(e.unlockUserButton, -1);
await this.modPage.clickOnLocator(unlockUserButton);
await this.userPage.hasElementDisabled(e.joinVideo);
await this.userPage2.hasElementEnabled(e.joinVideo);

View File

@ -7,7 +7,7 @@ class MobileDevices extends MultiUsers {
}
async mobileTagName() {
await this.modPage.waitAndClick(e.userList);
await this.modPage.waitAndClick(e.userListToggleBtn);
await this.modPage.waitForSelector(e.firstUser);
await this.modPage.hasElement(e.mobileUser);
}

View File

@ -2,6 +2,8 @@ const { expect } = require('@playwright/test');
const Page = require('../core/page');
const e = require('../core/elements');
const { waitAndClearNotification } = require('../notifications/util');
const { sleep } = require('../core/helpers');
const { checkAvatarIcon, checkPresenterClass } = require('./util');
class MultiUsers {
constructor(browser, context) {
@ -16,14 +18,26 @@ class MultiUsers {
async initModPage(page, shouldCloseAudioModal = true, { fullName = 'Moderator', ...restOptions } = {}) {
const options = {
fullName,
...restOptions,
fullName,
};
this.modPage = new Page(this.browser, page);
await this.modPage.init(true, shouldCloseAudioModal, options);
}
async initModPage2(shouldCloseAudioModal = true, context = this.context, { fullName = 'Moderator2', useModMeetingId = true, ...restOptions } = {}) {
const options = {
...restOptions,
fullName,
meetingId: (useModMeetingId) ? this.modPage.meetingId : undefined,
};
const page = await context.newPage();
this.modPage2 = new Page(this.browser, page);
await this.modPage2.init(true, shouldCloseAudioModal, options);
}
async initUserPage(shouldCloseAudioModal = true, context = this.context, { fullName = 'Attendee', useModMeetingId = true, ...restOptions } = {}) {
const options = {
...restOptions,
@ -59,6 +73,51 @@ class MultiUsers {
await expect(secondUserOnUserPage).toHaveCount(1);
}
async makePresenter() {
await this.modPage.waitAndClick(e.userListItem);
await this.modPage.waitAndClick(e.makePresenter);
await this.userPage.hasElement(e.startScreenSharing);
await this.userPage.hasElement(e.presentationToolbarWrapper);
await this.userPage.hasElement(e.toolsButton);
await this.userPage.hasElement(e.actions);
const hasPresenterClass = await checkPresenterClass(this.userPage);
expect(hasPresenterClass).toBeTruthy();
}
async takePresenter() {
await this.modPage2.waitAndClick(e.firstUser);
await this.modPage2.waitAndClick(e.takePresenter);
await this.modPage2.hasElement(e.startScreenSharing);
await this.modPage2.hasElement(e.toolsButton);
await this.modPage2.hasElement(e.presentationToolbarWrapper);
const hasPresenterClass = await checkPresenterClass(this.modPage2);
expect(hasPresenterClass).toBeTruthy();
await this.modPage2.waitAndClick(e.actions);
await this.modPage2.hasElement(e.managePresentations);
await this.modPage2.hasElement(e.polling);
await this.modPage2.hasElement(e.shareExternalVideoBtn);
}
async promoteToModerator() {
await checkAvatarIcon(this.userPage, false);
await this.userPage.wasRemoved(e.manageUsers);
await this.modPage.waitAndClick(e.userListItem);
await this.modPage.waitAndClick(e.promoteToModerator);
await checkAvatarIcon(this.userPage);
await this.userPage.hasElement(e.manageUsers);
}
async demoteToViewer() {
await checkAvatarIcon(this.modPage2);
await this.modPage2.hasElement(e.manageUsers);
await this.modPage.waitAndClick(e.userListItem);
await this.modPage.waitAndClick(e.demoteToViewer);
await checkAvatarIcon(this.modPage2, false);
await this.modPage2.wasRemoved(e.manageUsers);
}
async raiseHandTest() {
await this.userPage.waitAndClick(e.raiseHandBtn);
await this.userPage.hasElement(e.lowerHandBtn);
@ -78,6 +137,52 @@ class MultiUsers {
await this.userPage.hasElement(e.raiseHandBtn);
}
async toggleUserList() {
await this.modPage.hasElement(e.chatWelcomeMessageText);
await this.modPage.hasElement(e.chatBox);
await this.modPage.hasElement(e.chatButton);
await this.modPage.waitAndClick(e.userListToggleBtn);
await this.modPage.wasRemoved(e.chatWelcomeMessageText);
await this.modPage.wasRemoved(e.chatBox);
await this.modPage.wasRemoved(e.chatButton);
await this.modPage.waitAndClick(e.userListToggleBtn);
await this.modPage.wasRemoved(e.chatWelcomeMessageText);
await this.modPage.wasRemoved(e.chatBox);
await this.modPage.hasElement(e.chatButton);
}
async selectRandomUser() {
// check with no viewer joined
await this.modPage.waitAndClick(e.actions);
await this.modPage.waitAndClick(e.selectRandomUser);
await this.modPage.hasElement(e.noViewersSelectedMessage);
// check with only one viewer
await this.modPage.waitAndClick(e.closeModal);
await this.initUserPage();
await this.modPage.waitAndClick(e.actions);
await this.modPage.waitAndClick(e.selectRandomUser);
await this.modPage.hasText(e.selectedUserName, this.userPage.username);
// check with more users
await this.modPage.waitAndClick(e.closeModal);
await this.initUserPage2();
await this.modPage.waitAndClick(e.actions);
await this.modPage.waitAndClick(e.selectRandomUser);
const nameSelected = await this.modPage.getLocator(e.selectedUserName).textContent();
await this.userPage.hasText(e.selectedUserName, nameSelected);
await this.userPage2.hasText(e.selectedUserName, nameSelected);
// user close modal just for you
await this.userPage.waitAndClick(e.closeModal);
await this.userPage.wasRemoved(e.selectedUserName);
await this.userPage2.hasElement(e.selectedUserName);
await this.modPage.hasElement(e.selectedUserName);
// moderator close modal
await this.modPage.waitAndClick(e.selectAgainRadomUser);
await sleep(500);
await this.modPage.waitAndClick(e.closeModal);
await this.userPage.wasRemoved(e.selectedUserName);
await this.userPage2.wasRemoved(e.selectedUserName);
}
async whiteboardAccess() {
await this.modPage.waitForSelector(e.whiteboard);
await this.modPage.waitAndClick(e.userListItem);

View File

@ -20,7 +20,7 @@ class Status extends Page {
}
async mobileTagName() {
await this.waitAndClick(e.userList);
await this.waitAndClick(e.userListToggleBtn);
await this.waitForSelector(e.firstUser);
await this.hasElement(e.mobileUser);
}

View File

@ -16,6 +16,12 @@ test.describe.parallel('User', () => {
await multiusers.getAvatarColorAndCompareWithUserListItem();
await multiusers.lowerHandTest();
});
test('Toggle user list', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page);
await multiusers.toggleUserList();
});
});
test.describe.parallel('List', () => {
@ -30,6 +36,32 @@ test.describe.parallel('User', () => {
await multiusers.initPages(page);
await multiusers.userPresence();
});
test('Make presenter', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initPages(page);
await multiusers.makePresenter();
});
test('Take presenter', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page);
await multiusers.initModPage2();
await multiusers.takePresenter();
});
test('Promote to moderator', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initPages(page);
await multiusers.promoteToModerator();
});
test('Demote to viewer', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page);
await multiusers.initModPage2();
await multiusers.demoteToViewer();
});
});
test.describe.parallel('Manage', () => {
@ -52,6 +84,69 @@ test.describe.parallel('User', () => {
await guestPolicy.alwaysDeny();
});
});
test.describe.parallel('Lock viewers', () => {
test('Lock Share webcam', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockShareWebcam();
});
test('Lock See other viewers webcams', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSeeOtherViewersWebcams();
});
test('Lock Share microphone', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockShareMicrophone();
});
test('Lock Send public chat messages', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSendPublicChatMessages();
});
test('Lock Send private chat messages', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSendPrivateChatMessages();
});
test('Lock Edit Shared Notes', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.lockEditSharedNotes();
});
test('Lock See other viewers in the Users list', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSeeOtherViewersUserList();
});
test('Unlock a user', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.unlockUser();
});
});
test('Select random user', async ({ browser, context, page }) => {
const multiusers = new MultiUsers(browser, context);
await multiusers.initModPage(page);
await multiusers.selectRandomUser();
});
});
test.describe.parallel('Mobile devices', () => {
@ -94,61 +189,4 @@ test.describe.parallel('User', () => {
await mobileDevices.chatPanelNotAppearOnMobile();
});
});
test.describe.parallel('Lock viewers', () => {
test('Lock Share webcam', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockShareWebcam();
});
test('Lock See other viewers webcams', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSeeOtherViewersWebcams();
});
test('Lock Share microphone', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockShareMicrophone();
});
test('Lock Send public chat messages', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSendPublicChatMessages();
});
test('Lock Send private chat messages', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSendPrivateChatMessages();
});
test('Lock Edit Shared Notes', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.lockEditSharedNotes();
});
test('Lock See other viewers in the Users list', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.lockSeeOtherViewersUserList();
});
test('Unlock a user', async ({ browser, context, page }) => {
const lockViewers = new LockViewers(browser, context);
await lockViewers.initPages(page);
await lockViewers.initUserPage2();
await lockViewers.unlockUser();
});
});
});

View File

@ -1,4 +1,5 @@
const e = require('../core/elements');
const { checkIncludeClass } = require('../core/util');
async function setStatus(page, status) {
await page.waitAndClick(e.firstUser);
@ -11,5 +12,22 @@ async function openLockViewers(test) {
await test.waitAndClick(e.lockViewersButton);
}
async function setGuestPolicyOption(test, option) {
await test.waitAndClick(e.manageUsers);
await test.waitAndClick(e.guestPolicyLabel);
await test.waitAndClick(option);
}
async function checkAvatarIcon(test, checkModIcon = true) {
await test.hasElement(`${e.firstUser} ${checkModIcon ? e.moderatorAvatar : e.viewerAvatar}`);
}
async function checkPresenterClass(test) {
return test.page.evaluate(checkIncludeClass, [e.userAvatar, e.presenterClassName]);
}
exports.setStatus = setStatus;
exports.openLockViewers = openLockViewers;
exports.setGuestPolicyOption = setGuestPolicyOption;
exports.checkAvatarIcon = checkAvatarIcon;
exports.checkPresenterClass = checkPresenterClass;

View File

@ -274,6 +274,11 @@ defaultKeepEvents=false
# Default 60s
usersTimeout=60000
# Timeout (millis) to remove guest users that stopped fetching for her/his status
# e.g. guest that closed the waiting page before being approved
# Default 15s
waitingGuestUsersTimeout=15000
# Timeout (millis) to remove users that called the enter API but did not join
# e.g. user's client hanged between the enter call and join event
# Default 45s

View File

@ -57,6 +57,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="gw" ref="bbbWebApiGWApp"/>
<property name="callbackUrlService" ref="callbackUrlService"/>
<property name="usersTimeout" value="${usersTimeout}"/>
<property name="waitingGuestUsersTimeout" value="${waitingGuestUsersTimeout}"/>
<property name="enteredUsersTimeout" value="${enteredUsersTimeout}"/>
<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
</bean>

View File

@ -322,6 +322,7 @@ class ApiController {
us.guestStatus = guestStatusVal
us.logoutUrl = meeting.getLogoutUrl()
us.defaultLayout = meeting.getMeetingLayout()
us.leftGuestLobby = false
if (!StringUtils.isEmpty(params.defaultLayout)) {
us.defaultLayout = params.defaultLayout;
@ -366,7 +367,8 @@ class ApiController {
us.guest,
us.authed,
guestStatusVal,
us.excludeFromDashboard
us.excludeFromDashboard,
us.leftGuestLobby
)
session.setMaxInactiveInterval(SESSION_TIMEOUT);
@ -733,7 +735,6 @@ class ApiController {
request.getParameterMap(),
request.getQueryString()
)
if(!(validationResponse == null)) {
msgKey = validationResponse.getKey()
msgValue = validationResponse.getValue()
@ -793,6 +794,14 @@ class ApiController {
break
}
if(meeting.didGuestUserLeaveGuestLobby(us.internalUserId)){
destURL = meeting.getLogoutUrl()
msgKey = "guestInvalid"
msgValue = "Invalid user"
status = GuestPolicy.DENY
redirectClient = false
}
if (redirectClient) {
// User may join the meeting
redirect(url: destURL)
@ -839,7 +848,6 @@ class ApiController {
request.getParameterMap(),
request.getQueryString(),
)
if(!(validationResponse == null)) {
respMessage = validationResponse.getValue()
reject = true

View File

@ -9,4 +9,8 @@ if [ ! -f /.dockerenv ]; then
mount -t tmpfs -o size=512m tmpfs /mnt/mongo-ramdisk
fi
chown -R mongodb:mongodb /mnt/mongo-ramdisk
if id mongod &> /dev/null; then
chown -R mongod:mongod /mnt/mongo-ramdisk
else
chown -R mongodb:mongodb /mnt/mongo-ramdisk
fi

View File

@ -24,7 +24,9 @@ sudo rm -rf /usr/local/bigbluebutton/core/lib
sudo cp -r core/lib /usr/local/bigbluebutton/core/
sudo rm -rf /usr/local/bigbluebutton/core/scripts
sudo cp -r core/scripts /usr/local/bigbluebutton/core/
sudo rm -rf /var/bigbluebutton/playback/*
sudo rm -rf /var/bigbluebutton/playback/presentation/0.81/
sudo rm -rf /var/bigbluebutton/playback/presentation/0.9.0/
sudo rm -rf /var/bigbluebutton/playback/presentation/2.0/
function deploy_format() {
local formats=$1
@ -95,10 +97,4 @@ if [ ! -d "$REC_STATUS_SANITY_DIR" ]; then
sudo mkdir -p $REC_STATUS_SANITY_DIR
fi
sudo mv /usr/local/bigbluebutton/core/scripts/*.nginx /etc/bigbluebutton/nginx/
sudo service nginx reload
sudo chown -R bigbluebutton:bigbluebutton /var/bigbluebutton/ /var/log/bigbluebutton/
sudo chown -R red5:red5 /var/bigbluebutton/screenshare/
#cd /usr/local/bigbluebutton/core/
#sudo bundle install