diff --git a/server/ee/libs/embedded/embedded-configuration/embedded-configuration-service/src/main/java/com/bytechef/ee/embedded/configuration/facade/IntegrationInstanceConfigurationFacadeImpl.java b/server/ee/libs/embedded/embedded-configuration/embedded-configuration-service/src/main/java/com/bytechef/ee/embedded/configuration/facade/IntegrationInstanceConfigurationFacadeImpl.java index 4dfb94eec0d..4abff3da1c3 100644 --- a/server/ee/libs/embedded/embedded-configuration/embedded-configuration-service/src/main/java/com/bytechef/ee/embedded/configuration/facade/IntegrationInstanceConfigurationFacadeImpl.java +++ b/server/ee/libs/embedded/embedded-configuration/embedded-configuration-service/src/main/java/com/bytechef/ee/embedded/configuration/facade/IntegrationInstanceConfigurationFacadeImpl.java @@ -62,6 +62,7 @@ import java.time.Instant; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -237,7 +238,14 @@ public void deleteIntegrationInstanceConfiguration(long id) { triggerExecutionService.deleteJobTriggerExecution(jobId); principalJobService.deletePrincipalJobs(jobId, PlatformType.EMBEDDED); + } + + List orderedJobIds = jobIds.stream() + .distinct() + .sorted(Comparator.reverseOrder()) + .toList(); + for (long jobId : orderedJobIds) { jobFacade.deleteJob(jobId); } diff --git a/server/libs/atlas/atlas-execution/atlas-execution-service/build.gradle.kts b/server/libs/atlas/atlas-execution/atlas-execution-service/build.gradle.kts index cb028a67e63..da316a62dc6 100644 --- a/server/libs/atlas/atlas-execution/atlas-execution-service/build.gradle.kts +++ b/server/libs/atlas/atlas-execution/atlas-execution-service/build.gradle.kts @@ -11,8 +11,11 @@ dependencies { implementation(project(":server:libs:core:commons:commons-util")) testImplementation("org.springframework.data:spring-data-jdbc") + testImplementation("tools.jackson.core:jackson-databind") + testImplementation(project(":server:libs:atlas:atlas-configuration:atlas-configuration-converter")) testImplementation(project(":server:libs:atlas:atlas-configuration:atlas-configuration-repository:atlas-configuration-repository-jdbc")) testImplementation(project(":server:libs:atlas:atlas-execution:atlas-execution-repository:atlas-execution-repository-jdbc")) testImplementation(project(":server:libs:config:liquibase-config")) + testImplementation(project(":server:libs:core:commons:commons-data")) testImplementation(project(":server:libs:test:test-int-support")) } diff --git a/server/libs/atlas/atlas-execution/atlas-execution-service/src/test/java/com/bytechef/atlas/execution/facade/JobFacadeIntTest.java b/server/libs/atlas/atlas-execution/atlas-execution-service/src/test/java/com/bytechef/atlas/execution/facade/JobFacadeIntTest.java index f687cc4d627..013711f8ef1 100644 --- a/server/libs/atlas/atlas-execution/atlas-execution-service/src/test/java/com/bytechef/atlas/execution/facade/JobFacadeIntTest.java +++ b/server/libs/atlas/atlas-execution/atlas-execution-service/src/test/java/com/bytechef/atlas/execution/facade/JobFacadeIntTest.java @@ -16,19 +16,41 @@ package com.bytechef.atlas.execution.facade; +import static org.assertj.core.api.Assertions.assertThat; + +import com.bytechef.atlas.configuration.converter.StringToWorkflowTaskConverter; +import com.bytechef.atlas.configuration.converter.WorkflowTaskToStringConverter; +import com.bytechef.atlas.configuration.domain.WorkflowTask; import com.bytechef.atlas.configuration.service.WorkflowService; +import com.bytechef.atlas.execution.domain.Job; +import com.bytechef.atlas.execution.domain.TaskExecution; import com.bytechef.atlas.execution.dto.JobParametersDTO; import com.bytechef.atlas.execution.repository.JobRepository; +import com.bytechef.atlas.execution.repository.TaskExecutionRepository; +import com.bytechef.atlas.execution.repository.jdbc.converter.StringToWebhooksConverter; +import com.bytechef.atlas.execution.repository.jdbc.converter.WebhooksToStringConverter; import com.bytechef.atlas.execution.service.ContextService; import com.bytechef.atlas.execution.service.JobService; import com.bytechef.atlas.execution.service.JobServiceImpl; import com.bytechef.atlas.execution.service.TaskExecutionService; +import com.bytechef.atlas.execution.service.TaskExecutionServiceImpl; import com.bytechef.atlas.file.storage.TaskFileStorage; +import com.bytechef.commons.data.jdbc.converter.ExecutionErrorToStringConverter; +import com.bytechef.commons.data.jdbc.converter.FileEntryToStringConverter; +import com.bytechef.commons.data.jdbc.converter.MapWrapperToStringConverter; +import com.bytechef.commons.data.jdbc.converter.StringToFileEntryConverter; +import com.bytechef.commons.data.jdbc.converter.StringToMapWrapperConverter; import com.bytechef.jackson.config.JacksonConfiguration; import com.bytechef.liquibase.config.LiquibaseConfiguration; import com.bytechef.test.config.jdbc.AbstractIntTestJdbcConfiguration; import com.bytechef.test.config.testcontainers.PostgreSQLContainerConfiguration; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.Validate; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -41,6 +63,7 @@ import org.springframework.context.annotation.Import; import org.springframework.data.jdbc.repository.config.EnableJdbcAuditing; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import tools.jackson.databind.ObjectMapper; /** * Ivica Cardic @@ -57,6 +80,12 @@ public class JobFacadeIntTest { @Autowired private JobFacade jobFacade; + @Autowired + private JobRepository jobRepository; + + @Autowired + private TaskExecutionRepository taskExecutionRepository; + @Test public void testRequiredParameters() { Assertions.assertThrows( @@ -64,7 +93,67 @@ public void testRequiredParameters() { () -> jobFacade.createJob(new JobParametersDTO("aGVsbG8x", Collections.emptyMap()))); } - @ComponentScan(basePackages = "com.bytechef.atlas.execution.facade") + @Test + public void testDeleteJobRecursivelyDeletesChildJobsAndTaskExecutions() { + Job parentJob = jobRepository.save(newJob()); + long parentJobId = Validate.notNull(parentJob.getId(), "id"); + + TaskExecution parentTaskExecution = taskExecutionRepository.save(newTaskExecution(parentJobId, null)); + + long parentTaskExecutionId = Validate.notNull(parentTaskExecution.getId(), "id"); + + Job childJob = jobRepository.save(newJob()); + + childJob.setParentTaskExecutionId(parentTaskExecutionId); + + jobRepository.save(childJob); + + long childJobId = Validate.notNull(childJob.getId(), "id"); + + TaskExecution childTaskExecution = taskExecutionRepository.save(newTaskExecution(childJobId, null)); + + long childTaskExecutionId = Validate.notNull(childTaskExecution.getId(), "id"); + + jobFacade.deleteJob(parentJobId); + + assertThat(jobRepository.findById(parentJobId)).isEmpty(); + assertThat(jobRepository.findById(childJobId)).isEmpty(); + assertThat(taskExecutionRepository.findById(parentTaskExecutionId)).isEmpty(); + assertThat(taskExecutionRepository.findById(childTaskExecutionId)).isEmpty(); + } + + private static Job newJob() { + Job job = new Job(); + + job.setStatus(Job.Status.COMPLETED); + job.setWorkflowId("demo:1234"); + + return job; + } + + private static TaskExecution newTaskExecution(long jobId, Long parentId) { + Map taskMap = new HashMap<>(); + + taskMap.put("name", "task1"); + taskMap.put("type", "test/v1/noop"); + + TaskExecution taskExecution = TaskExecution.builder() + .workflowTask(new WorkflowTask(taskMap)) + .build(); + + taskExecution.setJobId(jobId); + taskExecution.setTaskNumber(1); + + if (parentId != null) { + taskExecution.setParentId(parentId); + } + + return taskExecution; + } + + @ComponentScan(basePackages = { + "com.bytechef.atlas.execution.facade", "com.bytechef.atlas.configuration.converter" + }) @EnableAutoConfiguration @Configuration public static class WorkflowExecutionIntTestConfiguration { @@ -75,14 +164,14 @@ public static class WorkflowExecutionIntTestConfiguration { @MockitoBean private WorkflowService workflowService; - @MockitoBean - private TaskExecutionService taskExecutionService; - @MockitoBean private TaskFileStorage taskFileStorage; @Bean - JobFacade jobFacade(ApplicationEventPublisher eventPublisher, JobService jobService) { + JobFacade jobFacade( + ApplicationEventPublisher eventPublisher, JobService jobService, + TaskExecutionService taskExecutionService) { + return new JobFacadeImpl( eventPublisher, contextService, jobService, taskExecutionService, taskFileStorage, workflowService); } @@ -92,8 +181,34 @@ JobService jobService(JobRepository jobRepository) { return new JobServiceImpl(jobRepository); } + @Bean + TaskExecutionService taskExecutionService(TaskExecutionRepository taskExecutionRepository) { + return new TaskExecutionServiceImpl(taskExecutionRepository); + } + @EnableJdbcAuditing(auditorAwareRef = "auditorProvider", dateTimeProviderRef = "auditingDateTimeProvider") public static class WorkflowIntTestJdbcConfiguration extends AbstractIntTestJdbcConfiguration { + + private final ObjectMapper objectMapper; + + @SuppressFBWarnings("EI") + public WorkflowIntTestJdbcConfiguration(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + protected List userConverters() { + return Arrays.asList( + new ExecutionErrorToStringConverter(objectMapper), + new FileEntryToStringConverter(objectMapper), + new MapWrapperToStringConverter(objectMapper), + new StringToFileEntryConverter(objectMapper), + new StringToMapWrapperConverter(objectMapper), + new StringToWebhooksConverter(objectMapper), + new StringToWorkflowTaskConverter(objectMapper), + new WebhooksToStringConverter(objectMapper), + new WorkflowTaskToStringConverter(objectMapper)); + } } } } diff --git a/server/libs/automation/automation-configuration/automation-configuration-service/src/main/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeImpl.java b/server/libs/automation/automation-configuration/automation-configuration-service/src/main/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeImpl.java index e5a3c49031b..ba0d9261769 100644 --- a/server/libs/automation/automation-configuration/automation-configuration-service/src/main/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeImpl.java +++ b/server/libs/automation/automation-configuration/automation-configuration-service/src/main/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeImpl.java @@ -62,6 +62,7 @@ import java.time.Instant; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -222,7 +223,14 @@ public void deleteProjectDeployment(long id) { triggerExecutionService.deleteJobTriggerExecution(jobId); principalJobService.deletePrincipalJobs(jobId, PlatformType.AUTOMATION); + } + + List orderedJobIds = jobIds.stream() + .distinct() + .sorted(Comparator.reverseOrder()) + .toList(); + for (long jobId : orderedJobIds) { jobFacade.deleteJob(jobId); } diff --git a/server/libs/automation/automation-configuration/automation-configuration-service/src/test/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeIntTest.java b/server/libs/automation/automation-configuration/automation-configuration-service/src/test/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeIntTest.java index c8390df5f87..5fb628f9175 100644 --- a/server/libs/automation/automation-configuration/automation-configuration-service/src/test/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeIntTest.java +++ b/server/libs/automation/automation-configuration/automation-configuration-service/src/test/java/com/bytechef/automation/configuration/facade/ProjectDeploymentFacadeIntTest.java @@ -24,6 +24,8 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.calls; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -60,6 +62,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; @@ -188,6 +191,36 @@ public void testDeleteProjectDeployment() { assertThat(workspaceProjectDeployments).hasSize(0); } + @Test + public void testDeleteProjectDeploymentRemovesAllPrincipalJobsBeforeAnyJob() { + ProjectDTO projectDTO = projectDeploymentFacadeHelper.createProject(workspace.getId()); + ProjectDeploymentDTO projectDeploymentDTO = + projectDeploymentFacadeHelper.createProjectDeployment(workspace.getId(), projectDTO); + + long deploymentId = projectDeploymentDTO.id(); + long parentJobId = 501L; + long childJobId = 502L; + + when(principalJobService.getJobIds(deploymentId, PlatformType.AUTOMATION)) + .thenReturn(List.of(parentJobId, childJobId)); + + projectDeploymentFacade.deleteProjectDeployment(deploymentId); + + InOrder inOrder = inOrder(principalJobService, jobFacade); + + inOrder.verify(principalJobService) + .deletePrincipalJobs(parentJobId, PlatformType.AUTOMATION); + inOrder.verify(principalJobService) + .deletePrincipalJobs(childJobId, PlatformType.AUTOMATION); + inOrder.verify(jobFacade, calls(1)) + .deleteJob(anyLong()); + inOrder.verify(jobFacade, calls(1)) + .deleteJob(anyLong()); + + verify(jobFacade).deleteJob(parentJobId); + verify(jobFacade).deleteJob(childJobId); + } + @Disabled @Test public void testGetProjectDeployment() {