feat(接口自动化): 完成定时任务

This commit is contained in:
fit2-zhao 2020-12-08 19:41:48 +08:00
parent 6a8a718ff2
commit 2ebfcc2db1
8 changed files with 214 additions and 12 deletions

View File

@ -9,6 +9,7 @@ import io.metersphere.api.dto.automation.SaveApiScenarioRequest;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.base.domain.ApiScenario;
import io.metersphere.base.domain.Schedule;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
@ -46,6 +47,16 @@ public class ApiAutomationController {
apiAutomationService.update(request, bodyFiles);
}
@PostMapping(value = "/schedule/update")
public void updateSchedule(@RequestBody Schedule schedule) {
apiAutomationService.updateSchedule(schedule);
}
@PostMapping(value = "/schedule/create")
public void createSchedule(@RequestBody Schedule schedule) {
apiAutomationService.createSchedule(schedule);
}
@GetMapping("/delete/{id}")
public void delete(@PathVariable String id) {
apiAutomationService.delete(id);

View File

@ -15,5 +15,7 @@ public class RunScenarioRequest {
private String environmentId;
private String triggerMode;
private List<String> scenarioIds;
}

View File

@ -17,12 +17,14 @@ import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiScenarioMapper;
import io.metersphere.base.mapper.ApiTagMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.job.sechedule.ApiTestJob;
import io.metersphere.job.sechedule.ScenarioJob;
import io.metersphere.service.ScheduleService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
@ -55,6 +57,8 @@ public class ApiAutomationService {
private ApiTestEnvironmentService environmentService;
@Resource
private ApiScenarioReportService apiReportService;
@Resource
private ScheduleService scheduleService;
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
@ -120,6 +124,27 @@ public class ApiAutomationService {
createBodyFiles(bodyUploadIds, bodyFiles);
}
private Schedule buildApiTestSchedule(Schedule request) {
Schedule schedule = scheduleService.buildApiTestSchedule(request);
schedule.setJob(ScenarioJob.class.getName());
schedule.setGroup(ScheduleGroup.SCENARIO_TEST.name());
schedule.setType(ScheduleType.CRON.name());
return schedule;
}
private void addOrUpdateApiTestCronJob(Schedule request) {
scheduleService.addOrUpdateCronJob(request, ApiTestJob.getJobKey(request.getResourceId()), ApiTestJob.getTriggerKey(request.getResourceId()), ApiTestJob.class);
}
public void updateSchedule(Schedule schedule) {
scheduleService.addSchedule(buildApiTestSchedule(schedule));
addOrUpdateApiTestCronJob(schedule);
}
public void createSchedule(Schedule request) {
scheduleService.addSchedule(buildApiTestSchedule(request));
addOrUpdateApiTestCronJob(request);
}
public void update(SaveApiScenarioRequest request, List<MultipartFile> bodyFiles) {
checkNameExist(request);
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
@ -218,7 +243,7 @@ public class ApiAutomationService {
}
}
private void createAPIReportResult(String id) {
private void createAPIReportResult(String id, String triggerMode) {
APIReportResult report = new APIReportResult();
report.setId(id);
report.setTestId(id);
@ -228,6 +253,7 @@ public class ApiAutomationService {
report.setUpdateTime(System.currentTimeMillis());
report.setStatus(APITestStatus.Running.name());
report.setUserId(SessionUtils.getUserId());
report.setTriggerMode(triggerMode);
apiReportService.addResult(report);
}
@ -254,9 +280,11 @@ public class ApiAutomationService {
MsScenario scenario = JSONObject.parseObject(item.getScenarioDefinition(), MsScenario.class);
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
LinkedList<MsTestElement> elements = mapper.readValue(element.getString("hashTree"),
new TypeReference<LinkedList<MsTestElement>>() {});
new TypeReference<LinkedList<MsTestElement>>() {
});
LinkedList<KeyValue> variables = mapper.readValue(element.getString("variables"),
new TypeReference<LinkedList<KeyValue>>() {});
new TypeReference<LinkedList<KeyValue>>() {
});
scenario.setHashTree(elements);
scenario.setVariables(variables);
LinkedList<MsTestElement> scenarios = new LinkedList<>();
@ -270,7 +298,8 @@ public class ApiAutomationService {
testPlan.toHashTree(jmeterTestPlanHashTree, testPlan.getHashTree(), new ParameterConfig());
// 调用执行方法
jMeterService.runDefinition(request.getId(), jmeterTestPlanHashTree, request.getReportId(), ApiRunMode.SCENARIO.name());
createAPIReportResult(request.getId());
createAPIReportResult(request.getId(), request.getTriggerMode() == null ? ReportTriggerMode.API.name() : request.getTriggerMode());
return request.getId();
}
@ -297,7 +326,7 @@ public class ApiAutomationService {
// 调用执行方法
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.SCENARIO.name());
createAPIReportResult(request.getId());
createAPIReportResult(request.getId(), ReportTriggerMode.MANUAL.name());
return request.getId();
}
}

View File

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

View File

@ -0,0 +1,42 @@
package io.metersphere.job.sechedule;
import io.metersphere.api.dto.automation.RunScenarioRequest;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
import java.util.ArrayList;
import java.util.List;
public class ScenarioJob extends MsScheduleJob {
ApiAutomationService apiAutomationService;
public ScenarioJob() {
apiAutomationService = (ApiAutomationService) CommonBeanFactory.getBean(ApiAutomationService.class);
}
@Override
void businessExecute(JobExecutionContext context) {
RunScenarioRequest request = new RunScenarioRequest();
request.setId(resourceId);
List<String> ids = new ArrayList<>();
ids.add(resourceId);
request.setScenarioIds(ids);
request.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
apiAutomationService.run(request);
}
public static JobKey getJobKey(String testId) {
return new JobKey(testId, ScheduleGroup.SCENARIO_TEST.name());
}
public static TriggerKey getTriggerKey(String testId) {
return new TriggerKey(testId, ScheduleGroup.SCENARIO_TEST.name());
}
}

View File

@ -64,8 +64,8 @@
<!--要生成的数据库表 -->
<table tableName="api_scenario"/>
<table tableName="api_scenario_report"/>
<table tableName="api_scenario_report_detail"/>
</context>
</generatorConfiguration>

View File

@ -45,13 +45,14 @@
</el-table-column>
<el-table-column prop="passRate" :label="$t('api_test.automation.passing_rate')"
show-overflow-tooltip/>
<el-table-column :label="$t('commons.operating')" width="180">
<el-table-column :label="$t('commons.operating')" width="160">
<template v-slot:default="{row}">
<el-button type="text" @click="edit(row)">{{ $t('api_test.automation.edit') }}</el-button>
<el-button type="text" @click="execute(row)">{{ $t('api_test.automation.execute') }}</el-button>
<el-button type="text" @click="copy(row)">{{ $t('api_test.automation.copy') }}</el-button>
<el-button type="text" @click="remove(row)">{{ $t('api_test.automation.remove') }}</el-button>
<ms-table-more-btn :is-show="true" :row="row" :buttons="tableButtons"/>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
@ -62,6 +63,13 @@
<el-drawer :visible.sync="runVisible" :destroy-on-close="true" direction="ltr" :withHeader="false" :title="$t('test_track.plan_view.test_result')" :modal="false" size="90%">
<ms-api-report-detail @refresh="search" :infoDb="infoDb" :report-id="reportId" :currentProjectId="currentProject!=undefined ? currentProject.id:''"/>
</el-drawer>
<!--定时任务-->
<!--<ms-schedule-config :schedule="currentScenario.schedule" :is-read-only="false" :save="saveCronExpression"-->
<!--@scheduleChange="saveSchedule" :test-id="currentScenario.id" :check-open="checkScheduleEdit"/>-->
<ms-schedule-edit :is-read-only="false" :schedule="schedule" :test-id="currentScenario.id" :save="saveCronExpression"
ref="scheduleEdit"/>
</div>
</el-card>
@ -75,10 +83,13 @@
import MsTag from "../../../common/components/MsTag";
import {getUUID} from "@/common/js/utils";
import MsApiReportDetail from "../report/ApiReportDetail";
import MsTableMoreBtn from "./TableMoreBtn";
import MsScheduleConfig from "../../../common/components/MsScheduleConfig";
import MsScheduleEdit from "../../../common/components/MsScheduleEdit";
export default {
name: "MsApiScenarioList",
components: {ShowMoreBtn, MsTablePagination, MsTableHeader, MsTag, MsApiReportDetail},
components: {ShowMoreBtn, MsTablePagination, MsTableHeader, MsTag, MsApiReportDetail, MsTableMoreBtn, MsScheduleConfig, MsScheduleEdit},
props: {
currentProject: Object,
currentModule: Object,
@ -87,6 +98,8 @@
return {
result: {},
condition: {},
currentScenario: {},
schedule: {},
selectAll: false,
selection: [],
tableData: [],
@ -104,6 +117,15 @@
name: this.$t('api_test.automation.batch_execute'), handleClick: this.handleBatchExecute
}
],
tableButtons: [
{
name: this.$t('api_test.automation.remove'), handleClick: this.remove
}, {
name: '查看引用', handleClick: this.handleQuote
}, {
name: this.$t('commons.trigger_mode.schedule'), handleClick: this.handleSchedule
}
],
}
},
watch: {
@ -202,6 +224,42 @@
this.infoDb = true;
this.reportId = row.reportId;
},
handleQuote() {
},
handleSchedule(row) {
this.currentScenario = row;
if (row.schedule) {
if (Object.prototype.toString.call(row.schedule).match(/\[object (\w+)\]/)[1].toLowerCase() === 'object') {
this.schedule = row.schedule;
} else {
this.schedule = JSON.parse(row.schedule);
}
}
this.$refs.scheduleEdit.open();
},
saveCronExpression(cronExpression) {
this.schedule.enable = true;
this.schedule.value = cronExpression;
this.saveSchedule();
},
saveSchedule() {
this.checkScheduleEdit();
let param = {};
param = this.schedule;
param.resourceId = this.currentScenario.id;
let url = '/api/automation/schedule/create';
if (param.id) {
url = '/api/automation/schedule/update';
}
this.$post(url, param, () => {
this.$success(this.$t('commons.save_success'));
this.search();
});
},
checkScheduleEdit() {
return true;
},
remove(row) {
if (this.currentModule !== undefined && this.currentModule != null && this.currentModule.id === "gc") {
this.$get('/api/automation/delete/' + row.id, () => {

View File

@ -0,0 +1,60 @@
<template>
<div v-if="isShow" style="float: right">
<el-dropdown placement="bottom" trigger="click" size="medium">
<div @click.stop class="show-more-btn">
<i class="el-icon-more ms-icon-more"/>
</div>
<el-dropdown-menu slot="dropdown" class="dropdown-menu-class">
<el-dropdown-item v-for="(btn,index) in buttons" :key="index" @click.native.stop="click(btn)">
{{btn.name}}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
name: "ShowMoreBtn",
props: {
isShow: {
type: Boolean,
default: false
},
buttons: Array,
row: Object,
size: Number
},
methods: {
click(btn) {
if (btn.handleClick instanceof Function) {
btn.handleClick(this.row);
}
}
}
}
</script>
<style scoped>
.ms-icon-more {
margin-top: 15px;
transform: rotate(0deg);
}
.show-more-btn {
width: 20px;
height: 25px;
line-height: 25px;
}
.show-more-btn-title {
color: #696969;
background-color: #e2e2e2;
padding: 5px;
}
.dropdown-menu-class {
padding: 1px 0;
text-align: center;
}
</style>