diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItem.java b/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItem.java index 072d71f63d..50bdb432ba 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItem.java +++ b/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItem.java @@ -78,16 +78,19 @@ public class ExecTaskItem implements Serializable { @Size(min = 1, max = 50, message = "{exec_task_item.executor.length_range}", groups = {Created.class, Updated.class}) private String executor; + @Schema(description = "测试集ID") + private String collectionId; + @Schema(description = "删除标识", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "{exec_task_item.deleted.not_blank}", groups = {Created.class}) private Boolean deleted; - @Schema(description = "测试集ID") - private String collectionId; - @Schema(description = "用例表id") private String caseId; + @Schema(description = "异常信息") + private String errorMessage; + private static final long serialVersionUID = 1L; public enum Column { @@ -107,9 +110,10 @@ public class ExecTaskItem implements Serializable { startTime("start_time", "startTime", "BIGINT", false), endTime("end_time", "endTime", "BIGINT", false), executor("executor", "executor", "VARCHAR", false), - deleted("deleted", "deleted", "BIT", false), collectionId("collection_id", "collectionId", "VARCHAR", false), - caseId("case_id", "caseId", "VARCHAR", false); + deleted("deleted", "deleted", "BIT", false), + caseId("case_id", "caseId", "VARCHAR", false), + errorMessage("error_message", "errorMessage", "VARCHAR", false); private static final String BEGINNING_DELIMITER = "`"; diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItemExample.java b/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItemExample.java index ddb35d7e27..c4e202f49a 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItemExample.java +++ b/backend/framework/domain/src/main/java/io/metersphere/system/domain/ExecTaskItemExample.java @@ -1204,66 +1204,6 @@ public class ExecTaskItemExample { return (Criteria) this; } - public Criteria andDeletedIsNull() { - addCriterion("deleted is null"); - return (Criteria) this; - } - - public Criteria andDeletedIsNotNull() { - addCriterion("deleted is not null"); - return (Criteria) this; - } - - public Criteria andDeletedEqualTo(Boolean value) { - addCriterion("deleted =", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedNotEqualTo(Boolean value) { - addCriterion("deleted <>", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedGreaterThan(Boolean value) { - addCriterion("deleted >", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedGreaterThanOrEqualTo(Boolean value) { - addCriterion("deleted >=", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedLessThan(Boolean value) { - addCriterion("deleted <", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedLessThanOrEqualTo(Boolean value) { - addCriterion("deleted <=", value, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedIn(List values) { - addCriterion("deleted in", values, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedNotIn(List values) { - addCriterion("deleted not in", values, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedBetween(Boolean value1, Boolean value2) { - addCriterion("deleted between", value1, value2, "deleted"); - return (Criteria) this; - } - - public Criteria andDeletedNotBetween(Boolean value1, Boolean value2) { - addCriterion("deleted not between", value1, value2, "deleted"); - return (Criteria) this; - } - public Criteria andCollectionIdIsNull() { addCriterion("collection_id is null"); return (Criteria) this; @@ -1334,6 +1274,66 @@ public class ExecTaskItemExample { return (Criteria) this; } + public Criteria andDeletedIsNull() { + addCriterion("deleted is null"); + return (Criteria) this; + } + + public Criteria andDeletedIsNotNull() { + addCriterion("deleted is not null"); + return (Criteria) this; + } + + public Criteria andDeletedEqualTo(Boolean value) { + addCriterion("deleted =", value, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedNotEqualTo(Boolean value) { + addCriterion("deleted <>", value, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedGreaterThan(Boolean value) { + addCriterion("deleted >", value, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedGreaterThanOrEqualTo(Boolean value) { + addCriterion("deleted >=", value, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedLessThan(Boolean value) { + addCriterion("deleted <", value, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedLessThanOrEqualTo(Boolean value) { + addCriterion("deleted <=", value, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedIn(List values) { + addCriterion("deleted in", values, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedNotIn(List values) { + addCriterion("deleted not in", values, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedBetween(Boolean value1, Boolean value2) { + addCriterion("deleted between", value1, value2, "deleted"); + return (Criteria) this; + } + + public Criteria andDeletedNotBetween(Boolean value1, Boolean value2) { + addCriterion("deleted not between", value1, value2, "deleted"); + return (Criteria) this; + } + public Criteria andCaseIdIsNull() { addCriterion("case_id is null"); return (Criteria) this; @@ -1403,6 +1403,76 @@ public class ExecTaskItemExample { addCriterion("case_id not between", value1, value2, "caseId"); return (Criteria) this; } + + public Criteria andErrorMessageIsNull() { + addCriterion("error_message is null"); + return (Criteria) this; + } + + public Criteria andErrorMessageIsNotNull() { + addCriterion("error_message is not null"); + return (Criteria) this; + } + + public Criteria andErrorMessageEqualTo(String value) { + addCriterion("error_message =", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageNotEqualTo(String value) { + addCriterion("error_message <>", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageGreaterThan(String value) { + addCriterion("error_message >", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageGreaterThanOrEqualTo(String value) { + addCriterion("error_message >=", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageLessThan(String value) { + addCriterion("error_message <", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageLessThanOrEqualTo(String value) { + addCriterion("error_message <=", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageLike(String value) { + addCriterion("error_message like", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageNotLike(String value) { + addCriterion("error_message not like", value, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageIn(List values) { + addCriterion("error_message in", values, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageNotIn(List values) { + addCriterion("error_message not in", values, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageBetween(String value1, String value2) { + addCriterion("error_message between", value1, value2, "errorMessage"); + return (Criteria) this; + } + + public Criteria andErrorMessageNotBetween(String value1, String value2) { + addCriterion("error_message not between", value1, value2, "errorMessage"); + return (Criteria) this; + } } public static class Criteria extends GeneratedCriteria { diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/mapper/ExecTaskItemMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/ExecTaskItemMapper.xml index bec38d19c7..01c0e78027 100644 --- a/backend/framework/domain/src/main/java/io/metersphere/system/mapper/ExecTaskItemMapper.xml +++ b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/ExecTaskItemMapper.xml @@ -18,9 +18,10 @@ - + + @@ -83,7 +84,7 @@ id, task_id, resource_id, resource_name, task_origin, `status`, `result`, resource_pool_id, resource_pool_node, resource_type, project_id, organization_id, thread_id, start_time, - end_time, executor, deleted, collection_id, case_id + end_time, executor, collection_id, deleted, case_id, error_message @@ -309,15 +316,18 @@ executor = #{record.executor,jdbcType=VARCHAR}, - - deleted = #{record.deleted,jdbcType=BIT}, - collection_id = #{record.collectionId,jdbcType=VARCHAR}, + + deleted = #{record.deleted,jdbcType=BIT}, + case_id = #{record.caseId,jdbcType=VARCHAR}, + + error_message = #{record.errorMessage,jdbcType=VARCHAR}, + @@ -341,9 +351,10 @@ start_time = #{record.startTime,jdbcType=BIGINT}, end_time = #{record.endTime,jdbcType=BIGINT}, executor = #{record.executor,jdbcType=VARCHAR}, - deleted = #{record.deleted,jdbcType=BIT}, collection_id = #{record.collectionId,jdbcType=VARCHAR}, - case_id = #{record.caseId,jdbcType=VARCHAR} + deleted = #{record.deleted,jdbcType=BIT}, + case_id = #{record.caseId,jdbcType=VARCHAR}, + error_message = #{record.errorMessage,jdbcType=VARCHAR} @@ -396,15 +407,18 @@ executor = #{executor,jdbcType=VARCHAR}, - - deleted = #{deleted,jdbcType=BIT}, - collection_id = #{collectionId,jdbcType=VARCHAR}, + + deleted = #{deleted,jdbcType=BIT}, + case_id = #{caseId,jdbcType=VARCHAR}, + + error_message = #{errorMessage,jdbcType=VARCHAR}, + where id = #{id,jdbcType=VARCHAR} @@ -425,16 +439,17 @@ start_time = #{startTime,jdbcType=BIGINT}, end_time = #{endTime,jdbcType=BIGINT}, executor = #{executor,jdbcType=VARCHAR}, - deleted = #{deleted,jdbcType=BIT}, collection_id = #{collectionId,jdbcType=VARCHAR}, - case_id = #{caseId,jdbcType=VARCHAR} + deleted = #{deleted,jdbcType=BIT}, + case_id = #{caseId,jdbcType=VARCHAR}, + error_message = #{errorMessage,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR} insert into exec_task_item (id, task_id, resource_id, resource_name, task_origin, `status`, `result`, resource_pool_id, resource_pool_node, resource_type, project_id, organization_id, thread_id, start_time, - end_time, executor, deleted, collection_id, case_id) + end_time, executor, collection_id, deleted, case_id, error_message) values (#{item.id,jdbcType=VARCHAR}, #{item.taskId,jdbcType=VARCHAR}, #{item.resourceId,jdbcType=VARCHAR}, @@ -442,8 +457,8 @@ #{item.result,jdbcType=VARCHAR}, #{item.resourcePoolId,jdbcType=VARCHAR}, #{item.resourcePoolNode,jdbcType=VARCHAR}, #{item.resourceType,jdbcType=VARCHAR}, #{item.projectId,jdbcType=VARCHAR}, #{item.organizationId,jdbcType=VARCHAR}, #{item.threadId,jdbcType=VARCHAR}, #{item.startTime,jdbcType=BIGINT}, #{item.endTime,jdbcType=BIGINT}, - #{item.executor,jdbcType=VARCHAR}, #{item.deleted,jdbcType=BIT}, #{item.collectionId,jdbcType=VARCHAR}, - #{item.caseId,jdbcType=VARCHAR}) + #{item.executor,jdbcType=VARCHAR}, #{item.collectionId,jdbcType=VARCHAR}, #{item.deleted,jdbcType=BIT}, + #{item.caseId,jdbcType=VARCHAR}, #{item.errorMessage,jdbcType=VARCHAR}) @@ -504,15 +519,18 @@ #{item.executor,jdbcType=VARCHAR} - - #{item.deleted,jdbcType=BIT} - #{item.collectionId,jdbcType=VARCHAR} + + #{item.deleted,jdbcType=BIT} + #{item.caseId,jdbcType=VARCHAR} + + #{item.errorMessage,jdbcType=VARCHAR} + ) diff --git a/backend/framework/domain/src/main/resources/migration/3.5.0/ddl/V3.5.0_2__ga_ddl.sql b/backend/framework/domain/src/main/resources/migration/3.5.0/ddl/V3.5.0_2__ga_ddl.sql index 8156b4e08b..07cb3334c5 100644 --- a/backend/framework/domain/src/main/resources/migration/3.5.0/ddl/V3.5.0_2__ga_ddl.sql +++ b/backend/framework/domain/src/main/resources/migration/3.5.0/ddl/V3.5.0_2__ga_ddl.sql @@ -78,6 +78,8 @@ ALTER TABLE exec_task_item ADD COLUMN case_id VARCHAR(50) COMMENT '用例表id'; CREATE INDEX idx_case_id ON exec_task_item(case_id); -- 任务项添加测试集字段 ALTER TABLE exec_task_item ADD collection_id varchar(50) NULL COMMENT '测试集ID'; +-- 任务项添加异常信息字段 +ALTER TABLE exec_task_item ADD error_message varchar(50) NULL COMMENT '异常信息'; -- set innodb lock wait timeout to default SET SESSION innodb_lock_wait_timeout = DEFAULT; diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/TaskItemErrorMessage.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/TaskItemErrorMessage.java new file mode 100644 index 0000000000..0bbc118360 --- /dev/null +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/TaskItemErrorMessage.java @@ -0,0 +1,9 @@ +package io.metersphere.sdk.constants; + +/** + * 任务项执行异常类型 + * @author jianxing + */ +public enum TaskItemErrorMessage { + INVALID_RESOURCE_POOL, CASE_NOT_EXIST +} diff --git a/backend/framework/sdk/src/main/resources/i18n/commons.properties b/backend/framework/sdk/src/main/resources/i18n/commons.properties index ce343c0a83..5617d04e12 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons.properties @@ -559,4 +559,8 @@ user_view.all_data=全部数据 user_view.my_follow=我关注的 user_view.my_create=我创建的 user_view.my_review=我评审的 -user_view_exist=视图已存在 \ No newline at end of file +user_view_exist=视图已存在 + +#task_error_message +task_error_message.invalid_resource_pool=没有可用的资源池 +task_error_message.case_not_exist=用例不存在 \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties index fb1385589f..a061367627 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties @@ -598,4 +598,8 @@ user_view.my_follow=I followed user_view.my_create=I created user_view.my_review=I review user_view.archived=Archived -user_view_exist=The view already exists \ No newline at end of file +user_view_exist=The view already exists + +#task_error_message +task_error_message.invalid_resource_pool=There is no resource pool available +task_error_message.case_not_exist=The case doesn't exist \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties index 72613c2103..19e1a7317a 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties @@ -593,4 +593,8 @@ user_view.my_follow=我关注的 user_view.my_create=我创建的 user_view.my_review=我评审的 user_view.archived=已归档 -user_view_exist=视图已存在 \ No newline at end of file +user_view_exist=视图已存在 + +#task_error_message +task_error_message.invalid_resource_pool=没有可用的资源池 +task_error_message.case_not_exist=用例不存在 \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties index ad2591e573..54fc9fd0b6 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties @@ -593,4 +593,8 @@ user_view.my_follow=我關注的 user_view.my_create=我創建的 user_view.my_review=我評審的 user_view.archived=已歸檔 -user_view_exist=視圖已存在 \ No newline at end of file +user_view_exist=視圖已存在 + +#task_error_message +task_error_message.invalid_resource_pool=沒有可用的資源池 +task_error_message.case_not_exist=用例不存在 \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/result/ApiResultCode.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/result/ApiResultCode.java index 7b13875a43..eeaf2fb944 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/result/ApiResultCode.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/result/ApiResultCode.java @@ -16,7 +16,9 @@ public enum ApiResultCode implements IResultCode { API_DEFINITION_MOCK_EXIST(104007, "api_definition_mock_exist"), API_SCENARIO_EXIST(104008, "api_scenario_exist"), API_RESPONSE_NAME_CODE_UNIQUE(104009, "api_response_name_code_unique"), - API_SCENARIO_CIRCULAR_REFERENCE(104010, "api_scenario_circular_reference_error") + API_SCENARIO_CIRCULAR_REFERENCE(104010, "api_scenario_circular_reference_error"), + TASK_ERROR_MESSAGE_INVALID_RESOURCE_POOL(104011, "task_error_message.invalid_resource_pool"), + CASE_NOT_EXIST(104012, "task_error_message.case_not_exist"), ; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java index 395746a5bd..2bb9e25803 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCaseExecuteCallbackService.java @@ -8,11 +8,14 @@ import io.metersphere.api.service.definition.ApiTestCaseBatchRunService; import io.metersphere.api.service.definition.ApiTestCaseRunService; import io.metersphere.sdk.constants.ApiExecuteResourceType; import io.metersphere.sdk.constants.ApiExecuteRunMode; +import io.metersphere.sdk.constants.TaskItemErrorMessage; import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.api.task.TaskItem; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.Translator; import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.Resource; import org.apache.commons.lang3.BooleanUtils; @@ -33,7 +36,8 @@ public class ApiCaseExecuteCallbackService implements ApiExecuteCallbackService private ApiTestCaseMapper apiTestCaseMapper; @Resource private ApiTestCaseBatchRunService apiTestCaseBatchRunService; - + @Resource + private ApiCommonService apiCommonService; public ApiCaseExecuteCallbackService() { ApiExecuteCallbackServiceInvoker.register(ApiExecuteResourceType.API_CASE, this); @@ -45,6 +49,10 @@ public class ApiCaseExecuteCallbackService implements ApiExecuteCallbackService @Override public GetRunScriptResult getRunScript(GetRunScriptRequest request) { ApiTestCase apiTestCase = apiTestCaseMapper.selectByPrimaryKey(request.getTaskItem().getResourceId()); + if (apiTestCase == null || apiTestCase.getDeleted()) { + apiCommonService.updateTaskItemErrorMassage(request.getTaskItem().getId(), TaskItemErrorMessage.CASE_NOT_EXIST); + throw new MSException(Translator.get("task_error_message.case_not_exist")); + } String reportId = initReport(request, apiTestCase); GetRunScriptResult result = apiTestCaseRunService.getRunScript(request, apiTestCase); result.setReportId(reportId); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCommonService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCommonService.java index f0006f4066..4d3de3cd43 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCommonService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiCommonService.java @@ -31,18 +31,30 @@ import io.metersphere.project.service.FileAssociationService; import io.metersphere.project.service.FileMetadataService; import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.ExecStatus; +import io.metersphere.sdk.constants.TaskItemErrorMessage; +import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; +import io.metersphere.sdk.dto.api.task.TaskBatchRequestDTO; +import io.metersphere.sdk.dto.api.task.TaskItem; import io.metersphere.sdk.util.BeanUtils; +import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.LogUtils; import io.metersphere.system.domain.ExecTask; import io.metersphere.system.domain.ExecTaskItem; +import io.metersphere.system.mapper.ExecTaskItemMapper; +import io.metersphere.system.mapper.ExecTaskMapper; import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; @@ -65,6 +77,10 @@ public class ApiCommonService { private FileMetadataService fileMetadataService; @Resource private CustomFunctionService customFunctionService; + @Resource + private ExecTaskItemMapper execTaskItemMapper; + @Resource + private ExecTaskMapper execTaskMapper; /** * 根据 fileId 查找 MsHTTPElement 中的 ApiFile @@ -511,4 +527,55 @@ public class ApiCommonService { apiReportRelateTask.setTaskResourceId(taskItemId); return apiReportRelateTask; } + + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + public void batchUpdateTaskItemErrorMassage(TaskItemErrorMessage errorMessage, TaskBatchRequestDTO batchRequest) { + SqlSessionFactory sqlSessionFactory = CommonBeanFactory.getBean(SqlSessionFactory.class); + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + try { + if (CollectionUtils.isNotEmpty(batchRequest.getTaskItems())) { + ExecTaskItemMapper batchExecTaskItemMapper = sqlSession.getMapper(ExecTaskItemMapper.class); + batchRequest.getTaskItems().forEach(taskItem -> { + // 更新任务项的异常信息 + ExecTaskItem execTaskItem = new ExecTaskItem(); + execTaskItem.setId(taskItem.getId()); + execTaskItem.setErrorMessage(errorMessage.name()); + batchExecTaskItemMapper.updateByPrimaryKeySelective(execTaskItem); + }); + } + } finally { + sqlSession.flushStatements(); + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + } + + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + public void updateTaskItemErrorMassage(String taskItemId, TaskItemErrorMessage errorMessage) { + // 更新任务项的异常信息 + ExecTaskItem execTaskItem = new ExecTaskItem(); + execTaskItem.setId(taskItemId); + execTaskItem.setErrorMessage(errorMessage.name()); + execTaskItemMapper.updateByPrimaryKeySelective(execTaskItem); + } + + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + public void updateTaskRunningStatus(String taskId) { + ExecTask execTask = new ExecTask(); + execTask.setId(taskId); + execTask.setStartTime(System.currentTimeMillis()); + execTask.setStatus(ExecStatus.RUNNING.name()); + execTaskMapper.updateByPrimaryKeySelective(execTask); + } + + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) + public void updateTaskItemRunningStatus(GetRunScriptRequest request) { + TaskItem taskItem = request.getTaskItem(); + // 更新任务项状态 + ExecTaskItem execTaskItem = new ExecTaskItem(); + execTaskItem.setId(taskItem.getId()); + execTaskItem.setStartTime(System.currentTimeMillis()); + execTaskItem.setStatus(ExecStatus.RUNNING.name()); + execTaskItem.setThreadId(request.getThreadId()); + execTaskItemMapper.updateByPrimaryKeySelective(execTaskItem); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java index ffcf634907..b2cfc89111 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteResourceService.java @@ -4,17 +4,15 @@ import io.metersphere.api.invoker.ApiExecuteCallbackServiceInvoker; import io.metersphere.api.service.definition.ApiReportService; import io.metersphere.api.service.scenario.ApiScenarioReportService; import io.metersphere.api.utils.TaskRunningCache; -import io.metersphere.sdk.constants.*; +import io.metersphere.sdk.constants.ApiExecuteResourceType; +import io.metersphere.sdk.constants.ApiExecuteRunMode; import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.api.task.TaskItem; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.EnumValidator; import io.metersphere.sdk.util.LogUtils; -import io.metersphere.system.domain.ExecTask; -import io.metersphere.system.domain.ExecTaskItem; -import io.metersphere.system.mapper.ExecTaskItemMapper; -import io.metersphere.system.mapper.ExecTaskMapper; import jakarta.annotation.Resource; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -28,10 +26,6 @@ import java.util.Optional; @Transactional(rollbackFor = Exception.class) public class ApiExecuteResourceService { - @Resource - private ExecTaskMapper execTaskMapper; - @Resource - private ExecTaskItemMapper execTaskItemMapper; @Resource private ApiReportService apiReportService; @Resource @@ -40,6 +34,8 @@ public class ApiExecuteResourceService { private StringRedisTemplate stringRedisTemplate; @Resource private TaskRunningCache taskRunningCache; + @Resource + private ApiCommonService apiCommonService; public GetRunScriptResult getRunScript(GetRunScriptRequest request) { @@ -79,15 +75,15 @@ public class ApiExecuteResourceService { // 设置缓存成功说明是第一个任务,则设置任务的开始时间和运行状态 if (taskRunningCache.setIfAbsent(taskId)) { // 将任务状态更新为运行中 - updateTaskRunningStatus(taskId); + apiCommonService.updateTaskRunningStatus(taskId); } } else { // 非批量时,直接更新任务状态 - updateTaskRunningStatus(taskId); + apiCommonService.updateTaskRunningStatus(taskId); } // 更新任务项状态 - updateTaskItemRunningStatus(request); + apiCommonService.updateTaskItemRunningStatus(request); // 非调试执行,更新报告状态 switch (apiExecuteResourceType) { @@ -98,23 +94,4 @@ public class ApiExecuteResourceService { default -> throw new MSException("不支持的资源类型: " + request.getResourceType()); } } - - private void updateTaskRunningStatus(String taskId) { - ExecTask execTask = new ExecTask(); - execTask.setId(taskId); - execTask.setStartTime(System.currentTimeMillis()); - execTask.setStatus(ExecStatus.RUNNING.name()); - execTaskMapper.updateByPrimaryKeySelective(execTask); - } - - private void updateTaskItemRunningStatus(GetRunScriptRequest request) { - TaskItem taskItem = request.getTaskItem(); - // 更新任务项状态 - ExecTaskItem execTaskItem = new ExecTaskItem(); - execTaskItem.setId(taskItem.getId()); - execTaskItem.setStartTime(System.currentTimeMillis()); - execTaskItem.setStatus(ExecStatus.RUNNING.name()); - execTaskItem.setThreadId(request.getThreadId()); - execTaskItemMapper.updateByPrimaryKeySelective(execTaskItem); - } } \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java index df21bff957..6769462fda 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java @@ -214,10 +214,7 @@ public class ApiExecuteService { */ public TaskRequestDTO execute(TaskRequestDTO taskRequest) { TaskInfo taskInfo = taskRequest.getTaskInfo(); - TaskItem taskItem = taskRequest.getTaskItem(); - try { - taskInfo = setTaskRequestParams(taskInfo); // 获取资源池 @@ -231,39 +228,10 @@ public class ApiExecuteService { // 判断是否为 K8S 资源池 boolean isK8SResourcePool = StringUtils.equals(testResourcePoolDTO.getType(), ResourcePoolTypeEnum.K8S.getName()); - boolean isDebugMode = ApiExecuteRunMode.isDebug(taskInfo.getRunMode()); - if (isK8SResourcePool) { - TestResourceDTO testResourceDTO = new TestResourceDTO(); - BeanUtils.copyBean(testResourceDTO, testResourcePoolDTO.getTestResourceReturnDTO()); - testResourceDTO.setId(testResourcePoolDTO.getId()); - taskInfo.setPerTaskSize(testResourceDTO.getPodThreads()); - taskInfo.setPoolSize(testResourceDTO.getConcurrentNumber()); - LogUtils.info("开始发送请求【 {}_{} 】到 K8S 资源池执行", taskItem.getReportId(), taskItem.getResourceId()); - if (isDebugMode) { - EngineFactory.debugApi(taskRequest, testResourceDTO); - } else { - EngineFactory.runApi(taskRequest, testResourceDTO); - } + k8sExecute(taskRequest, testResourcePoolDTO); } else { - if (CollectionUtils.isEmpty(testResourcePoolDTO.getTestResourceReturnDTO().getNodesList())) { - throw new MSException(ApiResultCode.EXECUTE_RESOURCE_POOL_NOT_CONFIG); - } - TestResourceNodeDTO testResourceNodeDTO = getNextExecuteNode(testResourcePoolDTO); - if (!ApiExecuteRunMode.isDebug(taskInfo.getRunMode())) { - updateTaskItemNodeInfo(taskItem, testResourcePoolDTO, testResourceNodeDTO, execTaskItemMapper); - } - taskInfo.setPerTaskSize(Optional.ofNullable(testResourceNodeDTO.getSingleTaskConcurrentNumber()).orElse(3)); - taskInfo.setPoolSize(testResourceNodeDTO.getConcurrentNumber()); - - String endpoint = MsHttpClient.getEndpoint(testResourceNodeDTO.getIp(), testResourceNodeDTO.getPort()); - LogUtils.info("开始发送请求【 {}_{} 】到 {} 节点执行", taskItem.getReportId(), taskItem.getResourceId(), endpoint); - - if (isDebugMode) { - MsHttpClient.debugApi(endpoint, taskRequest); - } else { - MsHttpClient.runApi(endpoint, taskRequest); - } + nodeExecute(taskRequest, testResourcePoolDTO); } } catch (HttpServerErrorException e) { @@ -291,10 +259,87 @@ public class ApiExecuteService { return taskRequest; } - private void updateTaskItemNodeInfo(TaskItem taskItem, TestResourcePoolReturnDTO testResourcePoolDTO, TestResourceNodeDTO testResourceNodeDTO, ExecTaskItemMapper execTaskItemMapper) { + private void nodeExecute(TaskRequestDTO taskRequest, TestResourcePoolReturnDTO testResourcePoolDTO) throws Exception { + TaskItem taskItem = taskRequest.getTaskItem(); + TaskInfo taskInfo = taskRequest.getTaskInfo(); + if (CollectionUtils.isEmpty(testResourcePoolDTO.getTestResourceReturnDTO().getNodesList())) { + throw new MSException(ApiResultCode.EXECUTE_RESOURCE_POOL_NOT_CONFIG); + } + Set invalidNodeSet = new HashSet<>(); + Exception lastException = null; + while (true) { + TestResourceNodeDTO testResourceNodeDTO = getNextExecuteNode(testResourcePoolDTO); + taskInfo.setPerTaskSize(Optional.ofNullable(testResourceNodeDTO.getSingleTaskConcurrentNumber()).orElse(3)); + taskInfo.setPoolSize(testResourceNodeDTO.getConcurrentNumber()); + + String endpoint = MsHttpClient.getEndpoint(testResourceNodeDTO.getIp(), testResourceNodeDTO.getPort()); + LogUtils.info("开始发送请求【 {}_{} 】到 {} 节点执行", taskItem.getReportId(), taskItem.getResourceId(), endpoint); + if (invalidNodeSet.contains(endpoint)) { + // 全部节点都异常,更新任务项状态 + if (!ApiExecuteRunMode.isDebug(taskInfo.getRunMode())) { + apiCommonService.updateTaskItemErrorMassage(taskItem.getId(), TaskItemErrorMessage.INVALID_RESOURCE_POOL); + } + throw lastException; + } + try { + if (ApiExecuteRunMode.isDebug(taskInfo.getRunMode())) { + MsHttpClient.debugApi(endpoint, taskRequest); + } else { + MsHttpClient.runApi(endpoint, taskRequest); + // 更新状态项资源池信息 + updateTaskItemNodeInfo(taskItem.getId(), testResourcePoolDTO, testResourceNodeDTO, execTaskItemMapper); + } + break; + } catch (Exception e) { + lastException = e; + LogUtils.error(e); + // 记录异常节点,重试 + invalidNodeSet.add(endpoint); + } + } + } + + private void k8sExecute(TaskRequestDTO taskRequest, TestResourcePoolReturnDTO testResourcePoolDTO) throws Exception { + TaskItem taskItem = taskRequest.getTaskItem(); + TaskInfo taskInfo = taskRequest.getTaskInfo(); + TestResourceDTO testResourceDTO = new TestResourceDTO(); + BeanUtils.copyBean(testResourceDTO, testResourcePoolDTO.getTestResourceReturnDTO()); + testResourceDTO.setId(testResourcePoolDTO.getId()); + taskInfo.setPerTaskSize(testResourceDTO.getPodThreads()); + taskInfo.setPoolSize(testResourceDTO.getConcurrentNumber()); + LogUtils.info("开始发送请求【 {}_{} 】到 K8S 资源池执行", taskItem.getReportId(), taskItem.getResourceId()); + if (ApiExecuteRunMode.isDebug(taskInfo.getRunMode())) { + EngineFactory.debugApi(taskRequest, testResourceDTO); + } else { + try { + EngineFactory.runApi(taskRequest, testResourceDTO); + } catch (Exception e) { + apiCommonService.updateTaskItemErrorMassage(taskItem.getId(), TaskItemErrorMessage.INVALID_RESOURCE_POOL); + throw e; + } + } + } + + private void batchUpdateTaskItemNodeInfo(TestResourcePoolReturnDTO testResourcePool, TestResourceNodeDTO testResourceNode, TaskBatchRequestDTO batchRequest) { + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + try { + if (CollectionUtils.isNotEmpty(batchRequest.getTaskItems())) { + ExecTaskItemMapper batchExecTaskItemMapper = sqlSession.getMapper(ExecTaskItemMapper.class); + batchRequest.getTaskItems().forEach(taskItem -> { + // 非调试模式,更新任务项的资源池信息 + updateTaskItemNodeInfo(taskItem.getId(), testResourcePool, testResourceNode, batchExecTaskItemMapper); + }); + } + } finally { + sqlSession.flushStatements(); + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + } + + private void updateTaskItemNodeInfo(String taskItemId, TestResourcePoolReturnDTO testResourcePoolDTO, TestResourceNodeDTO testResourceNodeDTO, ExecTaskItemMapper execTaskItemMapper) { // 非调试模式,更新任务项的资源池信息 ExecTaskItem execTaskItem = new ExecTaskItem(); - execTaskItem.setId(taskItem.getId()); + execTaskItem.setId(taskItemId); execTaskItem.setResourcePoolId(testResourcePoolDTO.getId()); execTaskItem.setResourcePoolNode(StringUtils.join(testResourceNodeDTO.getIp(), ":", testResourceNodeDTO.getPort())); execTaskItemMapper.updateByPrimaryKeySelective(execTaskItem); @@ -319,78 +364,102 @@ public class ApiExecuteService { // 判断是否为 K8S 资源池 boolean isK8SResourcePool = StringUtils.equals(testResourcePool.getType(), ResourcePoolTypeEnum.K8S.getName()); if (isK8SResourcePool) { - TestResourceDTO testResourceDTO = new TestResourceDTO(); - BeanUtils.copyBean(testResourceDTO, testResourcePool.getTestResourceReturnDTO()); - testResourceDTO.setId(testResourcePool.getId()); - - taskInfo.setPoolSize(testResourceDTO.getConcurrentNumber()); - taskInfo.setPerTaskSize(testResourceDTO.getPodThreads()); - try { - EngineFactory.batchRunApi(taskRequest, testResourceDTO); - } catch (Exception e) { - LogUtils.error(e); - } + k8sBatchExecute(taskRequest, taskInfo, testResourcePool); } else { - // 将任务按资源池的数量拆分 - List nodesList = testResourcePool.getTestResourceReturnDTO().getNodesList(); - if (CollectionUtils.isEmpty(nodesList)) { - throw new MSException(ApiResultCode.EXECUTE_RESOURCE_POOL_NOT_CONFIG); - } - List distributeTasks = new ArrayList<>(nodesList.size()); - for (int i = 0; i < taskRequest.getTaskItems().size(); i++) { - TaskBatchRequestDTO distributeTask; - int nodeIndex = i % nodesList.size(); - if (distributeTasks.size() < nodesList.size()) { - distributeTask = BeanUtils.copyBean(new TaskBatchRequestDTO(), taskRequest); - distributeTask.setTaskItems(new ArrayList<>()); - distributeTasks.add(distributeTask); - } else { - distributeTask = distributeTasks.get(nodeIndex); - } - taskInfo.setPerTaskSize(Optional.ofNullable(nodesList.get(nodeIndex).getSingleTaskConcurrentNumber()).orElse(3)); - distributeTask.getTaskInfo().setPoolSize(nodesList.get(nodeIndex).getConcurrentNumber()); - distributeTask.getTaskItems().add(taskRequest.getTaskItems().get(i)); - } - for (int i = 0; i < nodesList.size(); i++) { - // todo 优化某个资源池不可用的情况,以及清理 executionSet - TestResourceNodeDTO testResourceNode = nodesList.get(i); - TaskBatchRequestDTO subTaskRequest = distributeTasks.get(i); + nodeBatchExecute(taskRequest, taskInfo, testResourcePool); + } + } + + private void nodeBatchExecute(TaskBatchRequestDTO taskRequest, TaskInfo taskInfo, TestResourcePoolReturnDTO testResourcePool) { + // 将任务按资源池的数量拆分 + List nodesList = testResourcePool.getTestResourceReturnDTO().getNodesList(); + if (CollectionUtils.isEmpty(nodesList)) { + throw new MSException(ApiResultCode.EXECUTE_RESOURCE_POOL_NOT_CONFIG); + } + // 按资源池节点数量,预分组 + List distributeTasks = getDistributeTaskBatchRequest(taskRequest, nodesList.size()); + + Iterator nodeIterator = nodesList.iterator(); + Iterator distributeTaskIterator = distributeTasks.iterator(); + Set invalidNodeSet = new HashSet<>(); + while (distributeTaskIterator.hasNext()) { + TaskBatchRequestDTO subTaskRequest = distributeTaskIterator.next(); + boolean success = false; + while (nodeIterator.hasNext()) { + TestResourceNodeDTO testResourceNode = nodeIterator.next(); String endpoint = MsHttpClient.getEndpoint(testResourceNode.getIp(), testResourceNode.getPort()); - - if (!ApiExecuteRunMode.isDebug(taskInfo.getRunMode())) { - batchUpdateTaskItemNodeInfo(testResourcePool, testResourceNode, subTaskRequest); - } - + subTaskRequest.getTaskInfo().setPerTaskSize(Optional.ofNullable(testResourceNode.getSingleTaskConcurrentNumber()).orElse(3)); + subTaskRequest.getTaskInfo().setPoolSize(testResourceNode.getConcurrentNumber()); try { List taskKeys = subTaskRequest.getTaskItems().stream() .map(TaskItem::getId) .toList(); LogUtils.info("开始发送批量任务到 {} 节点执行:\n" + taskKeys, endpoint); MsHttpClient.batchRunApi(endpoint, subTaskRequest); + + success = true; + // 更新任务项的资源池信息 + if (!ApiExecuteRunMode.isDebug(taskInfo.getRunMode())) { + batchUpdateTaskItemNodeInfo(testResourcePool, testResourceNode, subTaskRequest); + } + break; } catch (Exception e) { + invalidNodeSet.add(testResourceNode); LogUtils.error("发送批量任务到 {} 节点执行失败", endpoint); LogUtils.error(e); } + + if (!nodeIterator.hasNext()) { + // 去除异常节点 + invalidNodeSet.forEach(nodesList::remove); + // 资源池不够用,重新遍历资源池 + nodeIterator = nodesList.iterator(); + } + } + + if (!success) { + // 更新任务项错误信息 + if (!ApiExecuteRunMode.isDebug(taskInfo.getRunMode())) { + apiCommonService.batchUpdateTaskItemErrorMassage(TaskItemErrorMessage.INVALID_RESOURCE_POOL, subTaskRequest); + } else { + throw new MSException(ApiResultCode.TASK_ERROR_MESSAGE_INVALID_RESOURCE_POOL); + } } } } - private void batchUpdateTaskItemNodeInfo(TestResourcePoolReturnDTO testResourcePool, TestResourceNodeDTO testResourceNode, TaskBatchRequestDTO batchRequest) { - SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + private void k8sBatchExecute(TaskBatchRequestDTO taskRequest, TaskInfo taskInfo, TestResourcePoolReturnDTO testResourcePool) { + TestResourceDTO testResourceDTO = new TestResourceDTO(); + BeanUtils.copyBean(testResourceDTO, testResourcePool.getTestResourceReturnDTO()); + testResourceDTO.setId(testResourcePool.getId()); + + taskInfo.setPoolSize(testResourceDTO.getConcurrentNumber()); + taskInfo.setPerTaskSize(testResourceDTO.getPodThreads()); try { - if (CollectionUtils.isNotEmpty(batchRequest.getTaskItems())) { - ExecTaskItemMapper batchExecTaskItemMapper = sqlSession.getMapper(ExecTaskItemMapper.class); - batchRequest.getTaskItems().forEach(taskItem -> { - // 非调试模式,更新任务项的资源池信息 - updateTaskItemNodeInfo(taskItem, testResourcePool, testResourceNode, batchExecTaskItemMapper); - }); - } - } finally { - sqlSession.flushStatements(); - SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + EngineFactory.batchRunApi(taskRequest, testResourceDTO); + } catch (Exception e) { + LogUtils.error(e); + apiCommonService.batchUpdateTaskItemErrorMassage(TaskItemErrorMessage.INVALID_RESOURCE_POOL, taskRequest); } } + private List getDistributeTaskBatchRequest(TaskBatchRequestDTO taskRequest, int distributeSize) { + List distributeTasks = new ArrayList<>(distributeSize); + for (int i = 0; i < taskRequest.getTaskItems().size(); i++) { + TaskBatchRequestDTO distributeTask; + int nodeIndex = i % distributeSize; + if (distributeTasks.size() < distributeSize) { + distributeTask = BeanUtils.copyBean(new TaskBatchRequestDTO(), taskRequest); + distributeTask.setTaskItems(new ArrayList<>()); + distributeTasks.add(distributeTask); + } else { + distributeTask = distributeTasks.get(nodeIndex); + } + distributeTask.getTaskItems().add(taskRequest.getTaskItems().get(i)); + } + return distributeTasks; + } + protected static boolean validate() { try { LicenseService licenseService = CommonBeanFactory.getBean(LicenseService.class); @@ -759,14 +828,14 @@ public class ApiExecuteService { testElement.setProjectId(projectId); } - public ApiParamConfig getApiParamConfig(String reportId) { + public ApiParamConfig getApiParamConfig() { ApiParamConfig paramConfig = new ApiParamConfig(); paramConfig.setTestElementClassPluginIdMap(apiPluginService.getTestElementPluginMap()); paramConfig.setTestElementClassProtocolMap(apiPluginService.getTestElementProtocolMap()); return paramConfig; } - public ApiParamConfig getApiParamConfig(String reportId, String projectId) { + public ApiParamConfig getApiParamConfig(String projectId) { ApiParamConfig paramConfig = new ApiParamConfig(); paramConfig.setTestElementClassPluginIdMap(apiPluginService.getTestElementPluginMap()); paramConfig.setTestElementClassProtocolMap(apiPluginService.getTestElementProtocolMap()); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java index 8923369818..96d5f3dc86 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioExecuteCallbackService.java @@ -39,7 +39,7 @@ public class ApiScenarioExecuteCallbackService implements ApiExecuteCallbackServ */ @Override public GetRunScriptResult getRunScript(GetRunScriptRequest request) { - ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(request.getTaskItem().getResourceId()); + ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRunWithTaskItemErrorMassage(request.getTaskItem().getId(), request.getTaskItem().getResourceId()); String reportId = initReport(request, apiScenarioDetail); GetRunScriptResult result = apiScenarioRunService.getRunScript(request, apiScenarioDetail); result.setReportId(reportId); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java index acbf29a031..c134aa7882 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java @@ -211,7 +211,7 @@ public class ApiDebugService extends MoveNodeService { public TaskRequestDTO debug(ApiDebugRunRequest request) { ApiResourceRunRequest runRequest = apiExecuteService.getApiResourceRunRequest(request); - ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(request.getReportId()); + ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(); TaskRequestDTO taskRequest = apiExecuteService.getTaskRequest(request.getReportId(), request.getId(), request.getProjectId()); TaskInfo taskInfo = taskRequest.getTaskInfo(); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java index 93248ded85..777c07dbd0 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java @@ -1191,7 +1191,7 @@ public class ApiDefinitionService extends MoveNodeService { public TaskRequestDTO debug(ApiDefinitionRunRequest request) { ApiResourceRunRequest runRequest = apiExecuteService.getApiResourceRunRequest(request); EnvironmentInfoDTO environmentInfoDTO = environmentService.get(request.getEnvironmentId()); - ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(request.getReportId(), request.getProjectId()); + ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(request.getProjectId()); TaskRequestDTO taskRequest = apiExecuteService.getTaskRequest(request.getReportId(), request.getId(), request.getProjectId()); TaskInfo taskInfo = taskRequest.getTaskInfo(); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java index b89595b564..7e08d3c90a 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseRunService.java @@ -156,7 +156,7 @@ public class ApiTestCaseRunService { public TaskRequestDTO doExecute(TaskRequestDTO taskRequest, ApiResourceRunRequest runRequest, String apiDefinitionId, String envId) { - ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(taskRequest.getTaskItem().getReportId(), taskRequest.getTaskInfo().getProjectId()); + ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(taskRequest.getTaskInfo().getProjectId()); ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(apiDefinitionId); @@ -175,7 +175,7 @@ public class ApiTestCaseRunService { TaskItem taskItem = request.getTaskItem(); ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(apiTestCase.getApiDefinitionId()); ApiTestCaseBlob apiTestCaseBlob = apiTestCaseBlobMapper.selectByPrimaryKey(apiTestCase.getId()); - ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(taskItem.getReportId(), apiTestCase.getProjectId()); + ApiParamConfig apiParamConfig = apiExecuteService.getApiParamConfig(apiTestCase.getProjectId()); apiParamConfig.setRetryOnFail(request.getRunModeConfig().getRetryOnFail()); apiParamConfig.setRetryConfig(request.getRunModeConfig().getRetryConfig()); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java index 8fa1142c10..75962c4bfa 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioRunService.java @@ -3,6 +3,7 @@ package io.metersphere.api.service.scenario; import io.metersphere.api.constants.ApiResourceType; import io.metersphere.api.constants.ApiScenarioStepRefType; import io.metersphere.api.constants.ApiScenarioStepType; +import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.*; import io.metersphere.api.dto.*; import io.metersphere.api.dto.debug.ApiResourceRunRequest; @@ -34,6 +35,7 @@ import io.metersphere.project.service.EnvironmentGroupService; import io.metersphere.project.service.EnvironmentService; import io.metersphere.sdk.constants.*; import io.metersphere.sdk.dto.api.task.*; +import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.DateUtils; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.LogUtils; @@ -266,6 +268,21 @@ public class ApiScenarioRunService { return apiScenarioDetail; } + public ApiScenarioDetail getForRunWithTaskItemErrorMassage(String taskItemId, String scenarioId) { + try { + ApiScenarioDetail apiScenarioDetail = apiScenarioService.get(scenarioId); + apiScenarioDetail.setSteps(filerDisableSteps(apiScenarioDetail.getSteps())); + return apiScenarioDetail; + } catch (MSException msException) { + if (msException.getErrorCode().equals(ApiResultCode.CASE_NOT_EXIST)) { + // 用例不存在记录任务项,错误信息 + apiCommonService.updateTaskItemErrorMassage(taskItemId, TaskItemErrorMessage.CASE_NOT_EXIST); + throw new MSException(ApiResultCode.CASE_NOT_EXIST); + } + throw msException; + } + } + /** * 过滤掉禁用的步骤 */ diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java index 3ee8b36297..c9bc8a12a3 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java @@ -3,6 +3,7 @@ package io.metersphere.api.service.scenario; import io.metersphere.api.constants.ApiResourceType; import io.metersphere.api.constants.ApiScenarioStepRefType; import io.metersphere.api.constants.ApiScenarioStepType; +import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.*; import io.metersphere.api.dto.*; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; @@ -1520,7 +1521,7 @@ public class ApiScenarioService extends MoveNodeService { example.createCriteria().andIdEqualTo(id).andDeletedEqualTo(false); List apiScenarios = apiScenarioMapper.selectByExample(example); if (CollectionUtils.isEmpty(apiScenarios)) { - throw new MSException(Translator.get("api_scenario_is_not_exist")); + throw new MSException(ApiResultCode.CASE_NOT_EXIST); } return apiScenarios.getFirst(); } diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/taskhub/TaskHubItemDTO.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/taskhub/TaskHubItemDTO.java index 9051854fb3..793430244c 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/taskhub/TaskHubItemDTO.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/taskhub/TaskHubItemDTO.java @@ -1,11 +1,7 @@ package io.metersphere.system.dto.taskhub; import io.metersphere.system.domain.ExecTaskItem; -import io.metersphere.validation.groups.Created; -import io.metersphere.validation.groups.Updated; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; import lombok.Data; /** @@ -40,4 +36,7 @@ public class TaskHubItemDTO extends ExecTaskItem { @Schema(description = "组织名称") private String organizationName; + + @Schema(description = "错误信息") + private String errorMessage; } diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java index ba093d5aa0..a61cf4f601 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/BaseTaskHubService.java @@ -373,6 +373,9 @@ public class BaseTaskHubService { item.setResourcePoolName(resourcePoolMaps.getOrDefault(item.getResourcePoolId(), StringUtils.EMPTY)); item.setProjectName(projectMaps.getOrDefault(item.getProjectId(), StringUtils.EMPTY)); item.setOrganizationName(organizationMaps.getOrDefault(item.getOrganizationId(), StringUtils.EMPTY)); + if (StringUtils.isNotBlank(item.getErrorMessage())) { + item.setErrorMessage(Translator.get("task_error_message." + item.getErrorMessage().toLowerCase())); + } }); } @@ -740,7 +743,7 @@ public class BaseTaskHubService { public Map getTaskItemOrder(List taskIdItemIds) { List taskItemIds = getTaskItemByIds(taskIdItemIds); Map> nodeResourceMap = taskItemIds.stream() - .filter(item -> StringUtils.equals(item.getResourceType(), ResourcePoolTypeEnum.NODE.getName())) // 根据条件过滤 + .filter(item -> StringUtils.contains(item.getResourcePoolNode(), ":")) // 根据条件过滤 .collect(Collectors.groupingBy(ExecTaskItem::getResourcePoolNode)); List>> futures = nodeResourceMap.keySet().stream() diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java index a87a4c8e2f..dceddc3ad1 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiCaseExecuteCallbackService.java @@ -1,18 +1,22 @@ package io.metersphere.plan.service; +import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.ApiTestCase; import io.metersphere.api.invoker.ApiExecuteCallbackServiceInvoker; import io.metersphere.api.mapper.ApiTestCaseMapper; +import io.metersphere.api.service.ApiCommonService; import io.metersphere.api.service.ApiExecuteCallbackService; import io.metersphere.api.service.definition.ApiTestCaseRunService; import io.metersphere.plan.domain.TestPlanReportApiCase; import io.metersphere.plan.mapper.TestPlanReportApiCaseMapper; import io.metersphere.sdk.constants.ApiExecuteResourceType; +import io.metersphere.sdk.constants.TaskItemErrorMessage; import io.metersphere.sdk.dto.api.notice.ApiNoticeDTO; import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; +import io.metersphere.sdk.exception.MSException; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -39,6 +43,9 @@ public class PlanRunApiCaseExecuteCallbackService implements ApiExecuteCallbackS @Resource private ApiTestCaseRunService apiTestCaseRunService; + @Resource + private ApiCommonService apiCommonService; + public PlanRunApiCaseExecuteCallbackService() { ApiExecuteCallbackServiceInvoker.register(ApiExecuteResourceType.PLAN_RUN_API_CASE, this); } @@ -49,7 +56,11 @@ public class PlanRunApiCaseExecuteCallbackService implements ApiExecuteCallbackS @Override public GetRunScriptResult getRunScript(GetRunScriptRequest request) { TestPlanReportApiCase testPlanReportApiCase = testPlanReportApiCaseMapper.selectByPrimaryKey(request.getTaskItem().getResourceId()); - ApiTestCase apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testPlanReportApiCase.getApiCaseId()); + ApiTestCase apiTestCase = testPlanReportApiCase == null ? null : apiTestCaseMapper.selectByPrimaryKey(testPlanReportApiCase.getApiCaseId()); + if (testPlanReportApiCase == null || apiTestCase == null || apiTestCase.getDeleted()) { + apiCommonService.updateTaskItemErrorMassage(request.getTaskItem().getId(), TaskItemErrorMessage.CASE_NOT_EXIST); + throw new MSException(ApiResultCode.CASE_NOT_EXIST); + } String reportId = initReport(request, testPlanReportApiCase, apiTestCase); GetRunScriptResult result = apiTestCaseRunService.getRunScript(request, apiTestCase); result.setReportId(reportId); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java index 2b0474e8aa..ae84d7f2d3 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/PlanRunApiScenarioExecuteCallbackService.java @@ -1,17 +1,21 @@ package io.metersphere.plan.service; +import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.dto.scenario.ApiScenarioDetail; import io.metersphere.api.invoker.ApiExecuteCallbackServiceInvoker; +import io.metersphere.api.service.ApiCommonService; import io.metersphere.api.service.ApiExecuteCallbackService; import io.metersphere.api.service.scenario.ApiScenarioRunService; import io.metersphere.plan.domain.TestPlanReportApiScenario; import io.metersphere.plan.mapper.TestPlanReportApiScenarioMapper; import io.metersphere.sdk.constants.ApiExecuteResourceType; +import io.metersphere.sdk.constants.TaskItemErrorMessage; import io.metersphere.sdk.dto.api.notice.ApiNoticeDTO; import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; +import io.metersphere.sdk.exception.MSException; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,6 +35,8 @@ public class PlanRunApiScenarioExecuteCallbackService implements ApiExecuteCallb private TestPlanReportApiScenarioMapper testPlanReportApiScenarioMapper; @Resource private ApiScenarioRunService apiScenarioRunService; + @Resource + private ApiCommonService apiCommonService; public PlanRunApiScenarioExecuteCallbackService() { ApiExecuteCallbackServiceInvoker.register(ApiExecuteResourceType.PLAN_RUN_API_SCENARIO, this); @@ -43,7 +49,11 @@ public class PlanRunApiScenarioExecuteCallbackService implements ApiExecuteCallb public GetRunScriptResult getRunScript(GetRunScriptRequest request) { String resourceId = request.getTaskItem().getResourceId(); TestPlanReportApiScenario testPlanReportApiScenario = testPlanReportApiScenarioMapper.selectByPrimaryKey(resourceId); - ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(testPlanReportApiScenario.getApiScenarioId()); + if (testPlanReportApiScenario == null) { + apiCommonService.updateTaskItemErrorMassage(request.getTaskItem().getId(), TaskItemErrorMessage.CASE_NOT_EXIST); + throw new MSException(ApiResultCode.CASE_NOT_EXIST); + } + ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRunWithTaskItemErrorMassage(request.getTaskItem().getId(), testPlanReportApiScenario.getApiScenarioId()); apiScenarioDetail.setEnvironmentId(testPlanReportApiScenario.getEnvironmentId()); apiScenarioDetail.setGrouped(testPlanReportApiScenario.getGrouped()); GetRunScriptResult result = planRunTestPlanApiScenarioService.getRunScript(request); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java index 5b21894670..fe5c0d2839 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseExecuteCallbackService.java @@ -1,19 +1,23 @@ package io.metersphere.plan.service; +import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.ApiTestCase; import io.metersphere.api.domain.ApiTestCaseRecord; import io.metersphere.api.invoker.ApiExecuteCallbackServiceInvoker; import io.metersphere.api.mapper.ApiTestCaseMapper; +import io.metersphere.api.service.ApiCommonService; import io.metersphere.api.service.ApiExecuteCallbackService; import io.metersphere.api.service.definition.ApiTestCaseRunService; import io.metersphere.plan.domain.TestPlanApiCase; import io.metersphere.plan.mapper.TestPlanApiCaseMapper; import io.metersphere.sdk.constants.ApiExecuteResourceType; +import io.metersphere.sdk.constants.TaskItemErrorMessage; import io.metersphere.sdk.dto.api.notice.ApiNoticeDTO; import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; +import io.metersphere.sdk.exception.MSException; import jakarta.annotation.Resource; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -36,6 +40,8 @@ public class TestPlanApiCaseExecuteCallbackService implements ApiExecuteCallback private ApiTestCaseMapper apiTestCaseMapper; @Resource private ApiTestCaseRunService apiTestCaseRunService; + @Resource + private ApiCommonService apiCommonService; public TestPlanApiCaseExecuteCallbackService() { ApiExecuteCallbackServiceInvoker.register(ApiExecuteResourceType.TEST_PLAN_API_CASE, this); @@ -47,7 +53,11 @@ public class TestPlanApiCaseExecuteCallbackService implements ApiExecuteCallback @Override public GetRunScriptResult getRunScript(GetRunScriptRequest request) { TestPlanApiCase testPlanApiCase = testPlanApiCaseMapper.selectByPrimaryKey(request.getTaskItem().getResourceId()); - ApiTestCase apiTestCase = apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId()); + ApiTestCase apiTestCase = testPlanApiCase == null ? null : apiTestCaseMapper.selectByPrimaryKey(testPlanApiCase.getApiCaseId()); + if (testPlanApiCase == null || apiTestCase == null || apiTestCase.getDeleted()) { + apiCommonService.updateTaskItemErrorMassage(request.getTaskItem().getId(), TaskItemErrorMessage.CASE_NOT_EXIST); + throw new MSException(ApiResultCode.CASE_NOT_EXIST); + } String reportId = initReport(request, testPlanApiCase, apiTestCase); GetRunScriptResult result = apiTestCaseRunService.getRunScript(request, apiTestCase); result.setReportId(reportId); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java index f0e91af1e6..b4d7beab29 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioExecuteCallbackService.java @@ -1,18 +1,22 @@ package io.metersphere.plan.service; +import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.dto.scenario.ApiScenarioDetail; import io.metersphere.api.invoker.ApiExecuteCallbackServiceInvoker; +import io.metersphere.api.service.ApiCommonService; import io.metersphere.api.service.ApiExecuteCallbackService; import io.metersphere.api.service.scenario.ApiScenarioRunService; import io.metersphere.plan.domain.TestPlanApiScenario; import io.metersphere.plan.mapper.TestPlanApiScenarioMapper; import io.metersphere.sdk.constants.ApiExecuteResourceType; +import io.metersphere.sdk.constants.TaskItemErrorMessage; import io.metersphere.sdk.dto.api.notice.ApiNoticeDTO; import io.metersphere.sdk.dto.api.task.GetRunScriptRequest; import io.metersphere.sdk.dto.api.task.GetRunScriptResult; import io.metersphere.sdk.dto.api.task.TaskItem; import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; +import io.metersphere.sdk.exception.MSException; import jakarta.annotation.Resource; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -33,6 +37,8 @@ public class TestPlanApiScenarioExecuteCallbackService implements ApiExecuteCall private TestPlanApiScenarioMapper testPlanApiScenarioMapper; @Resource private ApiScenarioRunService apiScenarioRunService; + @Resource + private ApiCommonService apiCommonService; public TestPlanApiScenarioExecuteCallbackService() { ApiExecuteCallbackServiceInvoker.register(ApiExecuteResourceType.TEST_PLAN_API_SCENARIO, this); @@ -45,7 +51,11 @@ public class TestPlanApiScenarioExecuteCallbackService implements ApiExecuteCall public GetRunScriptResult getRunScript(GetRunScriptRequest request) { TaskItem taskItem = request.getTaskItem(); TestPlanApiScenario testPlanApiScenario = testPlanApiScenarioMapper.selectByPrimaryKey(taskItem.getResourceId()); - ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(testPlanApiScenario.getApiScenarioId()); + if (testPlanApiScenario == null) { + apiCommonService.updateTaskItemErrorMassage(request.getTaskItem().getId(), TaskItemErrorMessage.CASE_NOT_EXIST); + throw new MSException(ApiResultCode.CASE_NOT_EXIST); + } + ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRunWithTaskItemErrorMassage(taskItem.getId(), testPlanApiScenario.getApiScenarioId()); apiScenarioDetail.setEnvironmentId(testPlanApiScenario.getEnvironmentId()); apiScenarioDetail.setGrouped(testPlanApiScenario.getGrouped()); String reportId = initReport(request, testPlanApiScenario, apiScenarioDetail);