Merge pull request #12353 from germanocaumo/poll-record-improvs

Recorded polling events improvements
This commit is contained in:
Anton Georgiev 2021-05-13 13:43:36 -04:00 committed by GitHub
commit 1bc324b657
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 113 additions and 32 deletions

View File

@ -23,12 +23,12 @@ trait RespondToPollReqMsgHdlr {
bus.outGW.send(msgEvent)
}
def broadcastUserRespondedToPollRecordMsg(msg: RespondToPollReqMsg, pollId: String, answerId: Int): Unit = {
def broadcastUserRespondedToPollRecordMsg(msg: RespondToPollReqMsg, pollId: String, answerId: Int, answer: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(UserRespondedToPollRecordMsg.NAME, routing)
val header = BbbClientMsgHeader(UserRespondedToPollRecordMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = UserRespondedToPollRecordMsgBody(pollId, answerId)
val body = UserRespondedToPollRecordMsgBody(pollId, answerId, answer)
val event = UserRespondedToPollRecordMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
@ -50,7 +50,12 @@ trait RespondToPollReqMsgHdlr {
msg.body.questionId, msg.body.answerId, liveMeeting)
} yield {
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
broadcastUserRespondedToPollRecordMsg(msg, pollId, msg.body.answerId)
for {
simplePoll <- Polls.getSimplePoll(pollId, liveMeeting.polls)
} yield {
val answerText = simplePoll.answers(msg.body.answerId).key
broadcastUserRespondedToPollRecordMsg(msg, pollId, msg.body.answerId, answerText)
}
for {
presenter <- Users2x.findPresenter(liveMeeting.users2x)

View File

@ -18,7 +18,7 @@ object Polls {
val numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter
for {
poll <- PollFactory.createPoll(stampedPollId, pollType, numRespondents, None)
poll <- PollFactory.createPoll(stampedPollId, pollType, numRespondents, None, Some(question))
} yield {
lm.polls.save(poll)
poll
@ -172,7 +172,7 @@ object Polls {
def createPoll(stampedPollId: String): Option[Poll] = {
val numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter
for {
poll <- PollFactory.createPoll(stampedPollId, pollType, numRespondents, Some(answers))
poll <- PollFactory.createPoll(stampedPollId, pollType, numRespondents, Some(answers), Some(question))
} yield {
lm.polls.save(poll)
poll
@ -435,7 +435,7 @@ object PollType {
val CustomPollType = "CUSTOM"
val LetterPollType = "A-"
val NumberPollType = "1-"
val ResponsePollType = "RP"
val ResponsePollType = "R-"
}
object PollFactory {
@ -561,12 +561,12 @@ object PollFactory {
questionOption
}
def createPoll(id: String, pollType: String, numRespondents: Int, answers: Option[Seq[String]]): Option[Poll] = {
def createPoll(id: String, pollType: String, numRespondents: Int, answers: Option[Seq[String]], title: Option[String]): Option[Poll] = {
var poll: Option[Poll] = None
createQuestion(pollType, answers) match {
case Some(question) => {
poll = Some(new Poll(id, Array(question), numRespondents, None))
poll = Some(new Poll(id, Array(question), numRespondents, title))
}
case None => poll = None
}
@ -644,7 +644,7 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
}
def toSimplePollResultOutVO(): SimplePollResultOutVO = {
new SimplePollResultOutVO(id, questions(0).toSimpleVotesOutVO(), numRespondents, _numResponders)
new SimplePollResultOutVO(id, title, questions(0).toSimpleVotesOutVO(), numRespondents, _numResponders)
}
}

View File

@ -0,0 +1,53 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.core.record.events
import org.bigbluebutton.common2.domain.SimpleVoteOutVO
import org.bigbluebutton.common2.util.JsonUtil
class PollPublishedRecordEvent extends AbstractPollRecordEvent {
import PollPublishedRecordEvent._
setEvent("PollPublishedRecordEvent")
def setQuestion(question: String) {
eventMap.put(QUESTION, question)
}
def setAnswers(answers: Array[SimpleVoteOutVO]) {
eventMap.put(ANSWERS, JsonUtil.toJson(answers))
}
def setNumRespondents(numRespondents: Int) {
eventMap.put(NUM_RESPONDENTS, Integer.toString(numRespondents))
}
def setNumResponders(numResponders: Int) {
eventMap.put(NUM_RESPONDERS, Integer.toString(numResponders))
}
}
object PollPublishedRecordEvent {
protected final val USER_ID = "userId"
protected final val QUESTION = "question"
protected final val ANSWERS = "answers"
protected final val NUM_RESPONDENTS = "numRespondents"
protected final val NUM_RESPONDERS = "numResponders"
}

View File

@ -31,12 +31,22 @@ class PollStartedRecordEvent extends AbstractPollRecordEvent {
eventMap.put(USER_ID, userId)
}
def setQuestion(question: String) {
eventMap.put(QUESTION, question)
}
def setAnswers(answers: Array[SimpleAnswerOutVO]) {
eventMap.put(ANSWERS, JsonUtil.toJson(answers))
}
def setType(pollType: String) {
eventMap.put(TYPE, pollType)
}
}
object PollStartedRecordEvent {
protected final val USER_ID = "userId"
protected final val QUESTION = "question"
protected final val ANSWERS = "answers"
protected final val TYPE = "type"
}

View File

@ -31,9 +31,14 @@ class UserRespondedToPollRecordEvent extends AbstractPollRecordEvent {
def setAnswerId(answerId: Int) {
eventMap.put(ANSWER_ID, Integer.toString(answerId))
}
def setAnswer(answer: String) {
eventMap.put(ANSWER, answer)
}
}
object UserRespondedToPollRecordEvent {
protected final val USER_ID = "userId"
protected final val ANSWER_ID = "answerId"
protected final val ANSWER = "answer"
}

View File

@ -555,7 +555,9 @@ class RedisRecorderActor(
private def handlePollStartedEvtMsg(msg: PollStartedEvtMsg): Unit = {
val ev = new PollStartedRecordEvent()
ev.setPollId(msg.body.pollId)
ev.setQuestion(msg.body.question)
ev.setAnswers(msg.body.poll.answers)
ev.setType(msg.body.pollType)
record(msg.header.meetingId, ev.toMap.asJava)
}
@ -565,23 +567,27 @@ class RedisRecorderActor(
ev.setPollId(msg.body.pollId)
ev.setUserId(msg.header.userId)
ev.setAnswerId(msg.body.answerId)
ev.setAnswer(msg.body.answer)
record(msg.header.meetingId, ev.toMap.asJava)
}
private def handlePollStoppedEvtMsg(msg: PollStoppedEvtMsg): Unit = {
pollStoppedRecordHelper(msg.header.meetingId, msg.body.pollId)
val ev = new PollStoppedRecordEvent()
ev.setPollId(msg.body.pollId)
record(msg.header.meetingId, ev.toMap.asJava)
}
private def handlePollShowResultEvtMsg(msg: PollShowResultEvtMsg): Unit = {
pollStoppedRecordHelper(msg.header.meetingId, msg.body.pollId)
}
val ev = new PollPublishedRecordEvent()
ev.setPollId(msg.body.pollId)
ev.setQuestion(msg.body.poll.title.getOrElse(""))
ev.setAnswers(msg.body.poll.answers)
ev.setNumRespondents(msg.body.poll.numRespondents)
ev.setNumResponders(msg.body.poll.numResponders)
private def pollStoppedRecordHelper(meetingId: String, pollId: String): Unit = {
val ev = new PollStoppedRecordEvent()
ev.setPollId(pollId)
record(meetingId, ev.toMap.asJava)
record(msg.header.meetingId, ev.toMap.asJava)
}
private def checkRecordingDBStatus(): Unit = {

View File

@ -75,7 +75,7 @@ case class Meeting2x(defaultProps: DefaultProps, meetingStatus: MeetingStatus)
case class SimpleAnswerOutVO(id: Int, key: String)
case class SimplePollOutVO(id: String, answers: Array[SimpleAnswerOutVO])
case class SimpleVoteOutVO(id: Int, key: String, numVotes: Int)
case class SimplePollResultOutVO(id: String, answers: Array[SimpleVoteOutVO], numRespondents: Int, numResponders: Int)
case class SimplePollResultOutVO(id: String, title: Option[String], answers: Array[SimpleVoteOutVO], numRespondents: Int, numResponders: Int)
case class Responder(userId: String, name: String)
case class AnswerVO(id: Int, key: String, text: Option[String], responders: Option[Array[Responder]])
case class QuestionVO(id: Int, questionType: String, multiResponse: Boolean, questionText: Option[String], answers: Option[Array[AnswerVO]])

View File

@ -28,7 +28,7 @@ case class PollUpdatedEvtMsgBody(pollId: String, poll: SimplePollResultOutVO)
object UserRespondedToPollRecordMsg { val NAME = "UserRespondedToPollRecordMsg" }
case class UserRespondedToPollRecordMsg(header: BbbClientMsgHeader, body: UserRespondedToPollRecordMsgBody) extends BbbCoreMsg
case class UserRespondedToPollRecordMsgBody(pollId: String, answerId: Int)
case class UserRespondedToPollRecordMsgBody(pollId: String, answerId: Int, answer: String)
object RespondToPollReqMsg { val NAME = "RespondToPollReqMsg" }
case class RespondToPollReqMsg(header: BbbClientMsgHeader, body: RespondToPollReqMsgBody) extends StandardMsg

View File

@ -7,6 +7,7 @@ export default function userVoted({ body }, meetingId) {
check(meetingId, String);
check(poll, {
id: String,
title: String,
answers: [
{
id: Number,

View File

@ -229,7 +229,7 @@ class Poll extends Component {
const { optList, type, error } = this.state;
const list = [...optList];
const validatedVal = validateInput(e.target.value).replace(/\s{2,}/g, ' ');
const clearError = validatedVal.length > 0 && type !== 'RP';
const clearError = validatedVal.length > 0 && type !== 'R-';
list[index] = { val: validatedVal };
this.setState({ optList: list, error: clearError ? null : error });
}
@ -237,7 +237,7 @@ class Poll extends Component {
handleTextareaChange(e) {
const { type, error } = this.state;
const validatedQuestion = validateInput(e.target.value);
const clearError = validatedQuestion.length > 0 && type === 'RP';
const clearError = validatedQuestion.length > 0 && type === 'R-';
this.setState({ question: validateInput(e.target.value), error: clearError ? null : error });
}
@ -363,7 +363,7 @@ class Poll extends Component {
)
: <div style={{ width: '40px' }} />}
</div>
{!hasVal && type !== 'RP' && error ? (
{!hasVal && type !== 'R-' && error ? (
<div className={styles.inputError}>{error}</div>
) : (
<div className={styles.errorSpacer}>&nbsp;</div>
@ -425,7 +425,7 @@ class Poll extends Component {
maxLength={QUESTION_MAX_INPUT_CHARS}
placeholder={intl.formatMessage(intlMessages.questionLabel)}
/>
{(type === 'RP' && question.length === 0 && error) ? (
{(type === 'R-' && question.length === 0 && error) ? (
<div className={styles.inputError}>{error}</div>
) : (
<div className={styles.errorSpacer}>&nbsp;</div>
@ -483,8 +483,8 @@ class Poll extends Component {
<Button
label={intl.formatMessage(intlMessages.userResponse)}
color="default"
onClick={() => { this.setState({ type: 'RP' }); }}
className={cx(styles.pBtn, styles.fullWidth, { [styles.selectedBtnWhite]: type === 'RP' })}
onClick={() => { this.setState({ type: 'R-' }); }}
className={cx(styles.pBtn, styles.fullWidth, { [styles.selectedBtnWhite]: type === 'R-' })}
/>
</div>
{ type
@ -492,7 +492,7 @@ class Poll extends Component {
<div data-test="responseChoices">
<h4>{intl.formatMessage(intlMessages.responseChoices)}</h4>
{
type === 'RP'
type === 'R-'
&& (
<div>
<span>{intl.formatMessage(intlMessages.typedResponseDesc)}</span>
@ -506,7 +506,7 @@ class Poll extends Component {
)
}
{
(defaultPoll || type === 'RP')
(defaultPoll || type === 'R-')
&& (
<div style={{
display: 'flex',
@ -538,8 +538,8 @@ class Poll extends Component {
});
let err = null;
if (type === 'RP' && question.length === 0) err = intl.formatMessage(intlMessages.questionErr);
if (!hasVal && type !== 'RP') err = intl.formatMessage(intlMessages.optionErr);
if (type === 'R-' && question.length === 0) err = intl.formatMessage(intlMessages.questionErr);
if (!hasVal && type !== 'R-') err = intl.formatMessage(intlMessages.optionErr);
if (err) return this.setState({ error: err });
return this.setState({ isPolling: true }, () => {
@ -561,7 +561,7 @@ class Poll extends Component {
}}
/>
{
FILE_DRAG_AND_DROP_ENABLED && type !== 'RP' && this.renderDragDrop()
FILE_DRAG_AND_DROP_ENABLED && type !== 'R-' && this.renderDragDrop()
}
</div>
)

View File

@ -167,6 +167,7 @@
.title {
font-weight: bold;
word-break: break-word;
}
@keyframes ellipsis {

View File

@ -129,7 +129,7 @@ class Polling extends Component {
)
}
{
poll.pollType !== 'RP' && (
poll.pollType !== 'R-' && (
<span>
{
question.length === 0
@ -184,7 +184,7 @@ class Polling extends Component {
)
}
{
poll.pollType === 'RP'
poll.pollType === 'R-'
&& (
<div className={styles.typedResponseWrapper}>
<input