This commit is contained in:
fit2-zhao 2021-01-22 18:12:25 +08:00
commit 5340a37cb1
33 changed files with 394 additions and 141 deletions

View File

@ -144,6 +144,10 @@ public class ApiDefinitionController {
public void createSchedule(@RequestBody Schedule request) {
apiDefinitionService.createSchedule(request);
}
@PostMapping(value = "/schedule/update")
public void updateSchedule(@RequestBody Schedule request){
apiDefinitionService.updateSchedule(request);
}
@PostMapping("/getReference")
public ReferenceDTO getReference(@RequestBody ApiScenarioRequest request) {

View File

@ -13,8 +13,8 @@ public class TaskInfoResult {
private int index;
//任务ID
private String taskID;
//场景名称
private String scenario;
//任务名称
private String name;
//场景ID
private String scenarioId;
//规则

View File

@ -69,7 +69,7 @@ public class Swagger2Parser extends SwaggerAbstractParser {
ApiDefinitionResult apiDefinition = buildApiDefinition(request.getId(), operation, pathName, method.name());
parseParameters(operation, request);
apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation, operation.getResponses())));
buildModule(parentNode, apiDefinition, operation.getTags(), importRequest.isSaved());
results.add(apiDefinition);
}
@ -163,11 +163,13 @@ public class Swagger2Parser extends SwaggerAbstractParser {
"", parameter.getRequired());
}
private HttpResponse parseResponse(Map<String, Response> responses) {
private HttpResponse parseResponse(Operation operation, Map<String, Response> responses) {
HttpResponse msResponse = new HttpResponse();
msResponse.setBody(new Body());
msResponse.getBody().setKvs(new ArrayList<>());
msResponse.setHeaders(new ArrayList<>());
msResponse.setType(RequestType.HTTP);
msResponse.getBody().setType(getBodyType(operation));
// todo 状态码要调整
msResponse.setStatusCode(new ArrayList<>());
if (responses != null) {
@ -234,6 +236,15 @@ public class Swagger2Parser extends SwaggerAbstractParser {
}
}
return propertyList.toString();
} else if (schema instanceof ModelImpl) {
ModelImpl model = (ModelImpl) schema;
if (StringUtils.equals("object", model.getType())) {
model.getProperties();
if (model != null) {
JSONObject bodyParameters = getBodyParameters(model.getProperties(), new HashSet<>());
return bodyParameters.toJSONString();
}
}
}
return "";
}

View File

