diff --git a/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionsController.java b/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionsController.java index df3b0a7..b3d7048 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionsController.java +++ b/src/main/java/it/cnr/isti/workflow/manager/controllers/ExecutionsController.java @@ -432,6 +432,46 @@ public class ExecutionsController { executionService.setGlobalInputs(visibleExecution(executionId, userDetails).getId(), values)); } + /** + * The file counterpart of {@link #setGlobalInput}, which takes JSON and so could never carry an + * upload: a global input of a file kind had nowhere to be uploaded to, and the attempt came back + * as an unsupported content type. Named like the node-input upload endpoints because it is the + * same operation on the other scope. + */ + @PutMapping(path = "{executionId}/globals/{key}/file", consumes = "multipart/form-data") + @Operation(summary = "Prepares a global file input", + description = "Uploads one file and stores it as the value of a global flow input.") + public ExecutionView prepareGlobalFileInput(@PathVariable String executionId, @PathVariable String key, + @RequestParam MultipartFile file, @AuthenticationPrincipal LoginEntity userDetails) { + try { + File myFile = createUploadTempFile(key, file.getOriginalFilename()); + file.transferTo(myFile); + return ExecutionView.fromExecution( + executionService.setGlobalInput(visibleExecution(executionId, userDetails).getId(), key, myFile)); + } catch (IOException e) { + throw new WebServerException("Error while creating file", e); + } + } + + @PutMapping(path = "{executionId}/globals/{key}/files", consumes = "multipart/form-data") + @Operation(summary = "Prepares a global file array input", + description = "Uploads multiple files and stores them as the value of a global flow input.") + public ExecutionView prepareGlobalFileArrayInput(@PathVariable String executionId, @PathVariable String key, + @RequestParam List files, @AuthenticationPrincipal LoginEntity userDetails) { + List preparedFiles = new ArrayList<>(); + try { + for (MultipartFile file : files) { + File myFile = createUploadTempFile(key, file.getOriginalFilename()); + file.transferTo(myFile); + preparedFiles.add(myFile); + } + return ExecutionView.fromExecution( + executionService.setGlobalInput(visibleExecution(executionId, userDetails).getId(), key, preparedFiles)); + } catch (IOException e) { + throw new WebServerException("Error while creating file", e); + } + } + private ExecutionObject visibleExecution(String id, LoginEntity userDetails) { if (userDetails == null) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Authentication required"); diff --git a/src/test/java/it/cnr/isti/workflow/manager/controllers/ExecutionControllerTest.java b/src/test/java/it/cnr/isti/workflow/manager/controllers/ExecutionControllerTest.java index d47fa49..f022a05 100644 --- a/src/test/java/it/cnr/isti/workflow/manager/controllers/ExecutionControllerTest.java +++ b/src/test/java/it/cnr/isti/workflow/manager/controllers/ExecutionControllerTest.java @@ -779,6 +779,38 @@ public class ExecutionControllerTest { "the uploaded file keeps its extension, so whatever reads it back still sees a PDF: " + storedInput); } + @Test + public void aGlobalInputOfAFileKindCanBeUploadedTo() { + // The only endpoint for a global input took JSON, so a global declared as a file had nowhere + // to be uploaded to: the request came back as HttpMediaTypeNotSupportedException. + Block block = blocksController.create(LLMBlockConfiguration.builder() + .name("Summarize") + .prompt("Summarize ${{global.document}}") + .llmDescriptor(LLMDescriptor.builder().provider("testProvider").model("testModel").build()) + .build()); + + FlowCreateRequest request = new FlowCreateRequest( + "Global File Input Flow", + "Flow whose global input carries an uploaded file", + FlowData.builder() + .block(block) + .globalInput(IODescriptor.of("document", IOType.FILE)) + .build()); + ResponseEntity createdFlow = flowController.createFlow(request, testUser()); + ExecutionView execution = executionsController.create(createdFlow.getBody().id(), testUser()); + + org.springframework.mock.web.MockMultipartFile upload = new org.springframework.mock.web.MockMultipartFile( + "file", "plan.pdf", "application/pdf", "%PDF-1.7\nbody".getBytes(java.nio.charset.StandardCharsets.US_ASCII)); + + ExecutionView withFile = executionsController.prepareGlobalFileInput(execution.getId(), "document", + upload, testUser()); + + Object stored = withFile.getContext().getGlobalInputs().get("document"); + org.junit.jupiter.api.Assertions.assertNotNull(stored); + org.junit.jupiter.api.Assertions.assertTrue(String.valueOf(stored).endsWith("plan.pdf"), + "the global holds the uploaded file, extension intact: " + stored); + } + @Test public void cancelExecutionClearsRuntimeStateAndMarksExecutionCancelled() { LLMDescriptor llmDescriptor = LLMDescriptor.builder()