Merge pull request #10195 from KDSBrowne/dev2.3-fix-poll-chat-grouping

Improve poll results in public chat
This commit is contained in:
Anton Georgiev 2020-08-11 09:53:53 -04:00 committed by GitHub
commit 9bdaf3960b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 126 deletions

View File

@ -4,6 +4,7 @@ import { FormattedTime, defineMessages, injectIntl } from 'react-intl';
import _ from 'lodash';
import Icon from '/imports/ui/components/icon/component';
import UserAvatar from '/imports/ui/components/user-avatar/component';
import cx from 'classnames';
import Message from './message/component';
import { styles } from './styles';
@ -42,10 +43,6 @@ const intlMessages = defineMessages({
id: 'app.chat.pollResult',
description: 'used in place of user name who published poll to chat',
},
legendTitle: {
id: 'app.polling.pollingTitle',
description: 'heading for chat poll legend',
},
});
class MessageListItem extends Component {
@ -111,13 +108,14 @@ class MessageListItem extends Component {
handleReadMessage,
scrollArea,
intl,
chats,
messages,
} = this.props;
if (chats.length < 1) return null;
if (messages && messages[0].text.includes('bbb-published-poll-<br/>')) {
return this.renderPollItem();
}
const dateTime = new Date(time);
const regEx = /<a[^>]+>/i;
return (
@ -149,7 +147,7 @@ class MessageListItem extends Component {
</time>
</div>
<div className={styles.messages} data-test="chatUserMessage">
{chats.map(message => (
{messages.map(message => (
<Message
className={(regEx.test(message.text) ? styles.hyperlink : styles.message)}
key={message.id}
@ -173,53 +171,17 @@ class MessageListItem extends Component {
user,
time,
intl,
polls,
isDefaultPoll,
messages,
scrollArea,
chatAreaId,
lastReadMessageTime,
handleReadMessage,
} = this.props;
if (polls.length < 1) return null;
const dateTime = new Date(time);
let pollText = [];
const pollElement = [];
const legendElements = [
(<div
className={styles.optionsTitle}
key={_.uniqueId('chat-poll-options-')}
>
{intl.formatMessage(intlMessages.legendTitle)}
</div>),
];
let isDefault = true;
polls.forEach((poll) => {
isDefault = isDefaultPoll(poll.text);
pollText = poll.text.split('<br/>');
pollElement.push(pollText.map((p, index) => {
if (!isDefault) {
legendElements.push(
<div key={_.uniqueId('chat-poll-legend-')} className={styles.pollLegend}>
<span>{`${index + 1}: `}</span>
<span className={styles.pollOption}>{p.split(':')[0]}</span>
</div>,
);
}
return (
<div key={_.uniqueId('chat-poll-result-')} className={styles.pollLine}>
{!isDefault ? p.replace(p.split(':')[0], index + 1) : p}
</div>
);
}));
});
if (!isDefault) {
pollElement.push(<div key={_.uniqueId('chat-poll-separator-')} className={styles.divider} />);
pollElement.push(legendElements);
}
return polls ? (
return messages ? (
<div className={styles.item} key={_.uniqueId('message-poll-item-')}>
<div className={styles.wrapper} ref={(ref) => { this.item = ref; }}>
<div className={styles.avatarWrapper}>
@ -240,15 +202,19 @@ class MessageListItem extends Component {
<FormattedTime value={dateTime} />
</time>
</div>
<div className={styles.messages}>
{polls[0] ? (
<div className={styles.pollWrapper} style={{ borderLeft: `3px ${user.color} solid` }}>
{
pollElement
}
</div>
) : null}
</div>
<Message
type="poll"
className={cx(styles.message, styles.pollWrapper)}
key={messages[0].id}
text={messages[0].text}
time={messages[0].time}
chatAreaId={chatAreaId}
lastReadMessageTime={lastReadMessageTime}
handleReadMessage={handleReadMessage}
scrollArea={scrollArea}
color={user.color}
isDefaultPoll={isDefaultPoll(messages[0].text.replace('bbb-published-poll-<br/>', ''))}
/>
</div>
</div>
</div>
@ -266,10 +232,7 @@ class MessageListItem extends Component {
return (
<div className={styles.item}>
{[
this.renderPollItem(),
this.renderMessageItem(),
]}
{this.renderMessageItem()}
</div>
);
}

View File

@ -15,26 +15,10 @@ export default withTracker(({ message }) => {
const mappedMessage = ChatService.mapGroupMessage(message);
const messages = mappedMessage.content;
const chats = [];
const polls = [];
if (messages.length > 0) {
messages.forEach((m) => {
if (m.text.includes('bbb-published-poll-<br/>')) {
m.text = m.text.replace(/^bbb-published-poll-<br\/>/g, '');
polls.push(m);
} else {
chats.push(m);
}
});
}
return {
messages,
user: mappedMessage.sender,
time: mappedMessage.time,
chats,
polls,
isDefaultPoll: (pollText) => {
const pollValue = pollText.replace(/<br\/>|[ :|%\n\d+]/g, '');
switch (pollValue) {

View File

@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import fastdom from 'fastdom';
import { defineMessages, injectIntl } from 'react-intl';
const propTypes = {
text: PropTypes.string.isRequired,
@ -34,13 +35,22 @@ const isElementInViewport = (el) => {
);
};
export default class MessageListItem extends PureComponent {
const intlMessages = defineMessages({
legendTitle: {
id: 'app.polling.pollingTitle',
description: 'heading for chat poll legend',
},
});
class MessageListItem extends PureComponent {
constructor(props) {
super(props);
this.ticking = false;
this.handleMessageInViewport = _.debounce(this.handleMessageInViewport.bind(this), 50);
this.renderPollListItem = this.renderPollListItem.bind(this);
}
componentDidMount() {
@ -145,17 +155,56 @@ export default class MessageListItem extends PureComponent {
});
}
render() {
renderPollListItem() {
const {
intl,
text,
className,
color,
isDefaultPoll,
} = this.props;
const formatBoldBlack = s => s.bold().fontcolor('black');
let _text = text.replace('bbb-published-poll-<br/>', '');
if (!isDefaultPoll) {
const entries = _text.split('<br/>');
const options = [];
entries.map((e) => { options.push([e.slice(0, e.indexOf(':'))]); return e; });
options.map((o, idx) => {
_text = formatBoldBlack(_text.replace(o, idx + 1));
return _text;
});
_text += formatBoldBlack(`<br/><br/>${intl.formatMessage(intlMessages.legendTitle)}`);
options.map((o, idx) => { _text += `<br/>${idx + 1}: ${o}`; return _text; });
}
return (
<p
className={className}
style={{ borderLeft: `3px ${color} solid` }}
ref={(ref) => { this.text = ref; }}
dangerouslySetInnerHTML={{ __html: isDefaultPoll ? formatBoldBlack(_text) : _text }}
data-test="chatMessageText"
/>
);
}
render() {
const {
text,
type,
className,
} = this.props;
if (type === 'poll') return this.renderPollListItem();
return (
<p
className={className}
ref={(ref) => { this.text = ref; }}
dangerouslySetInnerHTML={{ __html: text }}
className={className}
data-test="chatMessageText"
/>
);
@ -164,3 +213,5 @@ export default class MessageListItem extends PureComponent {
MessageListItem.propTypes = propTypes;
MessageListItem.defaultProps = defaultProps;
export default injectIntl(MessageListItem);

View File

@ -4,7 +4,7 @@
--systemMessage-background-color: #F9FBFC;
--systemMessage-border-color: #C5CDD4;
--systemMessage-font-color: var(--color-dark-grey);
--chat-poll-margin-sm: .25rem;
--chat-poll-margin-sm: .5rem;
}
.item {
@ -159,42 +159,11 @@
bottom: var(--border-size-large);
}
.pollLine {
overflow-wrap: break-word;
font-size: var(--font-size-large);
font-weight: 600;
}
.pollWrapper {
background: var(--systemMessage-background-color);
border: solid 1px var(--color-gray-lighter);
border-radius: var(--border-radius);
padding: var(--chat-poll-margin-sm);
padding-left: 1rem;
margin-top: .5rem;
background: var(--systemMessage-background-color);
}
.pollLegend {
display: flex;
flex-direction: row;
margin-top: var(--chat-poll-margin-sm);
}
.pollOption {
word-break: break-word;
margin-left: var(--md-padding-y);
}
.optionsTitle {
font-weight: bold;
margin-top: var(--md-padding-y);
}
.divider {
position: relative;
height: 1px;
width: 95%;
margin-right: auto;
margin-top: var(--md-padding-y);
border-bottom: solid 1px var(--color-gray-lightest);
margin-top: var(--chat-poll-margin-sm) !important;
}

View File

@ -31,6 +31,8 @@ const UnsentMessagesCollection = new Mongo.Collection(null);
// session for closed chat list
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
const POLL_MESSAGE_PREFIX = 'bbb-published-poll-<br/>';
const getUser = userId => Users.findOne({ userId });
const getWelcomeProp = () => Meetings.findOne({ meetingId: Auth.meetingID },
@ -83,11 +85,15 @@ const reduceGroupMessages = (previous, current) => {
return previous.concat(currentMessage);
}
// Check if the last message is from the same user and time discrepancy
// between the two messages exceeds window and then group current message
// with the last one
// between the two messages exceeds window and then group current
// message with the last one
const timeOfLastMessage = lastMessage.content[lastMessage.content.length - 1].time;
const isOrWasPoll = currentMessage.message.includes(POLL_MESSAGE_PREFIX)
|| lastMessage.message.includes(POLL_MESSAGE_PREFIX);
const groupingWindow = isOrWasPoll ? 0 : GROUPING_MESSAGES_WINDOW;
if (lastMessage.sender === currentMessage.sender
&& (currentMessage.timestamp - timeOfLastMessage) <= GROUPING_MESSAGES_WINDOW) {
&& (currentMessage.timestamp - timeOfLastMessage) <= groupingWindow) {
lastMessage.content.push(currentMessage.content.pop());
return previous;
}

View File

@ -194,12 +194,15 @@ class LiveResult extends PureComponent {
Session.set('pollInitiated', false);
Service.publishPoll();
const { answers, numRespondents } = currentPoll;
let responded = 0;
let resultString = 'bbb-published-poll-\n';
answers.forEach((item) => {
const pct = Math.round(item.numVotes / numRespondents * 100);
answers.map((item) => {
responded += item.numVotes;
return item;
}).map((item) => {
const numResponded = responded === numRespondents ? numRespondents : responded;
const pct = Math.round(item.numVotes / numResponded * 100);
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;
resultString += `${item.key}: ${item.numVotes || 0} | ${pctFotmatted}\n`;
});

View File

@ -59,7 +59,7 @@ const sendGroupMessage = (message) => {
color: '0',
correlationId: `${PUBLIC_CHAT_SYSTEM_ID}-${Date.now()}`,
sender: {
id: PUBLIC_CHAT_SYSTEM_ID,
id: Auth.userID,
name: '',
},
message,
@ -69,9 +69,12 @@ const sendGroupMessage = (message) => {
};
export default {
amIPresenter: () => Users.findOne({ userId: Auth.userID }, { fields: { presenter: 1 } }).presenter,
amIPresenter: () => Users.findOne(
{ userId: Auth.userID },
{ fields: { presenter: 1 } },
).presenter,
pollTypes,
stopPoll: () => makeCall('stopPoll', Auth.userId),
stopPoll: () => makeCall('stopPoll', Auth.userID),
currentPoll: () => Polls.findOne({ meetingId: Auth.meetingID }),
pollAnswerIds,
sendGroupMessage,