@ -314,7 +314,7 @@ public class ApiDefinitionService {
} else {
//如果存在则修改
apiDefinition.setId(sameRequest.get(0).getId());
apiDefinitionMapper.updateByPrimaryKey(apiDefinition);
apiDefinitionMapper.updateByPrimaryKeyWithBLOBs(apiDefinition);
}
return apiDefinition;
}
@ -604,6 +604,10 @@ public class ApiDefinitionService {
this.addOrUpdateSwaggerImportCronJob(request);
}
public void updateSchedule(Schedule request){
scheduleService.editSchedule(request);
this.addOrUpdateSwaggerImportCronJob(request);
}
private void addOrUpdateSwaggerImportCronJob(Schedule request) {
scheduleService.addOrUpdateCronJob(request, SwaggerUrlImportJob.getJobKey(request.getResourceId()), SwaggerUrlImportJob.getTriggerKey(request.getResourceId()), SwaggerUrlImportJob.class);

View File

@ -198,6 +198,7 @@
FROM
api_test_case t1
LEFT JOIN api_definition_exec_result t2 ON t1.last_result_id = t2.id
inner join api_definition a on t1.api_definition_id = a.id
LEFT JOIN USER u1 ON t1.update_user_id = u1.id
LEFT JOIN USER u2 ON t1.create_user_id = u2.id
LEFT JOIN USER u3 ON t2.user_id = u3.id
@ -224,6 +225,12 @@
<if test="request.apiDefinitionId != null and request.apiDefinitionId!=''">
AND t1.api_definition_id = #{request.apiDefinitionId}
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and a.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
</foreach>
</if>
</where>
<if test="request.orders != null and request.orders.size() > 0">

View File

@ -50,14 +50,14 @@
AND create_time BETWEEN #{startTime} and #{endTime}
</select>
<select id="findRunningTaskInfoByProjectID" resultType="io.metersphere.api.dto.datacount.response.TaskInfoResult">
SELECT apiScene.id AS scenarioId,apiScene.`name` AS scenario,sch.id AS taskID,sch.`value` AS rule,sch.`enable` AS `taskStatus`,u.`name` AS creator,sch.update_time AS updateTime,
SELECT apiScene.id AS scenarioId,apiScene.`name` AS `name` ,sch.id AS taskID,sch.`value` AS rule,sch.`enable` AS `taskStatus`,u.`name` AS creator,sch.update_time AS updateTime,
'scenario' AS taskType
FROM api_scenario apiScene
INNER JOIN `schedule` sch ON apiScene.id = sch.resource_id
INNER JOIN `user` u ON u.id = sch.user_id
WHERE sch.`enable` = true AND apiScene.project_id = #{0,jdbcType=VARCHAR}
UNION
SELECT testPlan.id AS scenarioId,testPlan.`name` AS scenario,sch.id AS taskID,sch.`value` AS rule,sch.`enable` AS `taskStatus`,u.`name` AS creator,sch.update_time AS updateTime,
SELECT testPlan.id AS scenarioId,testPlan.`name` AS `name`,sch.id AS taskID,sch.`value` AS rule,sch.`enable` AS `taskStatus`,u.`name` AS creator,sch.update_time AS updateTime,
'testPlan' AS taskType
FROM test_plan testPlan
INNER JOIN `schedule` sch ON testPlan.id = sch.resource_id

View File

@ -268,7 +268,14 @@
<select id="list" resultType="io.metersphere.track.dto.TestCaseDTO">
select
<include refid="io.metersphere.base.mapper.TestCaseMapper.Base_Column_List"/>
<if test="request.selectFields != null and request.selectFields.size() > 0">
<foreach collection="request.selectFields" item="field" separator=",">
${field}
</foreach>
</if>
<if test="request.selectFields == null or request.selectFields.size() == 0">
<include refid="io.metersphere.base.mapper.TestCaseMapper.Base_Column_List"/>
</if>
from test_case
<where>
<if test="request.combine != null">

View File

@ -1,30 +1,23 @@
package io.metersphere.commons.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JsonPathUtils {
public static List<HashMap> getListJson(String jsonString) {
JSONObject jsonObject = JSONObject.parseObject(jsonString);
JSON jsonObject = jsonString.startsWith("[")?JSONObject.parseArray(jsonString):JSONObject.parseObject(jsonString);
List<HashMap> allJsons =new ArrayList<>();
// 获取到所有jsonpath后获取所有的key
List<String> jsonPaths = JSONPath.paths(jsonObject).keySet()
.stream()
.collect(Collectors.toList());
List<String> jsonPaths = new ArrayList<>(JSONPath.paths(jsonObject).keySet());
//去掉根节点key
List<String> parentNode = new ArrayList<>();
//根节点key

View File

@ -290,11 +290,12 @@ public class ScheduleManager {
addOrUpdateCronJob(jobKey, triggerKey, jobClass, cron, null);
}
public JobDataMap getDefaultJobDataMap(String resourceId, String expression, String userId) {
public JobDataMap getDefaultJobDataMap(String resourceId, String expression, String userId,String swaggerUrl) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("resourceId", resourceId);
jobDataMap.put("expression", expression);
jobDataMap.put("userId", userId);
jobDataMap.put("swaggerUrl",swaggerUrl);
return jobDataMap;
}

View File

@ -24,6 +24,8 @@ public class SwaggerUrlImportJob extends MsScheduleJob {
ApiTestImportRequest request = new ApiTestImportRequest();
request.setProjectId(resourceId);
request.setSwaggerUrl(swaggerUrl);
request.setPlatform("Swagger2");
request.setSaved(true);
apiDefinitionService.apiTestImport(null, request);
}

View File

@ -111,7 +111,7 @@ public class ScheduleService {
LogUtil.error("初始化任务:" + JSON.toJSONString(schedule));
scheduleManager.addOrUpdateCronJob(new JobKey(schedule.getKey(), schedule.getGroup()),
new TriggerKey(schedule.getKey(), schedule.getGroup()), Class.forName(schedule.getJob()), schedule.getValue(),
scheduleManager.getDefaultJobDataMap(schedule.getResourceId(), schedule.getValue(), schedule.getUserId()));
scheduleManager.getDefaultJobDataMap(schedule.getResourceId(), schedule.getValue(), schedule.getUserId(),schedule.getSwaggerUrl()));
}
} catch (Exception e) {
LogUtil.error("初始化任务失败", e);
@ -139,7 +139,8 @@ public class ScheduleService {
String cronExpression = request.getValue();
if (enable != null && enable && StringUtils.isNotBlank(cronExpression)) {
try {
scheduleManager.addOrUpdateCronJob(jobKey, triggerKey, clazz, cronExpression, scheduleManager.getDefaultJobDataMap(request.getResourceId(), cronExpression, SessionUtils.getUser().getId()));
scheduleManager.addOrUpdateCronJob(jobKey, triggerKey, clazz, cronExpression,
scheduleManager.getDefaultJobDataMap(request.getResourceId(), cronExpression, SessionUtils.getUser().getId(),request.getSwaggerUrl()));
} catch (SchedulerException e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException("定时任务开启异常");

View File

@ -14,10 +14,12 @@ import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.service.CheckPermissionService;
import io.metersphere.service.FileService;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.request.testcase.EditTestCaseRequest;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import io.metersphere.track.request.testcase.TestCaseBatchRequest;
import io.metersphere.track.request.testplan.FileOperationRequest;
import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest;
import io.metersphere.track.service.TestCaseService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
@ -65,6 +67,11 @@ public class TestCaseController {
return testCaseService.listTestCaseMthod(request);
}
@PostMapping("/list/ids")
public List<TestCaseDTO> getTestPlanCaseIds(@RequestBody QueryTestCaseRequest request) {
return testCaseService.listTestCaseIds(request);
}
@GetMapping("recent/{count}")
public List<TestCase> recentTestPlans(@PathVariable int count) {

View File

@ -22,6 +22,8 @@ public class QueryTestCaseRequest extends TestCase {
private Map<String, List<String>> filters;
private List<String> selectFields;
private String planId;
private String workspaceId;

View File

@ -676,4 +676,19 @@ public class TestCaseService {
editTestCase(request);
return request.getId();
}
public List<TestCaseDTO> listTestCaseIds(QueryTestCaseRequest request) {
List<OrderRequest> orderList = ServiceUtils.getDefaultOrder(request.getOrders());
OrderRequest order = new OrderRequest();
// 对模板导入的测试用例排序
order.setName("sort");
order.setType("desc");
orderList.add(order);
request.setOrders(orderList);
List<String> selectFields = new ArrayList<>();
selectFields.add("id");
selectFields.add("name");
request.setSelectFields(selectFields);
return extTestCaseMapper.list(request);
}
}

View File

@ -66,7 +66,6 @@
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
@ -92,7 +91,7 @@
<!-- 场景步骤-->
<div v-loading="loading">
<div @click="showAll">
<p class="tip">{{$t('api_test.automation.scenario_step')}} </p>
<p class="tip">{{ $t('api_test.automation.scenario_stepscenario_step') }} </p>
</div>
<el-row>
<el-col :span="21">
@ -852,7 +851,6 @@
this.result = this.$get("/api/automation/getApiScenario/" + this.currentScenario.id, response => {
if (response.data) {
this.path = "/api/automation/update";
this.currentScenario.description = response.data.description;
if (response.data.scenarioDefinition != null) {
let obj = JSON.parse(response.data.scenarioDefinition);
if (obj) {

View File

@ -1,5 +1,5 @@
<template>
<el-dialog :close-on-click-modal="false" width="60%" class="schedule-edit" :visible.sync="dialogVisible"
<el-dialog :close-on-click-modal="false" width="60%" class="schedule-edit" :visible.sync="dialogVisible" :append-to-body='true'
@close="close">
<template>
<div>

View File

@ -19,43 +19,60 @@
</div>
<el-form :model="formData" :rules="rules" label-width="100px" v-loading="result.loading" ref="form">
<el-row>
<el-col :span="9">
<el-form-item :label="$t('commons.import_module')"
v-if="selectedPlatformValue != 'Swagger2' || (selectedPlatformValue == 'Swagger2' && !swaggerUrlEable)">
<el-select size="small" v-model="formData.moduleId" class="project-select" clearable>
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.import_mode')"
v-if="selectedPlatformValue != 'Swagger2' || (selectedPlatformValue == 'Swagger2' && !swaggerUrlEable)">
<el-select size="small" v-model="formData.modeId" class="project-select" clearable>
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10" style="margin-left: 20px">
<el-upload
v-if="selectedPlatformValue != 'Swagger2' || (selectedPlatformValue == 'Swagger2' && !swaggerUrlEable)"
class="api-upload"
drag
action=""
:http-request="upload"
:limit="1"
:beforeUpload="uploadValidate"
:on-remove="handleRemove"
:file-list="fileList"
:on-exceed="handleExceed"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<div class="el-upload__tip" slot="tip">{{ $t('api_test.api_import.file_size_limit') }}</div>
</el-upload>
</el-col>
<el-upload
v-if="selectedPlatformValue != 'Swagger2' || (selectedPlatformValue == 'Swagger2' && !swaggerUrlEable)"
class="api-upload"
drag
action=""
:http-request="upload"
:limit="1"
:beforeUpload="uploadValidate"
:on-remove="handleRemove"
:file-list="fileList"
:on-exceed="handleExceed"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<div class="el-upload__tip" slot="tip">{{ $t('api_test.api_import.file_size_limit') }}</div>
</el-upload>
<el-form-item :label="'Swagger URL'" prop="wgerUrl" v-if="isSwagger2 && swaggerUrlEable" class="swagger-url">
<el-input size="small" v-model="formData.swaggerUrl" clearable show-word-limit/>
</el-form-item>
<el-form-item v-if="isSwagger2" class="swagger-enable" :class="{'swagger-url-disable': !swaggerUrlEable}">
<el-switch
v-model="swaggerUrlEable"
:active-text="$t('api_test.api_import.swagger_url_import')">
</el-switch>
</el-form-item>
<el-form-item :label="'Swagger URL'" prop="wgerUrl" v-if="isSwagger2 && swaggerUrlEable" class="swagger-url">
<el-input size="small" v-model="formData.swaggerUrl" clearable show-word-limit/>
</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-row>
<el-form-item v-if="isSwagger2" class="swagger-enable" :class="{'swagger-url-disable': !swaggerUrlEable}">
<el-switch
v-model="swaggerUrlEable"
:active-text="$t('api_test.api_import.swagger_url_import')">
</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>
@ -76,6 +93,8 @@ 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";
import {buildNodePath} from "@/business/components/api/definition/model/NodeTree";
export default {
name: "ApiImport",
components: {ScheduleImport, MsDialogFooter},
@ -83,14 +102,24 @@ export default {
saved: {
type: Boolean,
default: true,
}
},
moduleOptions: {}
},
data() {
return {
visible: false,
swaggerUrlEable: false,
swaggerSynchronization:false,
swaggerSynchronization: false,
showEnvironmentSelect: true,
modeOptions: [{
id: 'fullCoverage',
name: '全量覆盖'
},
{
id: 'incrementalMerge',
name: '增量合并'
}],
protocol: "",
platforms: [
{
name: 'Metersphere',
@ -122,7 +151,9 @@ export default {
useEnvironment: false,
formData: {
file: undefined,
swaggerUrl: ''
swaggerUrl: '',
modeId: '',
moduleId: ''
},
rules: {},
currentModule: {},
@ -148,15 +179,19 @@ export default {
}
},
methods: {
scheduleEdit(){
if(this.swaggerSynchronization){
this.$refs.scheduleEdit.open(this.buildParam());
scheduleEdit() {
if (this.swaggerSynchronization) {
this.$refs.scheduleEdit.open(this.buildParam());
} else {
this.result = this.$post("/api/definition/schedule/update", this.schedule, response => {
});
}
},
open(module) {
this.currentModule = module;
this.visible = true;
listenGoBack(this.close);
},
upload(file) {
this.formData.file = file.file;

View File

@ -72,7 +72,7 @@
}
},
planId: String,
relevanceProjectId: String
relevanceProjectId: String,
},
computed: {
isPlanModel() {

View File

@ -33,7 +33,7 @@
@saveAsEdit="saveAsEdit"
@refresh="refresh"
ref="basisApi"/>
<api-import ref="apiImport" @refresh="$emit('refresh')"/>
<api-import ref="apiImport" :moduleOptions="moduleOptions" @refresh="$emit('refresh')"/>
</div>
</template>
@ -43,6 +43,7 @@ import MsAddBasisApi from "../basis/AddBasisApi";
import ApiImport from "../import/ApiImport";
import ModuleTrashButton from "./ModuleTrashButton";
import {getCurrentProjectID} from "../../../../../../common/js/utils";
import {buildNodePath} from "@/business/components/api/definition/model/NodeTree";
export default {
name: "ApiModuleHeader",
@ -50,6 +51,7 @@ export default {
data() {
return {
options: OPTIONS,
moduleOptions: {}
}
},
props: {
@ -70,9 +72,10 @@ export default {
default() {
return false
}
}
},
},
methods: {
handleCommand(e) {
switch (e) {
case "debug":
@ -88,6 +91,17 @@ export default {
this.$warning(this.$t('commons.check_project_tip'));
return;
}
this.protocol = "HTTP";
this.result = this.$get("/api/module/list/" + getCurrentProjectID() + "/" + this.protocol, response => {
if (response.data != undefined && response.data != null) {
this.data = response.data;
let moduleOptions = [];
this.data.forEach(node => {
buildNodePath(node, {path: ''}, moduleOptions);
});
this.moduleOptions = moduleOptions
}
});
this.$refs.apiImport.open(this.currentModule);
break;
default:

View File

@ -7,13 +7,19 @@
</template>
<el-table border :data="tableData" class="adjust-table table-content" height="300px">
<el-table-column prop="index" :label="$t('api_test.home_page.running_task_list.table_coloum.index')" width="80" show-overflow-tooltip/>
<el-table-column prop="scenario" :label="$t('api_test.home_page.running_task_list.table_coloum.scenario')" width="200" >
<el-table-column prop="name" :label="$t('commons.name')" width="200" >
<template v-slot:default="{row}">
<el-link type="info" @click="redirect(row)">
{{ row.scenario }}
{{ row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column prop="taskType" :label="$t('api_test.home_page.running_task_list.table_coloum.task_type')" width="120" show-overflow-tooltip>
<template v-slot:default="scope">
<ms-tag v-if="scope.row.taskType == 'scenario'" type="success" effect="plain" :content="$t('api_test.home_page.running_task_list.scenario_schedule')"/>
<ms-tag v-if="scope.row.taskType == 'testPlan'" type="warning" effect="plain" :content="$t('api_test.home_page.running_task_list.test_plan_schedule')"/>
</template>
</el-table-column>
<el-table-column prop="rule" :label="$t('api_test.home_page.running_task_list.table_coloum.run_rule')" width="120" show-overflow-tooltip/>
<el-table-column width="100" :label="$t('api_test.home_page.running_task_list.table_coloum.task_status')">
<template v-slot:default="scope">
@ -46,9 +52,12 @@
<script>
import {getCurrentProjectID} from "@/common/js/utils";
import MsTag from "@/business/components/common/components/MsTag";
export default {
name: "MsRunningTaskList",
components: {
MsTag
},
data() {
return {

View File

@ -0,0 +1,57 @@
<template>
<span class="previous-next-button">
<span class="head-right-tip" v-if="index + 1 === list.length">
{{ $t('test_track.plan_view.pre_case') }} : {{list[index - 1] ? list[index - 1].name : ''}}
</span>
<span class="head-right-tip" v-if="index + 1 !== list.length">
{{ $t('test_track.plan_view.next_case') }} : {{list[index + 1] ? list[index + 1].name : ''}}
</span>
<el-button plain size="mini" icon="el-icon-arrow-up" :disabled="index + 1 <= 1" @click="handlePre()"/>
<span>
{{ index + 1 }}/{{ list.length }}
</span>
<el-button plain size="mini" icon="el-icon-arrow-down" :disabled="index + 1 >= list.length" @click="handleNext()"/>
</span>
</template>
<script>
export default {
name: "MsPreviousNextButton",
data() {
return {
}
},
props: {
list: {
type: Array,
default() {
return []
}
},
index: {
type: Number,
default() {
return 0
}
}
},
methods: {
handlePre() {
this.$emit('pre');
},
handleNext() {
this.$emit('next');
}
}
}
</script>
<style scoped>
.head-right-tip {
color: darkgrey;
}
</style>

View File

@ -291,7 +291,7 @@ export default {
},
fileChange(threadGroups) {
let handler = this.$refs.pressureConfig;
handler.threadGroups = threadGroups;
threadGroups.forEach(tg => {
tg.threadNumber = tg.threadNumber || 10;
tg.duration = tg.duration || 10;
@ -301,8 +301,13 @@ export default {
tg.threadType = tg.threadType || 'DURATION';
tg.iterateNum = tg.iterateNum || 1;
tg.iterateRampUp = tg.iterateRampUp || 10;
handler.calculateChart(tg);
});
this.$set(handler, "threadGroups", threadGroups);
threadGroups.forEach(tg => {
handler.calculateChart(tg);
})
}
}
}

View File

@ -58,6 +58,7 @@
v-model="threadGroup.rpsLimit"
@change="calculateChart(threadGroup)"
:min="1"
:max="500"
size="mini"/>
</el-form-item>
<br>
@ -87,6 +88,7 @@
:disabled="isReadOnly"
v-model="threadGroup.iterateNum"
:min="1"
:max="10000"
@change="calculateChart(threadGroup)"
size="mini"/>
</el-form-item>
@ -98,6 +100,7 @@
:disabled="isReadOnly || !threadGroup.rpsLimitEnable"
v-model="threadGroup.rpsLimit"
:min="1"
:max="500"
size="mini"/>
</el-form-item>
<br>

View File

@ -252,6 +252,9 @@ export default {
activated() {
this.initTableData();
},
created() {
this.list()
},
methods: {
create() {
this.dialogOrgAddVisible = true;

View File

@ -21,6 +21,7 @@
@refresh="refresh"
@refreshAll="refreshAll"
@moveToNode="moveToNode"
@setCondition="setCondition"
ref="testCaseList">
</test-case-list>
</ms-main-container>
@ -30,6 +31,7 @@
:read-only="testCaseReadOnly"
:tree-nodes="treeNodes"
:select-node="selectNode"
:select-condition="condition"
ref="testCaseEditDialog">
</test-case-edit>
@ -73,6 +75,7 @@ export default {
selectParentNodes: [],
testCaseReadOnly: true,
selectNode: {},
condition: {}
}
},
mounted() {
@ -165,6 +168,9 @@ export default {
setTreeNodes(data) {
this.treeNodes = data;
},
setCondition(data) {
this.condition = data;
},
moveSave(param) {
this.result = this.$post('/test/case/batch/edit', param, () => {
this.$success(this.$t('commons.save_success'));

View File

@ -5,6 +5,19 @@
:title="operationType == 'edit' ? ( readOnly ? $t('test_track.case.view_case') : $t('test_track.case.edit_case')) : $t('test_track.case.create')"
:visible.sync="dialogFormVisible" width="85%" v-if="dialogFormVisible">
<template v-slot:title>
<el-row>
<el-col :span="4">
<span>
{{operationType == 'edit' ? ( readOnly ? $t('test_track.case.view_case') : $t('test_track.case.edit_case')) : $t('test_track.case.create')}}
</span>
</el-col>
<el-col class="head-right" :span="19">
<ms-previous-next-button v-if="operationType == 'edit'" :index="index" @pre="handlePre" @next="handleNext" :list="testCases"/>
</el-col>
</el-row>
</template>
<el-row :gutter="10">
<div>
<el-col :span="17">
@ -279,10 +292,11 @@ import {getCurrentProjectID} from "../../../../../common/js/utils";
import {buildNodePath} from "../../../api/definition/model/NodeTree";
import CaseComment from "@/business/components/track/case/components/CaseComment";
import MsInputTag from "@/business/components/api/automation/scenario/MsInputTag";
import MsPreviousNextButton from "../../../common/components/MsPreviousNextButton";
export default {
name: "TestCaseEdit",
components: {MsInputTag, CaseComment, MsDialogFooter, TestCaseAttachment},
components: {MsPreviousNextButton, MsInputTag, CaseComment, MsDialogFooter, TestCaseAttachment},
data() {
return {
result: {},
@ -335,6 +349,8 @@ export default {
{value: 'manual', label: this.$t('test_track.case.manual')}
],
testCase: {},
testCases: [],
index: 0
};
},
props: {
@ -348,6 +364,9 @@ export default {
selectNode: {
type: Object
},
selectCondition: {
type: Object
},
},
mounted() {
this.getSelectOptions();
@ -363,18 +382,12 @@ export default {
this.$nextTick(() => (this.isStepTableAlive = true));
},
open(testCase) {
this.testCase = {};
if (testCase) {
testCase.tags = JSON.parse(testCase.tags);
//
this.testCase = testCase.isCopy ? {} : testCase;
}
this.resetForm();
this.projectId = getCurrentProjectID();
if (window.history && window.history.pushState) {
history.pushState(null, null, document.URL);
window.addEventListener('popstate', this.close);
}
this.resetForm();
listenGoBack(this.close);
this.operationType = 'add';
if (testCase) {
@ -383,13 +396,13 @@ export default {
//
if (testCase.name === '') {
this.operationType = 'add';
this.setFormData(testCase);
this.setTestCaseExtInfo(testCase);
this.getSelectOptions();
this.reload();
} else {
this.initTestCases(testCase);
}
let tmp = {};
Object.assign(tmp, testCase);
tmp.steps = JSON.parse(testCase.steps);
Object.assign(this.form, tmp);
this.form.module = testCase.nodeId;
this.getFileMetaData(testCase);
} else {
if (this.selectNode.data) {
this.form.module = this.selectNode.data.id;
@ -403,12 +416,56 @@ export default {
this.form.type = 'functional';
this.form.method = 'manual';
this.form.maintainer = user.id;
this.getSelectOptions();
this.reload();
}
this.getSelectOptions();
this.reload();
this.dialogFormVisible = true;
},
handlePre() {
this.index--;
this.getTestCase(this.index)
},
handleNext() {
this.index++;
this.getTestCase(this.index);
},
initTestCases(testCase) {
this.result = this.$post('/test/case/list/ids', this.selectCondition, response => {
this.testCases = response.data;
for (let i = 0; i < this.testCases.length; i++) {
if (this.testCases[i].id === testCase.id) {
this.index = i;
this.getTestCase(i);
}
}
});
},
getTestCase(index) {
let testCase = this.testCases[index];
this.result = this.$get('/test/case/get/' + testCase.id, response => {
let testCase = response.data;
this.setFormData(testCase);
this.setTestCaseExtInfo(testCase);
this.getSelectOptions();
this.reload();
})
},
setFormData(testCase) {
testCase.tags = JSON.parse(testCase.tags);
let tmp = {};
Object.assign(tmp, testCase);
tmp.steps = JSON.parse(testCase.steps);
Object.assign(this.form, tmp);
this.form.module = testCase.nodeId;
this.getFileMetaData(testCase);
},
setTestCaseExtInfo (testCase) {
this.testCase = {};
if (testCase) {
//
this.testCase = testCase.isCopy ? {} : testCase;
}
},
getFileMetaData(testCase) {
this.fileList = [];
this.tableData = [];
@ -607,28 +664,33 @@ export default {
if (this.$refs['caseFrom']) {
this.$refs['caseFrom'].validate((valid) => {
this.$refs['caseFrom'].resetFields();
this.form.name = '';
this.form.module = '';
this.form.type = '';
this.form.method = '';
this.form.maintainer = '';
this.form.priority = '';
this.form.prerequisite = '';
this.form.remark = '';
this.form.testId = '';
this.form.testName = '';
this.form.steps = [{
num: 1,
desc: '',
result: ''
}];
this.uploadList = [];
this.fileList = [];
this.tableData = [];
this._resetForm();
return true;
});
} else {
this._resetForm();
}
},
_resetForm() {
this.form.name = '';
this.form.module = '';
this.form.type = '';
this.form.method = '';
this.form.maintainer = '';
this.form.priority = '';
this.form.prerequisite = '';
this.form.remark = '';
this.form.testId = '';
this.form.testName = '';
this.form.steps = [{
num: 1,
desc: '',
result: ''
}];
this.uploadList = [];
this.fileList = [];
this.tableData = [];
},
handleExceed() {
this.$error(this.$t('load_test.file_size_limit'));
},
@ -748,4 +810,8 @@ export default {
height: calc(100vh - 120px);
}
.head-right {
text-align: right;
}
</style>

View File

@ -286,12 +286,16 @@ export default {
}
},
created: function () {
this.$emit('setCondition', this.condition);
this.initTableData();
},
watch: {
selectNodeIds() {
this.currentPage = 1;
this.initTableData();
},
condition() {
this.$emit('setCondition', this.condition);
}
},
methods: {

View File

@ -20,32 +20,15 @@
<el-row type="flex" class="head-bar">
<el-col>
<el-col :span="4">
<el-button plain size="mini"
icon="el-icon-back"
@click="cancel">{{ $t('test_track.return') }}
</el-button>
</el-col>
<el-col class="head-right">
<span class="head-right-tip" v-if="index + 1 === testCases.length">
{{ $t('test_track.plan_view.pre_case') }} : {{
testCases[index - 1] ? testCases[index - 1].name : ''
}}
</span>
<span class="head-right-tip" v-if="index + 1 !== testCases.length">
{{ $t('test_track.plan_view.next_case') }} : {{
testCases[index + 1] ? testCases[index + 1].name : ''
}}
</span>
<el-button plain size="mini" icon="el-icon-arrow-up"
:disabled="index + 1 <= 1"
@click="handlePre()"/>
<span> {{ index + 1 }}/{{ testCases.length }} </span>
<el-button plain size="mini" icon="el-icon-arrow-down"
:disabled="index + 1 >= testCases.length"
@click="handleNext()"/>
<el-col class="head-right" :span="20">
<ms-previous-next-button :index="index" @pre="handlePre" @next="handleNext" :list="testCases"/>
</el-col>
</el-row>
@ -370,10 +353,12 @@ import PerformanceTestResult from "../test/PerformanceTestResult";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment";
import CaseComment from "@/business/components/track/case/components/CaseComment";
import MsPreviousNextButton from "../../../../../common/components/MsPreviousNextButton";
export default {
name: "FunctionalTestCaseEdit",
components: {
MsPreviousNextButton,
CaseComment,
PerformanceTestResult,
PerformanceTestDetail,
@ -722,10 +707,6 @@ export default {
float: right;
}
.head-right-tip {
color: darkgrey;
}
.el-scrollbar {
height: 100%;
}

View File

@ -119,7 +119,7 @@
this.functionalCharData = [];
if (this.executeResult.functionalResult) {
this.executeResult.functionalResult.forEach(item => {
let data = this.dataMap.get(item.status);
let data = this.copyData(item.status);
data.value = item.count;
this.functionalCharData.push(data);
});
@ -129,7 +129,7 @@
this.apiCharData = [];
if (this.executeResult.apiResult) {
this.executeResult.apiResult.forEach(item => {
let data = this.dataMap.get(item.status);
let data = this.copyData(item.status);
data.value = item.count;
this.apiCharData.push(data);
});
@ -139,7 +139,7 @@
this.scenarioCharData = [];
if (this.executeResult.apiResult) {
this.executeResult.scenarioResult.forEach(item => {
let data = this.dataMap.get(item.status);
let data = this.copyData(item.status);
data.value = item.count;
this.scenarioCharData.push(data);
});
@ -149,12 +149,15 @@
this.loadCharData = [];
if (this.executeResult.loadResult) {
this.executeResult.loadResult.forEach(item => {
let data = this.dataMap.get(item.status);
let data = this.copyData(item.status);
data.value = item.count;
this.loadCharData.push(data);
});
}
},
copyData(status) {
return JSON.parse(JSON.stringify(this.dataMap.get(status)))
},
reload() {
this.isShow = false;
this.$nextTick(function () {

View File

@ -1 +1 @@
export const PHONE_REGEX = '^1(3|4|5|7|8|9)\\d{9}$';
export const PHONE_REGEX = '^1(3|4|56|7|8|9)\\d{9}$';

View File

@ -1,5 +1,7 @@
export default {
commons: {
import_mode: 'Import mode',
import_module: 'Import module',
please_fill_in_the_template: 'Please fill in the template',
cut_back_old_version: 'Cut back to old version',
cut_back_new_version: 'Switch back to new version',
@ -919,12 +921,15 @@ export default {
table_coloum: {
index: "Index",
scenario: "Scene",
task_type: "Task Type",
run_rule: "Rule",
task_status: "Status",
next_execution_time: "Next Execution Time",
create_user: "Creator",
update_time: "Update time",
},
scenario_schedule: "Scenario",
test_plan_schedule: "Test plan",
confirm: {
close_title: "Do you want to close this scheduled task",
}

View File

@ -1,5 +1,7 @@
export default {
commons: {
import_mode: '导入模式',
import_module: '导入模块',
please_fill_in_the_template: '请填写模版内容',
cut_back_old_version: '切回旧版',
cut_back_new_version: '切回新版',
@ -922,12 +924,15 @@ export default {
table_coloum: {
index: "序号",
scenario: "场景名称",
task_type: "任务类型",
run_rule: "运行规则",
task_status: "任务状态",
next_execution_time: "下次执行时间",
create_user: "创建人",
update_time: "更新时间",
},
scenario_schedule: "场景",
test_plan_schedule: "测试计划",
confirm: {
close_title: "要关闭这条定时任务吗?",
}

View File

@ -1,5 +1,7 @@
export default {
commons: {
import_mode: '導入模式',
import_module: '導入模塊',
please_fill_in_the_template: '請填寫模版內容',
cut_back_old_version: '切回舊版',
cut_back_new_version: '切回新版',
@ -920,12 +922,15 @@ export default {
table_coloum: {
index: "序號",
scenario: "場景名稱",
task_type: "任務類型",
run_rule: "運行規則",
task_status: "任務狀態",
next_execution_time: "下次執行時間",
create_user: "創建人",
update_time: "更新時間",
},
scenario_schedule: "場景",
test_plan_schedule: "測試計畫",
confirm: {
close_title: "要關閉這條定時任務嗎?",
}