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

Fix ballot encoding, and the results page #143

Merged
merged 2 commits into from
Jun 20, 2022
Merged
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
2 changes: 1 addition & 1 deletion docs/ballot_encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The answers to questions are encoded in the following way, with one question per

TYPE = "select"|"text"|"rank"
SEP = ":"
ID = up to 3 bytes, encoded in base64
ID = 3 bytes, encoded in base64
ANSWERS = <answer>[","<answer>]*
ANSWER = <select_answer>|<text_answer>|<rank_answer>
SELECT_ANSWER = "0"|"1"
Expand Down
6 changes: 3 additions & 3 deletions web/frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const CHANGE_STATUS_TIMER = 2000;
const INIT_TIMER = 1000;
const SETUP_TIMER = 2000;
const SHUFFLE_TIMER = 2000;
const DECRYPT_TIMER = 3000;
const DECRYPT_TIMER = 1000;

const defaultProxy = 'http://localhost/';

Expand Down Expand Up @@ -140,8 +140,8 @@ export const handlers = [
Result: [],
Roster: mockRoster,
Configuration: configuration,
BallotSize: 290,
ChunksPerBallot: 10,
BallotSize: 291,
ChunksPerBallot: 11,
});

return newElectionID;
Expand Down
56 changes: 56 additions & 0 deletions web/frontend/src/mocks/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,31 @@ const mockElection2: any = {
],
};

const mockElection3: any = {
MainTitle: 'Lunch',
Scaffold: [
{
ID: '3cVHIxpx',
Title: 'Choose your lunch',
Order: ['PGP'],
Ranks: [],
Selects: [],
Texts: [
{
ID: 'PGP',
Title: 'Select what you want',
MaxN: 4,
MinN: 0,
MaxLength: 50,
Regex: '',
Choices: ['Firstname', 'Main 🍕', 'Drink 🧃', 'Dessert 🍰'],
},
],
Subjects: [],
},
],
};

