Browse Source

feat: implement poll data fetching

develop
Apostolos Fanakis 4 years ago
parent
commit
e4967cc9d0
Signed by: Apostolof GPG Key ID: 8600B4C4163B3269
  1. 2
      packages/concordia-app/src/components/PollCreate/index.jsx
  2. 171
      packages/concordia-app/src/components/PollView/index.jsx
  3. 6
      packages/concordia-app/src/utils/hashUtils.js
  4. 8
      packages/concordia-app/src/views/Topic/TopicView/index.jsx

2
packages/concordia-app/src/components/PollCreate/index.jsx

@ -15,7 +15,7 @@ import { breeze, drizzle } from '../../redux/store';
import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../constants/TransactionStatus'; import { TRANSACTION_ERROR, TRANSACTION_SUCCESS } from '../../constants/TransactionStatus';
import './styles.css'; import './styles.css';
import { POLL_OPTIONS, POLL_QUESTION } from '../../constants/orbit/PollsDatabaseKeys'; import { POLL_OPTIONS, POLL_QUESTION } from '../../constants/orbit/PollsDatabaseKeys';
import generatePollHash from '../../utils/hashUtils'; import { generatePollHash } from '../../utils/hashUtils';
const { contracts: { [VOTING_CONTRACT]: { methods: { createPoll } } } } = drizzle; const { contracts: { [VOTING_CONTRACT]: { methods: { createPoll } } } } = drizzle;
const { orbit: { stores } } = breeze; const { orbit: { stores } } = breeze;

171
packages/concordia-app/src/components/PollView/index.jsx

