Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Gustavo Trott 2021-12-10 10:50:52 -03:00
commit 5eff7578cb
15 changed files with 156 additions and 148 deletions

View File

@ -17,6 +17,27 @@ object BreakoutHdlrHelpers extends SystemConfiguration {
roomSequence: String,
breakoutId: String
) {
for {
(redirectToHtml5JoinURL, redirectJoinURL) <- getRedirectUrls(liveMeeting, userId, externalMeetingId, roomSequence)
} yield {
sendJoinURLMsg(
outGW,
liveMeeting.props.meetingProp.intId,
breakoutId,
externalMeetingId,
userId,
redirectJoinURL,
redirectToHtml5JoinURL
)
}
}
def getRedirectUrls(
liveMeeting: LiveMeeting,
userId: String,
externalMeetingId: String,
roomSequence: String
): Option[(String, String)] = {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, userId)
apiCall = "join"
@ -31,15 +52,7 @@ object BreakoutHdlrHelpers extends SystemConfiguration {
redirectToHtml5JoinURL = BreakoutRoomsUtil.createJoinURL(bbbWebAPI, apiCall, redirectToHtml5BaseString,
BreakoutRoomsUtil.calculateChecksum(apiCall, redirectToHtml5BaseString, bbbWebSharedSecret))
} yield {
sendJoinURLMsg(
outGW,
liveMeeting.props.meetingProp.intId,
breakoutId,
externalMeetingId,
userId,
redirectJoinURL,
redirectToHtml5JoinURL
)
(redirectToHtml5JoinURL, redirectJoinURL)
}
}

View File

@ -25,7 +25,6 @@ trait BreakoutRoomCreatedMsgHdlr {
if (updatedModel.hasAllStarted()) {
updatedModel = updatedModel.copy(startedOn = Some(System.currentTimeMillis()))
updatedModel = sendBreakoutRoomsList(updatedModel)
updatedModel = sendBreakoutInvitations(updatedModel)
}
updatedModel
}
@ -36,25 +35,6 @@ trait BreakoutRoomCreatedMsgHdlr {
}
}
def sendBreakoutInvitations(breakoutModel: BreakoutModel): BreakoutModel = {
log.debug("Sending breakout invitations")
breakoutModel.rooms.values.foreach { room =>
log.debug("Sending invitations for room {} with num users {}", room.name, room.assignedUsers.toVector.length)
room.assignedUsers.foreach { user =>
BreakoutHdlrHelpers.sendJoinURL(
liveMeeting,
outGW,
user,
room.externalId,
room.sequence.toString(),
room.id
)
}
}
breakoutModel
}
def buildBreakoutRoomsListEvtMsg(meetingId: String, rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(BreakoutRoomsListEvtMsg.NAME, routing)
@ -63,12 +43,16 @@ trait BreakoutRoomCreatedMsgHdlr {
val body = BreakoutRoomsListEvtMsgBody(meetingId, rooms, roomsReady)
val event = BreakoutRoomsListEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def sendBreakoutRoomsList(breakoutModel: BreakoutModel): BreakoutModel = {
val breakoutRooms = breakoutModel.rooms.values.toVector map { r =>
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin)
val html5JoinUrls = for {
user <- r.assignedUsers
(redirectToHtml5JoinURL, redirectJoinURL) <- BreakoutHdlrHelpers.getRedirectUrls(liveMeeting, user, r.externalId, r.sequence.toString())
} yield (user -> redirectToHtml5JoinURL)
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin, html5JoinUrls.toMap)
}
log.info("Sending breakout rooms list to {} with containing {} room(s)", liveMeeting.props.meetingProp.intId, breakoutRooms.length)
@ -95,7 +79,7 @@ trait BreakoutRoomCreatedMsgHdlr {
BbbCommonEnvCoreMsg(envelope, event)
}
val breakoutInfo = BreakoutRoomInfo(room.name, room.externalId, room.id, room.sequence, room.shortName, room.isDefaultName, room.freeJoin)
val breakoutInfo = BreakoutRoomInfo(room.name, room.externalId, room.id, room.sequence, room.shortName, room.isDefaultName, room.freeJoin, Map())
val event = build(liveMeeting.props.meetingProp.intId, breakoutInfo)
outGW.send(event)

View File

@ -28,7 +28,7 @@ trait BreakoutRoomsListMsgHdlr {
breakoutModel <- state.breakout
} yield {
val rooms = breakoutModel.rooms.values.toVector map { r =>
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin)
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin, Map())
}
val ready = breakoutModel.hasAllStarted()
broadcastEvent(rooms, ready)

