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 { sendAnalytics } from '../../analytics/functions';
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 { registerVote, setVoteChanging } from '../actions';
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 { id: localId } = useSelector(getLocalParticipant) ?? { id: '' };
const [ checkBoxStates, setCheckBoxState ] = useState(() => {
if (poll.lastVote !== null) {
return [ ...poll.lastVote ];
@ -57,7 +55,8 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
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 newCheckBoxStates = [ ...checkBoxStates ];
@ -69,15 +68,10 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
const dispatch = useDispatch();
const localParticipant = useBoundSelector(getParticipantById, localId);
const localName: string = localParticipant.name ? localParticipant.name : 'Fellow Jitster';
const submitAnswer = useCallback(() => {
conference.sendMessage({
type: COMMAND_ANSWER_POLL,
pollId,
voterId: localId,
voterName: localName,
answers: checkBoxStates
});
@ -85,7 +79,7 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
dispatch(registerVote(pollId, checkBoxStates));
return false;
}, [ pollId, localId, localName, checkBoxStates, conference ]);
}, [ pollId, checkBoxStates, conference ]);
const skipAnswer = useCallback(() => {
dispatch(registerVote(pollId, null));
@ -101,7 +95,7 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
return (<Component
checkBoxStates = { checkBoxStates }
creatorName = { participant ? participant.name : '' }
creatorName = { participantName }
poll = { poll }
setCheckbox = { setCheckbox }
skipAnswer = { skipAnswer }

View File

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

View File

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

View File

@ -37,39 +37,20 @@ const parsePollData = (pollData: IPollData): IPoll | null => {
if (typeof pollData !== 'object' || pollData === 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)) {
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 {
changingVote: false,
senderId,
senderName,
question,
showResults: true,
lastVote: null,
answers: answersParsed
answers
};
};
@ -81,9 +62,15 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { conference } = action;
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,
(_: 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;
}
@ -118,19 +105,18 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch']) {
switch (data.type) {
case COMMAND_NEW_POLL: {
const { question, answers, pollId, senderId, senderName } = data;
const { question, answers, pollId, senderId } = data;
const poll = {
changingVote: false,
senderId,
senderName,
showResults: false,
lastVote: null,
question,
answers: answers.map((answer: IAnswer) => {
return {
name: answer,
voters: new Map()
voters: []
};
})
};
@ -146,11 +132,10 @@ function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch']) {
}
case COMMAND_ANSWER_POLL: {
const { pollId, answers, voterId, voterName } = data;
const { pollId, answers, voterId } = data;
const receivedAnswer: IAnswer = {
voterId,
voterName,
pollId,
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
const newAnswers = state.polls[pollId].answers
.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 {
name: _answer.name,
voters: new Map(_answer.voters)
voters: answerVoters
};
});
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
const voters = newAnswers[i].voters;
// if the answer was chosen, we add the senderId to the array of voters of this answer
const voters = newAnswers[i].voters as any;
const index = voters.indexOf(answer.voterId);
if (answer.answers[i]) {
voters.set(answer.voterId, answer.voterName);
} else {
voters.delete(answer.voterId);
if (index === -1) {
voters.push(answer.voterId);
}
} else if (index > -1) {
voters.splice(index, 1);
}
}

View File

@ -16,9 +16,9 @@ export interface IAnswer {
voterId: string;
/**
* Name of the voter.
* Name of the voter for this answer.
*/
voterName: string;
voterName?: string;
}
export interface IPoll {
@ -27,7 +27,7 @@ export interface IPoll {
* An array of answers:
* 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.
@ -50,12 +50,6 @@ export interface IPoll {
*/
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.
*/