fix(polls) refactor storage of poll data

This commit is contained in:
apetrus20 2022-11-03 17:08:20 +02:00 committed by GitHub
parent 5692c3cb4d
commit ed139f53ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 72 deletions

View File

@ -5,7 +5,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { createPollEvent } from '../../analytics/AnalyticsEvents'; import { createPollEvent } from '../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../analytics/functions'; import { sendAnalytics } from '../../analytics/functions';
import { IReduxState } from '../../app/types'; import { IReduxState } from '../../app/types';
import { getLocalParticipant, getParticipantById } from '../../base/participants/functions'; import { getParticipantDisplayName } from '../../base/participants/functions';
import { useBoundSelector } from '../../base/util/hooks'; import { useBoundSelector } from '../../base/util/hooks';
import { registerVote, setVoteChanging } from '../actions'; import { registerVote, setVoteChanging } from '../actions';
import { COMMAND_ANSWER_POLL } from '../constants'; import { COMMAND_ANSWER_POLL } from '../constants';
@ -48,8 +48,6 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
const poll: IPoll = useSelector((state: IReduxState) => state['features/polls'].polls[pollId]); const poll: IPoll = useSelector((state: IReduxState) => state['features/polls'].polls[pollId]);
const { id: localId } = useSelector(getLocalParticipant) ?? { id: '' };
const [ checkBoxStates, setCheckBoxState ] = useState(() => { const [ checkBoxStates, setCheckBoxState ] = useState(() => {
if (poll.lastVote !== null) { if (poll.lastVote !== null) {
return [ ...poll.lastVote ]; return [ ...poll.lastVote ];
@ -57,7 +55,8 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
return new Array(poll.answers.length).fill(false); return new Array(poll.answers.length).fill(false);
}); });
const participant = useBoundSelector(getParticipantById, poll.senderId);
const participantName = useBoundSelector(getParticipantDisplayName, poll.senderId);
const setCheckbox = useCallback((index, state) => { const setCheckbox = useCallback((index, state) => {
const newCheckBoxStates = [ ...checkBoxStates ]; const newCheckBoxStates = [ ...checkBoxStates ];
@ -69,15 +68,10 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
const dispatch = useDispatch(); const dispatch = useDispatch();
const localParticipant = useBoundSelector(getParticipantById, localId);
const localName: string = localParticipant.name ? localParticipant.name : 'Fellow Jitster';
const submitAnswer = useCallback(() => { const submitAnswer = useCallback(() => {
conference.sendMessage({ conference.sendMessage({
type: COMMAND_ANSWER_POLL, type: COMMAND_ANSWER_POLL,
pollId, pollId,
voterId: localId,
voterName: localName,
answers: checkBoxStates answers: checkBoxStates
}); });
@ -85,7 +79,7 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
dispatch(registerVote(pollId, checkBoxStates)); dispatch(registerVote(pollId, checkBoxStates));
return false; return false;
}, [ pollId, localId, localName, checkBoxStates, conference ]); }, [ pollId, checkBoxStates, conference ]);
const skipAnswer = useCallback(() => { const skipAnswer = useCallback(() => {
dispatch(registerVote(pollId, null)); dispatch(registerVote(pollId, null));
@ -101,7 +95,7 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
return (<Component return (<Component
checkBoxStates = { checkBoxStates } checkBoxStates = { checkBoxStates }
creatorName = { participant ? participant.name : '' } creatorName = { participantName }
poll = { poll } poll = { poll }
setCheckbox = { setCheckbox } setCheckbox = { setCheckbox }
skipAnswer = { skipAnswer } skipAnswer = { skipAnswer }

View File

@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { createPollEvent, sendAnalytics } from '../../analytics'; import { createPollEvent, sendAnalytics } from '../../analytics';
import { getParticipantDisplayName } from '../../base/participants';
import { COMMAND_NEW_POLL } from '../constants'; import { COMMAND_NEW_POLL } from '../constants';
/** /**
@ -85,8 +84,6 @@ const AbstractPollCreate = (Component: AbstractComponent<AbstractProps>) => (pro
}); });
const conference = useSelector(state => state['features/base/conference'].conference); const conference = useSelector(state => state['features/base/conference'].conference);
const myId = conference.myUserId();
const myName = useSelector(state => getParticipantDisplayName(state, myId));
const onSubmit = useCallback(ev => { const onSubmit = useCallback(ev => {
if (ev) { if (ev) {
@ -102,8 +99,6 @@ const AbstractPollCreate = (Component: AbstractComponent<AbstractProps>) => (pro
conference.sendMessage({ conference.sendMessage({
type: COMMAND_NEW_POLL, type: COMMAND_NEW_POLL,
pollId: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36), pollId: Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36),
senderId: myId,
senderName: myName,
question, question,
answers: filteredAnswers answers: filteredAnswers
}); });

View File

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { createPollEvent, sendAnalytics } from '../../analytics'; import { createPollEvent, sendAnalytics } from '../../analytics';
import { getParticipantDisplayName } from '../../base/participants';
import { getParticipantById } from '../../base/participants/functions'; import { getParticipantById } from '../../base/participants/functions';
import { useBoundSelector } from '../../base/util/hooks'; import { useBoundSelector } from '../../base/util/hooks';
import { setVoteChanging } from '../actions'; import { setVoteChanging } from '../actions';
@ -55,6 +56,7 @@ const AbstractPollResults = (Component: AbstractComponent<AbstractProps>) => (pr
const pollDetails = useSelector(getPoll(pollId)); const pollDetails = useSelector(getPoll(pollId));
const participant = useBoundSelector(getParticipantById, pollDetails.senderId); const participant = useBoundSelector(getParticipantById, pollDetails.senderId);
const reduxState = useSelector(state => state);
const [ showDetails, setShowDetails ] = useState(false); const [ showDetails, setShowDetails ] = useState(false);
const toggleIsDetailed = useCallback(() => { const toggleIsDetailed = useCallback(() => {
@ -63,27 +65,29 @@ const AbstractPollResults = (Component: AbstractComponent<AbstractProps>) => (pr
}); });
const answers: Array<AnswerInfo> = useMemo(() => { const answers: Array<AnswerInfo> = useMemo(() => {
const voterSet = new Set(); const allVoters = new Set();
// Getting every voters ID that participates to the poll // Getting every voters ID that participates to the poll
for (const answer of pollDetails.answers) { for (const answer of pollDetails.answers) {
for (const [ voterId ] of answer.voters) { // checking if the voters is an array for supporting old structure model
voterSet.add(voterId); const voters = answer.voters?.length ? answer.voters : Object.keys(answer.voters);
}
}
const totalVoters = voterSet.size; voters.forEach(voter => allVoters.add(voter));
}
return pollDetails.answers.map(answer => { return pollDetails.answers.map(answer => {
const percentage = totalVoters === 0 ? 0 : Math.round(answer.voters.size / totalVoters * 100); const nrOfVotersPerAnswer = answer.voters ? Object.keys(answer.voters).length : 0;
const percentage = allVoters.size > 0 ? Math.round(nrOfVotersPerAnswer / allVoters.size * 100) : 0;
let voters = null; let voters = null;
if (showDetails) { if (showDetails && answer.voters) {
voters = [ ...answer.voters ].map(([ id, name ]) => { const answerVoters = answer.voters?.length ? [ ...answer.voters ] : Object.keys({ ...answer.voters });
voters = answerVoters.map(id => {
return { return {
id, id,
name name: getParticipantDisplayName(reduxState, id)
}; };
}); });
} }
@ -92,7 +96,7 @@ const AbstractPollResults = (Component: AbstractComponent<AbstractProps>) => (pr
name: answer.name, name: answer.name,
percentage, percentage,
voters, voters,
voterCount: answer.voters.size voterCount: nrOfVotersPerAnswer
}; };
}); });
}, [ pollDetails.answers, showDetails ]); }, [ pollDetails.answers, showDetails ]);

View File

@ -37,39 +37,20 @@ const parsePollData = (pollData: IPollData): IPoll | null => {
if (typeof pollData !== 'object' || pollData === null) { if (typeof pollData !== 'object' || pollData === null) {
return null; return null;
} }
const { id, senderId, senderName, question, answers } = pollData; const { id, senderId, question, answers } = pollData;
if (typeof id !== 'string' || typeof senderId !== 'string' || typeof senderName !== 'string' if (typeof id !== 'string' || typeof senderId !== 'string'
|| typeof question !== 'string' || !(answers instanceof Array)) { || typeof question !== 'string' || !(answers instanceof Array)) {
return null; return null;
} }
const answersParsed = [];
for (const answer of answers) {
const voters = new Map();
for (const [ voterId, voter ] of Object.entries(answer.voters)) {
if (typeof voter !== 'string') {
return null;
}
voters.set(voterId, voter);
}
answersParsed.push({
name: answer.name,
voters
});
}
return { return {
changingVote: false, changingVote: false,
senderId, senderId,
senderName,
question, question,
showResults: true, showResults: true,
lastVote: null, lastVote: null,
answers: answersParsed answers
}; };
}; };
@ -81,9 +62,15 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { conference } = action; const { conference } = action;
conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(_: any, data: any) => _handleReceivePollsMessage(data, dispatch)); (user: any, data: any) => {
data.type === COMMAND_NEW_POLL ? data.senderId = user._id : data.voterId = user._id;
_handleReceivePollsMessage(data, dispatch);
});
conference.on(JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED, conference.on(JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(_: any, data: any) => _handleReceivePollsMessage(data, dispatch)); (id: any, data: any) => {
data.type === COMMAND_NEW_POLL ? data.senderId = id : data.voterId = id;
_handleReceivePollsMessage(data, dispatch);
});
break; break;
} }
@ -118,19 +105,18 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch']) { function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch']) {
switch (data.type) { switch (data.type) {
case COMMAND_NEW_POLL: { case COMMAND_NEW_POLL: {
const { question, answers, pollId, senderId, senderName } = data; const { question, answers, pollId, senderId } = data;
const poll = { const poll = {
changingVote: false, changingVote: false,
senderId, senderId,
senderName,
showResults: false, showResults: false,
lastVote: null, lastVote: null,
question, question,
answers: answers.map((answer: IAnswer) => { answers: answers.map((answer: IAnswer) => {
return { return {
name: answer, name: answer,
voters: new Map() voters: []
}; };
}) })
}; };
@ -146,11 +132,10 @@ function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch']) {
} }
case COMMAND_ANSWER_POLL: { case COMMAND_ANSWER_POLL: {
const { pollId, answers, voterId, voterName } = data; const { pollId, answers, voterId } = data;
const receivedAnswer: IAnswer = { const receivedAnswer: IAnswer = {
voterId, voterId,
voterName,
pollId, pollId,
answers answers
}; };

View File

@ -83,21 +83,30 @@ ReducerRegistry.register<IPollsState>('features/polls', (state = INITIAL_STATE,
// if the poll exists, we update it with the incoming answer // if the poll exists, we update it with the incoming answer
const newAnswers = state.polls[pollId].answers const newAnswers = state.polls[pollId].answers
.map(_answer => { .map(_answer => {
// checking if the voters is an array for supporting old structure model
const answerVoters = _answer.voters
? _answer.voters.length
? [ ..._answer.voters ] : Object.keys(_answer.voters) : [];
return { return {
name: _answer.name, name: _answer.name,
voters: new Map(_answer.voters) voters: answerVoters
}; };
}); });
for (let i = 0; i < newAnswers.length; i++) { for (let i = 0; i < newAnswers.length; i++) {
// if the answer was chosen, we add the sender to the set of voters of this answer // if the answer was chosen, we add the senderId to the array of voters of this answer
const voters = newAnswers[i].voters; const voters = newAnswers[i].voters as any;
const index = voters.indexOf(answer.voterId);
if (answer.answers[i]) { if (answer.answers[i]) {
voters.set(answer.voterId, answer.voterName); if (index === -1) {
voters.push(answer.voterId);
} else { }
voters.delete(answer.voterId); } else if (index > -1) {
voters.splice(index, 1);
} }
} }

View File

@ -16,9 +16,9 @@ export interface IAnswer {
voterId: string; voterId: string;
/** /**
* Name of the voter. * Name of the voter for this answer.
*/ */
voterName: string; voterName?: string;
} }
export interface IPoll { export interface IPoll {
@ -27,7 +27,7 @@ export interface IPoll {
* An array of answers: * An array of answers:
* the name of the answer name and a map of ids and names of voters voting for this option. * the name of the answer name and a map of ids and names of voters voting for this option.
*/ */
answers: Array<{ name: string; voters: Map<string, string>; }>; answers: Array<{ name: string; voters: Array<string>; }>;
/** /**
* Whether the poll vote is being edited/changed. * Whether the poll vote is being edited/changed.
@ -50,12 +50,6 @@ export interface IPoll {
*/ */
senderId: string; senderId: string;
/**
* Name of the sender of this poll
* Store poll sender name in case they exit the call.
*/
senderName: string;
/** /**
* Whether the results should be shown instead of the answer form. * Whether the results should be shown instead of the answer form.
*/ */