@ -1,89 +1,144 @@
import React, { useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames';
import { import {
Container, Header, Icon, Tab, Container, Header, Icon, Tab,
} from 'semantic-ui-react'; } from 'semantic-ui-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { drizzle } from '../../redux/store'; import PropTypes from 'prop-types';
import { POLLS_DATABASE } from 'concordia-shared/src/constants/orbit/OrbitDatabases';
import { breeze, drizzle } from '../../redux/store';
import PollGraph from './PollGraph'; import PollGraph from './PollGraph';
import CustomLoadingTabPane from '../CustomLoadingTabPane'; import CustomLoadingTabPane from '../CustomLoadingTabPane';
import { GRAPH_TAB, VOTE_TAB } from '../../constants/polls/PollTabs'; import { GRAPH_TAB, VOTE_TAB } from '../../constants/polls/PollTabs';
import PollVote from './PollVote'; import PollVote from './PollVote';
import { FETCH_USER_DATABASE } from '../../redux/actions/peerDbReplicationActions';
import { generatePollHash, generateHash } from '../../utils/hashUtils';
import { POLL_OPTIONS, POLL_QUESTION } from '../../constants/orbit/PollsDatabaseKeys';
const { contracts: { [VOTING_CONTRACT]: { methods: { pollExists: { cacheCall: pollExistsChainData } } } } } = drizzle; const { contracts: { [VOTING_CONTRACT]: { methods: { getPoll: { cacheCall: getPollChainData } } } } } = drizzle;
const { orbit } = breeze;
const hashOption = (val) => {
let hash = 0;
let i;
let chr;
for (i = 0; i < val.length; i++) {
chr = val.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
}
return `${hash}`;
};
const PollView = (props) => { const PollView = (props) => {
const { topicId } = props; const { topicId } = props;
const userAddress = useSelector((state) => state.user.address); const userAddress = useSelector((state) => state.user.address);
const hasSignedUp = useSelector((state) => state.user.hasSignedUp); const hasSignedUp = useSelector((state) => state.user.hasSignedUp);
const getPollInfoResults = useSelector((state) => state.contracts[VOTING_CONTRACT].getPollInfo); const getPollResults = useSelector((state) => state.contracts[VOTING_CONTRACT].getPoll);
const [getPollInfoCallHash, setGetPollInfoCallHash] = useState(null); const polls = useSelector((state) => state.orbitData.polls);
const [pollOptions, setPollOptions] = useState([ const [getPollCallHash, setGetPollCallHash] = useState(null);
{ const [pollHash, setPollHash] = useState('');
label: 'option 1', const [pollChangeVoteEnabled, setPollChangeVoteEnabled] = useState(false);
hash: hashOption('option 1'), const [pollOptions, setPollOptions] = useState([]);
}, const [voteCounts, setVoteCounts] = useState([]);
{ const [voters, setVoters] = useState([]);
label: 'option 2', const [pollHashValid, setPollHashValid] = useState(false);
hash: hashOption('option 2'), const [loading, setLoading] = useState(true);
}, const dispatch = useDispatch();
{
label: 'option 3',
hash: hashOption('option 3'),
},
{
label: 'option 4',
hash: hashOption('option 4'),
},
{
label: 'option 5',
hash: hashOption('option 5'),
},
]);
const [voteCounts, setVoteCounts] = useState([2, 8, 4, 12, 7]);
const [voteLoading, setVoteLoading] = useState(false);
const [resultsLoading, setResultsLoading] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
// TODO: get vote options useEffect(() => {
if (!getPollCallHash) {
setGetPollCallHash(getPollChainData(topicId));
}
}, [getPollCallHash, topicId]);
useEffect(() => {
dispatch({
type: FETCH_USER_DATABASE,
orbit,
dbName: POLLS_DATABASE,
userAddress,
});
}, [dispatch, userAddress]);
useEffect(() => {
if (getPollCallHash && getPollResults && getPollResults[getPollCallHash]) {
setPollHash(getPollResults[getPollCallHash].value[1]);
setPollChangeVoteEnabled(getPollResults[getPollCallHash].value[2]);
setVoteCounts(getPollResults[getPollCallHash].value[4].map((voteCount) => parseInt(voteCount, 10)));
const cumulativeSum = getPollResults[getPollCallHash].value[4]
.map((voteCount) => parseInt(voteCount, 10))
.reduce((accumulator, voteCount) => (accumulator.length === 0
? [voteCount]
: [...accumulator, accumulator[accumulator.length - 1] + voteCount]), []);
setVoters(cumulativeSum
.map((subArrayEnd, index) => getPollResults[getPollCallHash].value[5]
.slice(index > 0 ? cumulativeSum[index - 1] : 0,
subArrayEnd)));
}
}, [getPollCallHash, getPollResults]);
// TODO: get current results useEffect(() => {
const pollFound = polls
.find((poll) => poll.id === topicId);
// TODO: check poll hash validity, add invalid view if (pollHash && pollFound) {
if (generatePollHash(pollFound[POLL_QUESTION], pollFound[POLL_OPTIONS]) === pollHash) {
setPollHashValid(true);
setPollOptions(pollFound[POLL_OPTIONS].map((pollOption) => ({
label: pollOption,
hash: generateHash(pollOption),
})));
} else {
setPollHashValid(false);
}
setLoading(false);
}
}, [pollHash, polls, topicId]);
// TODO: add a "Signup to enable voting" view
const userHasVoted = useMemo(() => hasSignedUp && voters
.some((optionVoters) => optionVoters.includes(userAddress)),
[hasSignedUp, userAddress, voters]);
const userVoteHash = useMemo(() => {
if (userHasVoted) {
return pollOptions[voters
.findIndex((optionVoters) => optionVoters.includes(userAddress))].hash;
}
return '';
}, [pollOptions, userAddress, userHasVoted, voters]);
const pollVoteTab = useMemo(() => ( const pollVoteTab = useMemo(() => (
<PollVote pollOptions={pollOptions} enableVoteChanges hasUserVoted userVoteHash={pollOptions[2].hash} /> !loading
), [pollOptions]); ? (
<PollVote
pollOptions={pollOptions}
enableVoteChanges={pollChangeVoteEnabled}
hasUserVoted={userHasVoted}
userVoteHash={userVoteHash}
/>
)
: <div />
), [loading, pollChangeVoteEnabled, pollOptions, userHasVoted, userVoteHash]);
const pollGraphTab = useMemo(() => ( const pollGraphTab = useMemo(() => (
<PollGraph pollOptions={pollOptions} voteCounts={voteCounts} hasUserVoted userVoteHash={pollOptions[2].hash} /> !loading
), [pollOptions, voteCounts]); ? (
<PollGraph
pollOptions={pollOptions}
voteCounts={voteCounts}
hasUserVoted={userHasVoted}
userVoteHash={userVoteHash}
/>
)
: <div />
), [loading, pollOptions, userHasVoted, userVoteHash, voteCounts]);
const panes = useMemo(() => { const panes = useMemo(() => {
const pollVotePane = (<CustomLoadingTabPane loading={voteLoading}>{pollVoteTab}</CustomLoadingTabPane>); const pollVotePane = (<CustomLoadingTabPane loading={loading}>{pollVoteTab}</CustomLoadingTabPane>);
const pollGraphPane = (<CustomLoadingTabPane loading={resultsLoading}>{pollGraphTab}</CustomLoadingTabPane>); const pollGraphPane = (<CustomLoadingTabPane loading={loading}>{pollGraphTab}</CustomLoadingTabPane>);
return ([ return ([
{ menuItem: t(VOTE_TAB.intl_display_name_id), render: () => pollVotePane }, { menuItem: t(VOTE_TAB.intl_display_name_id), render: () => pollVotePane },
{ menuItem: t(GRAPH_TAB.intl_display_name_id), render: () => pollGraphPane }, { menuItem: t(GRAPH_TAB.intl_display_name_id), render: () => pollGraphPane },
]); ]);
}, [pollGraphTab, pollVoteTab, resultsLoading, t, voteLoading]); }, [loading, pollGraphTab, pollVoteTab, t]);
return ( return (
<Container id="topic-poll-container" textAlign="left"> <Container id="topic-poll-container" textAlign="left">
@ -96,4 +151,8 @@ const PollView = (props) => {
); );
}; };
PollView.propTypes = {
topicId: PropTypes.number.isRequired,
};
export default PollView; export default PollView;

