fix(polls) refactor storage of poll data
This commit is contained in:
parent
5692c3cb4d
commit
ed139f53ca
|
@ -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 }
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 ]);
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue