This commit is contained in:
fit2-zhao 2021-01-20 14:53:58 +08:00
commit cdf1b73253
16 changed files with 533 additions and 202 deletions

View File

@ -10,6 +10,7 @@ import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.Schedule;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.json.JSONSchemaGenerator;
import io.metersphere.commons.utils.PageUtils;
@ -139,6 +140,11 @@ public class ApiDefinitionController {
return apiDefinitionService.apiTestImport(file, request);
}
@PostMapping(value = "/schedule/create")
public void createSchedule(@RequestBody Schedule request) {
apiDefinitionService.createSchedule(request);
}
@PostMapping("/getReference")
public ReferenceDTO getReference(@RequestBody ApiScenarioRequest request) {
return apiDefinitionService.getReference(request);
@ -165,5 +171,4 @@ public class ApiDefinitionController {
public String preview(@RequestBody String jsonSchema) {
return JSONSchemaGenerator.getJson(jsonSchema);
}
}

View File

@ -24,10 +24,14 @@ 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.ScheduleGroup;
import io.metersphere.commons.constants.ScheduleType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.i18n.Translator;
import io.metersphere.job.sechedule.SwaggerUrlImportJob;
import io.metersphere.service.FileService;
import io.metersphere.service.ScheduleService;
import io.metersphere.track.request.testcase.ApiCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import org.apache.commons.collections.CollectionUtils;
@ -73,6 +77,8 @@ public class ApiDefinitionService {
private ExtTestPlanMapper extTestPlanMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private ScheduleService scheduleService;
private static Cache cache = Cache.newHardMemoryCache(0, 3600 * 24);
@ -587,4 +593,19 @@ public class ApiDefinitionService {
}
}
}
/*swagger定时导入*/
public void createSchedule(Schedule request) {
Schedule schedule = scheduleService.buildApiTestSchedule(request);
schedule.setJob(SwaggerUrlImportJob.class.getName());
schedule.setGroup(ScheduleGroup.SWAGGER_IMPORT.name());
schedule.setType(ScheduleType.CRON.name());
scheduleService.addSchedule(schedule);
this.addOrUpdateSwaggerImportCronJob(request);
}
private void addOrUpdateSwaggerImportCronJob(Schedule request) {
scheduleService.addOrUpdateCronJob(request, SwaggerUrlImportJob.getJobKey(request.getResourceId()), SwaggerUrlImportJob.getTriggerKey(request.getResourceId()), SwaggerUrlImportJob.class);
}
}

View File