6
packages/concordia-app/src/utils/hashUtils.js

@ -1,8 +1,6 @@
import sha256 from 'crypto-js/sha256'; import sha256 from 'crypto-js/sha256';
const generateHash = (message) => sha256(message).toString().substring(0, 16); export const generateHash = (message) => sha256(message).toString().substring(0, 16);
const generatePollHash = (pollQuestion, pollOptions) => generateHash(JSON export const generatePollHash = (pollQuestion, pollOptions) => generateHash(JSON
.stringify({ question: pollQuestion, optionValues: pollOptions })); .stringify({ question: pollQuestion, optionValues: pollOptions }));
export default generatePollHash;

8
packages/concordia-app/src/views/Topic/TopicView/index.jsx

@ -62,8 +62,10 @@ const TopicView = (props) => {
}, [postIds, timestamp, topicAuthor, topicAuthorAddress, topicId]); }, [postIds, timestamp, topicAuthor, topicAuthorAddress, topicId]);
useEffect(() => { useEffect(() => {
setPollExistsCallHash(pollExistsChainData(topicId)); if (!pollExistsCallHash) {
}, [topicId]); setPollExistsCallHash(pollExistsChainData(topicId));
}
}, [pollExistsCallHash, topicId]);
useEffect(() => { useEffect(() => {
if (getTopicCallHash && getTopicResults && getTopicResults[getTopicCallHash]) { if (getTopicCallHash && getTopicResults && getTopicResults[getTopicCallHash]) {
@ -130,7 +132,7 @@ const TopicView = (props) => {
} }
}, [topicId, topics]); }, [topicId, topics]);
const poll = useMemo(() => hasPoll && <PollView />, [hasPoll]); const poll = useMemo(() => hasPoll && <PollView topicId={topicId} />, [hasPoll, topicId]);
const stopClickPropagation = (event) => { const stopClickPropagation = (event) => {
event.stopPropagation(); event.stopPropagation();

Loading…
Cancel
Save