feat: 测试计划增加定时任务,执行接口案例和场景案例

测试计划增加定时任务,执行接口案例和场景案例
This commit is contained in:
song.tianyang 2021-01-07 13:41:54 +08:00
parent edf8eaed08
commit d208d358be
12 changed files with 341 additions and 22 deletions

View File

@ -528,15 +528,12 @@ public class ApiAutomationService {
}
public void createSchedule(Schedule request) {
Schedule schedule = scheduleService.buildApiTestSchedule(request);
schedule.setJob(ApiScenarioTestJob.class.getName());
schedule.setGroup(ScheduleGroup.API_SCENARIO_TEST.name());
schedule.setType(ScheduleType.CRON.name());
scheduleService.addSchedule(schedule);
this.addOrUpdateApiScenarioCronJob(request);
}
public void updateSchedule(Schedule request) {

View File

@ -2,6 +2,9 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.automation.ApiScenarioRequest;
@ -9,6 +12,8 @@ import io.metersphere.api.dto.automation.ReferenceDTO;
import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.*;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.TestResult;
@ -24,6 +29,7 @@ import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.i18n.Translator;
@ -36,6 +42,7 @@ import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.ListedHashTree;
import org.aspectj.util.FileUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -331,6 +338,55 @@ public class ApiDefinitionService {
return request.getId();
}
/**
* 内部构建HashTree 定时任务发起的执行
* @param request
* @return
*/
public String run(RunDefinitionRequest request,ApiTestCaseWithBLOBs item) {
MsTestPlan testPlan = new MsTestPlan();
testPlan.setHashTree(new LinkedList<>());
HashTree jmeterHashTree = new ListedHashTree();
try {
MsThreadGroup group = new MsThreadGroup();
group.setLabel(item.getName());
group.setName(UUID.randomUUID().toString());
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JSONObject element = JSON.parseObject(item.getRequest());
MsScenario scenario = JSONObject.parseObject(item.getRequest(), MsScenario.class);
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (element != null && StringUtils.isNotEmpty(element.getString("hashTree"))) {
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {});
scenario.setHashTree(elements);
}
if (StringUtils.isNotEmpty(element.getString("variables"))) {
LinkedList<KeyValue> variables = mapper.readValue(element.getString("variables"),
new TypeReference<LinkedList<KeyValue>>() {});
scenario.setVariables(variables);
}
group.setEnableCookieShare(scenario.isEnableCookieShare());
LinkedList<MsTestElement> scenarios = new LinkedList<>();
scenarios.add(scenario);
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
String runMode = ApiRunMode.DELIMIT.name();
if (StringUtils.isNotBlank(request.getType()) && StringUtils.equals(request.getType(), ApiRunMode.API_PLAN.name())) {
runMode = ApiRunMode.API_PLAN.name();
}
// 调用执行方法
jMeterService.runDefinition(request.getId(), jmeterHashTree, request.getReportId(), runMode);
return request.getId();
}
public void addResult(TestResult res) {
if (!res.getScenarios().isEmpty() && !res.getScenarios().get(0).getRequestResults().isEmpty()) {
cache.put(res.getTestId(), res.getScenarios().get(0).getRequestResults().get(0));

View File

@ -32,4 +32,7 @@ public class Schedule implements Serializable {
private String customData;
private static final long serialVersionUID = 1L;
//定时任务来源 测试计划/测试场景
private String scheduleFrom;
}

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum ScheduleGroup {
API_TEST, PERFORMANCE_TEST, API_SCENARIO_TEST
API_TEST, PERFORMANCE_TEST, API_SCENARIO_TEST,TEST_PLAN_TEST
}

View File

@ -2,6 +2,7 @@ package io.metersphere.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.base.domain.Schedule;
import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.ScheduleDao;
@ -16,6 +17,8 @@ import java.util.List;
public class ScheduleController {
@Resource
private ScheduleService scheduleService;
@Resource
private ApiAutomationService apiAutomationService;
@PostMapping("/list/{goPage}/{pageSize}")
public List<ScheduleDao> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryScheduleRequest request) {
@ -28,4 +31,19 @@ public class ScheduleController {
Schedule schedule = scheduleService.getScheduleByResource(testId,group);
return schedule;
}
@PostMapping(value = "/update")
public void updateSchedule(@RequestBody Schedule request) {
scheduleService.updateSchedule(request);
}
@PostMapping(value = "/create")
public void createSchedule(@RequestBody Schedule request) {
scheduleService.createSchedule(request);
}
@GetMapping(value = "/getTaskInfo")
public Object getTaskInfo() {
return scheduleService.getCurrentlyExecutingJobs();
}
}

View File

@ -7,7 +7,9 @@ import org.quartz.*;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Component
@ -296,4 +298,21 @@ public class ScheduleManager {
return jobDataMap;
}
public Object getCurrentlyExecutingJobs(){
Map<String, String> returnMap = new HashMap<>();
try {
List<JobExecutionContext> currentJobs = scheduler.getCurrentlyExecutingJobs();
for (JobExecutionContext jobCtx : currentJobs) {
String jobName = jobCtx.getJobDetail().getKey().getName();
String groupName = jobCtx.getJobDetail().getJobClass().getName();
returnMap.put("jobName", jobName);
returnMap.put("groupName", groupName);
}
}catch (Exception e){
e.printStackTrace();
}
return returnMap;
}
}

View File

@ -0,0 +1,130 @@
package io.metersphere.job.sechedule;
import io.metersphere.api.dto.automation.ExecuteType;
import io.metersphere.api.dto.automation.RunScenarioRequest;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.base.domain.TestPlanApiCase;
import io.metersphere.base.domain.TestPlanApiScenario;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.track.request.testplan.RunTestPlanRequest;
import io.metersphere.track.service.TestPlanApiCaseService;
import io.metersphere.track.service.TestPlanScenarioCaseService;
import org.quartz.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 情景测试Job
* @author song.tianyang
* @Date 2020/12/22 2:59 下午
* @Description
*/
public class TestPlanTestJob extends MsScheduleJob {
private String projectID;
private List<String> scenarioIds;
private List<String> apiTestCaseIds;
private List<String> performanceIds;
private ApiAutomationService apiAutomationService;
private PerformanceTestService performanceTestService;
private TestPlanScenarioCaseService testPlanScenarioCaseService;
private TestPlanApiCaseService testPlanApiCaseService;
private ApiTestCaseService apiTestCaseService;
private ApiDefinitionService apiDefinitionService;
public TestPlanTestJob() {
this.apiAutomationService = CommonBeanFactory.getBean(ApiAutomationService.class);
this.performanceTestService = CommonBeanFactory.getBean(PerformanceTestService.class);
this.testPlanScenarioCaseService = CommonBeanFactory.getBean(TestPlanScenarioCaseService.class);
this.testPlanApiCaseService = CommonBeanFactory.getBean(TestPlanApiCaseService.class);
this.apiTestCaseService = CommonBeanFactory.getBean(ApiTestCaseService.class);
apiDefinitionService = CommonBeanFactory.getBean(ApiDefinitionService.class);
}
/**
* 情景部分的准备工作
* @param context
* @throws JobExecutionException
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobKey jobKey = context.getTrigger().getJobKey();
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
this.userId = jobDataMap.getString("userId");
this.expression = jobDataMap.getString("expression");
this.projectID = jobDataMap.getString("projectId");
scenarioIds = new ArrayList<>();
String testPlanID = jobDataMap.getString("resourceId");
List<TestPlanApiScenario> testPlanApiScenarioList = testPlanScenarioCaseService.getCasesByPlanId(testPlanID);
for (TestPlanApiScenario model:
testPlanApiScenarioList) {
scenarioIds.add(model.getApiScenarioId());
}
List<TestPlanApiCase> testPlanApiCaseList = testPlanApiCaseService.getCasesByPlanId(testPlanID);
for (TestPlanApiCase model :
testPlanApiCaseList) {
apiTestCaseIds.add(model.getApiCaseId());
}
businessExecute(context);
}
@Override
void businessExecute(JobExecutionContext context) {
LogUtil.info("-------------- start testplan schedule ----------");
//执行接口案例任务
for (String apiCaseID:apiTestCaseIds) {
ApiTestCaseWithBLOBs blobs = apiTestCaseService.get(apiCaseID);
String caseReportID = UUID.randomUUID().toString();
RunDefinitionRequest apiCaseReqeust = new RunDefinitionRequest();
apiCaseReqeust.setId(apiCaseID);
apiCaseReqeust.setReportId(caseReportID);
apiCaseReqeust.setProjectId(projectID);
apiCaseReqeust.setExecuteType(ExecuteType.Saved.name());
apiCaseReqeust.setType(ApiRunMode.API_PLAN.name());
apiDefinitionService.run(apiCaseReqeust,blobs);
}
LogUtil.info("-------------- testplan schedule ---------- api case over -----------------");
//执行场景执行任务
RunScenarioRequest scenarioRequest = new RunScenarioRequest();
String senarionReportID = UUID.randomUUID().toString();
scenarioRequest.setId(senarionReportID);
scenarioRequest.setReportId(senarionReportID);
scenarioRequest.setProjectId(projectID);
scenarioRequest.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
scenarioRequest.setExecuteType(ExecuteType.Saved.name());
scenarioRequest.setScenarioIds(this.scenarioIds);
scenarioRequest.setReportUserID(this.userId);
scenarioRequest.setRunMode(ApiRunMode.SCENARIO_PLAN.name());
String reportID = apiAutomationService.run(scenarioRequest);
LogUtil.info("-------------- testplan schedule ---------- scenario case over -----------------");
//执行性能测试任务 --- 保留待功能实现后再继续
// RunTestPlanRequest performanceRequest = new RunTestPlanRequest();
// performanceRequest.setId(resourceId);
// performanceRequest.setUserId(userId);
// performanceRequest.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
// performanceTestService.run(performanceRequest);
}
public static JobKey getJobKey(String testId) {
return new JobKey(testId, ScheduleGroup.TEST_PLAN_TEST.name());
}
public static TriggerKey getTriggerKey(String testId) {
return new TriggerKey(testId, ScheduleGroup.TEST_PLAN_TEST.name());
}
}

View File

@ -9,6 +9,8 @@ import io.metersphere.base.domain.UserExample;
import io.metersphere.base.mapper.ScheduleMapper;
import io.metersphere.base.mapper.UserMapper;
import io.metersphere.base.mapper.ext.ExtScheduleMapper;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.constants.ScheduleType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.commons.utils.LogUtil;
@ -17,8 +19,10 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.OrderRequest;
import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.ScheduleDao;
import io.metersphere.job.sechedule.ApiScenarioTestJob;
import io.metersphere.job.sechedule.ApiTestJob;
import io.metersphere.job.sechedule.ScheduleManager;
import io.metersphere.job.sechedule.TestPlanTestJob;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
@ -188,4 +192,54 @@ public class ScheduleService {
List<TaskInfoResult> runningTaskInfoList = extScheduleMapper.findRunningTaskInfoByProjectID(projectID);
return runningTaskInfoList;
}
public void createSchedule(Schedule request) {
Schedule schedule = this.buildApiTestSchedule(request);
schedule.setJob(ApiScenarioTestJob.class.getName());
JobKey jobKey = null;
TriggerKey triggerKey = null;
Class clazz = null;
if("testPlan".equals(request.getScheduleFrom())){
schedule.setGroup(ScheduleGroup.TEST_PLAN_TEST.name());
schedule.setType(ScheduleType.CRON.name());
jobKey = TestPlanTestJob.getJobKey(request.getResourceId());
triggerKey = TestPlanTestJob.getTriggerKey(request.getResourceId());
clazz = TestPlanTestJob.class;
}else {
//默认为情景
schedule.setGroup(ScheduleGroup.API_SCENARIO_TEST.name());
schedule.setType(ScheduleType.CRON.name());
jobKey = ApiScenarioTestJob.getJobKey(request.getResourceId());
triggerKey = ApiScenarioTestJob.getTriggerKey(request.getResourceId());
clazz = ApiScenarioTestJob.class;
}
this.addSchedule(schedule);
this.addOrUpdateCronJob(request,jobKey ,triggerKey , clazz);
}
public void updateSchedule(Schedule request) {
this.editSchedule(request);
JobKey jobKey = null;
TriggerKey triggerKey = null;
Class clazz = null;
if(ScheduleGroup.TEST_PLAN_TEST.name().equals(request.getGroup())){
jobKey = TestPlanTestJob.getJobKey(request.getResourceId());
triggerKey = TestPlanTestJob.getTriggerKey(request.getResourceId());
clazz = TestPlanTestJob.class;
}else {
//默认为情景
jobKey = ApiScenarioTestJob.getJobKey(request.getResourceId());
triggerKey = ApiScenarioTestJob.getTriggerKey(request.getResourceId());
clazz = ApiScenarioTestJob.class;
}
this.addOrUpdateCronJob(request,jobKey ,triggerKey , clazz);
}
public Object getCurrentlyExecutingJobs() {
return scheduleManager.getCurrentlyExecutingJobs();
}
}

View File

@ -1,23 +1,34 @@
package io.metersphere.track.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.dto.definition.request.MsThreadGroup;
import io.metersphere.api.service.ApiDefinitionExecResultService;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
import io.metersphere.base.domain.TestPlanApiCase;
import io.metersphere.base.domain.TestPlanApiCaseExample;
import io.metersphere.base.mapper.TestPlanApiCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanApiCaseMapper;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.track.request.testcase.TestPlanApiCaseBatchRequest;
import org.apache.jmeter.testelement.TestElement;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@Service
@ -30,6 +41,8 @@ public class TestPlanApiCaseService {
ApiTestCaseService apiTestCaseService;
@Resource
ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
@Resource
ApiDefinitionService apiDefinitionService;
@Lazy
@Resource
ApiDefinitionExecResultService apiDefinitionExecResultService;

View File

@ -30,7 +30,7 @@
</el-tab-pane>
<el-tab-pane :label="$t('schedule.task_notification')" name="second">
<ms-schedule-notification :is-tester-permission="isTesterPermission" :test-id="testId"
:schedule-receiver-options="scheduleReceiverOptions"/>
:schedule-receiver-options="scheduleReceiverOptions"/>
</el-tab-pane>
</el-tabs>
</div>
@ -55,7 +55,7 @@ const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./
export default {
name: "MsScheduleMaintain",
components: {CrontabResult, Crontab, MsScheduleNotification,"NoticeTemplate": noticeTemplate.default},
components: {CrontabResult, Crontab, MsScheduleNotification, "NoticeTemplate": noticeTemplate.default},
props: {
customValidate: {
@ -81,8 +81,7 @@ export default {
callback(new Error(this.$t('commons.input_content')));
} else if (!cronValidate(cronValue)) {
callback(new Error(this.$t('schedule.cron_expression_format_error')));
}
else if (!customValidate.pass) {
} else if (!customValidate.pass) {
callback(new Error(customValidate.info));
} else {
callback();
@ -93,9 +92,10 @@ export default {
operation: true,
dialogVisible: false,
schedule: {
value : "",
value: "",
},
testId:String,
scheduleTaskType: "",
testId: String,
showCron: false,
form: {
cronValue: ""
@ -130,20 +130,30 @@ export default {
return param;
},
open(row) {
this.testId = row.id;
this.findSchedule(row.id);
//
let paramTestId = "";
if (row.redirectFrom == 'testPlan') {
paramTestId = row.id;
this.scheduleTaskType = "TEST_PLAN_TEST";
} else {
paramTestId = row.id;
this.scheduleTaskType = "API_SCENARIO_TEST";
}
this.testId = paramTestId;
this.findSchedule(paramTestId);
this.initUserList();
this.dialogVisible = true;
this.form.cronValue = this.schedule.value;
listenGoBack(this.close);
this.activeName = 'first'
},
findSchedule(){
var scenarioID = this.testId;
this.result = this.$get("/schedule/findOne/"+scenarioID+"/API_SCENARIO_TEST", response => {
if(response.data!=null){
findSchedule() {
var scheduleResourceID = this.testId;
var taskType = this.scheduleTaskType;
this.result = this.$get("/schedule/findOne/" + scheduleResourceID + "/" +taskType, response => {
if (response.data != null) {
this.schedule = response.data;
}else {
} else {
this.schedule = {};
}
});
@ -177,9 +187,20 @@ export default {
param = this.schedule;
param.resourceId = this.testId;
let url = '/api/automation/schedule/create';
if (param.id) {
url = '/api/automation/schedule/update';
if(this.scheduleTaskType === "TEST_PLAN_TEST"){
param.scheduleFrom = "testPlan";
//
url = '/schedule/create';
if (param.id) {
url = '/schedule/update';
}
}else {
param.scheduleFrom = "scenario";
if (param.id) {
url = '/api/automation/schedule/update';
}
}
this.$post(url, param, () => {
this.$success(this.$t('commons.save_success'));
});

View File

@ -4,7 +4,7 @@
<ms-table-operator-button :isTesterPermission="isTesterPermission" :tip="tip1" icon="el-icon-edit" @exec="editClick" @click.stop="editClickStop"/>
<slot name="middle"></slot>
<ms-table-operator-button :isTesterPermission="isTesterPermission" :tip="tip2" icon="el-icon-delete" type="danger" @exec="deleteClick" @click.stop="deleteClickStop"/>
<slot name="behind"></slot>
<slot name="beheind"></slot>
</span>
</template>

View File

@ -128,19 +128,21 @@
@exec="openReport(scope.row.id, scope.row.reportId)"/>
</template>
</ms-table-operator>
<ms-table-operator-button style="margin-left: 10px;color:#6C317C" type=""
:tip="$t('test_track.plan_view.view_report')" icon="el-icon-time"
@exec="scheduleTask(scope.row)"/>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
<test-report-template-list @openReport="openReport" ref="testReportTemplateList"/>
<test-case-report-view @refresh="initTableData" ref="testCaseReportView"/>
<ms-delete-confirm :title="$t('test_track.plan.plan_delete')" @delete="_handleDelete" ref="deleteConfirm" :with-tip="enableDeleteTip">
{{$t('test_track.plan.plan_delete_tip')}}
</ms-delete-confirm>
<ms-schedule-maintain ref="scheduleMaintain" />
</el-card>
</template>
@ -160,6 +162,7 @@ import MsDeleteConfirm from "../../../common/components/MsDeleteConfirm";
import {TEST_PLAN_CONFIGS} from "../../../common/components/search/search-components";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import {getCurrentProjectID} from "../../../../../common/js/utils";
import MsScheduleMaintain from "@/business/components/api/automation/schedule/ScheduleMaintain"
export default {
name: "TestPlanList",
@ -169,6 +172,7 @@ export default {
TestReportTemplateList,
PlanStageTableItem,
PlanStatusTableItem,
MsScheduleMaintain,
MsTableOperator, MsTableOperatorButton, MsDialogFooter, MsTableHeader, MsCreateBox, MsTablePagination
},
data() {
@ -289,6 +293,10 @@ export default {
this.$refs.testCaseReportView.open(planId, reportId);
}
},
scheduleTask(row){
row.redirectFrom = "testPlan";
this.$refs.scheduleMaintain.open(row);
},
}
}
</script>