const mockElectionResult21: Results = {
SelectResultIDs: [(0x3fb2).toString(), (0xa319).toString()],
SelectResult: [
Expand Down Expand Up @@ -220,14 +245,45 @@ const mockElectionResult23: Results = {
TextResult: [['Another Name', 'Jane Doe']],
};

const mockElectionResult31: Results = {
SelectResultIDs: [],
SelectResult: [],
RankResultIDs: [],
RankResult: [],
TextResultIDs: ['PGP'],
TextResult: [['Alice', 'Pizza', 'Ice cold water', '🍒🍒🍒🍒']],
};

const mockElectionResult32: Results = {
SelectResultIDs: [],
SelectResult: [],
RankResultIDs: [],
RankResult: [],
TextResultIDs: ['PGP'],
TextResult: [['Bob', 'Pizza', 'Coke', '🍒🍒🍒']],
};

const mockElectionResult33: Results = {
SelectResultIDs: null,
SelectResult: null,
RankResultIDs: null,
RankResult: null,
TextResultIDs: null,
TextResult: null,
};

export {
mockElection1,
mockElectionResult11,
mockElectionResult12,
mockElection2,
mockElectionResult21,
mockElectionResult22,
mockElection3,
mockElectionResult23,
mockElectionResult31,
mockElectionResult32,
mockElectionResult33,
mockRoster,
mockNodes,
};
19 changes: 19 additions & 0 deletions web/frontend/src/mocks/setupMockElections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import { NodeStatus } from 'types/node';
import {
mockElection1,
mockElection2,
mockElection3,
mockElectionResult11,
mockElectionResult12,
mockElectionResult21,
mockElectionResult22,
mockElectionResult23,
mockElectionResult31,
mockElectionResult32,
mockElectionResult33,
mockNodes,
mockRoster,
} from './mockData';
Expand Down Expand Up @@ -39,6 +43,7 @@ const setupMockElection = () => {

const electionID1 = '36kSJ0tH';
const electionID2 = 'Bnq9gLmf';
const electionID3 = 'Afdv4ffl';

mockElections.set(electionID1, {
ElectionID: electionID1,
Expand Down Expand Up @@ -69,6 +74,20 @@ const setupMockElection = () => {
mockResults.set(electionID2, [mockElectionResult21, mockElectionResult22, mockElectionResult23]);
mockDKG.set(electionID2, mockDKGSetup);

mockElections.set(electionID3, {
ElectionID: electionID3,
Status: Status.Open,
Pubkey: 'XL4V6EMIICW',
Result: [],
Roster: mockRoster,
Configuration: unmarshalConfig(mockElection3),
BallotSize: 291,
ChunksPerBallot: 11,
});

mockResults.set(electionID3, [mockElectionResult31, mockElectionResult32, mockElectionResult33]);
mockDKG.set(electionID3, mockDKGSetup);

return { mockElections, mockResults, mockDKG, mockNodeProxyAddresses };
};

Expand Down
2 changes: 1 addition & 1 deletion web/frontend/src/pages/ballot/Show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const Ballot: FC = () => {

const { electionId } = useParams();
const UserID = sessionStorage.getItem('id');
const { loading, configObj, electionID, status, pubKey, ballotSize, chunksPerBallot } =
const { loading, configObj, electionID, status, pubKey, chunksPerBallot } =
useElection(electionId);
const { configuration, answers, setAnswers } = useConfiguration(configObj);

Expand Down
6 changes: 3 additions & 3 deletions web/frontend/src/pages/ballot/components/VoteEncode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ export function voteEncode(answers: Answers, chunksPerBallot: number): string[]
let encodedBallot = '';

answers.SelectAnswers.forEach((selectAnswer, id) => {
encodedBallot += SELECT + ':' + id + ':';
encodedBallot += SELECT + ':' + Buffer.from(id).toString('base64') + ':';
selectAnswer.forEach((answer) => (encodedBallot += answer ? '1,' : '0,'));
encodedBallot = encodedBallot.slice(0, -1);
encodedBallot += '\n';
});

answers.RankAnswers.forEach((rankAnswer, id) => {
encodedBallot += RANK + ':' + id + ':';
encodedBallot += RANK + ':' + Buffer.from(id).toString('base64') + ':';
const position = Array<number>(rankAnswer.length);
for (let i = 0; i < rankAnswer.length; i++) {
position[rankAnswer[i]] = i;
Expand All @@ -25,7 +25,7 @@ export function voteEncode(answers: Answers, chunksPerBallot: number): string[]
});

answers.TextAnswers.forEach((textAnswer, id) => {
encodedBallot += TEXT + ':' + id + ':';
encodedBallot += TEXT + ':' + Buffer.from(id).toString('base64') + ':';
// each answer is first transformed into bytes then encoded in base64
textAnswer.forEach((answer) => (encodedBallot += Buffer.from(answer).toString('base64') + ','));
encodedBallot = encodedBallot.slice(0, -1);
Expand Down
56 changes: 36 additions & 20 deletions web/frontend/src/pages/election/Result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,15 @@ const ElectionResult: FC = () => {
let textRes: TextResults = new Map<ID, string[][]>();

result.forEach((res) => {
groupByID(selectRes, res.SelectResultIDs, res.SelectResult, true);
groupByID(rankRes, res.RankResultIDs, res.RankResult);
groupByID(textRes, res.TextResultIDs, res.TextResult);
if (
res.SelectResultIDs !== null &&
res.RankResultIDs !== null &&
res.TextResultIDs !== null
) {
groupByID(selectRes, res.SelectResultIDs, res.SelectResult, true);
groupByID(rankRes, res.RankResultIDs, res.RankResult);
groupByID(textRes, res.TextResultIDs, res.TextResult);
}
});

return { rankRes, selectRes, textRes };
Expand All @@ -104,31 +110,39 @@ const ElectionResult: FC = () => {
switch (element.Type) {
case RANK:
const rank = element as RankQuestion;
res = countRankResult(rankResult.get(id), element as RankQuestion).resultsInPercent.map(
(percent, index) => {
return { Candidate: rank.Choices[index], Percentage: `${percent}%` };
}
);
dataToDownload.push({ Title: element.Title, Results: res });

if (rankResult.has(id)) {
res = countRankResult(rankResult.get(id), element as RankQuestion).resultsInPercent.map(
(percent, index) => {
return { Candidate: rank.Choices[index], Percentage: `${percent}%` };
}
);
dataToDownload.push({ Title: element.Title, Results: res });
}
break;

case SELECT:
const select = element as SelectQuestion;
res = countSelectResult(selectResult.get(id)).resultsInPercent.map((percent, index) => {
return { Candidate: select.Choices[index], Percentage: `${percent}%` };
});
dataToDownload.push({ Title: element.Title, Results: res });

if (selectResult.has(id)) {
res = countSelectResult(selectResult.get(id)).resultsInPercent.map((percent, index) => {
return { Candidate: select.Choices[index], Percentage: `${percent}%` };
});
dataToDownload.push({ Title: element.Title, Results: res });
}
break;

case SUBJECT:
getResultData(element as Subject, dataToDownload);
break;

case TEXT:
res = Array.from(countTextResult(textResult.get(id)).resultsInPercent).map((r) => {
return { Candidate: r[0], Percentage: `${r[1]}%` };
});
dataToDownload.push({ Title: element.Title, Results: res });
if (textResult.has(id)) {
res = Array.from(countTextResult(textResult.get(id)).resultsInPercent).map((r) => {
return { Candidate: r[0], Percentage: `${r[1]}%` };
});
dataToDownload.push({ Title: element.Title, Results: res });
}
break;
}
});
Expand Down Expand Up @@ -160,16 +174,18 @@ const ElectionResult: FC = () => {
return (
<div className="pl-4 pb-4 sm:pl-6 sm:pb-6">
<h2 className="text-lg pb-2">{element.Title}</h2>
{element.Type === RANK && (
{element.Type === RANK && rankResult.has(element.ID) && (
<RankResult rank={element as RankQuestion} rankResult={rankResult.get(element.ID)} />
)}
{element.Type === SELECT && (
{element.Type === SELECT && selectResult.has(element.ID) && (
<SelectResult
select={element as SelectQuestion}
selectResult={selectResult.get(element.ID)}
/>
)}
{element.Type === TEXT && <TextResult textResult={textResult.get(element.ID)} />}
{element.Type === TEXT && textResult.has(element.ID) && (
<TextResult textResult={textResult.get(element.ID)} />
)}
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion web/frontend/src/types/getObjectType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ShortUniqueId from 'short-unique-id';
import * as types from './configuration';
import { ID, RANK, SELECT, SUBJECT, TEXT } from './configuration';

const uid: Function = new ShortUniqueId({ length: 8 });
const uid: Function = new ShortUniqueId({ length: 3 });

const emptyConfiguration = (): types.Configuration => {
return { MainTitle: '', Scaffold: [] };
Expand Down