fix(测试计划): 修复测试计划手动执行、jenkins执行、定时任务执行接口案例时可以配置并行、串行以及资源池
修复测试计划手动执行、jenkins执行、定时任务执行接口案例时可以配置并行、串行以及资源池
This commit is contained in:
parent
907bd792e6
commit
99d6e8d171
|
@ -46,6 +46,10 @@ public class TestPlanReportExecuteCatch {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized static boolean containsReport(String reportId){
|
||||
return testPlanReportMap != null && testPlanReportMap.containsKey(reportId);
|
||||
}
|
||||
|
||||
public synchronized static void updateApiTestPlanExecuteInfo(String reportId,
|
||||
Map<String, String> apiCaseExecInfo, Map<String, String> apiScenarioCaseExecInfo, Map<String, String> loadCaseExecInfo) {
|
||||
if(testPlanReportMap != null && testPlanReportMap.containsKey(reportId)){
|
||||
|
|
|
@ -17,4 +17,6 @@ public class BatchRunDefinitionRequest {
|
|||
|
||||
private RunModeConfig config;
|
||||
|
||||
//测试计划报告ID。 测试计划执行时使用
|
||||
private String planReportId;
|
||||
}
|
||||
|
|
|
@ -352,7 +352,6 @@ public class MockApiUtils {
|
|||
String baseScript = parseScript(url,headerMap,requestMockParams);
|
||||
try {
|
||||
script = baseScript + script;
|
||||
System.out.println(script);
|
||||
if(StringUtils.isEmpty(scriptLanguage)){
|
||||
scriptLanguage = "beanshell";
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package io.metersphere.track.dto;
|
||||
|
||||
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
|
||||
import io.metersphere.base.domain.TestPlanReport;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -17,6 +16,6 @@ import java.util.Map;
|
|||
public class TestPlanScheduleReportInfoDTO {
|
||||
private TestPlanReport testPlanReport;
|
||||
private Map<String, String> planScenarioIdMap = new LinkedHashMap<>();
|
||||
private Map<ApiTestCaseWithBLOBs, String> apiTestCaseDataMap = new LinkedHashMap<>();
|
||||
private Map<String, String> apiTestCaseDataMap = new LinkedHashMap<>();
|
||||
private Map<String, String> performanceIdMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.api.cache.TestPlanReportExecuteCatch;
|
||||
import io.metersphere.api.dto.JvmInfoDTO;
|
||||
import io.metersphere.api.dto.RunModeDataDTO;
|
||||
import io.metersphere.api.dto.RunRequest;
|
||||
|
@ -430,6 +431,11 @@ public class TestPlanApiCaseService {
|
|||
ExecutorService executorService = Executors.newFixedThreadPool(1, new NamedThreadFactory("TestPlanApiCaseService"));
|
||||
try {
|
||||
Thread.currentThread().setName("TestPlanCase串行执行线程");
|
||||
|
||||
//记录案例线程结果以及执行失败的案例ID
|
||||
Map<String, String> executeThreadIdMap = new HashMap<>();
|
||||
List<String> executeErrorList = new ArrayList<>();
|
||||
|
||||
for (TestPlanApiCase testPlanApiCase : executeQueue.keySet()) {
|
||||
try {
|
||||
if (executeQueue.get(testPlanApiCase) != null && MessageCache.terminationOrderDeque.contains(executeQueue.get(testPlanApiCase).getId())) {
|
||||
|
@ -441,13 +447,15 @@ public class TestPlanApiCaseService {
|
|||
execResult.setStatus(APITestStatus.Running.name());
|
||||
reportIds.add(execResult.getId());
|
||||
RunModeDataDTO modeDataDTO;
|
||||
|
||||
String randomUUID = UUID.randomUUID().toString();
|
||||
if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
|
||||
modeDataDTO = new RunModeDataDTO(testPlanApiCase.getId(), UUID.randomUUID().toString());
|
||||
modeDataDTO = new RunModeDataDTO(testPlanApiCase.getId(), randomUUID);
|
||||
} else {
|
||||
// 生成报告和HashTree
|
||||
try {
|
||||
HashTree hashTree = generateHashTree(testPlanApiCase.getId());
|
||||
modeDataDTO = new RunModeDataDTO(hashTree, UUID.randomUUID().toString());
|
||||
modeDataDTO = new RunModeDataDTO(hashTree, randomUUID);
|
||||
} catch (Exception e) {
|
||||
RunRequest runRequest = new RunRequest();
|
||||
runRequest.setTestId(testPlanApiCase.getId());
|
||||
|
@ -468,7 +476,9 @@ public class TestPlanApiCaseService {
|
|||
break;
|
||||
}
|
||||
}
|
||||
executeThreadIdMap.put(testPlanApiCase.getApiCaseId(),randomUUID);
|
||||
} catch (Exception e) {
|
||||
executeErrorList.add(testPlanApiCase.getApiCaseId());
|
||||
reportIds.remove(executeQueue.get(testPlanApiCase).getId());
|
||||
LogUtil.error("执行终止:" + e.getMessage());
|
||||
break;
|
||||
|
@ -484,6 +494,21 @@ public class TestPlanApiCaseService {
|
|||
example.createCriteria().andIdIn(removeList);
|
||||
mapper.deleteByExample(example);
|
||||
}
|
||||
|
||||
//如果是测试计划生成报告的执行,则更新执行信息、执行线程信息。
|
||||
if(TestPlanReportExecuteCatch.containsReport(request.getPlanReportId())){
|
||||
if (!executeErrorList.isEmpty()) {
|
||||
Map<String,String> executeErrorMap = new HashMap<>();
|
||||
for(String id : executeErrorList){
|
||||
executeErrorMap.put(id,TestPlanApiExecuteStatus.FAILD.name());
|
||||
}
|
||||
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(request.getPlanReportId(), executeErrorMap, null, null);
|
||||
}
|
||||
if (!executeThreadIdMap.isEmpty()) {
|
||||
TestPlanReportExecuteCatch.updateTestPlanExecuteResultInfo(request.getPlanReportId(), executeThreadIdMap,null, null);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
} finally {
|
||||
|
@ -502,12 +527,39 @@ public class TestPlanApiCaseService {
|
|||
sqlSession.commit();
|
||||
|
||||
// 开始并发执行
|
||||
|
||||
//记录案例线程结果以及执行失败的案例ID
|
||||
Map<String, String> executeThreadIdMap = new HashMap<>();
|
||||
List<String> executeErrorList = new ArrayList<>();
|
||||
|
||||
for (String reportId : executeQueue.keySet()) {
|
||||
if (request.getConfig() != null && StringUtils.isNotEmpty(request.getConfig().getResourcePoolId())) {
|
||||
jMeterService.runTest(executeQueue.get(reportId).getId(), reportId, request.getTriggerMode(), null, request.getConfig());
|
||||
} else {
|
||||
HashTree hashTree = generateHashTree(executeQueue.get(reportId).getId());
|
||||
jMeterService.runLocal(reportId,request.getConfig(), hashTree, TriggerMode.BATCH.name(), request.getTriggerMode());
|
||||
TestPlanApiCase testPlanApiCase = executeQueue.get(reportId);
|
||||
try {
|
||||
if (request.getConfig() != null && StringUtils.isNotEmpty(request.getConfig().getResourcePoolId())) {
|
||||
jMeterService.runTest(testPlanApiCase.getId(), reportId, request.getTriggerMode(), null, request.getConfig());
|
||||
executeThreadIdMap.put(testPlanApiCase.getApiCaseId(),testPlanApiCase.getId());
|
||||
} else {
|
||||
HashTree hashTree = generateHashTree(testPlanApiCase.getId());
|
||||
jMeterService.runLocal(reportId,request.getConfig(), hashTree, TriggerMode.BATCH.name(), request.getTriggerMode());
|
||||
executeThreadIdMap.put(testPlanApiCase.getApiCaseId(),reportId);
|
||||
}
|
||||
}catch (Exception e){
|
||||
executeErrorList.add(testPlanApiCase.getApiCaseId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//如果是测试计划生成报告的执行,则更新执行信息、执行线程信息。
|
||||
if(TestPlanReportExecuteCatch.containsReport(request.getPlanReportId())){
|
||||
if (!executeErrorList.isEmpty()) {
|
||||
Map<String,String> executeErrorMap = new HashMap<>();
|
||||
for(String id : executeErrorList){
|
||||
executeErrorMap.put(id,TestPlanApiExecuteStatus.FAILD.name());
|
||||
}
|
||||
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(request.getPlanReportId(), executeErrorMap, null, null);
|
||||
}
|
||||
if (!executeThreadIdMap.isEmpty()) {
|
||||
TestPlanReportExecuteCatch.updateTestPlanExecuteResultInfo(request.getPlanReportId(), executeThreadIdMap,null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ public class TestPlanReportService {
|
|||
public TestPlanScheduleReportInfoDTO genTestPlanReportBySchedule(String projectID, String planId, String userId, String triggerMode) {
|
||||
Map<String, String> planScenarioIdMap = new LinkedHashMap<>();
|
||||
Map<String, String> apiTestCaseIdMap = new LinkedHashMap<>();
|
||||
Map<ApiTestCaseWithBLOBs, String> apiTestCaseDataMap = new LinkedHashMap<>();
|
||||
Map<String, String> apiTestCaseDataMap = new LinkedHashMap<>();
|
||||
Map<String, String> performanceIdMap = new LinkedHashMap<>();
|
||||
|
||||
List<TestPlanApiScenario> testPlanApiScenarioList = extTestPlanScenarioCaseMapper.selectLegalDataByTestPlanId(planId);
|
||||
|
@ -152,15 +152,15 @@ public class TestPlanReportService {
|
|||
if (!apiTestCaseIdMap.isEmpty()) {
|
||||
ApiTestCaseExample apiTestCaseExample = new ApiTestCaseExample();
|
||||
apiTestCaseExample.createCriteria().andIdIn(new ArrayList<>(apiTestCaseIdMap.keySet()));
|
||||
List<ApiTestCaseWithBLOBs> apiCaseList = apiTestCaseMapper.selectByExampleWithBLOBs(apiTestCaseExample);
|
||||
Map<String, ApiTestCaseWithBLOBs> apiCaseDataMap = new HashMap<>();
|
||||
List<ApiTestCase> apiCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
|
||||
Map<String, ApiTestCase> apiCaseDataMap = new HashMap<>();
|
||||
if (!apiCaseList.isEmpty()) {
|
||||
apiCaseDataMap = apiCaseList.stream().collect(Collectors.toMap(ApiTestCaseWithBLOBs::getId, k -> k));
|
||||
apiCaseDataMap = apiCaseList.stream().collect(Collectors.toMap(ApiTestCase::getId, k -> k));
|
||||
for (String id : apiCaseDataMap.keySet()) {
|
||||
apiCaseInfoMap.put(id, TestPlanApiExecuteStatus.PREPARE.name());
|
||||
String testPlanApiCaseId = apiTestCaseIdMap.get(id);
|
||||
if (StringUtils.isNotEmpty(testPlanApiCaseId)) {
|
||||
apiTestCaseDataMap.put(apiCaseDataMap.get(id), testPlanApiCaseId);
|
||||
apiTestCaseDataMap.put(apiCaseDataMap.get(id).getId(), testPlanApiCaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import io.metersphere.api.cache.TestPlanReportExecuteCatch;
|
|||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.dto.automation.*;
|
||||
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
|
||||
import io.metersphere.api.dto.definition.BatchRunDefinitionRequest;
|
||||
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
|
||||
import io.metersphere.api.dto.definition.request.*;
|
||||
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
|
||||
|
@ -20,7 +21,6 @@ import io.metersphere.api.jmeter.JMeterService;
|
|||
import io.metersphere.api.service.ApiAutomationService;
|
||||
import io.metersphere.api.service.ApiDefinitionService;
|
||||
import io.metersphere.api.service.ApiScenarioReportService;
|
||||
import io.metersphere.api.service.ApiTestCaseService;
|
||||
import io.metersphere.api.service.task.NamedThreadFactory;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.*;
|
||||
|
@ -144,8 +144,7 @@ public class TestPlanService {
|
|||
private ExtTestPlanLoadCaseMapper extTestPlanLoadCaseMapper;
|
||||
@Resource
|
||||
private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
|
||||
@Resource
|
||||
private ApiTestCaseService apiTestCaseService;
|
||||
|
||||
@Resource
|
||||
private PerformanceTestService performanceTestService;
|
||||
@Resource
|
||||
|
@ -1075,7 +1074,7 @@ public class TestPlanService {
|
|||
|
||||
TestPlanReport testPlanReport = reportInfoDTO.getTestPlanReport();
|
||||
Map<String, String> planScenarioIdMap = reportInfoDTO.getPlanScenarioIdMap();
|
||||
Map<ApiTestCaseWithBLOBs, String> apiTestCaseDataMap = reportInfoDTO.getApiTestCaseDataMap();
|
||||
Map<String, String> apiTestCaseDataMap = reportInfoDTO.getApiTestCaseDataMap();
|
||||
Map<String, String> performanceIdMap = reportInfoDTO.getPerformanceIdMap();
|
||||
|
||||
String planReportId = testPlanReport.getId();
|
||||
|
@ -1135,9 +1134,9 @@ public class TestPlanService {
|
|||
|
||||
}
|
||||
|
||||
for (Map.Entry<ApiTestCaseWithBLOBs, String> entry : apiTestCaseDataMap.entrySet()) {
|
||||
ApiTestCaseWithBLOBs model = entry.getKey();
|
||||
executeApiCaseIdMap.put(model.getId(), TestPlanApiExecuteStatus.RUNNING.name());
|
||||
for (Map.Entry<String, String> entry : apiTestCaseDataMap.entrySet()) {
|
||||
String id = entry.getKey();
|
||||
executeApiCaseIdMap.put(id, TestPlanApiExecuteStatus.RUNNING.name());
|
||||
}
|
||||
for (String id : planScenarioIdMap.keySet()) {
|
||||
executeScenarioCaseIdMap.put(id, TestPlanApiExecuteStatus.RUNNING.name());
|
||||
|
@ -1145,10 +1144,29 @@ public class TestPlanService {
|
|||
testPlanLog.info("ReportId[" + planReportId + "] start run. TestPlanID:[" + testPlanID + "]. Execute api :" + JSONObject.toJSONString(executeApiCaseIdMap) + "; Execute scenario:" + JSONObject.toJSONString(executeScenarioCaseIdMap) + "; Execute performance:" + JSONObject.toJSONString(executePerformanceIdMap));
|
||||
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(planReportId, executeApiCaseIdMap, executeScenarioCaseIdMap, executePerformanceIdMap);
|
||||
|
||||
RunModeConfig runModeConfig = null;
|
||||
try {
|
||||
runModeConfig = JSONObject.parseObject(apiRunConfig, RunModeConfig.class);
|
||||
runModeConfig.setOnSampleError(false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (runModeConfig == null) {
|
||||
runModeConfig = new RunModeConfig();
|
||||
runModeConfig.setMode("serial");
|
||||
runModeConfig.setReportType("iddReport");
|
||||
runModeConfig.setEnvMap(new HashMap<>());
|
||||
runModeConfig.setOnSampleError(false);
|
||||
}else {
|
||||
if(runModeConfig.getEnvMap() == null){
|
||||
runModeConfig.setEnvMap(new HashMap<>());
|
||||
}
|
||||
}
|
||||
//执行接口案例任务
|
||||
this.executeApiTestCase(triggerMode, planReportId, testPlanID, apiTestCaseDataMap);
|
||||
this.executeApiTestCase(triggerMode, planReportId,new ArrayList<>(apiTestCaseDataMap.values()), runModeConfig);
|
||||
//执行场景执行任务
|
||||
this.executeScenarioCase(planReportId, testPlanID, projectID, apiRunConfig, triggerMode, userId, planScenarioIdMap);
|
||||
this.executeScenarioCase(planReportId, testPlanID, projectID, runModeConfig, triggerMode, userId, planScenarioIdMap);
|
||||
this.listenTaskExecuteStatus(planReportId);
|
||||
return testPlanReport.getId();
|
||||
}
|
||||
|
@ -1169,37 +1187,56 @@ public class TestPlanService {
|
|||
});
|
||||
}
|
||||
|
||||
private void executeApiTestCase(String triggerMode, String planReportId, String testPlanId, Map<ApiTestCaseWithBLOBs, String> apiTestCaseDataMap) {
|
||||
private void executeApiTestCase(String triggerMode, String planReportId, List<String> planCaseIds, RunModeConfig runModeConfig) {
|
||||
executorService.submit(() -> {
|
||||
Map<String, String> executeErrorMap = new HashMap<>();
|
||||
Map<String, String> executeReportIdMap = new HashMap<>();
|
||||
for (Map.Entry<ApiTestCaseWithBLOBs, String> entry : apiTestCaseDataMap.entrySet()) {
|
||||
ApiTestCaseWithBLOBs blobs = entry.getKey();
|
||||
try {
|
||||
String testId = UUID.randomUUID().toString();
|
||||
if (StringUtils.equals(triggerMode, ReportTriggerMode.API.name())) {
|
||||
apiTestCaseService.run(blobs, testId, planReportId, testPlanId, ApiRunMode.JENKINS_API_PLAN.name());
|
||||
} else if (StringUtils.equals(triggerMode, ReportTriggerMode.MANUAL.name())) {
|
||||
apiTestCaseService.run(blobs, testId, planReportId, testPlanId, ApiRunMode.MANUAL_PLAN.name());
|
||||
} else {
|
||||
apiTestCaseService.run(blobs, testId, planReportId, testPlanId, ApiRunMode.SCHEDULE_API_PLAN.name());
|
||||
}
|
||||
executeReportIdMap.put(blobs.getId(),testId);
|
||||
} catch (Exception e) {
|
||||
executeErrorMap.put(blobs.getId(), TestPlanApiExecuteStatus.FAILD.name());
|
||||
}
|
||||
// Map<String, String> executeErrorMap = new HashMap<>();
|
||||
// Map<String, String> executeReportIdMap = new HashMap<>();
|
||||
|
||||
BatchRunDefinitionRequest request = new BatchRunDefinitionRequest();
|
||||
// List<String> planIdList = new ArrayList<>(1);
|
||||
// planIdList.add(testPlanId);
|
||||
if (StringUtils.equals(triggerMode, ReportTriggerMode.API.name())) {
|
||||
request.setTriggerMode(ApiRunMode.JENKINS_API_PLAN.name());
|
||||
} else if (StringUtils.equals(triggerMode, ReportTriggerMode.MANUAL.name())) {
|
||||
request.setTriggerMode(ApiRunMode.MANUAL_PLAN.name());
|
||||
} else {
|
||||
request.setTriggerMode(ApiRunMode.SCHEDULE_API_PLAN.name());
|
||||
}
|
||||
|
||||
if (!executeErrorMap.isEmpty()) {
|
||||
TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(planReportId, executeErrorMap, null, null);
|
||||
}
|
||||
if (!executeReportIdMap.isEmpty()) {
|
||||
TestPlanReportExecuteCatch.updateTestPlanExecuteResultInfo(planReportId, executeReportIdMap,null, null);
|
||||
}
|
||||
request.setPlanIds(planCaseIds);
|
||||
request.setPlanReportId(planReportId);
|
||||
request.setConfig(runModeConfig);
|
||||
testPlanApiCaseService.run(request);
|
||||
// for (Map.Entry<ApiTestCaseWithBLOBs, String> entry : apiTestCaseDataMap.entrySet()) {
|
||||
// ApiTestCaseWithBLOBs blobs = entry.getKey();
|
||||
// try {
|
||||
// String testId = UUID.randomUUID().toString();
|
||||
//
|
||||
//
|
||||
//
|
||||
//// if (StringUtils.equals(triggerMode, ReportTriggerMode.API.name())) {
|
||||
//// apiTestCaseService.run(blobs, testId, planReportId, testPlanId, ApiRunMode.JENKINS_API_PLAN.name());
|
||||
//// } else if (StringUtils.equals(triggerMode, ReportTriggerMode.MANUAL.name())) {
|
||||
//// apiTestCaseService.run(blobs, testId, planReportId, testPlanId, ApiRunMode.MANUAL_PLAN.name());
|
||||
//// } else {
|
||||
//// apiTestCaseService.run(blobs, testId, planReportId, testPlanId, ApiRunMode.SCHEDULE_API_PLAN.name());
|
||||
//// }
|
||||
//// executeReportIdMap.put(blobs.getId(),testId);
|
||||
// } catch (Exception e) {
|
||||
// executeErrorMap.put(blobs.getId(), TestPlanApiExecuteStatus.FAILD.name());
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!executeErrorMap.isEmpty()) {
|
||||
// TestPlanReportExecuteCatch.updateApiTestPlanExecuteInfo(planReportId, executeErrorMap, null, null);
|
||||
// }
|
||||
// if (!executeReportIdMap.isEmpty()) {
|
||||
// TestPlanReportExecuteCatch.updateTestPlanExecuteResultInfo(planReportId, executeReportIdMap,null, null);
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
private void executeScenarioCase(String planReportId, String testPlanID, String projectID, String apiRunConfig, String triggerMode, String userId, Map<String, String> planScenarioIdMap) {
|
||||
private void executeScenarioCase(String planReportId, String testPlanID, String projectID, RunModeConfig runModeConfig, String triggerMode, String userId, Map<String, String> planScenarioIdMap) {
|
||||
executorService.submit(() -> {
|
||||
if (!planScenarioIdMap.isEmpty()) {
|
||||
SchedulePlanScenarioExecuteRequest scenarioRequest = new SchedulePlanScenarioExecuteRequest();
|
||||
|
@ -1225,19 +1262,6 @@ public class TestPlanService {
|
|||
scenarioRequest.setTestPlanID(testPlanID);
|
||||
|
||||
scenarioRequest.setTestPlanReportId(planReportId);
|
||||
RunModeConfig runModeConfig = null;
|
||||
try {
|
||||
runModeConfig = JSONObject.parseObject(apiRunConfig, RunModeConfig.class);
|
||||
runModeConfig.setOnSampleError(false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (runModeConfig == null) {
|
||||
runModeConfig = new RunModeConfig();
|
||||
runModeConfig.setMode("serial");
|
||||
runModeConfig.setReportType("iddReport");
|
||||
runModeConfig.setOnSampleError(false);
|
||||
}
|
||||
|
||||
scenarioRequest.setConfig(runModeConfig);
|
||||
this.scenarioRunModeConfig(scenarioRequest);
|
||||
|
|
Loading…
Reference in New Issue