Handle message to all breakouts sent

This commit is contained in:
Gustavo Trott 2022-01-28 14:41:10 -03:00
parent 759535eb62
commit eb567eb106
19 changed files with 210 additions and 87 deletions

View File

@ -15,8 +15,7 @@ trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
val outGW: OutMsgRouter
def handleSendMessageToAllBreakoutRoomsMsg(msg: SendMessageToAllBreakoutRoomsMsg, state: MeetingState2x): MeetingState2x = {
log.debug("handleSendMessageToAllBreakoutRoomsMsg {} in meeting {}", msg.body.msg, props.meetingProp.intId)
def handleSendMessageToAllBreakoutRoomsMsg(msg: SendMessageToAllBreakoutRoomsReqMsg, state: MeetingState2x): MeetingState2x = {
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to send message to all breakout rooms for meeting."
@ -30,6 +29,10 @@ trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
breakoutModel.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, SendMessageToBreakoutRoomInternalMsg(props.breakoutProps.parentId, room.id, senderUser.name, msg.body.msg)))
}
val event = buildSendMessageToAllBreakoutRoomsEvtMsg(msg.header.userId, msg.body.msg, breakoutModel.rooms.size)
outGW.send(event)
log.debug("Sending message '{}' for breakout rooms in meeting {}", msg.body.msg, props.meetingProp.intId)
}
@ -37,4 +40,14 @@ trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
}
}
def buildSendMessageToAllBreakoutRoomsEvtMsg(senderId: String, msg: String, totalOfRooms: Int): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(SendMessageToAllBreakoutRoomsEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendMessageToAllBreakoutRoomsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = SendMessageToAllBreakoutRoomsEvtMsgBody(props.meetingProp.intId, senderId, msg, totalOfRooms)
val event = SendMessageToAllBreakoutRoomsEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
}

View File

@ -201,8 +201,8 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[TransferUserToMeetingRequestMsg](envelope, jsonNode)
case ExtendBreakoutRoomsTimeReqMsg.NAME =>
routeGenericMsg[ExtendBreakoutRoomsTimeReqMsg](envelope, jsonNode)
case SendMessageToAllBreakoutRoomsMsg.NAME =>
routeGenericMsg[SendMessageToAllBreakoutRoomsMsg](envelope, jsonNode)
case SendMessageToAllBreakoutRoomsReqMsg.NAME =>
routeGenericMsg[SendMessageToAllBreakoutRoomsReqMsg](envelope, jsonNode)
// Layout
case GetCurrentLayoutReqMsg.NAME =>

View File

