Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features- Ballot to matrix #107

Open
wants to merge 6 commits into
base: test/laprimaire.org
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/src/models/Vote.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Vote.add({
voteContractABI: { type: Types.Text, noedit: true },
restricted: { type: Types.Boolean, default: false },
labels: { type: Types.TextArray },
candidateNames: { types: Types.TextArray },
question: { type: Types.Text },
salt: { type: Types.Text, noedit: true, default: () => bcrypt.genSaltSync(10) },
key: { type: Types.Key, noedit: true, default: () => srs(32).toLowerCase() },
Expand Down Expand Up @@ -115,9 +116,11 @@ function pushVoteOnQueue(vote, callback) {
return;
}

var defaultNumCandidates = 1;
var voteMsg = { vote : {
id: vote.id,
numProposals: vote.labels.length === 0 ? 3 : vote.labels.length,
numCandidates: vote.candidateNames.length || defaultNumCandidates,
} };

ch.assertQueue('votes', {autoDelete: false, durable: true});
Expand Down
21 changes: 19 additions & 2 deletions api/src/routes/api/vote.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,33 @@ export async function result(req, res) {

const web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider(
'http://127.0.0.1:8545'
'http://127.0.0.1:8545' // TODO configurable ?
));

try {
const contract = web3.eth.contract(JSON.parse(vote.voteContractABI));
const instance = await promise((...c)=>contract.at(...c))(
vote.voteContractAddress
);
const flatResponses = instance.getVoteResults().map((s) => parseInt(s));
const matrixResponses = [];
const namedResponses = {};
for (let i = 0; i < vote.candidateNames.length; i++) {
var candidateName = vote.candidateNames[i];
matrixResponses[i] = [];
namedResponses[candidateName] = {};

for (let j = 0; j < vote.labels.length; j++) {
var label = vote.labels[j];
let response = flatResponses[i*vote.candidateNames.length + j];
matrixResponses[i][j] = response;
namedResponses[candidateName][label] = response;
}
}

const response = {
result : instance.getVoteResults().map((s) => parseInt(s)),
result : matrixResponses,
// result : namedResponses, // depend if we want indexes or names
};

cache.set(cacheKey, response);
Expand Down
6 changes: 3 additions & 3 deletions app/src/script/component/VoteButtonBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ var VoteButtonBar = React.createClass({
return (
<ButtonToolbar className="vote-step-actions">
<Button className="btn-vote btn-positive"
onClick={(e)=>this.props.onVote(e, 0)}>
onClick={(e)=>this.props.onVote(e, [0])}>
<span className="icon-thumb_up"/>
<FormattedMessage
message={this.getIntlMessage('vote.I_VOTE')}
value={this.getIntlMessage('vote.VOTE_YES')}/>
</Button>
<Button className="btn-vote btn-neutral"
onClick={(e)=>this.props.onVote(e, 1)}>
onClick={(e)=>this.props.onVote(e, [1])}>
<span className="icon-stop"/>
<FormattedMessage
message={this.getIntlMessage('vote.I_VOTE')}
value={this.getIntlMessage('vote.VOTE_BLANK')}/>
</Button>
<Button className="btn-vote btn-negative"
onClick={(e)=>this.props.onVote(e, 2)}>
onClick={(e)=>this.props.onVote(e, [2])}>
<span className="icon-thumb_down"/>
<FormattedMessage
message={this.getIntlMessage('vote.I_VOTE')}
Expand Down
99 changes: 99 additions & 0 deletions app/src/script/component/VoteMatrixButtons.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
var React = require('react');
var ReactBootstrap = require('react-bootstrap');
var ReactIntl = require('react-intl');

var Title = require('./Title');

var Button = ReactBootstrap.Button;

var VoteMatrixButtons = React.createClass({

mixins: [
ReactIntl.IntlMixin,
],

getDefaultProps: function() {
return {
onVote: (e, ballotValue) => null,
vote: null,
};
},

getInitialState() {
return {
ballotValue: [],
}
},

handleMatrixVote: (candidateIndex, labelIndex) => (e) => {
let ballotValue = this.state.ballotValue;
ballotValue[candidateIndex] = labelIndex;

this.setState({ballotValue})
},

isMatrixFilled() {
this.props.vote.candidateNames.map((candidate, candidateIndex) => {
return (!!this.state.ballotValue[candidateIndex])
}).reduce((a,b) => (a && b));
},

sendBallot() {
this.props.onVote(e, this.state.ballotValue);
},

render: function() {
var labels = this.props.vote.labels;
var candidates = this.props.vote.candidateNames;

return (
<form>
<table>
<thead>
<tr>
<td>
/
</td>
{
candidates.map((candidate, candidateIndex) => (
<td key={candidateIndex}>
<Title text={candidate}/>
{/*{candidate}*/}
</td>
))
}
</tr>
</thead>
<tbody>
{
labels.map((label, labelIndex) => (
<tr key={labelIndex}>
<td>
<Title text={label}/>
{/*{label}*/}
</td>
{
candidates.map((candidate, candidateIndex) => (
<td key={candidateIndex}>
<input type="radio" name={"ballot-"+candidateIndex} value={labelIndex}
onChange={this.handleMatrixVote(candidateIndex, labelIndex)}/>
</td>
))
}
</tr>
))
}
</tbody>
</table>

<Button disabled={this.isMatrixFilled}
className="btn-vote btn-primary"
onClick={this.sendBallot}>
{this.getIntlMessage('vote.VALIDATE_BALLOT')}
</Button>
</form>
);
},
});

module.exports = VoteMatrixButtons;
4 changes: 2 additions & 2 deletions app/src/script/component/VoteRadioButtons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var VoteRadioButtons = React.createClass({

getInitialState() {
return {
ballotValue: null,
ballotValue: [],
}
},

Expand All @@ -36,7 +36,7 @@ var VoteRadioButtons = React.createClass({
<li>
<label className="vote-label">
<input type="radio" name="ballot" value={index}
onChange={(e) => this.setState({ballotValue: index})}/>
onChange={(e) => this.setState({ballotValue: [index]})}/>
<i>
<Title text={label}/>
</i>
Expand Down
17 changes: 14 additions & 3 deletions app/src/script/component/VoteWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var LoadingIndicator = require('./LoadingIndicator'),
SVGDownloadButton = require('./SVGDownloadButton'),
VoteButtonBar = require('./VoteButtonBar'),
VoteRadioButtons = require('./VoteRadioButtons'),
VoteMatrixButtons = require('./VoteMatrixButtons'),
LoginPage = require('../page/Login'),
SVGImage = require('./SVGImage'),
PrintButton = require('./PrintButton');
Expand Down Expand Up @@ -435,6 +436,18 @@ var VoteWidget = React.createClass({
renderVoteDialog: function() {
var vote = this.state.vote;

let voteDialog = null;

if(!!vote.labels && vote.labels.length) {
if (!!vote.candidateNames && vote.candidateNames.length) {
voteDialog = <VoteMatrixButtons vote={vote} onVote={this.voteHandler}/>;
} else {
voteDialog = <VoteRadioButtons vote={vote} onVote={this.voteHandler}/>;
}
} else {
voteDialog = <VoteButtonBar vote={vote} onVote={this.voteHandler}/>;
}

return (
<Grid>
<Row>
Expand All @@ -444,9 +457,7 @@ var VoteWidget = React.createClass({
? <p>{vote.question}</p>
: null}
<div className="vote-step-actions">
{!!vote.labels && vote.labels.length !== 0
? <VoteRadioButtons vote={vote} onVote={this.voteHandler}/>
: <VoteButtonBar vote={vote} onVote={this.voteHandler}/>}
{ voteDialog }
</div>
</div>
</Col>
Expand Down
24 changes: 13 additions & 11 deletions blockchain-worker/src/vote/vote-consumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ function getCompiledVoteContract(web3, callback) {
);

web3.eth.compile.solidity(source, (error, compiled) => {
if (!error) {
logger.info(
{ md5Hash: md5(source) },
'compiled smart contract'
);
}

callback(error, !!compiled.Vote ? compiled.Vote : compiled);
if (error) {
callback(error);
} else {
logger.info(
{ md5Hash: md5(source) },
'compiled smart contract'
);
callback(null, !!compiled.Vote ? compiled.Vote : compiled);
}
});
}

function mineVoteContract(numProposals, next) {
function mineVoteContract(numCandidates, numProposals, next) {
var hash = '';
var web3 = new Web3();

Expand All @@ -61,7 +62,8 @@ function mineVoteContract(numProposals, next) {
);

web3.eth.contract(abi).new(
numProposals, // num proposals
numCandidates,
numProposals,
{
from: accounts[0],
data: code,
Expand Down Expand Up @@ -98,7 +100,7 @@ function mineVoteContract(numProposals, next) {
}

function handleVote(voteMsg, callback) {
mineVoteContract(voteMsg.numProposals, (err, contract, abi) => {
mineVoteContract(voteMsg.numCandidates, voteMsg.numProposals, (err, contract, abi) => {
if (err) {
callback(err, null);
return;
Expand Down
63 changes: 51 additions & 12 deletions contract/Vote.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ contract Vote {

event Ballot (
address indexed voter,
uint8 proposal
uint8[] proposals
);

event VoterRegistered (
Expand All @@ -26,7 +26,9 @@ contract Vote {

mapping(address => Voter) voters;
address chairperson;
uint[] results;
uint8 _numCandidates;
uint8 _numProposals;
uint[][] results;
Status status;

/* This unnamed function is called whenever someone tries to send ether to it */
Expand All @@ -35,11 +37,17 @@ contract Vote {
}

// Create a new vote with numProposals different results.
function Vote(uint8 numProposals)
function Vote(uint8 numCandidates, uint8 numProposals)
public
{
// Format results matrix
_numCandidates = numCandidates;
_numProposals = numProposals;
results.length = numCandidates;
for(uint8 i = 0; i < numCandidates; i++) {
results[i].length = numProposals;
}
chairperson = msg.sender;
results.length = numProposals;
status = Status.Open;
}

Expand Down Expand Up @@ -100,25 +108,37 @@ contract Vote {
VoterRegistered(voterAddress);
}

// Give a single vote to proposal proposal.
function vote(uint8 proposal)
// Give a multiple vote to proposal.
function vote(uint8[] ballot)
public
onlyRegisteredVoter()
notAlreadyVoted()
onlyWhenStatusIs(Status.Open)
{
Voter voter = voters[msg.sender];

if (proposal >= results.length) {
VoteError(msg.sender, 'invalid proposal');
if(ballot.length < _numCandidates)
{
VoteError(msg.sender, 'invalid number of candidates');
return;
}
for(uint8 i = 0; i < numCandidates; i++)
{
if (ballot[i] >= _numProposals)
{
VoteError(msg.sender, 'invalid proposal');
return;
}
}

voter.voted = true;
voter.vote = proposal;
results[proposal] += 1;

Ballot(msg.sender, proposal);
for(uint8 i = 0; i < numCandidates; i++)
{
results[i][ballot[i]] += 1;
}

Ballot(msg.sender, proposals);
}

/*function cancelVote() onlyRegisteredVoter onlyWhenStatusIs(Status.Open) {
Expand All @@ -134,7 +154,26 @@ contract Vote {
onlyWhenStatusIs(Status.Closed)
constant returns (uint[])
{
return results;
uint[] flatResults;
flatResults.length = _numProposals * _numCandidates;

for(uint8 i = 0; i < _numCandidates; i++)
{
for(uint8 j = 0; j < _numProposals; j++)
{
uint index = i * _numCandidates + j;
flatResults[index] = results[i][j];
}
}
return flatResults;
}

function getCandidateResults(uint candidate)
public
onlyWhenStatusIs(Status.Closed)
constant returns (uint[])
{
return results[candidate];
}

function close()
Expand Down