Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
e93622d8ec
|
@ -60,13 +60,13 @@ public class APITestController {
|
|||
}
|
||||
|
||||
@PostMapping(value = "/create", consumes = {"multipart/form-data"})
|
||||
public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file) {
|
||||
apiTestService.create(request, file);
|
||||
public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
apiTestService.create(request, file, bodyFiles);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
|
||||
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file) {
|
||||
apiTestService.update(request, file);
|
||||
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
apiTestService.update(request, file, bodyFiles);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/copy")
|
||||
|
|
|
@ -24,4 +24,6 @@ public class SaveAPITestRequest {
|
|||
private Schedule schedule;
|
||||
|
||||
private String triggerMode;
|
||||
|
||||
private List<String> bodyUploadIds;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package io.metersphere.api.dto.scenario;
|
||||
|
||||
import io.metersphere.api.dto.scenario.request.BodyFile;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class KeyValue {
|
||||
private String name;
|
||||
private String value;
|
||||
private String type;
|
||||
private List<BodyFile> files;
|
||||
private String description;
|
||||
|
||||
public KeyValue() {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package io.metersphere.api.dto.scenario.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BodyFile {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
}
|
|
@ -29,6 +29,7 @@ import io.metersphere.service.ScheduleService;
|
|||
import io.metersphere.track.service.TestCaseService;
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.aspectj.util.FileUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
@ -36,9 +37,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -63,6 +62,8 @@ public class APITestService {
|
|||
@Resource
|
||||
private TestCaseService testCaseService;
|
||||
|
||||
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||
|
||||
public List<APITestResult> list(QueryAPITestRequest request) {
|
||||
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
||||
return extApiTestMapper.list(request);
|
||||
|
@ -73,24 +74,63 @@ public class APITestService {
|
|||
return extApiTestMapper.list(request);
|
||||
}
|
||||
|
||||
public void create(SaveAPITestRequest request, MultipartFile file) {
|
||||
public void create(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
||||
}
|
||||
checkQuota();
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()) ;
|
||||
request.setBodyUploadIds(null);
|
||||
ApiTest test = createTest(request);
|
||||
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||
saveFile(test.getId(), file);
|
||||
}
|
||||
|
||||
public void update(SaveAPITestRequest request, MultipartFile file) {
|
||||
public void update(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
||||
}
|
||||
deleteFileByTestId(request.getId());
|
||||
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()) ;
|
||||
request.setBodyUploadIds(null);
|
||||
ApiTest test = updateTest(request);
|
||||
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||
saveFile(test.getId(), file);
|
||||
}
|
||||
|
||||
private void createBodyFiles(ApiTest test, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
|
||||
if (bodyFiles == null || bodyFiles.isEmpty()) {
|
||||
|
||||
}
|
||||
String dir = BODY_FILE_DIR + "/" + test.getId();
|
||||
File testDir = new File(dir);
|
||||
if (!testDir.exists()) {
|
||||
testDir.mkdirs();
|
||||
}
|
||||
for (int i = 0; i < bodyUploadIds.size(); i++) {
|
||||
MultipartFile item = bodyFiles.get(i);
|
||||
File file = new File(testDir + "/" + bodyUploadIds.get(i) + "_" + item.getOriginalFilename());
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
file.createNewFile();
|
||||
in = item.getInputStream();
|
||||
out = new FileOutputStream(file);
|
||||
FileUtil.copyStream(in, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void copy(SaveAPITestRequest request) {
|
||||
checkQuota();
|
||||
request.setName(request.getName() + " Copy");
|
||||
|
@ -117,6 +157,20 @@ public class APITestService {
|
|||
apiTestFile.setFileId(fileMetadata.getId());
|
||||
apiTestFileMapper.insert(apiTestFile);
|
||||
}
|
||||
copyBodyFiles(copy.getId(), request.getId());
|
||||
}
|
||||
|
||||
public void copyBodyFiles(String target, String source) {
|
||||
String sourceDir = BODY_FILE_DIR + "/" + source;
|
||||
String targetDir = BODY_FILE_DIR + "/" + target;
|
||||
File sourceFile = new File(sourceDir);
|
||||
if (sourceFile.exists()) {
|
||||
try {
|
||||
FileUtil.copyDir(sourceFile, new File(targetDir));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public APITestResult get(String id) {
|
||||
|
@ -142,6 +196,15 @@ public class APITestService {
|
|||
apiReportService.deleteByTestId(testId);
|
||||
scheduleService.deleteByResourceId(testId);
|
||||
apiTestMapper.deleteByPrimaryKey(testId);
|
||||
deleteBodyFiles(testId);
|
||||
}
|
||||
|
||||
public void deleteBodyFiles(String testId) {
|
||||
File file = new File(BODY_FILE_DIR + "/" + testId);
|
||||
FileUtil.deleteContents(file);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public String run(SaveAPITestRequest request) {
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
package io.metersphere.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/test")
|
||||
public class TestController {
|
||||
|
||||
|
||||
@PostMapping(value = "/upload", consumes = {"multipart/form-data"})
|
||||
public Object testUpload(@RequestPart(value = "id") String id, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("id", id);
|
||||
jsonObject.put("file", file.getOriginalFilename());
|
||||
jsonObject.put("files", bodyFiles.stream().map(MultipartFile::getOriginalFilename).collect(Collectors.toList()));
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/{str}")
|
||||
public Object getString(@PathVariable String str) throws InterruptedException {
|
||||
if (StringUtils.equals("error", str)) {
|
||||
|
@ -28,4 +40,5 @@ public class TestController {
|
|||
}
|
||||
return ResultHolder.success(str);
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit ecb30d83c575c6ed14adb1a1ebea389730f410a9
|
||||
Subproject commit b86032cbbda9a9e6028308aa95a887cff2192f1c
|
|
@ -70,6 +70,7 @@
|
|||
import {checkoutTestManagerOrTestUser, downloadFile} from "@/common/js/utils";
|
||||
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
||||
import ApiImport from "./components/import/ApiImport";
|
||||
import {getUUID} from "../../../../common/js/utils";
|
||||
|
||||
export default {
|
||||
name: "MsApiTestConfig",
|
||||
|
@ -149,13 +150,15 @@
|
|||
return;
|
||||
}
|
||||
this.change = false;
|
||||
let bodyFiles = this.getBodyUploadFiles();
|
||||
let url = this.create ? "/api/create" : "/api/update";
|
||||
let jmx = this.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.result = this.$fileUpload(url, file, this.test,response => {
|
||||
this.result = this.$fileUpload(url, file, bodyFiles, this.test,response => {
|
||||
if (callback) callback();
|
||||
this.create = false;
|
||||
this.resetBodyFile();
|
||||
});
|
||||
},
|
||||
saveTest() {
|
||||
|
@ -184,6 +187,49 @@
|
|||
this.runTest();
|
||||
})
|
||||
},
|
||||
getBodyUploadFiles() {
|
||||
let bodyUploadFiles = [];
|
||||
this.test.bodyUploadIds = [];
|
||||
this.test.scenarioDefinition.forEach(scenario => {
|
||||
scenario.requests.forEach(request => {
|
||||
if (request.body) {
|
||||
request.body.kvs.forEach( param => {
|
||||
if (param.files) {
|
||||
param.files.forEach(item => {
|
||||
if (item.file) {
|
||||
let fileId = getUUID().substring(0, 8);
|
||||
item.name = item.file.name;
|
||||
item.id = fileId;
|
||||
this.test.bodyUploadIds.push(fileId);
|
||||
bodyUploadFiles.push(item.file);
|
||||
// item.file = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return bodyUploadFiles;
|
||||
},
|
||||
resetBodyFile() {
|
||||
//下次保存不再上传已传文件
|
||||
this.test.scenarioDefinition.forEach(scenario => {
|
||||
scenario.requests.forEach(request => {
|
||||
if (request.body) {
|
||||
request.body.kvs.forEach( param => {
|
||||
if (param.files) {
|
||||
param.files.forEach(item => {
|
||||
if (item.file) {
|
||||
item.file = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.$router.push('/api/test/list/all');
|
||||
},
|
||||
|
@ -256,7 +302,7 @@
|
|||
let jmx = runningTest.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.$fileUpload(url, file, this.test,response => {
|
||||
this.$fileUpload(url, file, null, this.test,response => {
|
||||
this.debugReportId = response.data;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,15 +6,21 @@
|
|||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col>
|
||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
|
||||
@change="change"
|
||||
:placeholder="keyText" show-word-limit/>
|
||||
|
||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200" @change="change" :placeholder="keyText" show-word-limit>
|
||||
<template v-slot:prepend>
|
||||
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type">
|
||||
<el-option value="text" />
|
||||
<el-option value="file"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" :maxlength="200" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText"
|
||||
show-word-limit/>
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-col v-if="item.type !== 'file'">
|
||||
<el-autocomplete
|
||||
:disabled="isReadOnly"
|
||||
size="small"
|
||||
|
@ -28,6 +34,10 @@
|
|||
<i slot="suffix" class="el-input__icon el-icon-edit" style="cursor: pointer;" @click="advanced(item)"></i>
|
||||
</el-autocomplete>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="item.type === 'file'">
|
||||
<ms-api-body-file-upload :parameter="item"/>
|
||||
</el-col>
|
||||
<el-col class="kv-delete">
|
||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||
:disabled="isDisable(index) || isReadOnly"/>
|
||||
|
@ -44,10 +54,11 @@
|
|||
import {KeyValue, Scenario} from "../model/ScenarioModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
|
||||
import MsApiVariableAdvance from "@/business/components/api/test/components/ApiVariableAdvance";
|
||||
import MsApiBodyFileUpload from "./body/ApiBodyFileUpload";
|
||||
|
||||
export default {
|
||||
name: "MsApiVariable",
|
||||
components: {MsApiVariableAdvance},
|
||||
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
|
@ -55,6 +66,10 @@ export default {
|
|||
parameters: Array,
|
||||
environment: Object,
|
||||
scenario: Scenario,
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -93,7 +108,7 @@ export default {
|
|||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.parameters.push(new KeyValue());
|
||||
this.parameters.push(new KeyValue(null, null, 'text'));
|
||||
}
|
||||
this.$emit('change', this.parameters);
|
||||
// TODO 检查key重复
|
||||
|
@ -133,35 +148,38 @@ export default {
|
|||
},
|
||||
created() {
|
||||
if (this.parameters.length === 0) {
|
||||
this.parameters.push(new KeyValue());
|
||||
this.parameters.push(new KeyValue(null, null, 'text'));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.advanced-item-value >>> .el-dialog__body {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
.advanced-item-value >>> .el-dialog__body {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.el-row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.kv-type {
|
||||
width: 70px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
:environment="environment"
|
||||
:scenario="scenario"
|
||||
:extract="extract"
|
||||
type="body"
|
||||
:description="$t('api_test.request.parameters_desc')"
|
||||
v-if="body.isKV()"/>
|
||||
<div class="body-raw" v-if="body.type == 'Raw'">
|
||||
|
@ -26,10 +27,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "./ApiKeyValue";
|
||||
import {Body, BODY_FORMAT, BODY_TYPE, Scenario} from "../model/ScenarioModel";
|
||||
import MsCodeEdit from "../../../common/components/MsCodeEdit";
|
||||
import MsDropdown from "../../../common/components/MsDropdown";
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import {Body, BODY_FORMAT, BODY_TYPE, Scenario} from "../../model/ScenarioModel";
|
||||
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
|
||||
import MsDropdown from "../../../../common/components/MsDropdown";
|
||||
import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
|
||||
|
||||
export default {
|
||||
|
@ -66,6 +67,11 @@ export default {
|
|||
if (!this.body.format) {
|
||||
this.body.format = BODY_FORMAT.TEXT;
|
||||
}
|
||||
this.body.kvs.forEach(param => {
|
||||
if (!param.type) {
|
||||
param.type = 'text';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<span>
|
||||
<el-upload
|
||||
action="#"
|
||||
class="api-body-upload"
|
||||
list-type="picture-card"
|
||||
:http-request="upload"
|
||||
:beforeUpload="uploadValidate"
|
||||
:file-list="parameter.files"
|
||||
ref="upload">
|
||||
|
||||
<div class="upload-default">
|
||||
<i class="el-icon-plus"/>
|
||||
</div>
|
||||
<div class="upload-item" slot="file" slot-scope="{file}" >
|
||||
<span>{{file.file ? file.file.name : file.name}}</span>
|
||||
<span class="el-upload-list__item-actions">
|
||||
<!--<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">-->
|
||||
<!--<i class="el-icon-download"/>-->
|
||||
<!--</span>-->
|
||||
<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
|
||||
<i class="el-icon-delete"/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</el-upload>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsApiBodyFileUpload",
|
||||
data() {
|
||||
return {
|
||||
disabled: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
parameter: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRemove(file) {
|
||||
this.$refs.upload.handleRemove(file);
|
||||
for (let i = 0; i < this.parameter.files.length; i++) {
|
||||
if (file.name === this.parameter.files[i].name) {
|
||||
this.parameter.files.splice(i, 1);
|
||||
this.$refs.upload.handleRemove(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
upload(file) {
|
||||
this.parameter.files.push(file);
|
||||
},
|
||||
uploadValidate(file) {
|
||||
// if (file.size / 1024 / 1024 > 20) {
|
||||
// this.$warning(this.$t('test_track.case.import.upload_limit_size'));
|
||||
// return false;
|
||||
// }
|
||||
return true;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (!this.parameter.files) {
|
||||
this.parameter.files = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-upload {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.api-body-upload >>> .el-upload {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.upload-default {
|
||||
min-height: 32px;
|
||||
width: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.el-icon-plus {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.api-body-upload >>> .el-upload-list__item {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
padding: 6px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.api-body-upload >>> .el-upload-list--picture-card {
|
||||
}
|
||||
|
||||
.api-body-upload {
|
||||
min-height: 32px;
|
||||
/*border: 1px solid #EBEEF5;*/
|
||||
/*padding: 2px;*/
|
||||
/*border-radius: 4px;*/
|
||||
}
|
||||
|
||||
.upload-item {
|
||||
}
|
||||
|
||||
</style>
|
|
@ -237,7 +237,7 @@
|
|||
if (!this.swaggerUrlEable) {
|
||||
param.swaggerUrl = undefined;
|
||||
}
|
||||
this.result = this.$fileUpload('/api/import', param.file, param,response => {
|
||||
this.result = this.$fileUpload('/api/import', param.file, null, param,response => {
|
||||
let res = response.data;
|
||||
this.$success(this.$t('test_track.case.import.success'));
|
||||
this.visible = false;
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
|
||||
<script>
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import MsApiBody from "../ApiBody";
|
||||
import MsApiBody from "../body/ApiBody";
|
||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||
import {DubboRequest, Scenario} from "../../model/ScenarioModel";
|
||||
import MsApiExtract from "../extract/ApiExtract";
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<script>
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import MsApiBody from "../ApiBody";
|
||||
import MsApiBody from "../body/ApiBody";
|
||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||
import {HttpRequest, KeyValue, Scenario} from "../../model/ScenarioModel";
|
||||
import MsApiExtract from "../extract/ApiExtract";
|
||||
|
|
|
@ -328,6 +328,25 @@ export class HTTPSamplerArguments extends Element {
|
|||
}
|
||||
}
|
||||
|
||||
export class HTTPsamplerFiles extends Element {
|
||||
constructor(args) {
|
||||
super('elementProp', {
|
||||
name: "HTTPsampler.Files",
|
||||
elementType: "HTTPFileArgs",
|
||||
});
|
||||
|
||||
this.args = args || {};
|
||||
|
||||
let collectionProp = this.collectionProp('HTTPFileArgs.files');
|
||||
this.args.forEach(arg => {
|
||||
let elementProp = collectionProp.elementProp(arg.value, 'HTTPFileArg');
|
||||
elementProp.stringProp('File.path', arg.value);
|
||||
elementProp.stringProp('File.paramname', arg.name);
|
||||
elementProp.stringProp('File.mimetype', arg.metadata || "application/octet-stream");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class CookieManager extends DefaultTestElement {
|
||||
constructor(testName) {
|
||||
super('CookieManager', 'CookiePanel', 'CookieManager', testName);
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
Element,
|
||||
HashTree,
|
||||
HeaderManager,
|
||||
HTTPSamplerArguments,
|
||||
HTTPSamplerArguments, HTTPsamplerFiles,
|
||||
HTTPSamplerProxy,
|
||||
JSONPathAssertion,
|
||||
JSONPostProcessor,
|
||||
|
@ -38,6 +38,8 @@ export const uuid = function () {
|
|||
});
|
||||
}
|
||||
|
||||
export const BODY_FILE_DIR = "/opt/metersphere/data/body"; //存放body文件上传目录
|
||||
|
||||
export const calculate = function (itemValue) {
|
||||
if (!itemValue) {
|
||||
return;
|
||||
|
@ -528,7 +530,7 @@ export class Body extends BaseConfig {
|
|||
|
||||
export class KeyValue extends BaseConfig {
|
||||
constructor() {
|
||||
let options, key, value;
|
||||
let options, key, value, type;
|
||||
if (arguments.length === 1) {
|
||||
options = arguments[0];
|
||||
}
|
||||
|
@ -537,16 +539,27 @@ export class KeyValue extends BaseConfig {
|
|||
key = arguments[0];
|
||||
value = arguments[1];
|
||||
}
|
||||
if (arguments.length === 3) {
|
||||
key = arguments[0];
|
||||
value = arguments[1];
|
||||
type = arguments[2];
|
||||
}
|
||||
|
||||
super();
|
||||
this.name = key;
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.files = undefined;
|
||||
|
||||
this.set(options);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this.name || !!this.value;
|
||||
return (!!this.name || !!this.value) && this.type !== 'file';
|
||||
}
|
||||
|
||||
isFile() {
|
||||
return (!!this.name || !!this.value) && this.type === 'file';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -807,13 +820,13 @@ class JMXGenerator {
|
|||
if (!test || !test.id || !(test instanceof Test)) return undefined;
|
||||
|
||||
let testPlan = new TestPlan(test.name);
|
||||
this.addScenarios(testPlan, test.scenarioDefinition);
|
||||
this.addScenarios(testPlan, test.id, test.scenarioDefinition);
|
||||
|
||||
this.jmeterTestPlan = new JMeterTestPlan();
|
||||
this.jmeterTestPlan.put(testPlan);
|
||||
}
|
||||
|
||||
addScenarios(testPlan, scenarios) {
|
||||
addScenarios(testPlan, testId, scenarios) {
|
||||
scenarios.forEach(s => {
|
||||
|
||||
if (s.enable) {
|
||||
|
@ -842,7 +855,7 @@ class JMXGenerator {
|
|||
if (request.method.toUpperCase() === 'GET') {
|
||||
this.addRequestArguments(sampler, request);
|
||||
} else {
|
||||
this.addRequestBody(sampler, request);
|
||||
this.addRequestBody(sampler, request, testId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -965,10 +978,11 @@ class JMXGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
addRequestBody(httpSamplerProxy, request) {
|
||||
addRequestBody(httpSamplerProxy, request, testId) {
|
||||
let body = [];
|
||||
if (request.body.isKV()) {
|
||||
body = this.filterKV(request.body.kvs);
|
||||
this.addRequestBodyFile(httpSamplerProxy, request, testId);
|
||||
} else {
|
||||
httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
|
||||
body.push({name: '', value: request.body.raw, encode: false});
|
||||
|
@ -977,6 +991,22 @@ class JMXGenerator {
|
|||
httpSamplerProxy.add(new HTTPSamplerArguments(body));
|
||||
}
|
||||
|
||||
addRequestBodyFile(httpSamplerProxy, request, testId) {
|
||||
let files = [];
|
||||
let kvs = this.filterKVFile(request.body.kvs);
|
||||
kvs.forEach(kv => {
|
||||
if (kv.files) {
|
||||
kv.files.forEach(file => {
|
||||
let arg = {};
|
||||
arg.name = kv.name;
|
||||
arg.value = BODY_FILE_DIR + '/' + testId + '/' + file.id + '_' + file.name;
|
||||
files.push(arg);
|
||||
});
|
||||
}
|
||||
});
|
||||
httpSamplerProxy.add(new HTTPsamplerFiles(files));
|
||||
}
|
||||
|
||||
addRequestAssertion(httpSamplerProxy, request) {
|
||||
let assertions = request.assertions;
|
||||
if (assertions.regex.length > 0) {
|
||||
|
@ -1066,6 +1096,12 @@ class JMXGenerator {
|
|||
return kvs.filter(this.filter);
|
||||
}
|
||||
|
||||
filterKVFile(kvs) {
|
||||
return kvs.filter(kv => {
|
||||
return kv.isFile();
|
||||
});
|
||||
}
|
||||
|
||||
toXML() {
|
||||
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
||||
xml += this.jmeterTestPlan.toXML();
|
||||
|
|
|
@ -22,11 +22,12 @@
|
|||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="12" :offset="2">
|
||||
<el-button :disabled="isReadOnly" type="primary" plain @click="save">{{$t('commons.save')}}</el-button>
|
||||
<el-button :disabled="isReadOnly" type="primary" plain @click="save">{{ $t('commons.save') }}</el-button>
|
||||
<el-button :disabled="isReadOnly" type="primary" plain @click="saveAndRun">
|
||||
{{$t('load_test.save_and_run')}}
|
||||
{{ $t('load_test.save_and_run') }}
|
||||
</el-button>
|
||||
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{ $t('commons.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
|
||||
|
||||
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
|
||||
:check-open="checkScheduleEdit" :custom-validate="durationValidate"/>
|
||||
|
@ -52,272 +53,272 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import PerformanceBasicConfig from "./components/PerformanceBasicConfig";
|
||||
import PerformancePressureConfig from "./components/PerformancePressureConfig";
|
||||
import PerformanceAdvancedConfig from "./components/PerformanceAdvancedConfig";
|
||||
import MsContainer from "../../common/components/MsContainer";
|
||||
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
|
||||
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
||||
import PerformanceBasicConfig from "./components/PerformanceBasicConfig";
|
||||
import PerformancePressureConfig from "./components/PerformancePressureConfig";
|
||||
import PerformanceAdvancedConfig from "./components/PerformanceAdvancedConfig";
|
||||
import MsContainer from "../../common/components/MsContainer";
|
||||
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||
import {checkoutTestManagerOrTestUser} from "../../../../common/js/utils";
|
||||
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
||||
|
||||
export default {
|
||||
name: "EditPerformanceTestPlan",
|
||||
components: {
|
||||
MsScheduleConfig,
|
||||
PerformancePressureConfig,
|
||||
PerformanceBasicConfig,
|
||||
PerformanceAdvancedConfig,
|
||||
MsContainer,
|
||||
MsMainContainer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
testPlan: {schedule: {}},
|
||||
listProjectPath: "/project/listAll",
|
||||
savePath: "/performance/save",
|
||||
editPath: "/performance/edit",
|
||||
runPath: "/performance/run",
|
||||
projects: [],
|
||||
active: '0',
|
||||
testId: '',
|
||||
isReadOnly: false,
|
||||
tabs: [{
|
||||
title: this.$t('load_test.basic_config'),
|
||||
id: '0',
|
||||
component: 'PerformanceBasicConfig'
|
||||
}, {
|
||||
title: this.$t('load_test.pressure_config'),
|
||||
id: '1',
|
||||
component: 'PerformancePressureConfig'
|
||||
}, {
|
||||
title: this.$t('load_test.advanced_config'),
|
||||
id: '2',
|
||||
component: 'PerformanceAdvancedConfig'
|
||||
}]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route'(to) {
|
||||
// 如果是创建测试
|
||||
if (to.name === 'createPerTest') {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (to.name !== 'editPerTest') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isReadOnly = false;
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.isReadOnly = true;
|
||||
}
|
||||
this.getTest(to.params.testId);
|
||||
export default {
|
||||
name: "EditPerformanceTestPlan",
|
||||
components: {
|
||||
MsScheduleConfig,
|
||||
PerformancePressureConfig,
|
||||
PerformanceBasicConfig,
|
||||
PerformanceAdvancedConfig,
|
||||
MsContainer,
|
||||
MsMainContainer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
testPlan: {schedule: {}},
|
||||
listProjectPath: "/project/listAll",
|
||||
savePath: "/performance/save",
|
||||
editPath: "/performance/edit",
|
||||
runPath: "/performance/run",
|
||||
projects: [],
|
||||
active: '0',
|
||||
testId: '',
|
||||
isReadOnly: false,
|
||||
tabs: [{
|
||||
title: this.$t('load_test.basic_config'),
|
||||
id: '0',
|
||||
component: 'PerformanceBasicConfig'
|
||||
}, {
|
||||
title: this.$t('load_test.pressure_config'),
|
||||
id: '1',
|
||||
component: 'PerformancePressureConfig'
|
||||
}, {
|
||||
title: this.$t('load_test.advanced_config'),
|
||||
id: '2',
|
||||
component: 'PerformanceAdvancedConfig'
|
||||
}]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route'(to) {
|
||||
// 如果是创建测试
|
||||
if (to.name === 'createPerTest') {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (to.name !== 'editPerTest') {
|
||||
return;
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.isReadOnly = false;
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.isReadOnly = true;
|
||||
}
|
||||
this.getTest(this.$route.params.testId);
|
||||
this.listProjects();
|
||||
this.getTest(to.params.testId);
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
this.isReadOnly = false;
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.isReadOnly = true;
|
||||
}
|
||||
this.getTest(this.$route.params.testId);
|
||||
this.listProjects();
|
||||
},
|
||||
mounted() {
|
||||
this.importAPITest();
|
||||
},
|
||||
methods: {
|
||||
importAPITest() {
|
||||
let apiTest = this.$store.state.api.test;
|
||||
if (apiTest && apiTest.name) {
|
||||
this.$set(this.testPlan, "projectId", apiTest.projectId);
|
||||
this.$set(this.testPlan, "name", apiTest.name);
|
||||
let blob = new Blob([apiTest.jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], apiTest.jmx.name);
|
||||
this.$refs.basicConfig.beforeUpload(file);
|
||||
this.$refs.basicConfig.handleUpload({file: file});
|
||||
this.active = '1';
|
||||
this.$store.commit("clearTest");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.importAPITest();
|
||||
},
|
||||
methods: {
|
||||
importAPITest() {
|
||||
let apiTest = this.$store.state.api.test;
|
||||
if (apiTest && apiTest.name) {
|
||||
this.$set(this.testPlan, "projectId", apiTest.projectId);
|
||||
this.$set(this.testPlan, "name", apiTest.name);
|
||||
let blob = new Blob([apiTest.jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], apiTest.jmx.name);
|
||||
this.$refs.basicConfig.beforeUpload(file);
|
||||
this.$refs.basicConfig.handleUpload({file: file});
|
||||
this.active = '1';
|
||||
this.$store.commit("clearTest");
|
||||
}
|
||||
},
|
||||
getTest(testId) {
|
||||
if (testId) {
|
||||
this.testId = testId;
|
||||
this.result = this.$get('/performance/get/' + testId, response => {
|
||||
if (response.data) {
|
||||
this.testPlan = response.data;
|
||||
if (!this.testPlan.schedule) {
|
||||
this.testPlan.schedule = {};
|
||||
}
|
||||
getTest(testId) {
|
||||
if (testId) {
|
||||
this.testId = testId;
|
||||
this.result = this.$get('/performance/get/' + testId, response => {
|
||||
if (response.data) {
|
||||
this.testPlan = response.data;
|
||||
if (!this.testPlan.schedule) {
|
||||
this.testPlan.schedule = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
listProjects() {
|
||||
this.result = this.$get(this.listProjectPath, response => {
|
||||
this.projects = response.data;
|
||||
})
|
||||
},
|
||||
save() {
|
||||
if (!this.validTestPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = this.getSaveOption();
|
||||
|
||||
this.result = this.$request(options, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$refs.advancedConfig.cancelAllEdit();
|
||||
this.$router.push({path: '/performance/test/all'})
|
||||
});
|
||||
},
|
||||
saveAndRun() {
|
||||
if (!this.validTestPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = this.getSaveOption();
|
||||
|
||||
this.result = this.$request(options, (response) => {
|
||||
this.testPlan.id = response.data;
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, (response) => {
|
||||
let reportId = response.data;
|
||||
this.$router.push({path: '/performance/report/view/' + reportId})
|
||||
})
|
||||
});
|
||||
},
|
||||
getSaveOption() {
|
||||
let formData = new FormData();
|
||||
let url = this.testPlan.id ? this.editPath : this.savePath;
|
||||
|
||||
if (this.$refs.basicConfig.uploadList.length > 0) {
|
||||
this.$refs.basicConfig.uploadList.forEach(f => {
|
||||
formData.append("file", f);
|
||||
});
|
||||
}
|
||||
// 基本配置
|
||||
this.testPlan.updatedFileList = this.$refs.basicConfig.updatedFileList();
|
||||
// 压力配置
|
||||
this.testPlan.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
|
||||
this.testPlan.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
|
||||
// 高级配置
|
||||
this.testPlan.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
|
||||
|
||||
// file属性不需要json化
|
||||
let requestJson = JSON.stringify(this.testPlan, function (key, value) {
|
||||
return key === "file" ? undefined : value
|
||||
});
|
||||
|
||||
formData.append('request', new Blob([requestJson], {
|
||||
type: "application/json"
|
||||
}));
|
||||
|
||||
return {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
cancel() {
|
||||
});
|
||||
}
|
||||
},
|
||||
listProjects() {
|
||||
this.result = this.$get(this.listProjectPath, response => {
|
||||
this.projects = response.data;
|
||||
})
|
||||
},
|
||||
save() {
|
||||
if (!this.validTestPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = this.getSaveOption();
|
||||
|
||||
this.result = this.$request(options, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$refs.advancedConfig.cancelAllEdit();
|
||||
this.$router.push({path: '/performance/test/all'})
|
||||
},
|
||||
validTestPlan() {
|
||||
if (!this.testPlan.name) {
|
||||
this.$error(this.$t('load_test.test_name_is_null'));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
saveAndRun() {
|
||||
if (!this.validTestPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.testPlan.projectId) {
|
||||
this.$error(this.$t('load_test.project_is_null'));
|
||||
return false;
|
||||
}
|
||||
let options = this.getSaveOption();
|
||||
|
||||
if (!this.$refs.basicConfig.validConfig()) {
|
||||
return false;
|
||||
}
|
||||
this.result = this.$request(options, (response) => {
|
||||
this.testPlan.id = response.data;
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, (response) => {
|
||||
let reportId = response.data;
|
||||
this.$router.push({path: '/performance/report/view/' + reportId})
|
||||
})
|
||||
});
|
||||
},
|
||||
getSaveOption() {
|
||||
let formData = new FormData();
|
||||
let url = this.testPlan.id ? this.editPath : this.savePath;
|
||||
|
||||
if (!this.$refs.pressureConfig.validConfig()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$refs.advancedConfig.validConfig()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// todo: 其他校验
|
||||
|
||||
return true;
|
||||
},
|
||||
changeTabActive(activeName) {
|
||||
this.$nextTick(() => {
|
||||
this.active = activeName;
|
||||
if (this.$refs.basicConfig.uploadList.length > 0) {
|
||||
this.$refs.basicConfig.uploadList.forEach(f => {
|
||||
formData.append("file", f);
|
||||
});
|
||||
},
|
||||
saveCronExpression(cronExpression) {
|
||||
this.testPlan.schedule.enable = true;
|
||||
this.testPlan.schedule.value = cronExpression;
|
||||
this.saveSchedule();
|
||||
},
|
||||
saveSchedule() {
|
||||
this.checkScheduleEdit();
|
||||
let param = {};
|
||||
param = this.testPlan.schedule;
|
||||
param.resourceId = this.testPlan.id;
|
||||
let url = '/performance/schedule/create';
|
||||
if (param.id) {
|
||||
url = '/performance/schedule/update';
|
||||
}
|
||||
this.$post(url, param, response => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.getTest(this.testPlan.id);
|
||||
});
|
||||
},
|
||||
checkScheduleEdit() {
|
||||
if (!this.testPlan.id) {
|
||||
this.$message(this.$t('api_test.environment.please_save_test'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
durationValidate(intervalTime) {
|
||||
let duration = this.$refs.pressureConfig.duration * 60 * 1000;
|
||||
if (intervalTime < duration) {
|
||||
return {
|
||||
pass: false,
|
||||
info: this.$t('load_test.schedule_tip')
|
||||
}
|
||||
}
|
||||
// 基本配置
|
||||
this.testPlan.updatedFileList = this.$refs.basicConfig.updatedFileList();
|
||||
// 压力配置
|
||||
this.testPlan.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
|
||||
this.testPlan.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
|
||||
// 高级配置
|
||||
this.testPlan.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
|
||||
|
||||
// file属性不需要json化
|
||||
let requestJson = JSON.stringify(this.testPlan, function (key, value) {
|
||||
return key === "file" ? undefined : value
|
||||
});
|
||||
|
||||
formData.append('request', new Blob([requestJson], {
|
||||
type: "application/json"
|
||||
}));
|
||||
|
||||
return {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
cancel() {
|
||||
this.$router.push({path: '/performance/test/all'})
|
||||
},
|
||||
validTestPlan() {
|
||||
if (!this.testPlan.name) {
|
||||
this.$error(this.$t('load_test.test_name_is_null'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.testPlan.projectId) {
|
||||
this.$error(this.$t('load_test.project_is_null'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$refs.basicConfig.validConfig()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$refs.pressureConfig.validConfig()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$refs.advancedConfig.validConfig()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// todo: 其他校验
|
||||
|
||||
return true;
|
||||
},
|
||||
changeTabActive(activeName) {
|
||||
this.$nextTick(() => {
|
||||
this.active = activeName;
|
||||
});
|
||||
},
|
||||
saveCronExpression(cronExpression) {
|
||||
this.testPlan.schedule.enable = true;
|
||||
this.testPlan.schedule.value = cronExpression;
|
||||
this.saveSchedule();
|
||||
},
|
||||
saveSchedule() {
|
||||
this.checkScheduleEdit();
|
||||
let param = {};
|
||||
param = this.testPlan.schedule;
|
||||
param.resourceId = this.testPlan.id;
|
||||
let url = '/performance/schedule/create';
|
||||
if (param.id) {
|
||||
url = '/performance/schedule/update';
|
||||
}
|
||||
this.$post(url, param, response => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.getTest(this.testPlan.id);
|
||||
});
|
||||
},
|
||||
checkScheduleEdit() {
|
||||
if (!this.testPlan.id) {
|
||||
this.$message(this.$t('api_test.environment.please_save_test'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
durationValidate(intervalTime) {
|
||||
let duration = this.$refs.pressureConfig.duration * 60 * 1000;
|
||||
if (intervalTime < duration) {
|
||||
return {
|
||||
pass: true
|
||||
pass: false,
|
||||
info: this.$t('load_test.schedule_tip')
|
||||
}
|
||||
}
|
||||
return {
|
||||
pass: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.testplan-config {
|
||||
margin-top: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.testplan-config {
|
||||
margin-top: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.el-select {
|
||||
min-width: 130px;
|
||||
}
|
||||
.el-select {
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
.edit-testplan-container .input-with-select .el-input-group__prepend {
|
||||
background-color: #fff;
|
||||
}
|
||||
.edit-testplan-container .input-with-select .el-input-group__prepend {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.advanced-config {
|
||||
height: calc(100vh - 280px);
|
||||
overflow: auto;
|
||||
}
|
||||
.advanced-config {
|
||||
height: calc(100vh - 280px);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</el-tag>
|
||||
<el-tooltip placement="top" v-else-if="row.status === 'Error'" effect="light">
|
||||
<template v-slot:content>
|
||||
<div>{{row.description}}</div>
|
||||
<div>{{ row.description }}</div>
|
||||
</template>
|
||||
<el-tag size="mini" type="danger">
|
||||
{{ row.status }}
|
||||
|
@ -30,13 +30,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsPerformanceTestStatus",
|
||||
export default {
|
||||
name: "MsPerformanceTestStatus",
|
||||
|
||||
props: {
|
||||
row: Object
|
||||
}
|
||||
props: {
|
||||
row: Object
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
<div>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<h3>{{$t('load_test.params')}}</h3>
|
||||
<el-button :disabled="readOnly" icon="el-icon-circle-plus-outline" plain size="mini" @click="add('params')">{{$t('commons.add')}}
|
||||
<h3>{{ $t('load_test.params') }}</h3>
|
||||
<el-button :disabled="readOnly" icon="el-icon-circle-plus-outline" plain size="mini" @click="add('params')">
|
||||
{{ $t('commons.add') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
@ -26,7 +27,7 @@
|
|||
:placeholder="$t('load_test.param_name')"
|
||||
clearable>
|
||||
</el-input>
|
||||
<span>{{row.name}}</span>
|
||||
<span>{{ row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
@ -60,12 +61,13 @@
|
|||
v-model="row.value"
|
||||
:placeholder="$t('load_test.param_value')"
|
||||
clearable></el-input>
|
||||
<span>{{row.value}}</span>
|
||||
<span>{{ row.value }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('load_test.operating')">
|
||||
<template v-slot:default="{row, $index}">
|
||||
<ms-table-operator-button :disabled="readOnly" :tip="$t('commons.delete')" icon="el-icon-delete" type="danger"
|
||||
<ms-table-operator-button :disabled="readOnly" :tip="$t('commons.delete')" icon="el-icon-delete"
|
||||
type="danger"
|
||||
@exec="del(row, 'params', $index)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -77,10 +79,11 @@
|
|||
<el-col :span="8">
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div>{{$t('load_test.connect_timeout')}}</div>
|
||||
<div>{{ $t('load_test.connect_timeout') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number :disabled="readOnly" size="mini" v-model="timeout" :min="10" :max="100000"></el-input-number>
|
||||
<el-input-number :disabled="readOnly" size="mini" v-model="timeout" :min="10"
|
||||
:max="100000"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
ms
|
||||
|
@ -92,10 +95,11 @@
|
|||
<el-col :span="8">
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div>{{$t('load_test.custom_http_code')}}</div>
|
||||
<div>{{ $t('load_test.custom_http_code') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input :disabled="readOnly" size="mini" v-model="statusCodeStr" :placeholder="$t('load_test.separated_by_commas')"
|
||||
<el-input :disabled="readOnly" size="mini" v-model="statusCodeStr"
|
||||
:placeholder="$t('load_test.separated_by_commas')"
|
||||
@input="checkStatusCode"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
@ -105,169 +109,169 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
|
||||
import MsTableOperatorButton from "../../../common/components/MsTableOperatorButton";
|
||||
|
||||
export default {
|
||||
name: "PerformanceAdvancedConfig",
|
||||
components: {MsTableOperatorButton},
|
||||
data() {
|
||||
return {
|
||||
timeout: 2000,
|
||||
statusCode: [],
|
||||
domains: [],
|
||||
params: [],
|
||||
statusCodeStr: '',
|
||||
}
|
||||
export default {
|
||||
name: "PerformanceAdvancedConfig",
|
||||
components: {MsTableOperatorButton},
|
||||
data() {
|
||||
return {
|
||||
timeout: 2000,
|
||||
statusCode: [],
|
||||
domains: [],
|
||||
params: [],
|
||||
statusCodeStr: '',
|
||||
}
|
||||
},
|
||||
props: {
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
props: {
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
testId: String,
|
||||
},
|
||||
mounted() {
|
||||
testId: String,
|
||||
},
|
||||
mounted() {
|
||||
if (this.testId) {
|
||||
this.getAdvancedConfig();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testId() {
|
||||
if (this.testId) {
|
||||
this.getAdvancedConfig();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testId () {
|
||||
if (this.testId) {
|
||||
this.getAdvancedConfig();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAdvancedConfig() {
|
||||
this.$get('/performance/get-advanced-config/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
this.timeout = data.timeout || 10;
|
||||
this.statusCode = data.statusCode || [];
|
||||
this.statusCodeStr = this.statusCode.join(',');
|
||||
this.domains = data.domains || [];
|
||||
this.params = data.params || [];
|
||||
/*this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);*/
|
||||
}
|
||||
});
|
||||
},
|
||||
add(dataName) {
|
||||
if (dataName === 'domains') {
|
||||
this[dataName].push({
|
||||
domain: 'fit2cloud.com',
|
||||
enable: true,
|
||||
ip: '127.0.0.1',
|
||||
edit: true,
|
||||
});
|
||||
}
|
||||
if (dataName === 'params') {
|
||||
this[dataName].push({
|
||||
name: 'param1',
|
||||
enable: true,
|
||||
value: '0',
|
||||
edit: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAdvancedConfig() {
|
||||
this.$get('/performance/get-advanced-config/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
this.timeout = data.timeout || 10;
|
||||
this.statusCode = data.statusCode || [];
|
||||
this.statusCodeStr = this.statusCode.join(',');
|
||||
this.domains = data.domains || [];
|
||||
this.params = data.params || [];
|
||||
/*this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);*/
|
||||
}
|
||||
});
|
||||
},
|
||||
add(dataName) {
|
||||
if (dataName === 'domains') {
|
||||
this[dataName].push({
|
||||
domain: 'fit2cloud.com',
|
||||
enable: true,
|
||||
ip: '127.0.0.1',
|
||||
edit: true,
|
||||
});
|
||||
edit(row) {
|
||||
row.edit = !row.edit
|
||||
},
|
||||
del(row, dataName, index) {
|
||||
this[dataName].splice(index, 1);
|
||||
},
|
||||
confirmEdit(row) {
|
||||
row.edit = false;
|
||||
row.enable = true;
|
||||
},
|
||||
groupBy(data, key) {
|
||||
return data.reduce((p, c) => {
|
||||
let name = c[key];
|
||||
if (!p.hasOwnProperty(name)) {
|
||||
p[name] = 0;
|
||||
}
|
||||
if (dataName === 'params') {
|
||||
this[dataName].push({
|
||||
name: 'param1',
|
||||
enable: true,
|
||||
value: '0',
|
||||
edit: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
edit(row) {
|
||||
row.edit = !row.edit
|
||||
},
|
||||
del(row, dataName, index) {
|
||||
this[dataName].splice(index, 1);
|
||||
},
|
||||
confirmEdit(row) {
|
||||
row.edit = false;
|
||||
row.enable = true;
|
||||
},
|
||||
groupBy(data, key) {
|
||||
return data.reduce((p, c) => {
|
||||
let name = c[key];
|
||||
if (!p.hasOwnProperty(name)) {
|
||||
p[name] = 0;
|
||||
}
|
||||
p[name]++;
|
||||
return p;
|
||||
}, {});
|
||||
},
|
||||
validConfig() {
|
||||
let counts = this.groupBy(this.domains, 'domain');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.domain_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
counts = this.groupBy(this.params, 'name');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.param_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.domains.filter(d => !d.domain || !d.ip).length > 0) {
|
||||
this.$error(this.$t('load_test.domain_ip_is_empty'));
|
||||
p[name]++;
|
||||
return p;
|
||||
}, {});
|
||||
},
|
||||
validConfig() {
|
||||
let counts = this.groupBy(this.domains, 'domain');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.domain_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
if (this.params.filter(d => !d.name || !d.value).length > 0) {
|
||||
this.$error(this.$t('load_test.param_name_value_is_empty'));
|
||||
}
|
||||
counts = this.groupBy(this.params, 'name');
|
||||
for (let c in counts) {
|
||||
if (counts[c] > 1) {
|
||||
this.$error(this.$t('load_test.param_is_duplicate'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
checkStatusCode() {
|
||||
let license_num = this.statusCodeStr;
|
||||
license_num = license_num.replace(/[^\d,]/g, ''); // 清除“数字”和“.”以外的字符
|
||||
this.statusCodeStr = license_num;
|
||||
},
|
||||
cancelAllEdit() {
|
||||
this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);
|
||||
},
|
||||
configurations() {
|
||||
let statusCode = [];
|
||||
if (this.statusCodeStr) {
|
||||
statusCode = this.statusCodeStr.split(',');
|
||||
}
|
||||
return {
|
||||
timeout: this.timeout,
|
||||
statusCode: statusCode,
|
||||
params: this.params,
|
||||
domains: this.domains,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
if (this.domains.filter(d => !d.domain || !d.ip).length > 0) {
|
||||
this.$error(this.$t('load_test.domain_ip_is_empty'));
|
||||
return false;
|
||||
}
|
||||
if (this.params.filter(d => !d.name || !d.value).length > 0) {
|
||||
this.$error(this.$t('load_test.param_name_value_is_empty'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
checkStatusCode() {
|
||||
let license_num = this.statusCodeStr;
|
||||
license_num = license_num.replace(/[^\d,]/g, ''); // 清除“数字”和“.”以外的字符
|
||||
this.statusCodeStr = license_num;
|
||||
},
|
||||
cancelAllEdit() {
|
||||
this.domains.forEach(d => d.edit = false);
|
||||
this.params.forEach(d => d.edit = false);
|
||||
},
|
||||
configurations() {
|
||||
let statusCode = [];
|
||||
if (this.statusCodeStr) {
|
||||
statusCode = this.statusCodeStr.split(',');
|
||||
}
|
||||
return {
|
||||
timeout: this.timeout,
|
||||
statusCode: statusCode,
|
||||
params: this.params,
|
||||
domains: this.domains,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.el-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
padding-right: 0px;
|
||||
}
|
||||
.edit-input {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.tb-edit .el-textarea {
|
||||
display: none;
|
||||
}
|
||||
.tb-edit .el-textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tb-edit .current-row .el-textarea {
|
||||
display: block;
|
||||
}
|
||||
.tb-edit .current-row .el-textarea {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tb-edit .current-row .el-textarea + span {
|
||||
display: none;
|
||||
}
|
||||
.tb-edit .current-row .el-textarea + span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-col {
|
||||
text-align: left;
|
||||
}
|
||||
.el-col {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-col .el-table {
|
||||
align: center;
|
||||
}
|
||||
.el-col .el-table {
|
||||
align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<i class="el-icon-upload"/>
|
||||
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">{{$t('load_test.upload_type')}}</div>
|
||||
<div class="el-upload__tip">{{ $t('load_test.upload_type') }}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
|
@ -42,9 +42,11 @@
|
|||
<el-table-column
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button @click="handleDownload(scope.row)" :disabled="!scope.row.id || isReadOnly" type="primary" icon="el-icon-download"
|
||||
<el-button @click="handleDownload(scope.row)" :disabled="!scope.row.id || isReadOnly" type="primary"
|
||||
icon="el-icon-download"
|
||||
size="mini" circle/>
|
||||
<el-button :disabled="isReadOnly" @click="handleDelete(scope.row, scope.$index)" type="danger" icon="el-icon-delete" size="mini"
|
||||
<el-button :disabled="isReadOnly" @click="handleDelete(scope.row, scope.$index)" type="danger"
|
||||
icon="el-icon-delete" size="mini"
|
||||
circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -53,170 +55,170 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {Message} from "element-ui";
|
||||
import {Message} from "element-ui";
|
||||
|
||||
export default {
|
||||
name: "PerformanceBasicConfig",
|
||||
props: {
|
||||
testPlan: {
|
||||
type: Object
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
export default {
|
||||
name: "PerformanceBasicConfig",
|
||||
props: {
|
||||
testPlan: {
|
||||
type: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
getFileMetadataPath: "/performance/file/metadata",
|
||||
jmxDownloadPath: '/performance/file/download',
|
||||
jmxDeletePath: '/performance/file/delete',
|
||||
fileList: [],
|
||||
tableData: [],
|
||||
uploadList: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
getFileMetadataPath: "/performance/file/metadata",
|
||||
jmxDownloadPath: '/performance/file/download',
|
||||
jmxDeletePath: '/performance/file/delete',
|
||||
fileList: [],
|
||||
tableData: [],
|
||||
uploadList: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.testPlan.id) {
|
||||
this.getFileMetadata(this.testPlan)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testPlan() {
|
||||
if (this.testPlan.id) {
|
||||
this.getFileMetadata(this.testPlan)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testPlan() {
|
||||
if (this.testPlan.id) {
|
||||
this.getFileMetadata(this.testPlan)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFileMetadata(testPlan) {
|
||||
this.fileList = [];
|
||||
this.tableData = [];
|
||||
this.uploadList = [];
|
||||
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
|
||||
let files = response.data;
|
||||
|
||||
if (!files) {
|
||||
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
|
||||
return;
|
||||
}
|
||||
// deep copy
|
||||
this.fileList = JSON.parse(JSON.stringify(files));
|
||||
this.tableData = JSON.parse(JSON.stringify(files));
|
||||
this.tableData.map(f => {
|
||||
f.size = f.size + ' Bytes';
|
||||
});
|
||||
})
|
||||
},
|
||||
beforeUpload(file) {
|
||||
if (!this.fileValidator(file)) {
|
||||
/// todo: 显示错误信息
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.tableData.filter(f => f.name === file.name).length > 0) {
|
||||
this.$error(this.$t('load_test.delete_file'));
|
||||
return false;
|
||||
}
|
||||
|
||||
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
|
||||
|
||||
this.tableData.push({
|
||||
name: file.name,
|
||||
size: file.size + ' Bytes', /// todo: 按照大小显示Byte、KB、MB等
|
||||
type: type.toUpperCase(),
|
||||
updateTime: file.lastModified,
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
handleUpload(uploadResources) {
|
||||
this.uploadList.push(uploadResources.file);
|
||||
},
|
||||
handleDownload(file) {
|
||||
let data = {
|
||||
name: file.name,
|
||||
id: file.id,
|
||||
};
|
||||
let config = {
|
||||
url: this.jmxDownloadPath,
|
||||
method: 'post',
|
||||
data: data,
|
||||
responseType: 'blob'
|
||||
};
|
||||
this.result = this.$request(config).then(response => {
|
||||
const content = response.data;
|
||||
const blob = new Blob([content]);
|
||||
if ("download" in document.createElement("a")) {
|
||||
// 非IE下载
|
||||
// chrome/firefox
|
||||
let aTag = document.createElement('a');
|
||||
aTag.download = file.name;
|
||||
aTag.href = URL.createObjectURL(blob);
|
||||
aTag.click();
|
||||
URL.revokeObjectURL(aTag.href)
|
||||
} else {
|
||||
// IE10+下载
|
||||
navigator.msSaveBlob(blob, this.filename)
|
||||
}
|
||||
}).catch(e => {
|
||||
Message.error({message: e.message, showClose: true});
|
||||
});
|
||||
},
|
||||
handleDelete(file, index) {
|
||||
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(file, index);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleDelete(file, index) {
|
||||
this.fileList.splice(index, 1);
|
||||
this.tableData.splice(index, 1);
|
||||
//
|
||||
let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
|
||||
if (i > -1) {
|
||||
this.uploadList.splice(i, 1);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFileMetadata(testPlan) {
|
||||
this.fileList = [];
|
||||
this.tableData = [];
|
||||
this.uploadList = [];
|
||||
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
|
||||
let files = response.data;
|
||||
|
||||
if (!files) {
|
||||
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
|
||||
return;
|
||||
}
|
||||
// deep copy
|
||||
this.fileList = JSON.parse(JSON.stringify(files));
|
||||
this.tableData = JSON.parse(JSON.stringify(files));
|
||||
this.tableData.map(f => {
|
||||
f.size = f.size + ' Bytes';
|
||||
});
|
||||
})
|
||||
},
|
||||
beforeUpload(file) {
|
||||
if (!this.fileValidator(file)) {
|
||||
/// todo: 显示错误信息
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.tableData.filter(f => f.name === file.name).length > 0) {
|
||||
this.$error(this.$t('load_test.delete_file'));
|
||||
return false;
|
||||
}
|
||||
|
||||
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
|
||||
|
||||
this.tableData.push({
|
||||
name: file.name,
|
||||
size: file.size + ' Bytes', /// todo: 按照大小显示Byte、KB、MB等
|
||||
type: type.toUpperCase(),
|
||||
updateTime: file.lastModified,
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
handleUpload(uploadResources) {
|
||||
this.uploadList.push(uploadResources.file);
|
||||
},
|
||||
handleDownload(file) {
|
||||
let data = {
|
||||
name: file.name,
|
||||
id: file.id,
|
||||
};
|
||||
let config = {
|
||||
url: this.jmxDownloadPath,
|
||||
method: 'post',
|
||||
data: data,
|
||||
responseType: 'blob'
|
||||
};
|
||||
this.result = this.$request(config).then(response => {
|
||||
const content = response.data;
|
||||
const blob = new Blob([content]);
|
||||
if ("download" in document.createElement("a")) {
|
||||
// 非IE下载
|
||||
// chrome/firefox
|
||||
let aTag = document.createElement('a');
|
||||
aTag.download = file.name;
|
||||
aTag.href = URL.createObjectURL(blob);
|
||||
aTag.click();
|
||||
URL.revokeObjectURL(aTag.href)
|
||||
} else {
|
||||
// IE10+下载
|
||||
navigator.msSaveBlob(blob, this.filename)
|
||||
}
|
||||
}).catch(e => {
|
||||
Message.error({message: e.message, showClose: true});
|
||||
});
|
||||
},
|
||||
handleDelete(file, index) {
|
||||
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(file, index);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleDelete(file, index) {
|
||||
this.fileList.splice(index, 1);
|
||||
this.tableData.splice(index, 1);
|
||||
//
|
||||
let i = this.uploadList.findIndex(upLoadFile => upLoadFile.name === file.name);
|
||||
if (i > -1) {
|
||||
this.uploadList.splice(i, 1);
|
||||
}
|
||||
},
|
||||
handleExceed() {
|
||||
this.$error(this.$t('load_test.file_size_limit'));
|
||||
},
|
||||
fileValidator(file) {
|
||||
/// todo: 是否需要对文件内容和大小做限制
|
||||
return file.size > 0;
|
||||
},
|
||||
updatedFileList() {
|
||||
return this.fileList;// 表示修改了已经上传的文件列表
|
||||
},
|
||||
validConfig() {
|
||||
let newJmxNum = 0, oldJmxNum = 0;
|
||||
if (this.uploadList.length > 0) {
|
||||
newJmxNum = this.uploadList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
|
||||
}
|
||||
if (this.fileList.length > 0) {
|
||||
oldJmxNum = this.fileList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
|
||||
}
|
||||
if (newJmxNum + oldJmxNum !== 1) {
|
||||
this.$error(this.$t('load_test.jmx_is_null'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
handleExceed() {
|
||||
this.$error(this.$t('load_test.file_size_limit'));
|
||||
},
|
||||
}
|
||||
fileValidator(file) {
|
||||
/// todo: 是否需要对文件内容和大小做限制
|
||||
return file.size > 0;
|
||||
},
|
||||
updatedFileList() {
|
||||
return this.fileList;// 表示修改了已经上传的文件列表
|
||||
},
|
||||
validConfig() {
|
||||
let newJmxNum = 0, oldJmxNum = 0;
|
||||
if (this.uploadList.length > 0) {
|
||||
newJmxNum = this.uploadList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
|
||||
}
|
||||
if (this.fileList.length > 0) {
|
||||
oldJmxNum = this.fileList.filter(f => f.name.toLowerCase().endsWith(".jmx")).length;
|
||||
}
|
||||
if (newJmxNum + oldJmxNum !== 1) {
|
||||
this.$error(this.$t('load_test.jmx_is_null'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.basic-config {
|
||||
width: 100%
|
||||
}
|
||||
.basic-config {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.last-modified {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.last-modified {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<el-col :span="10">
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{$t('load_test.thread_num')}}</div>
|
||||
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
|
@ -18,7 +18,7 @@
|
|||
</el-form>
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{$t('load_test.duration')}}</div>
|
||||
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
|
@ -33,7 +33,7 @@
|
|||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{$t('load_test.rps_limit')}}</div>
|
||||
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-switch v-model="rpsLimitEnable"/>
|
||||
|
@ -52,7 +52,7 @@
|
|||
|
||||
<el-form :inline="true" class="input-bottom-border">
|
||||
<el-form-item>
|
||||
<div>{{$t('load_test.ramp_up_time_within')}}</div>
|
||||
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
|
@ -65,7 +65,7 @@
|
|||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>{{$t('load_test.ramp_up_time_minutes')}}</div>
|
||||
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
|
@ -78,12 +78,12 @@
|
|||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>{{$t('load_test.ramp_up_time_times')}}</div>
|
||||
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form :inline="true" class="input-bottom-border">
|
||||
<el-form-item>
|
||||
<div>{{$t('load_test.select_resource_pool')}}</div>
|
||||
<div>{{ $t('load_test.select_resource_pool') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="resourcePool" :disabled="isReadOnly" size="mini">
|
||||
|
@ -98,7 +98,7 @@
|
|||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<div class="title">{{$t('load_test.pressure_prediction_chart')}}</div>
|
||||
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
|
||||
<chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
@ -106,263 +106,263 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from "echarts";
|
||||
import echarts from "echarts";
|
||||
|
||||
const TARGET_LEVEL = "TargetLevel";
|
||||
const RAMP_UP = "RampUp";
|
||||
const STEPS = "Steps";
|
||||
const DURATION = "duration";
|
||||
const RPS_LIMIT = "rpsLimit";
|
||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||
const TARGET_LEVEL = "TargetLevel";
|
||||
const RAMP_UP = "RampUp";
|
||||
const STEPS = "Steps";
|
||||
const DURATION = "duration";
|
||||
const RPS_LIMIT = "rpsLimit";
|
||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||
|
||||
export default {
|
||||
name: "PerformancePressureConfig",
|
||||
props: {
|
||||
testPlan: {
|
||||
type: Object
|
||||
},
|
||||
testId: {
|
||||
type: String
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
export default {
|
||||
name: "PerformancePressureConfig",
|
||||
props: {
|
||||
testPlan: {
|
||||
type: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
threadNumber: 10,
|
||||
duration: 10,
|
||||
rampUpTime: 10,
|
||||
step: 10,
|
||||
rpsLimit: 10,
|
||||
rpsLimitEnable: false,
|
||||
orgOptions: {},
|
||||
resourcePool: null,
|
||||
resourcePools: [],
|
||||
}
|
||||
testId: {
|
||||
type: String
|
||||
},
|
||||
mounted() {
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
threadNumber: 10,
|
||||
duration: 10,
|
||||
rampUpTime: 10,
|
||||
step: 10,
|
||||
rpsLimit: 10,
|
||||
rpsLimitEnable: false,
|
||||
orgOptions: {},
|
||||
resourcePool: null,
|
||||
resourcePools: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.testId) {
|
||||
this.getLoadConfig();
|
||||
} else {
|
||||
this.calculateChart();
|
||||
}
|
||||
this.resourcePool = this.testPlan.testResourcePoolId;
|
||||
this.getResourcePools();
|
||||
},
|
||||
watch: {
|
||||
testPlan(n) {
|
||||
this.resourcePool = n.testResourcePoolId;
|
||||
},
|
||||
testId() {
|
||||
if (this.testId) {
|
||||
this.getLoadConfig();
|
||||
} else {
|
||||
this.calculateChart();
|
||||
}
|
||||
this.resourcePool = this.testPlan.testResourcePoolId;
|
||||
this.getResourcePools();
|
||||
},
|
||||
watch: {
|
||||
testPlan(n) {
|
||||
this.resourcePool = n.testResourcePoolId;
|
||||
},
|
||||
testId() {
|
||||
if (this.testId) {
|
||||
this.getLoadConfig();
|
||||
} else {
|
||||
this.calculateChart();
|
||||
},
|
||||
methods: {
|
||||
getResourcePools() {
|
||||
this.result = this.$get('/testresourcepool/list/quota/valid', response => {
|
||||
this.resourcePools = response.data;
|
||||
// 如果当前的资源池无效 设置 null
|
||||
if (response.data.filter(p => p.id === this.resourcePool).length === 0) {
|
||||
this.resourcePool = null;
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getResourcePools() {
|
||||
this.result = this.$get('/testresourcepool/list/quota/valid', response => {
|
||||
this.resourcePools = response.data;
|
||||
// 如果当前的资源池无效 设置 null
|
||||
if (response.data.filter(p => p.id === this.resourcePool).length === 0) {
|
||||
this.resourcePool = null;
|
||||
getLoadConfig() {
|
||||
if (this.testId) {
|
||||
|
||||
this.$get('/performance/get-load-config/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
|
||||
data.forEach(d => {
|
||||
switch (d.key) {
|
||||
case TARGET_LEVEL:
|
||||
this.threadNumber = d.value;
|
||||
break;
|
||||
case RAMP_UP:
|
||||
this.rampUpTime = d.value;
|
||||
break;
|
||||
case DURATION:
|
||||
this.duration = d.value;
|
||||
break;
|
||||
case STEPS:
|
||||
this.step = d.value;
|
||||
break;
|
||||
case RPS_LIMIT:
|
||||
this.rpsLimit = d.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.threadNumber = this.threadNumber || 10;
|
||||
this.duration = this.duration || 30;
|
||||
this.rampUpTime = this.rampUpTime || 12;
|
||||
this.step = this.step || 3;
|
||||
this.rpsLimit = this.rpsLimit || 10;
|
||||
|
||||
this.calculateChart();
|
||||
}
|
||||
})
|
||||
},
|
||||
getLoadConfig() {
|
||||
if (this.testId) {
|
||||
|
||||
this.$get('/performance/get-load-config/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
|
||||
data.forEach(d => {
|
||||
switch (d.key) {
|
||||
case TARGET_LEVEL:
|
||||
this.threadNumber = d.value;
|
||||
break;
|
||||
case RAMP_UP:
|
||||
this.rampUpTime = d.value;
|
||||
break;
|
||||
case DURATION:
|
||||
this.duration = d.value;
|
||||
break;
|
||||
case STEPS:
|
||||
this.step = d.value;
|
||||
break;
|
||||
case RPS_LIMIT:
|
||||
this.rpsLimit = d.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.threadNumber = this.threadNumber || 10;
|
||||
this.duration = this.duration || 30;
|
||||
this.rampUpTime = this.rampUpTime || 12;
|
||||
this.step = this.step || 3;
|
||||
this.rpsLimit = this.rpsLimit || 10;
|
||||
|
||||
this.calculateChart();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
calculateChart() {
|
||||
if (this.duration < this.rampUpTime) {
|
||||
this.rampUpTime = this.duration;
|
||||
}
|
||||
if (this.rampUpTime < this.step) {
|
||||
this.step = this.rampUpTime;
|
||||
}
|
||||
this.orgOptions = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{a}: {c0}',
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'User',
|
||||
data: [],
|
||||
type: 'line',
|
||||
step: 'start',
|
||||
smooth: false,
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(137, 189, 27, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(137, 189, 27, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(137,189,27)',
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
}
|
||||
},
|
||||
}]
|
||||
};
|
||||
let timePeriod = Math.floor(this.rampUpTime / this.step);
|
||||
let timeInc = timePeriod;
|
||||
|
||||
let threadPeriod = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc1 = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc2 = Math.ceil(this.threadNumber / this.step);
|
||||
let inc2count = this.threadNumber - this.step * threadInc1;
|
||||
for (let i = 0; i <= this.duration; i++) {
|
||||
// x 轴
|
||||
this.orgOptions.xAxis.data.push(i);
|
||||
if (i > timePeriod) {
|
||||
timePeriod += timeInc;
|
||||
if (inc2count > 0) {
|
||||
threadPeriod = threadPeriod + threadInc2;
|
||||
inc2count--;
|
||||
} else {
|
||||
threadPeriod = threadPeriod + threadInc1;
|
||||
}
|
||||
if (threadPeriod > this.threadNumber) {
|
||||
threadPeriod = this.threadNumber;
|
||||
}
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
} else {
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
}
|
||||
}
|
||||
},
|
||||
validConfig() {
|
||||
if (!this.resourcePool) {
|
||||
this.$warning(this.$t('load_test.resource_pool_is_null'));
|
||||
// 资源池为空,设置参数为资源池所在Tab的name
|
||||
this.$emit('changeActive', '1');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.threadNumber || !this.duration || !this.rampUpTime || !this.step || !this.rpsLimit) {
|
||||
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
||||
this.$emit('changeActive', '1');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
convertProperty() {
|
||||
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
|
||||
return [
|
||||
{key: TARGET_LEVEL, value: this.threadNumber},
|
||||
{key: RAMP_UP, value: this.rampUpTime},
|
||||
{key: STEPS, value: this.step},
|
||||
{key: DURATION, value: this.duration},
|
||||
{key: RPS_LIMIT, value: this.rpsLimit},
|
||||
{key: RPS_LIMIT_ENABLE, value: this.rpsLimitEnable},
|
||||
];
|
||||
});
|
||||
}
|
||||
},
|
||||
calculateChart() {
|
||||
if (this.duration < this.rampUpTime) {
|
||||
this.rampUpTime = this.duration;
|
||||
}
|
||||
if (this.rampUpTime < this.step) {
|
||||
this.step = this.rampUpTime;
|
||||
}
|
||||
this.orgOptions = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{a}: {c0}',
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#57617B'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'User',
|
||||
data: [],
|
||||
type: 'line',
|
||||
step: 'start',
|
||||
smooth: false,
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: 'rgba(137, 189, 27, 0.3)'
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: 'rgba(137, 189, 27, 0)'
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: 'rgb(137,189,27)',
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
}
|
||||
},
|
||||
}]
|
||||
};
|
||||
let timePeriod = Math.floor(this.rampUpTime / this.step);
|
||||
let timeInc = timePeriod;
|
||||
|
||||
let threadPeriod = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc1 = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc2 = Math.ceil(this.threadNumber / this.step);
|
||||
let inc2count = this.threadNumber - this.step * threadInc1;
|
||||
for (let i = 0; i <= this.duration; i++) {
|
||||
// x 轴
|
||||
this.orgOptions.xAxis.data.push(i);
|
||||
if (i > timePeriod) {
|
||||
timePeriod += timeInc;
|
||||
if (inc2count > 0) {
|
||||
threadPeriod = threadPeriod + threadInc2;
|
||||
inc2count--;
|
||||
} else {
|
||||
threadPeriod = threadPeriod + threadInc1;
|
||||
}
|
||||
if (threadPeriod > this.threadNumber) {
|
||||
threadPeriod = this.threadNumber;
|
||||
}
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
} else {
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
}
|
||||
}
|
||||
},
|
||||
validConfig() {
|
||||
if (!this.resourcePool) {
|
||||
this.$warning(this.$t('load_test.resource_pool_is_null'));
|
||||
// 资源池为空,设置参数为资源池所在Tab的name
|
||||
this.$emit('changeActive', '1');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.threadNumber || !this.duration || !this.rampUpTime || !this.step || !this.rpsLimit) {
|
||||
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
||||
this.$emit('changeActive', '1');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
convertProperty() {
|
||||
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
|
||||
return [
|
||||
{key: TARGET_LEVEL, value: this.threadNumber},
|
||||
{key: RAMP_UP, value: this.rampUpTime},
|
||||
{key: STEPS, value: this.step},
|
||||
{key: DURATION, value: this.duration},
|
||||
{key: RPS_LIMIT, value: this.rpsLimit},
|
||||
{key: RPS_LIMIT_ENABLE, value: this.rpsLimitEnable},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.pressure-config-container .el-input {
|
||||
width: 130px;
|
||||
.pressure-config-container .el-input {
|
||||
width: 130px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.pressure-config-container .config-form-label {
|
||||
width: 130px;
|
||||
}
|
||||
.pressure-config-container .config-form-label {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.pressure-config-container .input-bottom-border input {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #DCDFE6;
|
||||
}
|
||||
.pressure-config-container .input-bottom-border input {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #DCDFE6;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
}
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-col .el-form {
|
||||
margin-top: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
.el-col .el-form {
|
||||
margin-top: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.el-col {
|
||||
margin-top: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
.el-col {
|
||||
margin-top: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 60px;
|
||||
}
|
||||
.title {
|
||||
margin-left: 60px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
upload(file) {
|
||||
this.isLoading = false;
|
||||
this.fileList.push(file.file);
|
||||
this.result = this.$fileUpload('/test/case/import/' + this.projectId, file.file, {}, response => {
|
||||
this.result = this.$fileUpload('/test/case/import/' + this.projectId, file.file, null, {}, response => {
|
||||
let res = response.data;
|
||||
if (res.success) {
|
||||
this.$success(this.$t('test_track.case.import.success'));
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 390943d21e7d0196e0d7d5faa66f0131cb631614
|
||||
Subproject commit 7e4d80cc2b870a8cac6dbb9fe6711ab6041faf6d
|
|
@ -142,9 +142,16 @@ export default {
|
|||
});
|
||||
};
|
||||
|
||||
Vue.prototype.$fileUpload = function (url, file, param, success, failure) {
|
||||
Vue.prototype.$fileUpload = function (url, file, files, param, success, failure) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", file);
|
||||
if (file) {
|
||||
formData.append("file", file);
|
||||
}
|
||||
if (files) {
|
||||
files.forEach(f => {
|
||||
formData.append("files", f);
|
||||
})
|
||||
}
|
||||
formData.append('request', new Blob([JSON.stringify(param)], {type: "application/json"}));
|
||||
let axiosRequestConfig = {
|
||||
method: 'POST',
|
||||
|
|
|
@ -187,4 +187,11 @@ export function removeGoBackListener(callback) {
|
|||
window.removeEventListener('popstate', callback);
|
||||
}
|
||||
|
||||
export function getUUID() {
|
||||
function S4() {
|
||||
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
|
||||
}
|
||||
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue