Skip to content

Commit

Permalink
Implemented controller endpoints for getting files from processing re…
Browse files Browse the repository at this point in the history
…sults. Updated report to point to all these urls. Renamed report to velocicroptor. Fixed issue in preIngest page when source not set
  • Loading branch information
bbpennel committed Oct 1, 2024
1 parent f1eebd6 commit 688819b
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export default {
copy_msg: '',
columns: [
{ data: 'projectProperties.name', title: 'Chompb Project' },
{ data: 'projectProperties.projectSource', title: 'Source' },
{ title: 'Source',
data: function (row) {
return (row.projectProperties.projectSource) ? row.projectProperties.projectSource : '';
}
},
{ data: 'status', title: 'Status' },
{ data: null, title: 'Actions',
render: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<h2 class="chompb-ui has-text-weight-semibold is-size-3 has-text-centered">Cropping Report</h2>
<div class="buttons has-addons">
<span class="button is-success is-selected">
<a class="download" download="originals.csv" :href="downloadOriginals">
<a class="download" download="originals.csv" :href="downloadOriginalCsvUrl">
Original CSV
</a>
</span>
Expand All @@ -16,8 +16,6 @@
</span>
</div>
<data-table @click.prevent="markAsProblem($event)" class="table is-striped is-bordered is-fullwidth"
:data="dataSet"
:columns="columns"
:options="tableOptions">
<thead>
<tr>
Expand All @@ -43,28 +41,29 @@ import 'datatables.net-select-bm'
DataTable.use(DataTablesLib);

export default {
name: 'croppingReport',
name: 'velocicroptorReport',

components: {DataTable},

data() {
return {
columns: [
{data: 'Image'},
{data: 'Path'},
{data: 'Class'},
{data: 'Confidence'},
{data: 'Problem'},
{data: ''}
],
problems: [],
dataSet: []
dataSet: [],
projectName: this.$route.path.split('/')[4],
jobName: this.$route.path.split('/')[6]
}
},

computed: {
tableOptions() {
return {
ajax: {
url: `chompb/project/${this.projectName}/processing_results/${this.jobName}/files?path=data.json`,
dataSrc: (data) => {
this.dataSet = data;
return data.data;
}
},
columnDefs: this.columnDefs,
searching: true,
layout: {
Expand Down Expand Up @@ -95,7 +94,8 @@ export default {
{ searchable: false, target: excluded_columns },
{
render: (data, type, row) => {
return `<img src="${row.image}" alt="" role="presentation" />`;
let imgUrl = `/admin/chompb/project/${this.projectName}/processing_results/${this.jobName}/files?path=${row.image}`;
return `<img src="${imgUrl}" alt="" role="presentation" />`;
}, targets: 0
},
{
Expand Down Expand Up @@ -127,20 +127,16 @@ export default {
];
},

downloadOriginals() {
let csvContent = "original_path,normalized_path,predicted_class,predicted_conf\n";
csvContent += this.dataSet.map(d => {
return `${d.original},${d.image},${d.pred_class},${d.pred_conf}`;
}).join("\n");
return this.downloadResponse(csvContent);
downloadOriginalCsvUrl() {
return `/admin/chompb/project/${this.projectName}/processing_results/${this.jobName}/files?path=data.csv`;
},

downloadProblems() {
let csvContent = "path,predicted_class,corrected_class\n"
csvContent += this.problems.map(d => {
return `${d.path},${d.predicted},${d.predicted === '1' ? '0' : '1'}`
}).join("\n");
return this.downloadResponse(csvContent);
return this.downloadResponse(csvContent);
}
},

Expand Down
8 changes: 4 additions & 4 deletions static/js/admin/vue-cdr-admin/src/router.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router';
import modalEditor from "@/components/permissions-editor/modalEditor.vue";
import preIngest from "@/components/chompb/preIngest.vue";
import croppingReport from "@/components/chompb/croppingReport.vue";
import velocicroptorReport from "@/components/chompb/velocicroptorReport.vue";

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
Expand All @@ -22,9 +22,9 @@ const router = createRouter({
component: preIngest
},
{
path: '/admin/chompb/cropping_report/:project',
name: 'croppingReport',
component: croppingReport
path: '/admin/chompb/project/:project/processing_results/velocicroptor',
name: 'velocicroptorReport',
component: velocicroptorReport
},
]
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package edu.unc.lib.boxc.web.admin.controllers;

import edu.unc.lib.boxc.auth.api.models.AccessGroupSet;
import edu.unc.lib.boxc.auth.api.models.AgentPrincipals;
import edu.unc.lib.boxc.auth.fcrepo.models.AgentPrincipalsImpl;
import edu.unc.lib.boxc.auth.fcrepo.services.GroupsThreadStore;
import edu.unc.lib.boxc.web.admin.controllers.processing.ChompbPreIngestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

Expand Down Expand Up @@ -48,16 +51,45 @@ public String chompbCroppingReport() {
return "report/chompb";
}

@RequestMapping(value = "chompb/project/{projectName}/processing_results/{jobName}/data.json",
method = RequestMethod.GET,
produces = APPLICATION_JSON_VALUE)
public @ResponseBody String getProcessingResults(@PathVariable("projectName") String projectName,
@PathVariable("jobName") String jobName) {
/**
* Get processing result files for a specific job
* @param projectName
* @param jobName
* @param filePath
* @return
* @throws IOException
*/
@RequestMapping(value = "chompb/project/{projectName}/processing_results/{jobName}/files",
method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> getProcessingResults(@PathVariable("projectName") String projectName,
@PathVariable("jobName") String jobName,
@RequestParam(value = "path", defaultValue = "false") String filename)
throws IOException {
// var filename = extractFilename(request, jobName);
var agentPrincipals = AgentPrincipalsImpl.createFromThread();
try {
return chompbPreIngestService.getProcessingResults(agentPrincipals, projectName, jobName);
} catch (IOException e) {
throw new RuntimeException(e);
// filename = URLDecoder.decode(filename, StandardCharsets.UTF_8);
var nameSegment = Paths.get(filename).getFileName().toString();
var stream = chompbPreIngestService.getProcessingResults(agentPrincipals, projectName, jobName, filename);
InputStreamResource resource = new InputStreamResource(stream);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + nameSegment)
.contentType(getMediaType(filename))
.body(resource);
}

private String extractFilename(HttpServletRequest request, String jobName) {
var preceding = String.format("/processing_results/%s/", jobName);
var queryPath = request.getRequestURI();
return queryPath.substring(queryPath.indexOf(preceding) + preceding.length());
}

private MediaType getMediaType(String filename) {
if (filename.endsWith(".json")) {
return MediaType.APPLICATION_JSON;
} else if (filename.endsWith(".csv")) {
return MediaType.TEXT_PLAIN;
} else {
return MediaType.IMAGE_JPEG;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

/**
* Service for interacting with the chompb command-line tool for pre-ingest processing
* @author lfarrell
*/
public class ChompbPreIngestService {
private GlobalPermissionEvaluator globalPermissionEvaluator;
private Path baseProjectsPath;
private static final Set<String> VALID_FILENAMES = Set.of("data.json", "data.csv");

/**
* List all of the chompb projects in the base projects path
Expand Down Expand Up @@ -63,16 +66,31 @@ protected String executeChompbCommand(String... command) {
* @param agent
* @param projectName
* @param jobName
* @return String representation of json processing results
* @param filename
* @return Contents of the file as a InputStream
*/
public String getProcessingResults(AgentPrincipals agent, String projectName, String jobName) throws IOException {
public InputStream getProcessingResults(AgentPrincipals agent, String projectName, String jobName, String filename) throws IOException {
assertHasPermission(agent);
assertValidProcessingResultFilename(filename);

var projectPath = buildProjectPath(projectName);
// Build path to results file
var resultsPath = projectPath.resolve("processing/results").resolve(jobName).resolve("report/data.json");
// Read the file and return it as a string
return Files.readString(resultsPath);
Path resultsPath = projectPath.resolve("processing/results")
.resolve(jobName)
.resolve("report")
.resolve(filename)
.normalize();
if (!resultsPath.startsWith(projectPath)) {
throw new AccessRestrictionException("Cannot access specified file");
}
// Read the file and return it as a stream
return Files.newInputStream(resultsPath);
}

private void assertValidProcessingResultFilename(String filename) {
if (!VALID_FILENAMES.contains(filename) && !(filename.startsWith("images/") && filename.endsWith(".jpg"))) {
throw new IllegalArgumentException("Invalid filename: " + filename);
}
}

private void assertHasPermission(AgentPrincipals agent) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package edu.unc.lib.boxc.web.admin.controllers;

import edu.unc.lib.boxc.web.admin.controllers.processing.ChompbPreIngestService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import java.io.ByteArrayInputStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand All @@ -17,6 +24,8 @@
* @author lfarrell
*/
public class ChompbControllerIT {
@Mock
private ChompbPreIngestService chompbPreIngestService;
@InjectMocks
private ChompbController controller;
private MockMvc mvc;
Expand All @@ -43,9 +52,62 @@ public void testPageLoad() throws Exception {

@Test
public void testProcessingReportRequest() throws Exception {
MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor/"))
MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor"))
.andExpect(status().isOk())
.andReturn();
assertEquals("report/chompb", result.getResponse().getForwardedUrl());
}

@Test
public void testListProjects() throws Exception {
when(chompbPreIngestService.getProjectLists(any())).thenReturn("test");
MvcResult result = mvc.perform(get("/chompb/listProjects"))
.andExpect(status().isOk())
.andReturn();
var resp = result.getResponse().getContentAsString();
assertEquals("test", resp);
}

@Test
public void testGetProcessingResultsJson() throws Exception {
var resultContent = "jsontest";
var resultStream = new ByteArrayInputStream(resultContent.getBytes());
when(chompbPreIngestService.getProcessingResults(any(), any(), any(), any())).thenReturn(resultStream);
MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor/files?path=data.json"))
.andExpect(status().isOk())
.andReturn();
assertEquals("application/json", result.getResponse().getContentType());
assertEquals("attachment; filename=data.json", result.getResponse().getHeader("Content-Disposition"));
var resp = result.getResponse().getContentAsString();
assertEquals(resultContent, resp);
}

@Test
public void testGetProcessingResultsCsv() throws Exception {
var resultContent = "csvtest";
var resultStream = new ByteArrayInputStream(resultContent.getBytes());
when(chompbPreIngestService.getProcessingResults(any(), any(), any(), any())).thenReturn(resultStream);
MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor/files?path=data.csv"))
.andExpect(status().isOk())
.andReturn();
assertEquals("text/plain", result.getResponse().getContentType());
assertEquals("attachment; filename=data.csv", result.getResponse().getHeader("Content-Disposition"));
var resp = result.getResponse().getContentAsString();
assertEquals(resultContent, resp);
}

@Test
public void testGetProcessingResultsImage() throws Exception {
var resultContent = "testimage";
var resultStream = new ByteArrayInputStream(resultContent.getBytes());
var imagePath = "images/path/to/chomp.jpg";
when(chompbPreIngestService.getProcessingResults(any(), any(), any(), eq(imagePath))).thenReturn(resultStream);
MvcResult result = mvc.perform(get("/chompb/project/test_proj/processing_results/velocicroptor/files?path=" + imagePath))
.andExpect(status().isOk())
.andReturn();
assertEquals("image/jpeg", result.getResponse().getContentType());
assertEquals("attachment; filename=chomp.jpg", result.getResponse().getHeader("Content-Disposition"));
var resp = result.getResponse().getContentAsString();
assertEquals(resultContent, resp);
}
}
Loading

0 comments on commit 688819b

Please sign in to comment.