@ -1,8 +1,9 @@
package io.metersphere.base.domain;
import java.io.Serializable;
import lombok.Data;
import java.io.Serializable;
@Data
public class Schedule implements Serializable {
private String id;
@ -35,4 +36,6 @@ public class Schedule implements Serializable {
//定时任务来源 测试计划/测试场景
private String scheduleFrom;
private String swaggerUrl;
}

View File

@ -33,4 +33,6 @@ public interface ApiDefinitionExecResultMapper {
int updateByPrimaryKeyWithBLOBs(ApiDefinitionExecResult record);
int updateByPrimaryKey(ApiDefinitionExecResult record);
String selectExecResult(String resourceId);
}

View File

@ -210,6 +210,9 @@
<include refid="Example_Where_Clause" />
</if>
</select>
<select id="selectExecResult" resultType="java.lang.String">
select ader.status from api_definition_exec_result ader where ader.resource_id=#{resourceId}
</select>
<update id="updateByExampleSelective" parameterType="map">
update api_definition_exec_result
<set>

View File

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

View File

@ -0,0 +1,37 @@
package io.metersphere.job.sechedule;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
public class SwaggerUrlImportJob extends MsScheduleJob {
private ApiDefinitionService apiDefinitionService;
public SwaggerUrlImportJob() {
apiDefinitionService = (ApiDefinitionService) CommonBeanFactory.getBean(ApiDefinitionService.class);
}
@Override
void businessExecute(JobExecutionContext context) {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String resourceId = jobDataMap.getString("resourceId");
String swaggerUrl = jobDataMap.getString("swaggerUrl");
ApiTestImportRequest request = new ApiTestImportRequest();
request.setProjectId(resourceId);
request.setSwaggerUrl(swaggerUrl);
apiDefinitionService.apiTestImport(null, request);
}
public static JobKey getJobKey(String resourceId) {
return new JobKey(resourceId, ScheduleGroup.SWAGGER_IMPORT.name());
}
public static TriggerKey getTriggerKey(String resourceId) {
return new TriggerKey(resourceId, ScheduleGroup.SWAGGER_IMPORT.name());
}
}

View File

@ -104,6 +104,7 @@ public class ScheduleService {
public void startEnableSchedules() {
List<Schedule> Schedules = getEnableSchedule();
Schedules.forEach(schedule -> {
try {
if (schedule.getEnable()) {
@ -122,7 +123,7 @@ public class ScheduleService {
public Schedule buildApiTestSchedule(Schedule request) {
Schedule schedule = new Schedule();
schedule.setResourceId(request.getResourceId());
schedule.setEnable(request.getEnable());
schedule.setEnable(true);
schedule.setValue(request.getValue().trim());
schedule.setKey(request.getResourceId());
schedule.setUserId(SessionUtils.getUser().getId());

@ -1 +1 @@
Subproject commit c2ed883e9be6fc7e01589f81916bf4ddc62148c0
Subproject commit 7f7808c6f0457dd2df6b19a1622558f3f8122646

View File

@ -30,10 +30,13 @@
<el-table-column prop="level" :label="$t('api_test.automation.case_level')"
show-overflow-tooltip>
<template v-slot:default="scope">
<ms-tag v-if="scope.row.level == 'P0'" type="info" effect="plain" content="P0"/>
<ms-tag v-if="scope.row.level == 'P1'" type="warning" effect="plain" content="P1"/>
<ms-tag v-if="scope.row.level == 'P2'" type="success" effect="plain" content="P2"/>
<ms-tag v-if="scope.row.level == 'P3'" type="danger" effect="plain" content="P3"/>
<priority-table-item :value="scope.row.level"/>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('test_track.plan.plan_status')"
show-overflow-tooltip>
<template v-slot:default="scope">
<plan-status-table-item :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column prop="tags" :label="$t('api_test.automation.tag')" width="200px">
@ -115,10 +118,14 @@
import MsTableSelectAll from "../../../common/components/table/MsTableSelectAll";
import {API_CASE_CONFIGS} from "@/business/components/common/components/search/search-components";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import PriorityTableItem from "../../../track/common/tableItems/planview/PriorityTableItem";
import PlanStatusTableItem from "../../../track/common/tableItems/plan/PlanStatusTableItem";
export default {
name: "MsApiScenarioList",
components: {
PlanStatusTableItem,
PriorityTableItem,
MsTableSelectAll,
MsTablePagination,
MsTableMoreBtn,

View File

@ -4,7 +4,7 @@
{{description}}
</span>
<div class="kv-row" v-for="(item, index) in items" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-row type="flex" :gutter="5" justify="space-between" align="middle">
<el-col class="kv-checkbox">
<input type="checkbox" v-if="!isDisable(index)" @change="change" :value="item.uuid" v-model="item.enable"
:disabled="isDisable(index) || isReadOnly"/>
@ -18,6 +18,15 @@
<el-input :disabled="isReadOnly" v-model="item.value" size="small" @change="change"
:placeholder="$t('api_test.value')" show-word-limit/>
</el-col>
<el-col>
<el-input v-model="item.description" size="small" maxlength="200"
:placeholder="$t('commons.description')" show-word-limit>
</el-input>
</el-col>
<el-col class="kv-copy">
<el-button size="mini" class="el-icon-document-copy" circle @click="copy(item, index)"
:disabled="isDisable(index) || isReadOnly"/>
</el-col>
<el-col class="kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index) || isReadOnly"/>
@ -59,6 +68,11 @@
this.items.splice(index, 1);
this.$emit('change', this.items);
},
copy: function (item, index) {
let copy = {};
Object.assign(copy, item);
this.items.splice(index + 1, 0, copy);
},
change: function () {
let isNeedCreate = true;
let removeIndex = -1;
@ -105,7 +119,7 @@
margin-top: 10px;
}
.kv-delete {
.kv-delete,.kv-copy {
width: 60px;
}
</style>

View File

@ -48,6 +48,15 @@
</el-switch>
</el-form-item>
<el-form-item v-if="isSwagger2 && swaggerUrlEable">
<el-switch
v-model="swaggerSynchronization"
@click.native="scheduleEdit"
:active-text="$t('api_test.api_import.timing_synchronization')">
</el-switch>
</el-form-item>
<schedule-import ref="scheduleEdit"></schedule-import>
</el-form>
<div class="format-tip">
@ -66,10 +75,10 @@
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {getCurrentProjectID} from "../../../../../../common/js/utils";
import ScheduleImport from "@/business/components/api/definition/components/import/ImportScheduleEdit";
export default {
name: "ApiImport",
components: {MsDialogFooter},
components: {ScheduleImport, MsDialogFooter},
props: {
saved: {
type: Boolean,
@ -80,6 +89,7 @@
return {
visible: false,
swaggerUrlEable: false,
swaggerSynchronization:false,
showEnvironmentSelect: true,
platforms: [
{
@ -138,6 +148,11 @@
}
},
methods: {
scheduleEdit(){
if(this.swaggerSynchronization){
this.$refs.scheduleEdit.open(this.buildParam());
}
},
open(module) {
this.currentModule = module;
this.visible = true;

View File

@ -0,0 +1,213 @@
<template>
<el-dialog :close-on-click-modal="false" width="60%" class="schedule-edit" :visible.sync="dialogVisible"
append-to-body @close="close">
<template>
<div>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('schedule.edit_timer_task')" name="first">
<el-form :model="form" :rules="rules" ref="from">
<el-form-item
prop="cronValue">
<el-input :disabled="isReadOnly" v-model="form.cronValue" class="inp"
:placeholder="$t('schedule.please_input_cron_expression')"/>
<el-button :disabled="isReadOnly" type="primary" @click="saveCron" v-tester>{{
$t('commons.save')
}}
</el-button>
</el-form-item>
<el-form-item>
<el-link :disabled="isReadOnly" type="primary" @click="showCronDialog">
{{ $t('schedule.generate_expression') }}
</el-link>
</el-form-item>
<crontab-result :ex="form.cronValue" ref="crontabResult"/>
</el-form>
<el-dialog width="60%" :title="$t('schedule.generate_expression')" :visible.sync="showCron"
:modal="false">
<crontab @hide="showCron=false" @fill="crontabFill" :expression="schedule.value"
ref="crontab"/>
</el-dialog>
</el-tab-pane>
</el-tabs>
</div>
</template>
</el-dialog>
</template>
<script>
import {checkoutTestManagerOrTestUser, getCurrentUser, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import Crontab from "@/business/components/common/cron/Crontab";
import CrontabResult from "@/business/components/common/cron/CrontabResult";
import {cronValidate} from "@/common/js/cron";
function defaultCustomValidate() {
return {pass: true};
}
export default {
name: "ImportScheduleEdit",
components: {CrontabResult, Crontab},
props: {
customValidate: {
type: Function,
default: defaultCustomValidate
},
isReadOnly: {
type: Boolean,
default: false
},
},
watch: {
'schedule.value'() {
this.form.cronValue = this.schedule.value;
}
},
data() {
const validateCron = (rule, cronValue, callback) => {
let customValidate = this.customValidate(this.getIntervalTime());
if (!cronValue) {
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) {
callback(new Error(customValidate.info));
} else {
callback();
}
};
return {
operation: true,
dialogVisible: false,
schedule: {
value: "",
},
showCron: false,
form: {
cronValue: ""
},
activeName: 'first',
swaggerUrl:String,
projectId:String,
rules: {
cronValue: [{required: true, validator: validateCron, trigger: 'blur'}],
}
}
},
methods: {
currentUser: () => {
return getCurrentUser();
},
open(param) {
let paramTestId = "";
paramTestId=param.projectId
this.findSchedule(paramTestId);
this.project=param.projectId;
this.swaggerUrl=param.swaggerUrl;
this.dialogVisible = true;
this.form.cronValue = this.schedule.value;
listenGoBack(this.close);
this.activeName = 'first';
},
findSchedule(paramTestId) {
let scheduleResourceID = paramTestId;
let taskType="SWAGGER_IMPORT";
this.result = this.$get("/schedule/findOne/" + scheduleResourceID + "/" +taskType, response => {
if (response.data != null) {
this.schedule = response.data;
} else {
this.schedule = {};
}
});
},
crontabFill(value, resultList) {
//
this.form.cronValue = value;
this.$refs.crontabResult.resultList = resultList;
this.$refs['from'].validate();
},
showCronDialog() {
this.showCron = true;
},
saveCron() {
this.$refs['from'].validate((valid) => {
if (valid) {
this.intervalShortValidate();
let formCronValue = this.form.cronValue
this.schedule.enable = true;
this.schedule.value = formCronValue;
this.saveSchedule();
this.dialogVisible = false;
} else {
return false;
}
});
},
saveSchedule() {
this.checkScheduleEdit();
let param = {};
param = this.schedule;
param.resourceId =this.project;
param.swaggerUrl=this.swaggerUrl;
let url = '/api/definition/schedule/create';
this.$post(url, param, () => {
this.$success(this.$t('commons.save_success'));
});
},
checkScheduleEdit() {
if (this.create) {
this.$message(this.$t('api_test.environment.please_save_test'));
return false;
}
return true;
},
close() {
this.dialogVisible = false;
this.form.cronValue = '';
this.$refs['from'].resetFields();
if (!this.schedule.value) {
this.$refs.crontabResult.resultList = [];
}
removeGoBackListener(this.close);
},
intervalShortValidate() {
if (this.getIntervalTime() < 3 * 60 * 1000) {
// return false;
this.$info(this.$t('schedule.cron_expression_interval_short_error'));
}
return true;
},
resultListChange() {
this.$refs['from'].validate();
},
getIntervalTime() {
let resultList = this.$refs.crontabResult.resultList;
let time1 = new Date(resultList[0]);
let time2 = new Date(resultList[1]);
return time2 - time1;
},
},
computed: {
isTesterPermission() {
return checkoutTestManagerOrTestUser();
}
}
}
</script>
<style scoped>
.inp {
width: 50%;
margin-right: 20px;
}
.el-form-item {
margin-bottom: 10px;
}
</style>

View File

@ -829,6 +829,9 @@ export default {
swagger_export_tip: "Export jSON-formatted files via Swagger website",
suffixFormatErr: "The file format does not meet the requirements",
swagger_url_import: "Import using URL",
timing_synchronization:"Timing synchronization",
next_synchronization_time:"Next synchronization time",
},
home_page: {
unit_of_measurement: "",

View File

@ -831,6 +831,10 @@ export default {
swagger_export_tip: "通过 Swagger 页面导出",
suffixFormatErr: "文件格式不符合要求",
swagger_url_import: "使用URL导入",
timing_synchronization:"定时同步",
next_synchronization_time:"下次同步时间"
},
home_page: {
unit_of_measurement: "个",

View File

@ -830,6 +830,9 @@ export default {
swagger_export_tip: "通過 Swagger 頁面導出",
suffixFormatErr: "文件格式不符合要求",
swagger_url_import: "使用URL導入",
timing_synchronization:"定時同步",
next_synchronization_time:"下次同步時間",
},
home_page: {
unit_of_measurement: "個",