Skip to content
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
32 changes: 30 additions & 2 deletions src/electionguard_gui/components/upload_ballots_component.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import Any
from datetime import datetime
import eel
from electionguard.serialize import from_raw
from electionguard.ballot import SubmittedBallot
from electionguard_gui.components.component_base import ComponentBase
from electionguard_gui.eel_utils import eel_fail, eel_success
from electionguard_gui.services import ElectionService, BallotUploadService
Expand Down Expand Up @@ -66,15 +68,41 @@ def upload_ballot(
try:
db = self._db_service.get_db()
self._log.trace(f"adding ballot {file_name} to {ballot_upload_id}")
ballot = from_raw(SubmittedBallot, file_contents)
election = self._election_service.get(db, election_id)
context = election.get_context()
if context.manifest_hash != ballot.manifest_hash:
self._log.warn(
f"ballot '{ballot.object_id}' had a mismatched manifest hash. "
+ f"Expected {context.manifest_hash}, got {ballot.manifest_hash}."
)
return eel_fail(
"The uploaded ballot didn't match the encryption package for this election. "
+ "Please try a different ballot."
)
is_duplicate = self._ballot_upload_service.any_ballot_exists(
db, election_id, ballot.object_id
)
if is_duplicate:
self._log.warn(
"ballot '{ballot.object_id}' already exists in election '{election_id}'"
)
return eel_success({"is_duplicate": True})

success = self._ballot_upload_service.add_ballot(
db, ballot_upload_id, election_id, file_name, file_contents
db,
ballot_upload_id,
election_id,
file_name,
file_contents,
ballot.object_id,
)
if success:
self._ballot_upload_service.increment_ballot_count(db, ballot_upload_id)
self._election_service.increment_ballot_upload_ballot_count(
db, election_id, ballot_upload_id
)
return eel_success({"is_duplicate": not success})
return eel_success({"is_duplicate": False})
# pylint: disable=broad-except
except Exception as e:
return self.handle_error(e)
17 changes: 15 additions & 2 deletions src/electionguard_gui/models/election_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,21 @@ def to_dict(self) -> dict[str, Any]:
"contests": self._get_manifest_field("contests"),
"ballot_styles": self._get_manifest_field("ballot_styles"),
},
"ballot_uploads": self.ballot_uploads,
"decryptions": self.decryptions,
"ballot_uploads": [
{
"location": ballot_upload["location"],
"ballot_count": ballot_upload["ballot_count"],
"created_at": utc_to_str(ballot_upload.get("created_at")),
}
for ballot_upload in self.ballot_uploads
],
"decryptions": [
{
"name": decryption["name"],
"created_at": utc_to_str(decryption.get("created_at")),
}
for decryption in self.decryptions
],
"created_by": self.created_by,
"created_at": self.created_at_str,
}
Expand Down
8 changes: 1 addition & 7 deletions src/electionguard_gui/services/ballot_upload_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,9 @@ def add_ballot(
election_id: str,
file_name: str,
file_contents: str,
ballot_object_id: str,
) -> bool:
self._log.trace(f"adding ballot {file_name} to {ballot_upload_id}")
ballot = from_raw(SubmittedBallot, file_contents)
ballot_object_id = ballot.object_id
if self.any_ballot_exists(db, election_id, ballot_object_id):
self._log.warn(
"ballot '{ballot_object_id}' already exists in election '{election_id}'"
)
return False
db.ballot_uploads.insert_one(
{
"ballot_upload_id": ballot_upload_id,
Expand Down
10 changes: 9 additions & 1 deletion src/electionguard_gui/services/election_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,15 @@ def append_decryption(
)
db.elections.update_one(
{"_id": ObjectId(election_id)},
{"$push": {"decryptions": {"decryption_id": decryption_id, "name": name}}},
{
"$push": {
"decryptions": {
"decryption_id": decryption_id,
"name": name,
"created_at": datetime.utcnow(),
}
}
},
)

def increment_ballot_upload_ballot_count(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export default {
<div class="invalid-feedback">Please provide a ballot folder.</div>
</div>
<div class="col-12 mt-4">
<button type="submit" :disabled="loading" class="btn btn-primary me-2">Upload Ballots</button>
<button type="submit" :disabled="loading" class="btn btn-primary me-2">Upload</button>
<a :href="getElectionUrl()" class="btn btn-secondary me-2">Cancel</a>
<spinner :visible="loading"></spinner>
<p v-if="loading && ballotsProcessed">{{ ballotsProcessed }} of {{ ballotsTotal }} files processed.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,37 @@ export default {
spoiledBallotId: spoiledBallotId,
});
},
refresh_decryption: async function () {
await this.get_decryption(true);
refresh_decryption: async function (result) {
if (result.success) {
await this.get_decryption(true);
} else {
console.error(result.message);
this.error = true;
}
},
get_decryption: async function (is_refresh) {
console.log("getting decryption");
this.loading = true;
const result = await eel.get_decryption(this.decryptionId, is_refresh)();
this.error = !result.success;
if (result.success) {
this.decryption = result.result;
try {
const result = await eel.get_decryption(
this.decryptionId,
is_refresh
)();
this.error = !result.success;
if (result.success) {
this.decryption = result.result;
Comment thread
lprichar marked this conversation as resolved.
}
} finally {
this.loading = false;
}
this.loading = false;
},
},
async mounted() {
await this.get_decryption(false);
eel.expose(this.refresh_decryption, "refresh_decryption");
await this.get_decryption(false);
console.log("watching decryption");
// only watch for changes if the decryption is in-progress
if (!this.decryption.completed_at_str) {
if (this.decryption && !this.decryption.completed_at_str) {
eel.watch_decryption(this.decryptionId);
}
},
Expand All @@ -61,13 +72,10 @@ export default {
<p class="alert alert-danger" role="alert">An error occurred. Check the logs and try again.</p>
</div>
<div v-if="decryption">
<div class="row mb-4">
<div class="col-11">
<h1>{{decryption.decryption_name}}</h1>
</div>
<div class="col-1 text-end" v-if="decryption.completed_at_str">
<div class="text-end">
<div v-if="decryption.completed_at_str">
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<button class="btn btn-sm btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi-gear-fill me-1"></i>
</button>
<ul class="dropdown-menu">
Expand All @@ -85,31 +93,36 @@ export default {
</div>
</div>
</div>
<div class="row">
<h1>{{decryption.decryption_name}}</h1>
</div>
<div class="row">
<div class="col col-12 col-md-6 col-lg-5">
<div class="col-md-8">
<dt>Election</dt>
<dd><a :href="getElectionUrl(decryption.election_id)">{{decryption.election_name}}</a></dd>
</div>
<div class="row">
<div class="col-md-6">
<dt>Ballot Uploads</dt>
<dd>{{decryption.ballot_upload_count}}</dd>
</div>
<div class="col-md-6">
<dt>Total Ballots</dt>
<dd>{{decryption.ballot_count}}</dd>
<div class="mb-4">
<div class="row">
<div class="col-md-6">
<dt>Ballot Uploads</dt>
<dd>{{decryption.ballot_upload_count}}</dd>
</div>
<div class="col-md-6">
<dt>Total Ballots</dt>
<dd>{{decryption.ballot_count}}</dd>
</div>
</div>
<dl class="col-12">
<dt>Created</dt>
<dd>by {{decryption.created_by}} on {{decryption.created_at}}</dd>
</dl>
<dl class="col-12" v-if="decryption.completed_at_str">
<dt>Completed</dt>
<dd>{{decryption.completed_at_str}}</dd>
</dl>
</div>
<dl class="col-12">
<dt>Created</dt>
<dd>by {{decryption.created_by}} on {{decryption.created_at}}</dd>
</dl>
<dl class="col-12" v-if="decryption.completed_at_str">
<dt>Completed</dt>
<dd>{{decryption.completed_at_str}}</dd>
</dl>
<div class="col-12">
<div class="col-12 mb-4">
<h3>Joined Guardians</h3>
<ul v-if="decryption.guardians_joined.length">
<li v-for="guardian in decryption.guardians_joined">{{guardian}}</li>
Expand All @@ -118,11 +131,27 @@ export default {
<p>No guardians have joined yet</p>
</div>
</div>
<div v-if="decryption.completed_at_str" class="mb-4">
<h3>Tally Results</h3>
<a :href="getViewTallyUrl()" class="btn btn-sm btn-secondary m-2"><i class="bi bi-binoculars-fill me-1"></i> View Tally</a>
</div>
<div v-if="decryption.completed_at_str">
<h3>Decryption Results</h3>
<a :href="getViewTallyUrl()" class="btn btn-sm btn-primary m-2">View Tally</a>
<h4>Spoiled Ballots</h4>
<a :href="getSpoiledBallotUrl(spoiled_ballot)" class="btn btn-sm btn-primary m-2" v-for="spoiled_ballot in decryption.spoiled_ballots">{{spoiled_ballot}}</a>
<h3>Spoiled Ballots</h3>
<table class="table table-striped" v-if="decryption.spoiled_ballots.length">
<thead>
<tr>
<th>Ballot ID</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr v-for="spoiledBallot in decryption.spoiled_ballots">
<td><a :href="getSpoiledBallotUrl(spoiledBallot)">{{spoiledBallot}}</a></td>
</tr>
</tbody>
</table>
<div v-else>
<p>No spoiled ballots existed at the time this tally was run.</p>
</div>
</div>
</div>
<div class="col col-12 col-md-6 col-lg-7 text-center">
Expand All @@ -132,5 +161,8 @@ export default {
</div>
</div>
</div>
<div v-else>
<spinner :visible="loading"></spinner>
</div>
`,
};
Loading