View File

@ -101,8 +101,6 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
msgSender.send(fromAkkaAppsPresRedisChannel, json)
case BreakoutRoomsListEvtMsg.NAME =>
msgSender.send(fromAkkaAppsPresRedisChannel, json)
case BreakoutRoomJoinURLEvtMsg.NAME =>
msgSender.send(fromAkkaAppsPresRedisChannel, json)
case BreakoutRoomsTimeRemainingUpdateEvtMsg.NAME =>
msgSender.send(fromAkkaAppsPresRedisChannel, json)
case BreakoutRoomStartedEvtMsg.NAME =>

View File

@ -35,7 +35,7 @@ case class User(
name: String,
isModerator: Boolean,
isDialIn: Boolean = false,
answers: Map[String,String] = Map(),
answers: Map[String,Vector[String]] = Map(),
talk: Talk = Talk(),
emojis: Vector[Emoji] = Vector(),
webcams: Vector[Webcam] = Vector(),
@ -47,7 +47,8 @@ case class User(
case class Poll(
pollId: String,
pollType: String,
anonymous: Boolean,
anonymous: Boolean,
multiple: Boolean,
question: String,
options: Vector[String] = Vector(),
anonymousAnswers: Vector[String] = Vector(),
@ -390,7 +391,7 @@ class LearningDashboardActor(
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
} yield {
val options = msg.body.poll.answers.map(answer => answer.key)
val newPoll = Poll(msg.body.pollId, msg.body.pollType, msg.body.secretPoll, msg.body.question, options.toVector)
val newPoll = Poll(msg.body.pollId, msg.body.pollType, msg.body.secretPoll, msg.body.poll.isMultipleResponse, msg.body.question, options.toVector)
val updatedMeeting = meeting.copy(polls = meeting.polls + (newPoll.pollId -> newPoll))
meetings += (updatedMeeting.intId -> updatedMeeting)
@ -413,7 +414,7 @@ class LearningDashboardActor(
}
} else {
//Store Public Poll in `user.answers`
val updatedUser = user.copy(answers = user.answers + (msg.body.pollId -> msg.body.answer))
val updatedUser = user.copy(answers = user.answers + (msg.body.pollId -> (user.answers.get(msg.body.pollId).getOrElse(Vector()) :+ msg.body.answer)))
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
meetings += (updatedMeeting.intId -> updatedMeeting)
}

View File

@ -13,7 +13,7 @@ case class BreakoutRoomJoinURLEvtMsgBody(parentId: String, breakoutId: String, e
object BreakoutRoomsListEvtMsg { val NAME = "BreakoutRoomsListEvtMsg" }
case class BreakoutRoomsListEvtMsg(header: BbbClientMsgHeader, body: BreakoutRoomsListEvtMsgBody) extends BbbCoreMsg
case class BreakoutRoomsListEvtMsgBody(meetingId: String, rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean)
case class BreakoutRoomInfo(name: String, externalId: String, breakoutId: String, sequence: Int, shortName: String, isDefaultName: Boolean, freeJoin: Boolean)
case class BreakoutRoomInfo(name: String, externalId: String, breakoutId: String, sequence: Int, shortName: String, isDefaultName: Boolean, freeJoin: Boolean, html5JoinUrls: Map[String, String])
object BreakoutRoomsListMsg { val NAME = "BreakoutRoomsListMsg" }
case class BreakoutRoomsListMsg(header: BbbClientMsgHeader, body: BreakoutRoomsListMsgBody) extends StandardMsg
@ -81,14 +81,6 @@ object RequestBreakoutJoinURLReqMsg { val NAME = "RequestBreakoutJoinURLReqMsg"
case class RequestBreakoutJoinURLReqMsg(header: BbbClientMsgHeader, body: RequestBreakoutJoinURLReqMsgBody) extends StandardMsg
case class RequestBreakoutJoinURLReqMsgBody(meetingId: String, breakoutId: String, userId: String)
/**
* Response sent to client for a join url for a user.
*/
object RequestBreakoutJoinURLRespMsg { val NAME = "RequestBreakoutJoinURLRespMsg" }
case class RequestBreakoutJoinURLRespMsg(header: BbbClientMsgHeader, body: RequestBreakoutJoinURLRespMsgBody) extends BbbCoreMsg
case class RequestBreakoutJoinURLRespMsgBody(parentId: String, breakoutId: String,
userId: String, redirectJoinURL: String, redirectToHtml5JoinURL: String)
object TransferUserToMeetingEvtMsg { val NAME = "TransferUserToMeetingEvtMsg" }
case class TransferUserToMeetingEvtMsg(header: BbbClientMsgHeader, body: TransferUserToMeetingEvtMsgBody) extends BbbCoreMsg
case class TransferUserToMeetingEvtMsgBody(fromVoiceConf: String, toVoiceConf: String, userId: String)

View File

@ -11,7 +11,7 @@ class PollsTable extends React.Component {
if (typeof user.answers[poll.pollId] !== 'undefined') {
return user.answers[poll.pollId];
}
return '';
return [];
}
if (typeof polls === 'object' && Object.values(polls).length === 0) {
@ -96,35 +96,37 @@ class PollsTable extends React.Component {
</td>
{typeof polls === 'object' && Object.values(polls || {}).length > 0 ? (
Object.values(polls || {}).map((poll) => (
<td className="px-4 py-3 text-sm text-center">
{ getUserAnswer(user, poll) }
{ poll.anonymous
? (
<span title={intl.formatMessage({
id: 'app.learningDashboard.pollsTable.anonymousAnswer',
defaultMessage: 'Anonymous Poll (answers in the last row)',
})}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4 inline"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
Object.values(polls || {})
.sort((a, b) => ((a.createdOn > b.createdOn) ? 1 : -1))
.map((poll) => (
<td className="px-4 py-3 text-sm text-center">
{ getUserAnswer(user, poll).map((answer) => <p>{answer}</p>) }
{ poll.anonymous
? (
<span title={intl.formatMessage({
id: 'app.learningDashboard.pollsTable.anonymousAnswer',
defaultMessage: 'Anonymous Poll (answers in the last row)',
})}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</span>
)
: null }
</td>
))
<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.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</span>
)
: null }
</td>
))
) : null }
</tr>
))) : null }
@ -162,11 +164,13 @@ class PollsTable extends React.Component {
</div>
</td>
{typeof polls === 'object' && Object.values(polls || {}).length > 0 ? (
Object.values(polls || {}).map((poll) => (
<td className="px-4 py-3 text-sm text-center">
{ poll.anonymousAnswers.map((answer) => <p>{answer}</p>) }
</td>
))
Object.values(polls || {})
.sort((a, b) => ((a.createdOn > b.createdOn) ? 1 : -1))
.map((poll) => (
<td className="px-4 py-3 text-sm text-center">
{ poll.anonymousAnswers.map((answer) => <p>{answer}</p>) }
</td>
))
) : null }
</tr>
</tbody>

View File

@ -48,6 +48,8 @@ FILE_SUDOERS_CHECK=`[ -f /etc/sudoers.d/zzz-bbb-docker-libreoffice ] && echo 1 |
if [ "$FILE_SUDOERS_CHECK" = "0" ]; then
echo "Sudoers file doesn't exists, installing"
cp assets/zzz-bbb-docker-libreoffice /etc/sudoers.d/zzz-bbb-docker-libreoffice
chmod 0440 /etc/sudoers.d/zzz-bbb-docker-libreoffice
chown root:root /etc/sudoers.d/zzz-bbb-docker-libreoffice
else
echo "Sudoers file already exists"
fi;

View File

@ -1,15 +1,12 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleBreakoutJoinURL from './handlers/breakoutJoinURL';
import handleBreakoutStarted from './handlers/breakoutStarted';
import handleBreakoutRoomsList from './handlers/breakoutList';
import handleUpdateTimeRemaining from './handlers/updateTimeRemaining';
import handleBreakoutClosed from './handlers/breakoutClosed';
import joinedUsersChanged from './handlers/joinedUsersChanged';
import handleBreakoutRoomsList from '/imports/api/breakouts-history/server/handlers/breakoutRoomsList';
RedisPubSub.on('BreakoutRoomStartedEvtMsg', handleBreakoutStarted);
RedisPubSub.on('BreakoutRoomsListEvtMsg', handleBreakoutRoomsList);
RedisPubSub.on('BreakoutRoomJoinURLEvtMsg', handleBreakoutJoinURL);
RedisPubSub.on('RequestBreakoutJoinURLRespMsg', handleBreakoutJoinURL);
RedisPubSub.on('BreakoutRoomsTimeRemainingUpdateEvtMsg', handleUpdateTimeRemaining);
RedisPubSub.on('BreakoutRoomEndedEvtMsg', handleBreakoutClosed);
RedisPubSub.on('UpdateBreakoutUsersEvtMsg', joinedUsersChanged);
RedisPubSub.on('BreakoutRoomsListEvtMsg', handleBreakoutRoomsList);

View File

@ -0,0 +1,60 @@
import Breakouts from '/imports/api/breakouts';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import flat from 'flat';
import handleBreakoutRoomsListHist from '/imports/api/breakouts-history/server/handlers/breakoutRoomsList';
export default function handleBreakoutRoomsList({ body }, meetingId) {
// 0 seconds default breakout time, forces use of real expiration time
const DEFAULT_TIME_REMAINING = 0;
const {
meetingId: parentMeetingId,
rooms,
} = body;
// set firstly the last seq, then client will know when receive all
rooms.sort((a, b) => ((a.sequence < b.sequence) ? 1 : -1)).forEach((breakout) => {
const { breakoutId, html5JoinUrls, ...breakoutWithoutUrls } = breakout;
check(meetingId, String);
const selector = {
breakoutId,
};
const urls = {};
if (typeof html5JoinUrls === 'object' && Object.keys(html5JoinUrls).length > 0) {
Object.keys(html5JoinUrls).forEach((userId) => {
urls[`url_${userId}`] = {
redirectToHtml5JoinURL: html5JoinUrls[userId],
insertedTime: new Date().getTime(),
};
});
}
const modifier = {
$set: {
breakoutId,
joinedUsers: [],
timeRemaining: DEFAULT_TIME_REMAINING,
parentMeetingId,
...flat(breakoutWithoutUrls),
...urls,
},
};
try {
const { numberAffected } = Breakouts.upsert(selector, modifier);
if (numberAffected) {
Logger.info('Updated timeRemaining and externalMeetingId '
+ `for breakout id=${breakoutId}`);
}
} catch (err) {
Logger.error(`updating breakout: ${err}`);
}
});
handleBreakoutRoomsListHist({ body });
}

View File

@ -1,44 +0,0 @@
import Breakouts from '/imports/api/breakouts';
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import flat from 'flat';
export default function handleBreakoutRoomStarted({ body }, meetingId) {
// 0 seconds default breakout time, forces use of real expiration time
const DEFAULT_TIME_REMAINING = 0;
const {
parentMeetingId,
breakout,
} = body;
const { breakoutId } = breakout;
check(meetingId, String);
const selector = {
breakoutId,
};
const modifier = {
$set: Object.assign(
{
joinedUsers: [],
},
{ timeRemaining: DEFAULT_TIME_REMAINING },
{ parentMeetingId },
flat(breakout),
),
};
try {
const { numberAffected } = Breakouts.upsert(selector, modifier);
if (numberAffected) {
Logger.info('Updated timeRemaining and externalMeetingId '
+ `for breakout id=${breakoutId}`);
}
} catch (err) {
Logger.error(`updating breakout: ${err}`);
}
}

View File

@ -66,25 +66,27 @@ class BreakoutRoomInvitation extends Component {
const hasBreakouts = breakouts.length > 0;
if (hasBreakouts && !breakoutUserIsIn && BreakoutService.checkInviteModerators()) {
// Have to check for freeJoin breakouts first because currentBreakoutUrlData will
// populate after a room has been joined
const breakoutRoom = getBreakoutByUrlData(currentBreakoutUrlData);
const freeJoinBreakout = breakouts.find((breakout) => breakout.freeJoin);
if (freeJoinBreakout) {
if (!didSendBreakoutInvite) {
this.inviteUserToBreakout(breakoutRoom || freeJoinBreakout);
this.setState({ didSendBreakoutInvite: true });
}
} else if (currentBreakoutUrlData) {
const freeJoinRooms = breakouts.filter((breakout) => breakout.freeJoin);
if (currentBreakoutUrlData) {
const breakoutRoom = getBreakoutByUrlData(currentBreakoutUrlData);
const currentInsertedTime = currentBreakoutUrlData.insertedTime;
const oldCurrentUrlData = oldProps.currentBreakoutUrlData || {};
const oldInsertedTime = oldCurrentUrlData.insertedTime;
if (currentInsertedTime !== oldInsertedTime) {
const breakoutId = Session.get('lastBreakoutOpened');
if (breakoutRoom.breakoutId !== breakoutId) {
const lastBreakoutId = Session.get('lastBreakoutOpened');
if (breakoutRoom.breakoutId !== lastBreakoutId) {
this.inviteUserToBreakout(breakoutRoom);
}
}
} else if (freeJoinRooms.length > 0 && !didSendBreakoutInvite) {
const maxSeq = Math.max(...freeJoinRooms.map(((room) => room.sequence)));
// Check if received all rooms and Pick a room randomly
if (maxSeq === freeJoinRooms.length) {
const randomRoom = freeJoinRooms[Math.floor(Math.random() * freeJoinRooms.length)];
this.inviteUserToBreakout(randomRoom);
this.setState({ didSendBreakoutInvite: true });
}
}
}

View File

@ -1,7 +1,7 @@
import styled from 'styled-components';
import { colorDanger, colorGrayDark } from '/imports/ui/stylesheets/styled-components/palette';
import { borderSize } from '/imports/ui/stylesheets/styled-components/general';
import { fontSizeSmaller, fontSizeMD, fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
import { fontSizeSmaller, fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
const SingleTyper = styled.span`
overflow: hidden;
@ -29,7 +29,6 @@ const TypingIndicator = styled.span`
display: block;
margin-right: 0.05rem;
margin-left: 0.05rem;
line-height: ${fontSizeMD};
}
text-align: left;

View File

@ -22,7 +22,7 @@ export default withTracker((params) => {
} = params;
const fetchFunc = published
? AnnotationGroupService.getCurrentAnnotationsInfo : AnnotationGroupService.getUnsetAnnotations;
? AnnotationGroupService.getCurrentAnnotationsInfo : AnnotationGroupService.getUnsentAnnotations;
const annotationsInfo = fetchFunc(whiteboardId);
return {

View File

@ -16,7 +16,7 @@ const getCurrentAnnotationsInfo = (whiteboardId) => {
).fetch();
};
const getUnsetAnnotations = (whiteboardId) => {
const getUnsentAnnotations = (whiteboardId) => {
if (!whiteboardId) {
return null;
}
@ -34,5 +34,5 @@ const getUnsetAnnotations = (whiteboardId) => {
export default {
getCurrentAnnotationsInfo,
getUnsetAnnotations,
getUnsentAnnotations,
};