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

Csv export refinement #90

Merged
merged 5 commits into from
May 21, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import au.org.democracydevelopers.raireservice.service.RaireServiceException;
import au.org.democracydevelopers.raireservice.service.GenerateAssertionsService;
import au.org.democracydevelopers.raireservice.service.GetAssertionsJsonService;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCodes;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
Expand Down Expand Up @@ -121,7 +121,7 @@ public ResponseEntity<GenerateAssertionsResponse> serve(@RequestBody GenerateAss
if(solution.Err == null){
final String msg = "An error occurred in raire-java, yet no error information was returned.";
logger.error(String.format("%s %s", prefix, msg));
throw new RaireServiceException(msg, RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}

// raire-java returned error information, form and throw an exception using that data. (Note:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import au.org.democracydevelopers.raireservice.persistence.entity.NENAssertion;

import au.org.democracydevelopers.raireservice.service.RaireServiceException;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCodes;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode;
import java.util.Arrays;
import java.util.List;

Expand Down Expand Up @@ -128,7 +128,7 @@ default List<Assertion> getAssertionsThrowError(String contestName) throws Raire
final String msg = String.format("%s No assertions have been generated for the contest %s.",
prefix, contestName);
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.NO_ASSERTIONS_PRESENT);
throw new RaireServiceException(msg, RaireErrorCode.NO_ASSERTIONS_PRESENT);
}

return assertions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import au.org.democracydevelopers.raireservice.persistence.repository.CVRContestInfoRepository;
import au.org.democracydevelopers.raireservice.persistence.repository.ContestRepository;
import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCodes;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode;
import jakarta.transaction.Transactional;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -108,7 +108,7 @@ public RaireResultOrError generateAssertions(GenerateAssertionsRequest request)
+ "%d specified in the assertion generation request. Throwing a RaireServiceException.",
prefix, votes.size(), request.contestName, request.totalAuditableBallots);
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.INVALID_TOTAL_AUDITABLE_BALLOTS);
throw new RaireServiceException(msg, RaireErrorCode.INVALID_TOTAL_AUDITABLE_BALLOTS);
}

logger.debug(String.format("%s Adding all extracted rankings to a consolidator to identify " +
Expand Down Expand Up @@ -147,7 +147,7 @@ public RaireResultOrError generateAssertions(GenerateAssertionsRequest request)
final String msg = String.format("%s Invalid vote sent to RAIRE for contest %s. %s",
prefix, request.contestName, ex.getMessage());
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.WRONG_CANDIDATE_NAMES);
throw new RaireServiceException(msg, RaireErrorCode.WRONG_CANDIDATE_NAMES);
}
catch(RaireServiceException ex){
final String msg = String.format("%s A RaireServiceException was caught; passing to caller. %s",
Expand All @@ -159,13 +159,13 @@ public RaireResultOrError generateAssertions(GenerateAssertionsRequest request)
final String msg = String.format("%s A data access exception arose when extracting " +
"CVR/Contest data for contest %s. %s", prefix, request.contestName, ex.getMessage());
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}
catch(Exception ex){
final String msg = String.format("%s An exception arose when generating assertions. %s",
prefix, ex.getMessage());
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}
}

Expand Down Expand Up @@ -203,27 +203,27 @@ public void persistAssertions(RaireResult solution, GenerateAssertionsRequest re
"universe size, invalid margin, or invalid combination of winner, loser and list of " +
"assumed continuing candidates. %s", prefix, ex.getMessage());
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}
catch(ArrayIndexOutOfBoundsException ex){
final String msg = String.format("%s Array index out of bounds access in " +
"AssertionRepository::translateAndSaveAssertions. This was likely due to a winner " +
"or loser index in a raire-java assertion being invalid with respect to the " +
"candidates list for the contest. %s", prefix, ex.getMessage());
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}
catch(DataAccessException ex){
final String msg = String.format("%s Data access exception arose when persisting assertions. %s",
prefix, ex.getMessage());
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}
catch(Exception ex){
final String msg = String.format("%s An exception arose when persisting assertions. %s",
prefix, ex.getMessage());
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import static au.org.democracydevelopers.raireservice.service.Metadata.extremumHeaders;
import static au.org.democracydevelopers.raireservice.service.Metadata.csvHeaders;
import static au.org.democracydevelopers.raireservice.util.CSVUtils.escapeThenJoin;
import static au.org.democracydevelopers.raireservice.util.CSVUtils.intListToString;

import au.org.democracydevelopers.raireservice.persistence.entity.Assertion;
import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository;
import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCodes;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode;
import au.org.democracydevelopers.raireservice.util.DoubleComparator;
import java.math.BigDecimal;
import java.util.ArrayList;
Expand Down Expand Up @@ -110,7 +111,7 @@ public String generateCSV(GetAssertionsRequest request) throws RaireServiceExcep
} catch (Exception e) {
logger.error(String.format("%s Generic exception caught. Passing to caller: %s",
prefix, e.getMessage()));
throw new RaireServiceException(e.getMessage(), RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(e.getMessage(), RaireErrorCode.INTERNAL_ERROR);
}
}