@ -456,7 +456,7 @@ class MeetingActor(
case m: RequestBreakoutJoinURLReqMsg => state = handleRequestBreakoutJoinURLReqMsg(m, state)
case m: TransferUserToMeetingRequestMsg => state = handleTransferUserToMeetingRequestMsg(m, state)
case m: ExtendBreakoutRoomsTimeReqMsg => state = handleExtendBreakoutRoomsTimeMsg(m, state)
case m: SendMessageToAllBreakoutRoomsMsg => state = handleSendMessageToAllBreakoutRoomsMsg(m, state)
case m: SendMessageToAllBreakoutRoomsReqMsg => state = handleSendMessageToAllBreakoutRoomsMsg(m, state)
// Voice
case m: UserLeftVoiceConfEvtMsg => handleUserLeftVoiceConfEvtMsg(m)

View File

@ -61,7 +61,7 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
case m: EndAllBreakoutRoomsMsg => logMessage(msg)
case m: TransferUserToMeetingRequestMsg => logMessage(msg)
case m: ExtendBreakoutRoomsTimeReqMsg => logMessage(msg)
case m: SendMessageToAllBreakoutRoomsMsg => logMessage(msg)
case m: SendMessageToAllBreakoutRoomsReqMsg => logMessage(msg)
case m: UserLeftVoiceConfToClientEvtMsg => logMessage(msg)
case m: UserLeftVoiceConfEvtMsg => logMessage(msg)
case m: RecordingStartedVoiceConfEvtMsg => logMessage(msg)

View File

@ -102,9 +102,13 @@ object ExtendBreakoutRoomsTimeEvtMsg { val NAME = "ExtendBreakoutRoomsTimeEvtMsg
case class ExtendBreakoutRoomsTimeEvtMsg(header: BbbClientMsgHeader, body: ExtendBreakoutRoomsTimeEvtMsgBody) extends BbbCoreMsg
case class ExtendBreakoutRoomsTimeEvtMsgBody(meetingId: String, extendTimeInMinutes: Int)
object SendMessageToAllBreakoutRoomsMsg { val NAME = "SendMessageToAllBreakoutRoomsMsg" }
case class SendMessageToAllBreakoutRoomsMsg(header: BbbClientMsgHeader, body: SendMessageToAllBreakoutRoomsMsgBody) extends StandardMsg
case class SendMessageToAllBreakoutRoomsMsgBody(meetingId: String, msg: String)
object SendMessageToAllBreakoutRoomsReqMsg { val NAME = "SendMessageToAllBreakoutRoomsReqMsg" }
case class SendMessageToAllBreakoutRoomsReqMsg(header: BbbClientMsgHeader, body: SendMessageToAllBreakoutRoomsReqMsgBody) extends StandardMsg
case class SendMessageToAllBreakoutRoomsReqMsgBody(meetingId: String, msg: String)
object SendMessageToAllBreakoutRoomsEvtMsg { val NAME = "SendMessageToAllBreakoutRoomsEvtMsg" }
case class SendMessageToAllBreakoutRoomsEvtMsg(header: BbbClientMsgHeader, body: SendMessageToAllBreakoutRoomsEvtMsgBody) extends BbbCoreMsg
case class SendMessageToAllBreakoutRoomsEvtMsgBody(meetingId: String, senderId: String, msg: String, totalOfRooms: Int)
// Common Value objects
case class BreakoutUserVO(id: String, name: String)

View File

@ -1,4 +1,6 @@
import RedisPubSub from '/imports/startup/server/redis';
import handleBreakoutRoomsList from './handlers/breakoutRoomsList';
import messageToAllSent from '/imports/api/breakouts-history/server/handlers/messageToAllSent';
RedisPubSub.on('BreakoutRoomsListEvtMsg', handleBreakoutRoomsList);
RedisPubSub.on('SendMessageToAllBreakoutRoomsEvtMsg', messageToAllSent);

View File

@ -30,6 +30,6 @@ export default function handleBreakoutRoomsList({ body }) {
Logger.info(`Upserted rooms to breakout-history Data: meeting=${meetingId}`);
}
} catch (err) {
Logger.error(`Adding note to the collection breakout-history: ${err}`);
Logger.error(`Adding rooms to the collection breakout-history: ${err}`);
}
}

View File

@ -0,0 +1,43 @@
import Logger from '/imports/startup/server/logger';
import { check } from 'meteor/check';
import BreakoutsHistory from '/imports/api/breakouts-history';
export default function handleSendMessageToAllBreakoutRoomsEvtMsg({ body }, meetingId) {
const {
senderId,
msg,
totalOfRooms,
} = body;
check(meetingId, String);
check(senderId, String);
check(msg, String);
check(totalOfRooms, Number);
const selector = {
meetingId,
};
const modifier = {
$push: {
broadcastMsgs: {
senderId,
msg,
totalOfRooms,
},
},
};
try {
const { insertedId } = BreakoutsHistory.upsert(selector, modifier);
if (insertedId) {
Logger.info(`Added broadCastMsg to breakout-history Data: meeting=${meetingId}`);
} else {
Logger.info(`Upserted broadCastMsg to breakout-history Data: meeting=${meetingId}`);
}
} catch (err) {
Logger.error(`Adding broadCastMsg to the collection breakout-history: ${err}`);
}
}

View File

