Skip to content
6 changes: 5 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
id("org.orph2020.pst.common-plugin")
}
version = "0.7"
version = "1.0"

dependencies {
implementation("io.quarkus:quarkus-mailer")
Expand All @@ -17,6 +17,7 @@ dependencies {
implementation("io.quarkus:quarkus-hibernate-orm")
implementation("io.quarkus:quarkus-rest-client-reactive-jackson")
implementation("io.quarkus:quarkus-hibernate-validator")
implementation("io.quarkus:quarkus-liquibase")
implementation("io.quarkus:quarkus-jdbc-postgresql")
implementation("io.quarkus:quarkus-kubernetes-service-binding")

Expand All @@ -32,6 +33,9 @@ dependencies {
implementation("uk.ac.starlink:stil:4.1.4")

implementation("commons-io:commons-io:2.15.1")

implementation("org.apache.poi:poi:5.2.5")
implementation("org.apache.poi:poi-ooxml:5.2.5")
}


Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public Response downloadReviewerZip(@PathParam("proposalCode") Long proposalCode

SubmittedProposal proposal = findObject(SubmittedProposal.class, proposalCode);

return Response.ok(proposalResource.CreateZipFile("Review.zip", proposal, true ))
return Response.ok(proposalResource.CreateZipFile("Review.zip", proposal, true, false ))
.header("Content-Disposition", "attachment; filename=" + "Review.zip")
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import jakarta.persistence.Query;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.core.Response;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
Expand All @@ -22,6 +26,8 @@
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

Expand All @@ -36,6 +42,8 @@ public class ProposalCyclesResource extends ObjectResourceBase {
SubjectMapResource subjectMapResource;
@Inject
JsonWebToken userInfo;
@Inject
ProposalDocumentStore proposalDocumentStore;

private static final String notOnTACmsg = "This endpoint is restricted to TAC members only";

Expand Down Expand Up @@ -523,4 +531,70 @@ public Response replaceCycleObservatory(
return result;
}

@GET
@Path("{cycleCode}/excelReviews")
@Operation(summary="Create and download an excel sheet of all submitted proposals and their review scores")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response ExcelReviews(@PathParam("cycleCode") Long cycleCode) {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
// Get the whole proposal cycle
ProposalCycle proposalCycle = findObject(ProposalCycle.class, cycleCode);

// Create a sheet
XSSFSheet sheet = workbook.createSheet(proposalCycle.getCode());

// count the reviewers for the number of columns
HashMap<Reviewer, Integer > allReviewers = new HashMap<>();
Integer reviewerColumnPos = 0;
for(SubmittedProposal submittedProposal : proposalCycle.getSubmittedProposals())
for(ProposalReview review : submittedProposal.getReviews())
allReviewers.putIfAbsent(review.getReviewer(), reviewerColumnPos++);

// write headers
int rowNum = 0;

Row row = sheet.createRow(rowNum++);
Cell cell = row.createCell(0);
cell.setCellValue("Code");
Cell cellTitle = row.createCell(1);
cellTitle.setCellValue("Title");

for(Reviewer reviewer : allReviewers.keySet()) {
Cell rCell = row.createCell(2 + allReviewers.get(reviewer));
rCell.setCellValue(reviewer.getPerson().getFullName());
}

// write data
for(SubmittedProposal submittedProposal: proposalCycle.getSubmittedProposals()) {
int cellNum = 0;
Row submittedRow = sheet.createRow(rowNum++);
Cell code = submittedRow.createCell(cellNum++);
code.setCellValue(submittedProposal.getProposalCode());
Cell title = submittedRow.createCell(cellNum++);
title.setCellValue(submittedProposal.getTitle());
//Populate review scores
for(ProposalReview review : submittedProposal.getReviews()) {
Cell reviewScore = submittedRow.createCell(cellNum + allReviewers.get(review.getReviewer()));
reviewScore.setCellValue(review.getScore());
}

}

String filename = "/Reviews for " + proposalCycle.getCode() + ".xlsx";
try (FileOutputStream out = new FileOutputStream(proposalDocumentStore.getStoreRoot() + filename)) {
workbook.write(out);
} catch (IOException e) {
// error writing excel workbook to file
return Response.status(500).build();
}

return Response.ok(proposalDocumentStore.fetchFile(filename))
.header("Content-Disposition", "attachment; filename=" + filename)
.build();
}
catch (Exception e) {
return Response.status(500).build();
}
}

}
45 changes: 28 additions & 17 deletions src/main/java/org/orph2020/pst/apiimpl/rest/ProposalResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -955,24 +955,28 @@ private String observationsTable(List<Observation> observations) {
}


public File CreateZipFile(String zipFileName, AbstractProposal proposal, boolean anonymise) throws IOException {
public File CreateZipFile(String zipFileName, AbstractProposal proposal, boolean anonymise, boolean genericExportFilenames) throws IOException {
// Create zip file
File myZipFile = new File(zipFileName);
ZipOutputStream zipOs = new ZipOutputStream(new FileOutputStream(myZipFile));
String jsonFilename = "proposal.json";
String projFilename = "proposal";

// Write proposal data (if given)
if(proposal != null) {
if (proposal instanceof SubmittedProposal) {
projFilename = ((SubmittedProposal) proposal).getProposalCode() + "."
+ proposal.getTitle().replaceAll("[\\\\/:*?\"<>|]", "_")
.substring(0, Math.min(proposal.getTitle().length(), 30));
}
if (proposal instanceof ObservingProposal) {
projFilename = proposal.getTitle().replaceAll("[\\\\/:*?\"<>|]", "_")
.substring(0, Math.min(proposal.getTitle().length(), 30));
}

if(!anonymise) {
//json of Proposal
ByteArrayInputStream bais = new ByteArrayInputStream(writeAsJsonString(proposal).getBytes());
if (proposal instanceof SubmittedProposal) {
jsonFilename = ((SubmittedProposal) proposal).getProposalCode()
+ proposal.getTitle().substring(0, Math.min(proposal.getTitle().length(), 31))
+ ".json";
}

zipOs.putNextEntry(new ZipEntry(jsonFilename));
zipOs.putNextEntry(new ZipEntry(genericExportFilenames?"propsal.json":projFilename+ ".json"));

byte[] bytes = new byte[1024];
int length;
Expand All @@ -987,19 +991,25 @@ public File CreateZipFile(String zipFileName, AbstractProposal proposal, boolean

// HTML overview page
overviewHTMLDocument(proposal, anonymise);
zipOs.putNextEntry(new ZipEntry("Overview.html"));
zipOs.putNextEntry(new ZipEntry(genericExportFilenames?"Overview.html":projFilename + ".html"));
Files.copy(proposalDocumentStore.fetchFile(proposal.getId() + "/Overview.html").toPath(), zipOs);
zipOs.flush();
zipOs.closeEntry();

// Add all supporting documents unless anonymised, then only add compiled justification
for(SupportingDocument doc: proposal.getSupportingDocuments()) {
// If anonymise is true, only include the compiled justifications pdf
if(!anonymise || doc.getTitle().equals(justificationsResource.jobName+".pdf")) {
zipOs.putNextEntry(new ZipEntry(doc.getTitle()));
Files.copy(proposalDocumentStore
.fetchFile(proposalDocumentStore.getSupportingDocumentsPath(proposal.getId())
+ doc.getTitle()).toPath(),
zipOs);
// If genericExportFilenames is false, rename compiled justifications pdf
if(!genericExportFilenames && doc.getTitle().equals(justificationsResource.jobName+".pdf"))
zipOs.putNextEntry(new ZipEntry(projFilename + ".pdf"));
else
zipOs.putNextEntry(new ZipEntry(doc.getTitle()));

Files.copy(proposalDocumentStore.fetchFile(
proposalDocumentStore.getSupportingDocumentsPath(proposal.getId())
+ doc.getTitle()).toPath(),
zipOs);
zipOs.flush();
zipOs.closeEntry();
}
Expand All @@ -1020,11 +1030,12 @@ public Response exportProposalZip(@PathParam("proposalCode")Long proposalCode)
throws WebApplicationException, IOException {
ObservingProposal proposalForExport = singleObservingProposal(proposalCode);
String filename = "Export."
+ proposalForExport.getTitle().substring(0, Math.min(proposalForExport.getTitle().length(), 31))
+ proposalForExport.getTitle().replaceAll("[\\\\/:*?\"<>|]", "_")
.substring(0, Math.min(proposalForExport.getTitle().length(), 30))
+ ".zip";

File myZipFile = CreateZipFile(proposalDocumentStore.getStoreRoot() + proposalCode.toString()
+ "/" + filename, proposalForExport, false);
+ "/" + filename, proposalForExport, false, true);

return Response.ok(myZipFile)
.header("Content-Disposition", "attachment; filename=" + filename)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,16 @@ public Response downloadAdminZip(@PathParam("submittedProposalId") Long submitte

SubmittedProposal proposal = findObject(SubmittedProposal.class, submittedProposalId);

String filename = proposal.getProposalCode()
+ proposal.getTitle().substring(0, Math.min(proposal.getTitle().length(), 31))
String filename = proposal.getProposalCode() + "."
+ proposal.getTitle().replaceAll("[\\\\/:*?\"<>|]", "_")
.substring(0, Math.min(proposal.getTitle().length(), 30))
+ ".zip";

// Generate the Admin's pdf view of this submitted proposal
justificationsResource.createTACAdminPDF(submittedProposalId);

File myZipFile = proposalResource.CreateZipFile(proposalDocumentStore.getStoreRoot()
+ submittedProposalId + "/" + filename, proposal, false);
+ submittedProposalId + "/" + filename, proposal, false, false);

return Response.ok(myZipFile)
.header("Content-Disposition", "attachment; filename=" + "Example.zip")
Expand Down
6 changes: 5 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@ quarkus.mailer.start-tls=DISABLED
#https://quarkus.io/guides/hibernate-orm#multiple-persistence-units
#quarkus.hibernate-orm.datasource=pstdb
quarkus.hibernate-orm.packages=org.ivoa.dm,org.ivoa.vodml.stdtypes,org.orph2020.pst.apiimpl.entities
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
%dev,test.quarkus.hibernate-orm.database.generation=drop-and-create
#do not let the database update in production - has to be done manually now.
%prod.quarkus.hibernate-orm.database.generation=none
#note latest documentation has this
%prod.quarkus.hibernate-orm.schema-management.strategy = none
quarkus.hibernate-orm.database.generation.create-schemas=true
quarkus.hibernate-orm.database.generation.halt-on-error=false
# Liquibase minimal config properties
%dev.quarkus.liquibase.migrate-at-start=false
%prod.quarkus.liquibase.migrate-at-start=true

quarkus.hibernate-orm.quote-identifiers.strategy = all

%test.quarkus.hibernate-orm.log.sql=true
Expand Down
Loading