Expand Down Expand Up @@ -186,8 +187,7 @@ private record extremumResult<T>(
* @return a CSV row with the relevant data, as a string.
*/
String toCSVRow() {
return escapeThenJoin(List.of(statisticName, value.toString(),
String.join(", ", indices.stream().map(Object::toString).toList())));
return escapeThenJoin(List.of(statisticName, value.toString()))+","+intListToString(indices);
}
}

Expand Down Expand Up @@ -273,7 +273,7 @@ private String makeContents(List<Assertion> assertions) {
List<String> rows = new ArrayList<>();

for (Assertion assertion : assertions) {
rows.add(index++ + ", " + escapeThenJoin(assertion.asCSVRow()));
rows.add(index++ + "," + escapeThenJoin(assertion.asCSVRow()));
}

return String.join("\n", rows) + "\n";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import au.org.democracydevelopers.raireservice.persistence.entity.Assertion;
import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository;
import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCodes;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -133,7 +133,7 @@ public RaireSolution getRaireSolution(GetAssertionsRequest request)
catch(Exception ex){
logger.error(String.format("%s Generic exception caught. Passing to caller: %s",
prefix, ex.getMessage()));
throw new RaireServiceException(ex.getMessage(), RaireErrorCodes.INTERNAL_ERROR);
throw new RaireServiceException(ex.getMessage(), RaireErrorCode.INTERNAL_ERROR);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class RaireServiceException extends Exception {
* The error code - an enum used to describe what went wrong. Returned in the http response for
* colorado-rla to interpret for the user.
*/
public RaireErrorCodes errorCode;
public RaireErrorCode errorCode;

/**
* Main constructor, for translating a RaireError into a RaireServiceException.
Expand All @@ -56,34 +56,34 @@ public class RaireServiceException extends Exception {
public RaireServiceException(RaireError error, List<String> candidates) {
super(makeMessage(error, candidates));
switch (error) {
case TiedWinners e -> this.errorCode = RaireErrorCodes.TIED_WINNERS;
case TiedWinners e -> this.errorCode = RaireErrorCode.TIED_WINNERS;

case TimeoutFindingAssertions e ->
this.errorCode = RaireErrorCodes.TIMEOUT_FINDING_ASSERTIONS;
this.errorCode = RaireErrorCode.TIMEOUT_FINDING_ASSERTIONS;

case TimeoutTrimmingAssertions e ->
this.errorCode = RaireErrorCodes.TIMEOUT_TRIMMING_ASSERTIONS;
this.errorCode = RaireErrorCode.TIMEOUT_TRIMMING_ASSERTIONS;

case TimeoutCheckingWinner e -> this.errorCode = RaireErrorCodes.TIMEOUT_CHECKING_WINNER;
case TimeoutCheckingWinner e -> this.errorCode = RaireErrorCode.TIMEOUT_CHECKING_WINNER;

case CouldNotRuleOut e -> this.errorCode = RaireErrorCodes.COULD_NOT_RULE_OUT_ALTERNATIVE;
case CouldNotRuleOut e -> this.errorCode = RaireErrorCode.COULD_NOT_RULE_OUT_ALTERNATIVE;

// This is what we get if the candidate list entered in the request has the
// right number but wrong names vs the database.
case InvalidCandidateNumber e -> this.errorCode = RaireErrorCodes.WRONG_CANDIDATE_NAMES;
case InvalidCandidateNumber e -> this.errorCode = RaireErrorCode.WRONG_CANDIDATE_NAMES;

// Internal coding errors.
case InvalidTimeout e -> this.errorCode = RaireErrorCodes.INTERNAL_ERROR;
case InvalidTimeout e -> this.errorCode = RaireErrorCode.INTERNAL_ERROR;

case InternalErrorDidntRuleOutLoser e -> this.errorCode = RaireErrorCodes.INTERNAL_ERROR;
case InternalErrorDidntRuleOutLoser e -> this.errorCode = RaireErrorCode.INTERNAL_ERROR;

case InternalErrorRuledOutWinner e -> this.errorCode = RaireErrorCodes.INTERNAL_ERROR;
case InternalErrorRuledOutWinner e -> this.errorCode = RaireErrorCode.INTERNAL_ERROR;

case InternalErrorTrimming e -> this.errorCode = RaireErrorCodes.INTERNAL_ERROR;
case InternalErrorTrimming e -> this.errorCode = RaireErrorCode.INTERNAL_ERROR;

case InvalidNumberOfCandidates e -> this.errorCode = RaireErrorCodes.INTERNAL_ERROR;
case InvalidNumberOfCandidates e -> this.errorCode = RaireErrorCode.INTERNAL_ERROR;

case WrongWinner e -> this.errorCode = RaireErrorCodes.INTERNAL_ERROR;
case WrongWinner e -> this.errorCode = RaireErrorCode.INTERNAL_ERROR;
}
}

Expand All @@ -92,17 +92,17 @@ public RaireServiceException(RaireError error, List<String> candidates) {
* during assertion generation, for example a database error.
*
* @param message a human-readable message for the exception.
* @param code a RaireErrorCodes code to indicate the type of error that has arisen.
* @param code a RaireErrorCode code to indicate the type of error that has arisen.
*/
public RaireServiceException(String message, RaireErrorCodes code) {
public RaireServiceException(String message, RaireErrorCode code) {
super(message);
this.errorCode = code;
}

/**
* Error codes describing what went wrong, for returning via http to colorado-rla.
*/
public enum RaireErrorCodes {
public enum RaireErrorCode {

// Errors that the user can do something about.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ public class CSVUtils {
* in one cell.
*/
public static String escapeThenJoin(List<String> data) {
return String.join(", ", data.stream().map(StringEscapeUtils::escapeCsv).toList());
return String.join(",", data.stream().map(StringEscapeUtils::escapeCsv).toList());
}

/**
* Turn a list of integers into a pretty-printed string intended for a csv cell. This puts quotes
* around everything, including a singleton list, so that it is interpreted as a string by the
* spreadsheet/csv reader.
* @param data a list of Integers.
* @return the list as a pretty-printed comma-separated string with explicit quotes around it.
*/
public static String intListToString(List<Integer> data) {
if(data.size() == 1) {
// Put quotes around it so it will be interpreted as a string by the csv parser
return "\""+data.getFirst()+"\"";
} else {
// This will return an empty string for an empty input list, otherwise a string with the data
// items comma-separated.
return "\""+String.join(", ", data.stream().map(Object::toString).toList())+"\"";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra

package au.org.democracydevelopers.raireservice.controller;

import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCodes.TIMEOUT_CHECKING_WINNER;
import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode.TIMEOUT_CHECKING_WINNER;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCodes;
import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode;
import au.org.democracydevelopers.raireservice.testUtils;
import java.util.Objects;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -98,7 +98,7 @@ public void testValidRequestWithNoAssertions() {
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

assertTrue(response.getStatusCode().is5xxServerError());
assertEquals(RaireErrorCodes.NO_ASSERTIONS_PRESENT.toString(),
assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT.toString(),
Objects.requireNonNull(response.getHeaders().get("error_code")).getFirst());
assertTrue(StringUtils.containsIgnoreCase(response.getBody(),
"No assertions have been generated for the contest"));
Expand Down
Loading
Loading