diff --git a/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java b/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java index 67e332ed67..6c5ff8e895 100644 --- a/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java +++ b/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/ApiDocumentation.java @@ -115,6 +115,7 @@ void index() throws Exception { linkWithRel("tasks/executions/external").description("Returns Task execution by external id"), linkWithRel("tasks/executions/current").description("Provides the current count of running tasks"), linkWithRel("tasks/thinexecutions").description("Returns thin Task executions"), + linkWithRel("tasks/thinexecutions/name").description("Returns thin Task executions for a given task name"), linkWithRel("tasks/info/executions").description("Provides the task executions info"), linkWithRel("tasks/schedules").description("Provides schedule information of tasks"), linkWithRel("tasks/schedules/instances").description("Provides schedule information of a specific task "), @@ -122,7 +123,6 @@ void index() throws Exception { linkWithRel("tasks/executions/execution").description("Provides details for a specific task execution"), linkWithRel("tasks/platforms").description("Provides platform accounts for launching tasks. The results can be filtered to show the platforms that support scheduling by adding a request parameter of 'schedulesEnabled=true"), linkWithRel("tasks/logs").description("Retrieve the task application log"), - linkWithRel("tasks/thinexecutions").description("Returns thin Task executions"), linkWithRel("streams/definitions").description("Exposes the Streams resource"), linkWithRel("streams/definitions/definition").description("Handle a specific Stream definition"), @@ -221,14 +221,15 @@ void index() throws Exception { fieldWithPath("_links.tasks/thinexecutions.href").description("Link to the tasks/thinexecutions"), + fieldWithPath("_links.tasks/thinexecutions/name.href").description("Link to the tasks/thinexecutions/name"), + fieldWithPath("_links.tasks/thinexecutions/name.templated").description("Link to the tasks/thinexecutions/name is templated"), + fieldWithPath("_links.tasks/info/executions.href").description("Link to the tasks/info/executions"), fieldWithPath("_links.tasks/info/executions.templated").type(JsonFieldType.BOOLEAN).optional().description("Link tasks/info is templated"), fieldWithPath("_links.tasks/logs.href").description("Link to the tasks/logs"), fieldWithPath("_links.tasks/logs.templated").type(JsonFieldType.BOOLEAN).optional().description("Link tasks/logs is templated"), - fieldWithPath("_links.tasks/thinexecutions.href").description("Link to the tasks/thinexecutions"), - fieldWithPath("_links.tasks/schedules.href").description("Link to the tasks/executions/schedules"), fieldWithPath("_links.tasks/schedules/instances.href").description("Link to the tasks/schedules/instances"), fieldWithPath("_links.tasks/schedules/instances.templated").type(JsonFieldType.BOOLEAN).optional().description("Link tasks/schedules/instances is templated"), diff --git a/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/TaskExecutionsDocumentation.java b/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/TaskExecutionsDocumentation.java index 2302edc848..f1936fbcc5 100644 --- a/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/TaskExecutionsDocumentation.java +++ b/spring-cloud-dataflow-classic-docs/src/test/java/org/springframework/cloud/dataflow/server/rest/documentation/TaskExecutionsDocumentation.java @@ -312,9 +312,9 @@ void listTaskThinExecutions() throws Exception { } @Test - void listTaskExecutionsByName() throws Exception { + void listTaskThinExecutionsByName() throws Exception { this.mockMvc.perform( - get("/tasks/executions") + get("/tasks/thinexecutions") .queryParam("name", "taskB") .queryParam("page", "0") .queryParam("size", "10") @@ -328,11 +328,33 @@ void listTaskExecutionsByName() throws Exception { parameterWithName("name") .description("The name associated with the task execution")), responseFields( - subsectionWithPath("_embedded.taskExecutionResourceList") - .description("Contains a collection of Task Executions/"), + subsectionWithPath("_embedded.taskExecutionThinResourceList") + .description("Contains a collection of thin Task Executions/"), subsectionWithPath("_links.self").description("Link to the task execution resource"), subsectionWithPath("page").description("Pagination properties")))); } + @Test + void listTaskExecutionsByName() throws Exception { + this.mockMvc.perform( + get("/tasks/executions") + .queryParam("name", "taskB") + .queryParam("page", "0") + .queryParam("size", "10") + ) + .andExpect(status().isOk()).andDo(this.documentationHandler.document( + queryParameters( + parameterWithName("page") + .description("The zero-based page number (optional)"), + parameterWithName("size") + .description("The requested page size (optional)"), + parameterWithName("name") + .description("The name associated with the task execution")), + responseFields( + subsectionWithPath("_embedded.taskExecutionResourceList") + .description("Contains a collection of Task Executions/"), + subsectionWithPath("_links.self").description("Link to the task execution resource"), + subsectionWithPath("page").description("Pagination properties")))); + } @Test void stopTask() throws Exception { diff --git a/spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc b/spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc index 658c890a29..b4ebd95915 100644 --- a/spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc +++ b/spring-cloud-dataflow-docs/src/main/asciidoc/api-guide.adoc @@ -2067,6 +2067,85 @@ include::{snippets}/task-executions-documentation/list-task-executions-by-name/c include::{snippets}/task-executions-documentation/list-task-executions-by-name/http-response.adoc[] +[[api-guide-resources-task-thin-executions-list]] +==== List All Task Thin Executions + +The task executions endpoint lets you list all task executions with only top-level data. +The following topics provide more details: + +* <> +* <> +* <> +* <> + + + +[[api-guide-resources-task-thin-executions-list-request-structure]] +===== Request Structure + +include::{snippets}/task-executions-documentation/list-task-thin-executions/http-request.adoc[] + + + +[[api-guide-resources-task-thin-executions-list-request-parameters]] +===== Request Parameters + +include::{snippets}/task-executions-documentation/list-task-thin-executions/request-parameters.adoc[] + + + +[[api-guide-resources-task-thin-executions-list-example-request]] +===== Example Request + +include::{snippets}/task-executions-documentation/list-task-thin-executions/curl-request.adoc[] + + + +[[api-guide-resources-task-thin-executions-list-response-structure]] +===== Response Structure + +include::{snippets}/task-executions-documentation/list-task-thin-executions/http-response.adoc[] + + + +[[api-guide-resources-task-thin-executions-list-by-name]] +==== List All Task Thin Executions With a Specified Task Name + +The task thin executions endpoint lets you list task executions with a specified task name. +The following topics provide more details: + +* <> +* <> +* <> +* <> + + + +[[api-guide-resources-task-thin-executions-list-by-name-request-structure]] +===== Request Structure + +include::{snippets}/task-executions-documentation/list-task-thin-executions-by-name/http-request.adoc[] + + + +[[api-guide-resources-task-thin-executions-list-by-name-request-parameters]] +===== Request Parameters + +include::{snippets}/task-executions-documentation/list-task-thin-executions-by-name/request-parameters.adoc[] + + + +[[api-guide-resources-task-thin-executions-list-by-name-example-request]] +===== Example Request + +include::{snippets}/task-executions-documentation/list-task-thin-executions-by-name/curl-request.adoc[] + + + +[[api-guide-resources-task-thin-executions-list-by-name-response-structure]] +===== Response Structure + +include::{snippets}/task-executions-documentation/list-task-thin-executions-by-name/http-response.adoc[] [[api-guide-resources-task-executions-detail]] ==== Task Execution Detail diff --git a/spring-cloud-dataflow-docs/src/main/asciidoc/configuration.adoc b/spring-cloud-dataflow-docs/src/main/asciidoc/configuration.adoc index 41a0ad34f6..c2c17c5085 100644 --- a/spring-cloud-dataflow-docs/src/main/asciidoc/configuration.adoc +++ b/spring-cloud-dataflow-docs/src/main/asciidoc/configuration.adoc @@ -661,6 +661,7 @@ spring: - DELETE /tasks/executions/* => hasRole('ROLE_DESTROY') - GET /tasks/thinexecutions => hasRole('ROLE_VIEW') + - GET /tasks/thinexecutions/* => hasRole('ROLE_VIEW') # Task Schedules diff --git a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskOperations.java b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskOperations.java index e62bc8d784..a37a416a99 100644 --- a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskOperations.java +++ b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskOperations.java @@ -113,6 +113,12 @@ public interface TaskOperations { */ PagedModel thinExecutionList(); + /** + * List task executions filtered by task name. + * @return the page of task executions for the given task name. + */ + PagedModel thinExecutionListByTaskName(String taskName); + /** * List task executions known to the system filtered by task name. * diff --git a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskTemplate.java b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskTemplate.java index 121c8ab4d4..dd9f29b73a 100644 --- a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskTemplate.java +++ b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/TaskTemplate.java @@ -71,6 +71,8 @@ public class TaskTemplate implements TaskOperations { private static final String THIN_EXECUTIONS_RELATION = "tasks/thinexecutions"; + private static final String THIN_EXECUTIONS_BY_NAME_RELATION = "tasks/thinexecutions/name"; + private static final String EXECUTIONS_CURRENT_RELATION = "tasks/executions/current"; private static final String EXECUTION_RELATION = "tasks/executions/execution"; @@ -97,6 +99,8 @@ public class TaskTemplate implements TaskOperations { private final Link thinExecutionsLink; + private final Link thinExecutionsByNameLink; + private final Link executionLink; private final Link executionLaunchLink; @@ -160,6 +164,12 @@ public class TaskTemplate implements TaskOperations { } else { this.thinExecutionsLink = null; } + if(resources.getLink(THIN_EXECUTIONS_BY_NAME_RELATION).isPresent()) { + this.thinExecutionsByNameLink = resources.getLink(THIN_EXECUTIONS_BY_NAME_RELATION).get(); + } else { + this.thinExecutionsByNameLink = null; + } + if(resources.getLink(EXECUTION_LAUNCH_RELATION).isPresent()) { this.executionLaunchLink = resources.getLink(EXECUTION_LAUNCH_RELATION).get(); } else { @@ -275,6 +285,15 @@ public PagedModel thinExecutionList() { } } + @Override + public PagedModel thinExecutionListByTaskName(String taskName) { + if(thinExecutionsByNameLink != null) { + return restTemplate.getForObject(thinExecutionsByNameLink.expand(taskName).getHref(), TaskExecutionThinResource.Page.class); + } else { + return restTemplate.getForObject(executionByNameLink.expand(taskName).getHref(), TaskExecutionThinResource.Page.class); + } + } + @Override public TaskExecutionResource.Page executionListByTaskName(String taskName) { return restTemplate.getForObject(executionByNameLink.expand(taskName).getHref(), diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java index dc60f946f1..d7c35c7e41 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java @@ -286,8 +286,8 @@ public TaskExecutionController taskExecutionController( } @Bean - public TaskExecutionThinController taskExecutionThinController(DataflowTaskExplorer taskExplorer) { - return new TaskExecutionThinController(taskExplorer); + public TaskExecutionThinController taskExecutionThinController(DataflowTaskExplorer taskExplorer, TaskDefinitionRepository taskDefinitionRepository) { + return new TaskExecutionThinController(taskExplorer, taskDefinitionRepository); } @Bean diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/RootController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/RootController.java index 3ba0f50e9a..cd1826a86a 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/RootController.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/RootController.java @@ -155,6 +155,7 @@ public RootResource info() { root.add(linkTo(methodOn(TasksInfoController.class).getInfo(null, null, null)).withRel("tasks/info/executions")); root.add(linkTo(methodOn(TaskLogsController.class).getLog(null, null)).withRel("tasks/logs")); root.add(linkTo(methodOn(TaskExecutionThinController.class).listTasks(null, null)).withRel("tasks/thinexecutions")); + root.add(linkTo(methodOn(TaskExecutionThinController.class).retrieveTasksByName(null,null, null)).withRel("tasks/thinexecutions/name")); if (featuresProperties.isSchedulesEnabled()) { root.add(entityLinks.linkToCollectionResource(ScheduleInfoResource.class).withRel("tasks/schedules")); diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionController.java index 0b30e5be80..f5021a94e7 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionController.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionController.java @@ -185,9 +185,11 @@ public PagedModel retrieveTasksByName( Pageable pageable, PagedResourcesAssembler assembler ) { + long tasks = this.taskDefinitionRepository.countByTaskName(taskName); + if(tasks == 0) { + throw new NoSuchTaskDefinitionException(taskName); + } validatePageable(pageable); - this.taskDefinitionRepository.findById(taskName) - .orElseThrow(() -> new NoSuchTaskDefinitionException(taskName)); Page taskExecutions = this.explorer.findTaskExecutionsByName(taskName, pageable); Page result = getPageableRelationships(taskExecutions, pageable); return assembler.toModel(result, this.taskAssembler); diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionThinController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionThinController.java index e9bf6139de..e96e2c852f 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionThinController.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionThinController.java @@ -18,6 +18,8 @@ import org.springframework.cloud.dataflow.core.ThinTaskExecution; import org.springframework.cloud.dataflow.rest.resource.TaskExecutionThinResource; +import org.springframework.cloud.dataflow.server.repository.NoSuchTaskDefinitionException; +import org.springframework.cloud.dataflow.server.repository.TaskDefinitionRepository; import org.springframework.cloud.dataflow.server.task.DataflowTaskExplorer; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.data.domain.Page; @@ -30,6 +32,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -46,10 +49,12 @@ public class TaskExecutionThinController { private final DataflowTaskExplorer explorer; + private final TaskDefinitionRepository taskDefinitionRepository; private final TaskExecutionThinResourceAssembler resourceAssembler; - public TaskExecutionThinController(DataflowTaskExplorer explorer) { + public TaskExecutionThinController(DataflowTaskExplorer explorer, TaskDefinitionRepository taskDefinitionRepository) { this.explorer = explorer; + this.taskDefinitionRepository = taskDefinitionRepository; this.resourceAssembler = new TaskExecutionThinResourceAssembler(); } @@ -62,6 +67,23 @@ public PagedModel listTasks(Pageable pageable, PagedR return pagedAssembler.toModel(thinTaskExecutions, resourceAssembler); } + @GetMapping(value = "", params = "name") + @ResponseStatus(HttpStatus.OK) + public PagedModel retrieveTasksByName( + @RequestParam("name") String taskName, + Pageable pageable, + PagedResourcesAssembler pagedAssembler + ) { + long tasks = this.taskDefinitionRepository.countByTaskName(taskName); + if(tasks == 0) { + throw new NoSuchTaskDefinitionException(taskName); + } + Page page = this.explorer.findTaskExecutionsByName(taskName, pageable); + Page thinTaskExecutions = new PageImpl<>(page.stream().map(ThinTaskExecution::new).toList(), pageable, page.getTotalElements()); + explorer.populateCtrStatus(thinTaskExecutions.getContent()); + return pagedAssembler.toModel(thinTaskExecutions, resourceAssembler); + } + static class TaskExecutionThinResourceAssembler extends RepresentationModelAssemblerSupport { public TaskExecutionThinResourceAssembler() { super(TaskExecutionThinController.class, TaskExecutionThinResource.class); diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/repository/TaskDefinitionRepository.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/repository/TaskDefinitionRepository.java index eb7464fae6..9e56b8664f 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/repository/TaskDefinitionRepository.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/repository/TaskDefinitionRepository.java @@ -46,4 +46,5 @@ public interface TaskDefinitionRepository extends KeyValueRepository getTaskArguments(long taskExecutionId) { @Override public void populateCtrStatus(Collection thinTaskExecutions) { - Map taskExecutionMap = thinTaskExecutions.stream() + if(!thinTaskExecutions.isEmpty()) { + Map taskExecutionMap = thinTaskExecutions.stream() .collect(Collectors.toMap(ThinTaskExecution::getExecutionId, Function.identity())); - String ids = taskExecutionMap.keySet() - .stream() - .map(Object::toString) - .collect(Collectors.joining(",")); - String sql = FIND_CTR_STATUS.replace(":taskExecutionIds", ids); - jdbcTemplate.query(sql, rs -> { - Long id = rs.getLong("TASK_EXECUTION_ID"); - String ctrStatus = rs.getString("CTR_STATUS"); - logger.debug("populateCtrStatus:{}={}", id, ctrStatus); - ThinTaskExecution execution = taskExecutionMap.get(id); - Assert.notNull(execution, "Expected TaskExecution for " + id + " from " + ids); - execution.setCtrTaskStatus(ctrStatus); - }); + String ids = taskExecutionMap.keySet().stream().map(Object::toString).collect(Collectors.joining(",")); + String sql = FIND_CTR_STATUS.replace(":taskExecutionIds", ids); + jdbcTemplate.query(sql, rs -> { + Long id = rs.getLong("TASK_EXECUTION_ID"); + String ctrStatus = rs.getString("CTR_STATUS"); + logger.debug("populateCtrStatus:{}={}", id, ctrStatus); + ThinTaskExecution execution = taskExecutionMap.get(id); + Assert.notNull(execution, "Expected TaskExecution for " + id + " from " + ids); + execution.setCtrTaskStatus(ctrStatus); + }); + } } } diff --git a/spring-cloud-dataflow-server-core/src/main/resources/META-INF/dataflow-server-defaults.yml b/spring-cloud-dataflow-server-core/src/main/resources/META-INF/dataflow-server-defaults.yml index e5f31b4bfc..20cdf5c279 100644 --- a/spring-cloud-dataflow-server-core/src/main/resources/META-INF/dataflow-server-defaults.yml +++ b/spring-cloud-dataflow-server-core/src/main/resources/META-INF/dataflow-server-defaults.yml @@ -241,6 +241,7 @@ spring: - GET /tasks/info/* => hasRole('ROLE_VIEW') - GET /tasks/thinexecutions => hasRole('ROLE_VIEW') + - GET /tasks/thinexecutions/* => hasRole('ROLE_VIEW') # Task Schedules diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java index 3af2a3e696..6dc59a9139 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java @@ -270,8 +270,8 @@ public TaskExecutionController taskExecutionController( } @Bean - public TaskExecutionThinController taskExecutionThinController(DataflowTaskExplorer dataflowTaskExplorer) { - return new TaskExecutionThinController(dataflowTaskExplorer); + public TaskExecutionThinController taskExecutionThinController(DataflowTaskExplorer dataflowTaskExplorer, TaskDefinitionRepository taskDefinitionRepository) { + return new TaskExecutionThinController(dataflowTaskExplorer, taskDefinitionRepository); } @Bean diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java index dba23a53b7..0c71a7ffee 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java @@ -593,8 +593,8 @@ public TaskExecutionController taskExecutionController( } @Bean - public TaskExecutionThinController taskExecutionThinController(DataflowTaskExplorer dataflowTaskExplorer) { - return new TaskExecutionThinController(dataflowTaskExplorer); + public TaskExecutionThinController taskExecutionThinController(DataflowTaskExplorer dataflowTaskExplorer, TaskDefinitionRepository taskDefinitionRepository) { + return new TaskExecutionThinController(dataflowTaskExplorer, taskDefinitionRepository); } @Bean public TasksInfoController taskExecutionsInfoController(TaskExecutionService taskExecutionService) { diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionControllerTests.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionControllerTests.java index 394dced63f..3b24e2b22d 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionControllerTests.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/controller/TaskExecutionControllerTests.java @@ -204,6 +204,7 @@ void setupMockMVC() throws JobInstanceAlreadyCompleteException, JobExecutionAlre SAMPLE_CLEANSED_ARGUMENT_LIST.add("spring.datasource.password=******"); taskDefinitionRepository.save(new TaskDefinition(TASK_NAME_ORIG, "demo")); + taskDefinitionRepository.save(new TaskDefinition("nope", "demo")); TaskExecution taskExecution1 = taskExecutionDao.createTaskExecution(TASK_NAME_ORIG, LocalDateTime.now(), SAMPLE_ARGUMENT_LIST, "foobar"); @@ -324,7 +325,6 @@ void getExecutionForJob() throws Exception { void getAllExecutions() throws Exception { verifyTaskArgs(SAMPLE_CLEANSED_ARGUMENT_LIST, "$._embedded.taskExecutionResourceList[0].", mockMvc.perform(get("/tasks/executions").accept(MediaType.APPLICATION_JSON)) - .andDo(print()) .andExpect(status().isOk())) .andExpect(jsonPath("$._embedded.taskExecutionResourceList[*].executionId", containsInAnyOrder(4, 3, 2, 1))) .andExpect(jsonPath("$._embedded.taskExecutionResourceList[*].parentExecutionId", containsInAnyOrder(null, null, null, 1))) @@ -335,13 +335,19 @@ void getAllExecutions() throws Exception { @Test void getAllThinExecutions() throws Exception { mockMvc.perform(get("/tasks/thinexecutions").accept(MediaType.APPLICATION_JSON)) - .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.taskExecutionThinResourceList[*].executionId", containsInAnyOrder(4, 3, 2, 1))) .andExpect(jsonPath("$._embedded.taskExecutionThinResourceList[*].parentExecutionId", containsInAnyOrder(null, null, null, 1))) .andExpect(jsonPath("$._embedded.taskExecutionThinResourceList[*].taskExecutionStatus", containsInAnyOrder("RUNNING", "RUNNING","RUNNING","RUNNING"))) .andExpect(jsonPath("$._embedded.taskExecutionThinResourceList", hasSize(4))); } + @Test + void getThinExecutionsByName() throws Exception { + mockMvc.perform(get("/tasks/thinexecutions").queryParam("name", "nope").accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.page.totalElements", is(0))); + mockMvc.perform(get("/tasks/thinexecutions").queryParam("name", "none").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } @Test void getCurrentExecutions() throws Exception { @@ -381,7 +387,6 @@ void taskExecution() throws Exception { resultActions = mockMvc.perform( get("/tasks/executions/" + resource.getExecutionId()) .accept(MediaType.APPLICATION_JSON)) - .andDo(print()) .andExpect(status().isOk()) .andExpect(content().json("{taskName: \"timestamp3\"}")); response = resultActions.andReturn().getResponse().getContentAsString(); @@ -415,7 +420,7 @@ void getDefinitionsWithLastExecution() throws Exception { mockMvc.perform(get("/tasks/definitions").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andDo(print()) - .andExpect(jsonPath("$._embedded.taskDefinitionResourceList", hasSize(1))); + .andExpect(jsonPath("$._embedded.taskDefinitionResourceList", hasSize(2))); } @Test void getExecutionsByNameNotFound() throws Exception { diff --git a/spring-cloud-dataflow-server-core/src/test/resources/root-controller-result.json b/spring-cloud-dataflow-server-core/src/test/resources/root-controller-result.json index 9a0177745f..456c121607 100644 --- a/spring-cloud-dataflow-server-core/src/test/resources/root-controller-result.json +++ b/spring-cloud-dataflow-server-core/src/test/resources/root-controller-result.json @@ -116,6 +116,10 @@ "tasks/executions": { "href": "http://localhost/tasks/executions" }, + "tasks/thinexecutions/name": { + "href": "http://localhost/tasks/thinexecutions?name={name}", + "templated": true + }, "tasks/executions/external": { "href": "http://localhost/tasks/executions/external/{externalExecutionId}{?platform}", "templated": true diff --git a/spring-cloud-dataflow-server/src/test/java/org/springframework/cloud/dataflow/integration/test/tasks/AbstractLargeTaskExecutionDatabaseBase.java b/spring-cloud-dataflow-server/src/test/java/org/springframework/cloud/dataflow/integration/test/tasks/AbstractLargeTaskExecutionDatabaseBase.java index dbc80f7460..eadac7ac47 100644 --- a/spring-cloud-dataflow-server/src/test/java/org/springframework/cloud/dataflow/integration/test/tasks/AbstractLargeTaskExecutionDatabaseBase.java +++ b/spring-cloud-dataflow-server/src/test/java/org/springframework/cloud/dataflow/integration/test/tasks/AbstractLargeTaskExecutionDatabaseBase.java @@ -28,7 +28,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -110,35 +112,69 @@ private void addColumnTableType(Map> columnTypes, S @Test void queryWithLargeNumberOfTaskExecutions() throws Exception { + mockMvc.perform(post("/apps/task/nope/1.0").param("uri", "maven:io.spring:timestamp-task:3.0.0")) + .andExpect(status().is2xxSuccessful()); + mockMvc.perform(post("/tasks/definitions").param("name", "nope").param("definition", "nope")) + .andExpect(status().is2xxSuccessful()); + mockMvc.perform(post("/tasks/definitions").param("name", "ts-batch").param("definition", "nope")) + .andExpect(status().is2xxSuccessful()); long startTime = System.currentTimeMillis(); mockMvc - .perform(get("/tasks/executions").accept(MediaType.APPLICATION_JSON).param("size", "20").param("page", "1")) + .perform(get("/tasks/executions").param("size", "20").param("page", "1").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.taskExecutionResourceList", hasSize(greaterThanOrEqualTo(20)))); long totalTime = System.currentTimeMillis() - startTime; long startTime2 = System.currentTimeMillis(); mockMvc .perform( - get("/tasks/executions").accept(MediaType.APPLICATION_JSON).param("size", "200").param("page", "2")) + get("/tasks/executions").param("size", "200").param("page", "2").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.taskExecutionResourceList", hasSize(greaterThanOrEqualTo(200)))); long totalTime2 = System.currentTimeMillis() - startTime2; long startTime3 = System.currentTimeMillis(); mockMvc.perform( - get("/tasks/thinexecutions").accept(MediaType.APPLICATION_JSON).param("size", "20").param("page", "3")) + get("/tasks/thinexecutions").param("size", "20").param("page", "3").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.taskExecutionThinResourceList", hasSize(greaterThanOrEqualTo(20)))); long totalTime3 = System.currentTimeMillis() - startTime3; long startTime4 = System.currentTimeMillis(); mockMvc.perform( - get("/tasks/thinexecutions").accept(MediaType.APPLICATION_JSON).param("size", "200").param("page", "2")) + get("/tasks/thinexecutions").param("size", "200").param("page", "2").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$._embedded.taskExecutionThinResourceList", hasSize(greaterThanOrEqualTo(200)))); long totalTime4 = System.currentTimeMillis() - startTime4; + long startTime5 = System.currentTimeMillis(); + mockMvc.perform( + get("/tasks/executions").param("name", "nope").param("page", "0").param("size", "200").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + long totalTime5 = System.currentTimeMillis() - startTime5; + long startTime6 = System.currentTimeMillis(); + mockMvc.perform( + get("/tasks/thinexecutions").param("name", "nope").param("page", "0").param("size", "200").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(0))); + long totalTime6 = System.currentTimeMillis() - startTime6; + long startTime7 = System.currentTimeMillis(); + mockMvc.perform( + get("/tasks/executions").param("name", "ts-batch").param("page", "0").param("size", "200").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(83))); + long totalTime7 = System.currentTimeMillis() - startTime7; + long startTime8 = System.currentTimeMillis(); + mockMvc.perform( + get("/tasks/thinexecutions").param("name", "ts-batch").param("page", "0").param("size", "200").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.page.totalElements", is(83))); + long totalTime8 = System.currentTimeMillis() - startTime8; logger.info("result:totalTime={}ms", totalTime); logger.info("result:totalTime2={}ms", totalTime2); logger.info("result:totalTime3={}ms", totalTime3); logger.info("result:totalTime4={}ms", totalTime4); + logger.info("result:totalTime5={}ms", totalTime5); + logger.info("result:totalTime6={}ms", totalTime6); + logger.info("result:totalTime7={}ms", totalTime7); + logger.info("result:totalTime8={}ms", totalTime8); double ratioExecution = (double) totalTime / (double) totalTime2; double ratioThinExecution = (double) totalTime3 / (double) totalTime4; double ratioThinToExecution = (double) totalTime2 / (double) totalTime4; diff --git a/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/TaskCommands.java b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/TaskCommands.java index 34e66f1242..1033386f10 100644 --- a/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/TaskCommands.java +++ b/spring-cloud-dataflow-shell-core/src/main/java/org/springframework/cloud/dataflow/shell/command/TaskCommands.java @@ -34,6 +34,7 @@ import org.springframework.cloud.dataflow.rest.resource.TaskAppStatusResource; import org.springframework.cloud.dataflow.rest.resource.TaskDefinitionResource; import org.springframework.cloud.dataflow.rest.resource.TaskExecutionResource; +import org.springframework.cloud.dataflow.rest.resource.TaskExecutionThinResource; import org.springframework.cloud.dataflow.rest.util.DeploymentPropertiesUtils; import org.springframework.cloud.dataflow.shell.command.support.OpsType; import org.springframework.cloud.dataflow.shell.command.support.RoleType; @@ -298,11 +299,11 @@ public String destroyAll( public Table executionListByName( @ShellOption(value = {"", "--name"}, help = "the task name to be used as a filter", valueProvider = TaskNameValueProvider.class, defaultValue = ShellOption.NULL) String name) { - final PagedModel tasks; + PagedModel thinTasks = null; if (name == null) { - tasks = taskOperations().executionList(); + thinTasks = taskOperations().thinExecutionList(); } else { - tasks = taskOperations().executionListByTaskName(name); + thinTasks = taskOperations().thinExecutionListByTaskName(name); } LinkedHashMap headers = new LinkedHashMap<>(); headers.put("taskName", "Task Name"); @@ -310,7 +311,7 @@ public Table executionListByName( headers.put("startTime", "Start Time"); headers.put("endTime", "End Time"); headers.put("exitCode", "Exit Code"); - final TableBuilder builder = new TableBuilder(new BeanListTableModel<>(tasks, headers)); + TableBuilder builder = new TableBuilder(new BeanListTableModel<>(thinTasks, headers)); return DataFlowTables.applyStyle(builder).build(); }