@ -7,7 +7,7 @@ import Logger from '/imports/startup/server/logger';
export default function sendMessageToAllBreakouts({ msg }) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'SendMessageToAllBreakoutRoomsMsg';
const EVENT_NAME = 'SendMessageToAllBreakoutRoomsReqMsg';
try {
const { meetingId, requesterUserId } = extractCredentials(this.userId);

View File

@ -482,19 +482,6 @@ class BreakoutRoom extends PureComponent {
/>
</Styled.ExtendTimeContainer>
) : null}
{amIModerator
? (
<MessageFormContainer
{...{
title: intl.formatMessage(intlMessages.chatTitleMsgAllRooms),
}}
chatId="breakouts"
chatTitle={intl.formatMessage(intlMessages.chatTitleMsgAllRooms)}
disabled={!isMeteorConnected}
connected={isMeteorConnected}
locked={false}
/>
) : null }
<Styled.Duration>
<BreakoutRoomContainer
messageDuration={intlMessages.breakoutDuration}
@ -537,6 +524,20 @@ class BreakoutRoom extends PureComponent {
this.closePanel();
}}
/>
{amIModerator
? (
<MessageFormContainer
{...{
title: intl.formatMessage(intlMessages.chatTitleMsgAllRooms),
}}
chatId="breakouts"
chatTitle={intl.formatMessage(intlMessages.chatTitleMsgAllRooms)}
disabled={!isMeteorConnected}
connected={isMeteorConnected}
locked={false}
/>
) : null }
{amIModerator ? <Styled.Separator /> : null }
{this.renderBreakoutRooms()}
{this.renderDuration()}
{

View File

@ -3,9 +3,12 @@ import { defineMessages, injectIntl } from 'react-intl';
import deviceInfo from '/imports/utils/deviceInfo';
import PropTypes from 'prop-types';
import Styled from './styles';
import { notify } from '/imports/ui/services/notification';
const propTypes = {
intl: PropTypes.object.isRequired,
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
chatId: PropTypes.string.isRequired,
disabled: PropTypes.bool.isRequired,
minMessageLength: PropTypes.number.isRequired,
@ -39,6 +42,10 @@ const messages = defineMessages({
errorChatLocked: {
id: 'app.chat.locked',
},
msgToBreakoutsSent: {
id: 'app.createBreakoutRoom.msgToBreakoutsSent',
description: 'Message for chat sent successfully',
},
});
const CHAT_CONFIG = Meteor.settings.public.chat;
@ -74,16 +81,23 @@ class MessageForm extends PureComponent {
const {
connected,
locked,
userMessagesTooAllBreakouts,
intl,
} = this.props;
const { message } = this.state;
this.updateUnsentMessagesCollection(prevProps.chatId, message);
this.setState(
{
error: null,
hasErrors: false,
}, this.setMessageState(),
// Check for new messages sent and notify user
if (prevProps.userMessagesTooAllBreakouts.length < userMessagesTooAllBreakouts.length) {
for (let i = prevProps.userMessagesTooAllBreakouts.length;
i < userMessagesTooAllBreakouts.length;
i += 1) {
notify(
intl.formatMessage(messages.msgToBreakoutsSent, { 0: userMessagesTooAllBreakouts[i].totalOfRooms }), 'info', 'group_chat',
);
}
}
this.updateUnsentMessagesCollection(prevProps.chatId, message);
if (
connected !== prevProps.connected
@ -100,47 +114,6 @@ class MessageForm extends PureComponent {
this.setMessageState();
}
setMessageHint() {
const {
connected,
disabled,
intl,
locked,
} = this.props;
let chatDisabledHint = null;
if (disabled) {
if (connected) {
if (locked) {
chatDisabledHint = messages.errorChatLocked;
}
} else {
chatDisabledHint = messages.errorServerDisconnected;
}
}
this.setState({
hasErrors: disabled,
error: chatDisabledHint ? intl.formatMessage(chatDisabledHint) : null,
});
}
setMessageState() {
const { chatId, UnsentMessagesCollection } = this.props;
const unsentMessageByChat = UnsentMessagesCollection.findOne({ chatId },
{ fields: { message: 1 } });
this.setState({ message: unsentMessageByChat ? unsentMessageByChat.message : '' });
}
updateUnsentMessagesCollection(chatId, message) {
const { UnsentMessagesCollection } = this.props;
UnsentMessagesCollection.upsert(
{ chatId },
{ $set: { message } },
);
}
handleMessageKeyDown(e) {
// TODO Prevent send message pressing enter on mobile and/or virtual keyboard
if (e.keyCode === 13 && !e.shiftKey) {
@ -207,6 +180,47 @@ class MessageForm extends PureComponent {
this.setState({ message: '', hasErrors: false });
}
setMessageHint() {
const {
connected,
disabled,
intl,
locked,
} = this.props;
let chatDisabledHint = null;
if (disabled) {
if (connected) {
if (locked) {
chatDisabledHint = messages.errorChatLocked;
}
} else {
chatDisabledHint = messages.errorServerDisconnected;
}
}
this.setState({
hasErrors: disabled,
error: chatDisabledHint ? intl.formatMessage(chatDisabledHint) : null,
});
}
setMessageState() {
const { chatId, UnsentMessagesCollection } = this.props;
const unsentMessageByChat = UnsentMessagesCollection.findOne({ chatId },
{ fields: { message: 1 } });
this.setState({ message: unsentMessageByChat ? unsentMessageByChat.message : '' });
}
updateUnsentMessagesCollection(chatId, message) {
const { UnsentMessagesCollection } = this.props;
UnsentMessagesCollection.upsert(
{ chatId },
{ $set: { message } },
);
}
render() {
const {
intl,

View File

@ -1,4 +1,5 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import MessageForm from './component';
import BreakoutService from '/imports/ui/components/breakout-room/service';
import ChatService from '/imports/ui/components/chat/service';
@ -20,4 +21,12 @@ const MessageFormContainer = (props) => {
);
};
export default MessageFormContainer;
export default withTracker((props) => {
const userMessagesTooAllBreakouts = BreakoutService.getUserMessagesToAllBreakouts();
return {
...props,
userMessagesTooAllBreakouts,
};
})(MessageFormContainer);
// export default MessageFormContainer;

View File

@ -6,6 +6,7 @@ import Users from '/imports/api/users';
import UserListService from '/imports/ui/components/user-list/service';
import fp from 'lodash/fp';
import UsersPersistentData from '/imports/api/users-persistent-data';
import BreakoutsHistory from '/imports/api/breakouts-history';
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
@ -88,6 +89,15 @@ const sendMessageToAllBreakouts = (msg) => {
return true;
};
const getUserMessagesToAllBreakouts = () => {
const breakoutHistory = BreakoutsHistory.findOne(
{ meetingId: Auth.meetingID },
{ fields: { broadcastMsgs: 1 } },
) || {};
return (breakoutHistory.broadcastMsgs || []).filter((msg) => msg.senderId === Auth.userID);
};
const transferUserToMeeting = (fromMeetingId, toMeetingId) =>
makeCall('transferUser', fromMeetingId, toMeetingId);
@ -201,6 +211,7 @@ export default {
endAllBreakouts,
extendBreakoutsTime,
sendMessageToAllBreakouts,
getUserMessagesToAllBreakouts,
isExtendTimeHigherThanMeetingRemaining,
requestJoinURL,
getBreakoutRoomUrl,

View File

@ -3,7 +3,7 @@ import {
systemMessageBorderColor,
mdPaddingX,
borderSize,
listItemBgHover,
listItemBgHover, borderSizeSmall,
} from '/imports/ui/stylesheets/styled-components/general';
import {
colorPrimary,
@ -11,7 +11,7 @@ import {
colorDanger,
colorGrayDark,
userListBg,
colorWhite,
colorWhite, colorGrayLighter,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
headingsFontWeight,
@ -230,6 +230,15 @@ const HeaderButton = styled(Button)`
}
}`;
const Separator = styled.div`
position: relative;
width: 100%;
height: 10px;
height: ${borderSizeSmall};
background-color: ${colorGrayLighter};
margin: 30px 0px;
`;
export default {
BreakoutActions,
AlreadyConnected,
@ -251,4 +260,5 @@ export default {
Duration,
Panel,
HeaderButton,
Separator,
};

View File

@ -130,6 +130,7 @@ class TimeWindowChatItem extends PureComponent {
isModerator,
avatar,
isOnline,
isSystemSender,
} = this.props;
const dateTime = new Date(timestamp);
@ -140,7 +141,7 @@ class TimeWindowChatItem extends PureComponent {
return (
<Styled.Item key={`time-window-${messageKey}`}>
<Styled.Wrapper>
<Styled.Wrapper isSystemSender={isSystemSender}>
<Styled.AvatarWrapper>
<UserAvatar
color={color}

View File

@ -31,7 +31,7 @@ const TimeWindowChatItemContainer = (props) => {
name: senderName,
color: '#01579b',
avatar: '',
role: '',
role: ROLE_MODERATOR,
loggedOut: false,
} : users[Auth.meetingID][sender];
const messageKey = key;
@ -42,6 +42,7 @@ const TimeWindowChatItemContainer = (props) => {
...{
color: user?.color,
isModerator: user?.role === ROLE_MODERATOR,
isSystemSender: sender === 'SYSTEM',
isOnline: !user?.loggedOut,
avatar: user?.avatar,
name: user?.name,

View File

@ -9,6 +9,8 @@ import {
systemMessageBackgroundColor,
systemMessageBorderColor,
systemMessageFontColor,
highlightedMessageBackgroundColor,
highlightedMessageBorderColor,
colorHeading,
colorGrayLight,
palettePlaceholderText,
@ -63,6 +65,13 @@ const Wrapper = styled.div`
position: relative;
margin: ${borderSize} 0 0 ${borderSize};
${({ isSystemSender }) => isSystemSender && `
background-color: ${highlightedMessageBackgroundColor};
border-left: 2px solid ${highlightedMessageBorderColor};
border-radius: 0px 3px 3px 0px;
padding: 8px 2px;
`}
[dir="rtl"] & {
margin: ${borderSize} ${borderSize} 0 0;
}

View File

@ -75,6 +75,8 @@ const loaderBullet = `var(--loader-bullet, ${colorWhite})`;
const systemMessageBackgroundColor = 'var(--system-message-background-color, #F9FBFC)';
const systemMessageBorderColor = 'var(--system-message-border-color, #C5CDD4)';
const systemMessageFontColor = `var(--system-message-font-color, ${colorGrayDark})`;
const highlightedMessageBackgroundColor = 'var(--system-message-background-color, #fef9f1)';
const highlightedMessageBorderColor = 'var(--system-message-border-color, #f5c67f)';
const colorHeading = `var(--color-heading, ${colorGrayDark})`;
const palettePlaceholderText = 'var(--palette-placeholder-text, #787675)';
const pollAnnotationGray = 'var(--poll-annotation-gray, #333333)';
@ -164,6 +166,8 @@ export {
systemMessageBackgroundColor,
systemMessageBorderColor,
systemMessageFontColor,
highlightedMessageBackgroundColor,
highlightedMessageBorderColor,
colorHeading,
palettePlaceholderText,
pollAnnotationGray,

View File

@ -867,7 +867,8 @@
"app.createBreakoutRoom.resetAssignments": "Reset assignments",
"app.createBreakoutRoom.resetAssignmentsDesc": "Reset all user room assignments",
"app.createBreakoutRoom.endAllBreakouts": "End all breakout rooms",
"app.createBreakoutRoom.chatTitleMsgAllRooms": "all rooms TODAs",
"app.createBreakoutRoom.chatTitleMsgAllRooms": "all rooms",
"app.createBreakoutRoom.msgToBreakoutsSent": "Message was sent to {0} breakout rooms",
"app.createBreakoutRoom.roomName": "{0} (Room - {1})",
"app.createBreakoutRoom.doneLabel": "Done",
"app.createBreakoutRoom.nextLabel": "Next",