Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
cdf1b73253
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -33,4 +33,6 @@ public interface ApiDefinitionExecResultMapper {
|
|||
int updateByPrimaryKeyWithBLOBs(ApiDefinitionExecResult record);
|
||||
|
||||
int updateByPrimaryKey(ApiDefinitionExecResult record);
|
||||
|
||||
String selectExecResult(String resourceId);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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: "",
|
||||
|
|
|
@ -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: "个",
|
||||
|
|
|
@ -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: "個",
|
||||
|
|
Loading…
Reference in New Issue