feat(性能测试): 支持自由组合场景

This commit is contained in:
Captain.B 2021-03-04 17:26:35 +08:00
parent d18c1aa9ed
commit 329ed8dd77
17 changed files with 525 additions and 300 deletions

View File

@ -2,7 +2,7 @@ package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.LoadTest;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.LoadTestFileDTO;
import io.metersphere.performance.dto.LoadTestFileDTO;
import io.metersphere.track.request.testplan.QueryTestPlanRequest;
import org.apache.ibatis.annotations.Param;

View File

@ -132,7 +132,7 @@
select * from load_test lt where lt.project_id = #{projectId} ORDER BY num DESC LIMIT 1;
</select>
<select id="getProjectFiles" resultType="io.metersphere.dto.LoadTestFileDTO">
<select id="getProjectFiles" resultType="io.metersphere.performance.dto.LoadTestFileDTO">
SELECT file_metadata.*, load_test.id as testId, load_test.name as testName
FROM load_test
JOIN load_test_file ON load_test.id = load_test_file.test_id

View File

@ -12,8 +12,9 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.LoadTestFileDTO;
import io.metersphere.dto.ScheduleDao;
import io.metersphere.performance.dto.LoadTestExportJmx;
import io.metersphere.performance.dto.LoadTestFileDTO;
import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.CheckPermissionService;
import io.metersphere.service.FileService;
@ -108,11 +109,16 @@ public class PerformanceTestController {
}
@GetMapping("/get-jmx-content/{testId}")
public String getJmxContent(@PathVariable String testId) {
public List<LoadTestExportJmx> getJmxContent(@PathVariable String testId) {
checkPermissionService.checkPerformanceTestOwner(testId);
return performanceTestService.getJmxContent(testId);
}
@PostMapping("/export/jmx")
public List<LoadTestExportJmx> exportJmx(@RequestBody List<String> fileIds) {
return performanceTestService.exportJmx(fileIds);
}
@GetMapping("/project/{loadType}/{projectId}/{goPage}/{pageSize}")
public Pager<List<LoadTestFileDTO>> getProjectFiles(@PathVariable String projectId, @PathVariable String loadType,
@PathVariable int goPage, @PathVariable int pageSize) {

View File

@ -0,0 +1,16 @@
package io.metersphere.performance.dto;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class LoadTestExportJmx {
private String name;
private String jmx;
public LoadTestExportJmx(String name, String jmx) {
this.name = name;
this.jmx = jmx;
}
}

View File

@ -1,4 +1,4 @@
package io.metersphere.dto;
package io.metersphere.performance.dto;
import io.metersphere.base.domain.FileMetadata;
import lombok.Getter;

View File

@ -23,9 +23,22 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.reflections8.Reflections;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.annotation.Resource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.*;
import java.util.stream.Collectors;
@ -77,22 +90,17 @@ public class EngineFactory {
if (org.springframework.util.CollectionUtils.isEmpty(fileMetadataList)) {
MSException.throwException(Translator.get("run_load_test_file_not_found") + loadTest.getId());
}
FileMetadata jmxFile = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name()))
.findFirst().orElseGet(() -> {
throw new RuntimeException(Translator.get("run_load_test_file_not_found") + loadTest.getId());
});
List<FileMetadata> jmxFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name())).collect(Collectors.toList());
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
List<FileMetadata> jarFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JAR.name())).collect(Collectors.toList());
final FileContent fileContent = fileService.getFileContent(jmxFile.getId());
if (fileContent == null) {
MSException.throwException(Translator.get("run_load_test_file_content_not_found") + loadTest.getId());
}
// 合并上传的jmx
byte[] jmxBytes = mergeJmx(jmxFiles);
final EngineContext engineContext = new EngineContext();
engineContext.setTestId(loadTest.getId());
engineContext.setTestName(loadTest.getName());
engineContext.setNamespace(loadTest.getProjectId());
engineContext.setFileType(jmxFile.getType());
engineContext.setFileType(FileType.JMX.name());
engineContext.setResourcePoolId(loadTest.getTestResourcePoolId());
engineContext.setStartTime(startTime);
engineContext.setReportId(reportId);
@ -146,7 +154,7 @@ public class EngineFactory {
MSException.throwException("File type unknown");
}
try (ByteArrayInputStream source = new ByteArrayInputStream(fileContent.getFile())) {
try (ByteArrayInputStream source = new ByteArrayInputStream(jmxBytes)) {
String content = engineSourceParser.parse(engineContext, source);
engineContext.setContent(content);
} catch (MSException e) {
@ -178,6 +186,63 @@ public class EngineFactory {
return engineContext;
}
public static byte[] mergeJmx(List<FileMetadata> jmxFiles) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Element hashTree = null;
Document rootDocument = null;
for (FileMetadata fileMetadata : jmxFiles) {
FileContent fileContent = fileService.getFileContent(fileMetadata.getId());
final InputSource inputSource = new InputSource(new ByteArrayInputStream(fileContent.getFile()));
if (hashTree == null) {
rootDocument = docBuilder.parse(inputSource);
Element jmeterTestPlan = rootDocument.getDocumentElement();
NodeList childNodes = jmeterTestPlan.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof Element) {
// jmeterTestPlan的子元素肯定是<hashTree></hashTree>
hashTree = (Element) node;
break;
}
}
} else {
Document document = docBuilder.parse(inputSource);
Element jmeterTestPlan = document.getDocumentElement();
NodeList childNodes = jmeterTestPlan.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof Element) {
// jmeterTestPlan的子元素肯定是<hashTree></hashTree>
Element secondHashTree = (Element) node;
NodeList secondChildNodes = secondHashTree.getChildNodes();
for (int j = 0; j < secondChildNodes.getLength(); j++) {
Node item = secondChildNodes.item(j);
Node newNode = item.cloneNode(true);
rootDocument.adoptNode(newNode);
hashTree.appendChild(newNode);
}
}
}
}
}
return documentToBytes(rootDocument);
} catch (Exception e) {
MSException.throwException(e);
}
return new byte[0];
}
private static byte[] documentToBytes(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamResult result = new StreamResult(out);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return out.toByteArray();
}
@Resource
private void setFileService(FileService fileService) {

View File

@ -16,10 +16,11 @@ import io.metersphere.controller.request.OrderRequest;
import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.DashboardTestDTO;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.LoadTestFileDTO;
import io.metersphere.dto.ScheduleDao;
import io.metersphere.i18n.Translator;
import io.metersphere.job.sechedule.PerformanceTestJob;
import io.metersphere.performance.dto.LoadTestExportJmx;
import io.metersphere.performance.dto.LoadTestFileDTO;
import io.metersphere.performance.engine.Engine;
import io.metersphere.performance.engine.EngineFactory;
import io.metersphere.performance.engine.producer.LoadTestProducer;
@ -131,23 +132,13 @@ public class PerformanceTestService {
checkQuota(request, true);
LoadTestWithBLOBs loadTest = saveLoadTest(request);
// 新选择了一个文件删除原来的文件
List<FileMetadata> importFiles = request.getUpdatedFileList();
List<String> importFileIds = importFiles.stream().map(FileMetadata::getId).collect(Collectors.toList());
// 导入项目里其他的文件
this.importFiles(importFileIds, loadTest.getId());
// 保存上传的问题
// 保存上传的
this.saveUploadFiles(files, loadTest.getId());
// 直接上传了jmx用于API导入的场景
String jmx = request.getJmx();
if (StringUtils.isNotBlank(jmx)) {
byte[] bytes = jmx.getBytes(StandardCharsets.UTF_8);
FileMetadata fileMetadata = fileService.saveFile(bytes, request.getName() + ".jmx", (long) bytes.length);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(loadTest.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
}
return loadTest.getId();
}
@ -222,16 +213,6 @@ public class PerformanceTestService {
List<String> addFileIds = ListUtils.subtract(updatedFileIds, originFileIds);
this.importFiles(addFileIds, request.getId());
this.saveUploadFiles(files, request.getId());
// 直接上传了jmx用于API导入的场景
String jmx = request.getJmx();
if (StringUtils.isNotBlank(jmx)) {
byte[] bytes = jmx.getBytes(StandardCharsets.UTF_8);
FileMetadata fileMetadata = fileService.saveFile(bytes, request.getName() + ".jmx", (long) bytes.length);
LoadTestFile loadTestFile = new LoadTestFile();
loadTestFile.setTestId(request.getId());
loadTestFile.setFileId(fileMetadata.getId());
loadTestFileMapper.insert(loadTestFile);
}
loadTest.setName(request.getName());
loadTest.setProjectId(request.getProjectId());
@ -391,15 +372,16 @@ public class PerformanceTestService {
return Optional.ofNullable(loadTestWithBLOBs).orElse(new LoadTestWithBLOBs()).getLoadConfiguration();
}
public String getJmxContent(String testId) {
public List<LoadTestExportJmx> getJmxContent(String testId) {
List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(testId);
List<LoadTestExportJmx> results = new ArrayList<>();
for (FileMetadata metadata : fileMetadataList) {
if (FileType.JMX.name().equals(metadata.getType())) {
FileContent fileContent = fileService.getFileContent(metadata.getId());
return new String(fileContent.getFile());
results.add(new LoadTestExportJmx(metadata.getName(), new String(fileContent.getFile(), StandardCharsets.UTF_8)));
}
}
return null;
return results;
}
public List<LoadTestWithBLOBs> selectByTestResourcePoolId(String resourcePoolId) {
@ -557,4 +539,18 @@ public class PerformanceTestService {
}
return extLoadTestMapper.getProjectFiles(projectId, loadTypes);
}
public List<LoadTestExportJmx> exportJmx(List<String> fileIds) {
if (CollectionUtils.isEmpty(fileIds)) {
return null;
}
List<LoadTestExportJmx> results = new ArrayList<>();
fileIds.forEach(id -> {
FileMetadata fileMetadata = fileService.getFileMetadataById(id);
FileContent fileContent = fileService.getFileContent(id);
results.add(new LoadTestExportJmx(fileMetadata.getName(), new String(fileContent.getFile(), StandardCharsets.UTF_8)));
});
return results;
}
}

View File

@ -16,8 +16,6 @@ public class TestPlanRequest {
private String description;
private String jmx;
private Long createTime;
private Long updateTime;

View File

@ -117,6 +117,7 @@ import echarts from "echarts";
import MsChart from "@/business/components/common/chart/MsChart";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
const HANDLER = "handler";
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
const STEPS = "Steps";
@ -204,6 +205,9 @@ export default {
case DELETED:
this.threadGroups[i].deleted = item.value;
break;
case HANDLER:
this.threadGroups[i].handler = item.value;
break;
default:
break;
}
@ -241,13 +245,13 @@ export default {
return;
}
this.result = this.$get('/performance/get-jmx-content/' + this.report.testId, (response) => {
if (response.data) {
this.threadGroups = findThreadGroup(response.data);
response.data.forEach(d => {
this.threadGroups = this.threadGroups.concat(findThreadGroup(d.jmx, d.name));
this.threadGroups.forEach(tg => {
tg.options = {};
});
this.getLoadConfig();
}
});
this.getLoadConfig();
});
},
calculateTotalChart() {

View File

@ -0,0 +1,168 @@
<template>
<el-dialog :close-on-click-modal="false"
:destroy-on-close="true"
:title="$t('load_test.exist_jmx')" width="70%"
:visible.sync="loadFileVisible">
<el-table v-loading="projectLoadingResult.loading"
class="basic-config"
:data="existFiles"
@select-all="handleSelectAll"
@select="handleSelectionChange">
<el-table-column type="selection"/>
<el-table-column
prop="testName"
:label="$t('load_test.test')">
</el-table-column>
<el-table-column
prop="name"
:label="$t('load_test.file_name')">
</el-table-column>
<el-table-column
prop="type"
:label="$t('load_test.file_type')">
</el-table-column>
<el-table-column
:label="$t('load_test.last_modify_time')">
<template v-slot:default="scope">
<i class="el-icon-time"/>
<span class="last-modified">{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="getProjectFiles" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleImport"/>
</template>
</el-dialog>
</template>
<script>
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import {getCurrentProjectID} from "@/common/js/utils";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
export default {
name: "ExistFiles",
components: {MsTablePagination, MsDialogFooter},
props: {
fileList: Array,
tableData: Array,
uploadList: Array,
scenarios: Array
},
data() {
return {
loadFileVisible: false,
projectLoadingResult: {},
currentPage: 1,
pageSize: 5,
total: 0,
loadType: 'jmx',
existFiles: [],
selectIds: new Set,
}
},
methods: {
open(loadType) {
this.loadFileVisible = true;
this.loadType = loadType;
this.getProjectFiles();
},
close() {
this.loadFileVisible = false;
this.selectIds.clear();
},
handleSelectAll(selection) {
if (selection.length > 0) {
this.existFiles.forEach(item => {
this.selectIds.add(item.id);
});
} else {
this.existFiles.forEach(item => {
if (this.selectIds.has(item.id)) {
this.selectIds.delete(item.id);
}
});
}
},
handleSelectionChange(selection, row) {
if (this.selectIds.has(row.id)) {
this.selectIds.delete(row.id);
} else {
this.selectIds.add(row.id);
}
},
getProjectFiles() {
this.projectLoadingResult = this.$get('/performance/project/' + this.loadType + '/' + getCurrentProjectID() + "/" + this.currentPage + "/" + this.pageSize, res => {
let data = res.data;
this.total = data.itemCount;
this.existFiles = data.listObject;
})
},
handleImport() {
if (this.selectIds.size === 0) {
return;
}
let rows = this.existFiles.filter(f => this.selectIds.has(f.id));
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
if (this.tableData.filter(f => f.name === row.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
this.selectIds.clear();
return;
}
}
if (this.loadType === 'resource') {
rows.forEach(row => {
this.fileList.push(row);
this.tableData.push(row);
})
this.$success(this.$t('test_track.case.import.success'));
this.loadFileVisible = false;
this.selectIds.clear();
return;
}
this.projectLoadingResult = this.$post('/performance/export/jmx', [...this.selectIds], (response) => {
let data = response.data;
if (!data) {
return;
}
data.forEach(d => {
let threadGroups = findThreadGroup(d.jmx, d.name);
threadGroups.forEach(tg => {
tg.options = {};
this.scenarios.push(tg);
});
let file = new File([d.jmx], d.name);
this.fileList.push(file);
this.uploadList.push(file);
this.tableData.push({
name: file.name,
size: (file.size / 1024).toFixed(2) + ' KB',
type: 'JMX',
updateTime: file.lastModified,
});
});
this.$emit('fileChange', this.scenarios);
this.$success(this.$t('test_track.case.import.success'));
this.loadFileVisible = false;
this.selectIds.clear();
});
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,163 @@
<template>
<el-dialog :title="$t('load_test.scenario_list')"
:close-on-click-modal="false"
:destroy-on-close="true"
width="60%" :visible.sync="loadApiAutomationVisible"
:modal="true">
<el-table v-loading="projectLoadingResult.loading" class="basic-config"
:data="apiScenarios"
@select-all="handleSelectAll"
@select="handleSelectionChange">
<el-table-column type="selection"/>
<el-table-column
prop="num"
label="ID">
</el-table-column>
<el-table-column
prop="name"
:label="$t('load_test.scenario_name')">
</el-table-column>
<el-table-column prop="tags" min-width="120px"
:label="$t('api_test.automation.tag')">
<template v-slot:default="scope">
<ms-tag v-for="(itemName,index) in scope.row.tags" :key="index" type="success" effect="plain" :content="itemName"
style="margin-left: 5px"/>
</template>
</el-table-column>
<el-table-column
:label="$t('load_test.last_modify_time')">
<template v-slot:default="scope">
<i class="el-icon-time"/>
<span class="last-modified">{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="getProjectScenarios" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleImport"/>
</template>
</el-dialog>
</template>
<script>
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import {getCurrentProjectID} from "@/common/js/utils";
import MsTag from "@/business/components/common/components/MsTag";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
export default {
name: "ExistScenarios",
components: {MsTag, MsTablePagination, MsDialogFooter},
props: {
fileList: Array,
tableData: Array,
uploadList: Array,
scenarios: Array
},
data() {
return {
loadApiAutomationVisible: false,
projectLoadingResult: {},
currentPage: 1,
pageSize: 5,
total: 0,
apiScenarios: [],
selectIds: new Set,
}
},
methods: {
open() {
this.loadApiAutomationVisible = true;
this.getProjectScenarios();
},
close() {
this.loadApiAutomationVisible = false;
this.selectIds.clear();
},
handleSelectAll(selection) {
if (selection.length > 0) {
this.apiScenarios.forEach(item => {
this.selectIds.add(item.id);
});
} else {
this.apiScenarios.forEach(item => {
if (this.selectIds.has(item.id)) {
this.selectIds.delete(item.id);
}
});
}
},
handleSelectionChange(selection, row) {
if (this.selectIds.has(row.id)) {
this.selectIds.delete(row.id);
} else {
this.selectIds.add(row.id);
}
},
getProjectScenarios() {
let condition = {
projectId: getCurrentProjectID(),
filters: {status: ["Prepare", "Underway", "Completed"]}
}
this.projectLoadingResult = this.$post('/api/automation/list/' + this.currentPage + "/" + this.pageSize, condition, res => {
let data = res.data;
this.total = data.itemCount;
this.apiScenarios = data.listObject;
this.apiScenarios.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
});
})
},
handleImport() {
if (this.selectIds.size === 0) {
return;
}
let rows = this.apiScenarios.filter(f => this.selectIds.has(f.id));
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
if (this.tableData.filter(f => f.name === row.name + ".jmx").length > 0) {
this.$error(this.$t('load_test.delete_file'));
return;
}
}
let condition = {
projectId: getCurrentProjectID(),
ids: [...this.selectIds],
};
this.projectLoadingResult = this.$post('api/automation/export/jmx', condition, response => {
let data = response.data;
data.forEach(d => {
let threadGroups = findThreadGroup(d.jmx, d.name + ".jmx")
threadGroups.forEach(tg => {
tg.options = {};
this.scenarios.push(tg);
});
let file = new File([d.jmx], d.name + ".jmx");
this.fileList.push(file);
this.uploadList.push(file);
this.tableData.push({
name: file.name,
size: (file.size / 1024).toFixed(2) + ' KB',
type: 'JMX',
updateTime: file.lastModified,
});
})
this.$emit('fileChange', this.scenarios);
this.$success(this.$t('test_track.case.import.success'));
this.loadApiAutomationVisible = false;
})
this.selectIds.clear();
},
}
}
</script>
<style scoped>
</style>

View File

@ -8,6 +8,7 @@
style="padding-right: 10px;"
accept=".jmx"
action=""
multiple
:limit="fileNumLimit"
:show-file-list="false"
:before-upload="beforeUploadJmx"
@ -83,7 +84,7 @@
<ms-table-button :is-tester-permission="true" icon="el-icon-circle-plus-outline"
:content="$t('load_test.load_exist_file')" @click="loadFile()"/>
</el-row>
<el-table class="basic-config" :data="tableData.filter(f => !f.name.toUpperCase().endsWith('.JMX'))">
<el-table class="basic-config" :data="tableData">
<el-table-column
prop="name"
:label="$t('load_test.file_name')">
@ -116,79 +117,36 @@
</el-table-column>
</el-table>
<el-dialog :title="$t('load_test.exist_jmx')" width="70%" :visible.sync="loadFileVisible">
<exist-files ref="existFiles"
@fileChange="fileChange"
:file-list="fileList"
:table-data="tableData"
:upload-list="uploadList"
:scenarios="threadGroups"/>
<el-table class="basic-config" :data="existFiles" v-loading="projectLoadingResult.loading">
<el-table-column
prop="testName"
:label="$t('load_test.test')">
</el-table-column>
<el-table-column
prop="name"
:label="$t('load_test.file_name')">
</el-table-column>
<el-table-column
prop="type"
:label="$t('load_test.file_type')">
</el-table-column>
<el-table-column
:label="$t('load_test.last_modify_time')">
<template v-slot:default="scope">
<i class="el-icon-time"/>
<span class="last-modified">{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
:label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator-button :is-tester-permission="true"
:tip="$t('api_test.api_import.label')"
icon="el-icon-upload"
@exec="handleImport(scope.row)"/>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="getProjectFiles" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-dialog>
<el-dialog :title="$t('load_test.scenario_list')" width="60%" :visible.sync="loadApiAutomationVisible">
<exist-scenarios ref="existScenarios"
@fileChange="fileChange"
:file-list="fileList"
:table-data="tableData"
:upload-list="uploadList"
:scenarios="threadGroups"/>
<el-table class="basic-config" :data="apiScenarios" v-loading="projectLoadingResult.loading">
<el-table-column
prop="num"
label="ID">
</el-table-column>
<el-table-column
prop="name"
:label="$t('load_test.scenario_name')">
</el-table-column>
<el-table-column
:label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator-button :is-tester-permission="true"
:tip="$t('api_test.api_import.label')"
icon="el-icon-upload"
@exec="handleImportApi(scope.row)"/>
</template>
</el-table-column>
</el-table>
<ms-table-pagination :change="getProjectFiles" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-dialog>
</div>
</template>
<script>
import {Message} from "element-ui";
import {findTestPlan, findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
import MsTableButton from "@/business/components/common/components/MsTableButton";
import {getCurrentProjectID} from "@/common/js/utils";
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import ExistFiles from "@/business/components/performance/test/components/ExistFiles";
import ExistScenarios from "@/business/components/performance/test/components/ExistScenarios";
export default {
name: "PerformanceBasicConfig",
components: {MsTableOperatorButton, MsTablePagination, MsTableButton},
components: {ExistScenarios, ExistFiles, MsDialogFooter, MsTableOperatorButton, MsTablePagination, MsTableButton},
props: {
test: {
type: Object
@ -215,9 +173,9 @@ export default {
pageSize: 5,
total: 0,
existFiles: [],
loadType: 'jmx',
apiScenarios: [],
loadApiAutomationVisible: false,
selectIds: new Set(),
};
},
created() {
@ -230,19 +188,6 @@ export default {
if (this.test.id) {
this.getFileMetadata(this.test)
}
},
uploadList() {
let self = this;
let fileList = self.uploadList.filter(f => f.name.endsWith(".jmx"));
if (fileList.length > 0) {
let file = fileList[0];
let jmxReader = new FileReader();
jmxReader.onload = (event) => {
self.threadGroups = findThreadGroup(event.target.result);
self.$emit('fileChange', self.threadGroups);
};
jmxReader.readAsText(file);
}
}
},
methods: {
@ -256,7 +201,6 @@ export default {
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
return;
}
console.log(files);
// deep copy
this.fileList = JSON.parse(JSON.stringify(files));
this.tableData = JSON.parse(JSON.stringify(files));
@ -265,40 +209,12 @@ export default {
});
})
},
deleteExistJmx: function () {
// jmx
let jmxs = this.tableData.filter(f => {
let type = f.name.substring(f.name.lastIndexOf(".") + 1);
return type.toUpperCase() === 'JMX';
});
for (let i = 0; i < jmxs.length; i++) {
let index = this.tableData.indexOf(jmxs[i]);
if (index > -1) {
this.tableData.splice(index, 1);
}
let index2 = this.uploadList.indexOf(jmxs[i]);
if (index2 > -1) {
this.uploadList.splice(index2, 1);
}
}
jmxs = this.fileList.filter(f => {
let type = f.name.substring(f.name.lastIndexOf(".") + 1);
return type.toUpperCase() === 'JMX';
});
for (let i = 0; i < jmxs.length; i++) {
let index3 = this.fileList.indexOf(jmxs[i]);
if (index3 > -1) {
this.fileList.splice(index3, 1);
}
}
},
beforeUploadJmx(file) {
if (!this.fileValidator(file)) {
/// todo:
return false;
}
this.deleteExistJmx();
if (this.tableData.filter(f => f.name === file.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
return false;
@ -337,7 +253,16 @@ export default {
return true;
},
handleUpload(uploadResources) {
this.uploadList.push(uploadResources.file);
let self = this;
let file = uploadResources.file;
self.uploadList.push(file);
let jmxReader = new FileReader();
jmxReader.onload = (event) => {
self.threadGroups = self.threadGroups.concat(findThreadGroup(event.target.result, file.name));
self.$emit('fileChange', self.threadGroups);
};
jmxReader.readAsText(file);
},
handleDownload(file) {
let data = {
@ -369,78 +294,6 @@ export default {
Message.error({message: e.message, showClose: true});
});
},
handleImport(row) {
if (this.tableData.filter(f => f.name === row.name).length > 0) {
this.$error(this.$t('load_test.delete_file'));
return;
}
if (this.loadType === 'resource') {
this.fileList.push(row);
this.tableData.push(row);
this.$success(this.$t('test_track.case.import.success'));
this.loadFileVisible = false;
return;
}
this.result = this.$get('/performance/get-jmx-content/' + row.testId, (response) => {
if (response.data) {
let testPlan = findTestPlan(response.data);
testPlan.elements.forEach(e => {
if (e.attributes.name === 'TestPlan.serialize_threadgroups') {
this.serializeThreadgroups = Boolean(e.elements[0].text);
}
});
this.threadGroups = findThreadGroup(response.data);
this.threadGroups.forEach(tg => {
tg.options = {};
});
this.$emit('fileChange', this.threadGroups);
}
this.deleteExistJmx();
this.fileList.push(row);
this.tableData.push(row);
this.$success(this.$t('test_track.case.import.success'));
this.loadFileVisible = false;
});
},
countStrToBit(str) {
let count = 0
const arr = str.split('')
arr.forEach(item => {
count += Math.ceil(item.charCodeAt().toString(2).length / 8)
})
return count
},
handleImportApi(row) {
let condition = {
projectId: getCurrentProjectID(),
ids: [row.id]
};
this.projectLoadingResult = this.$post('api/automation/export/jmx', condition, response => {
let data = response.data[0];
this.threadGroups = findThreadGroup(data.jmx);
this.threadGroups.forEach(tg => {
tg.options = {};
});
this.$emit('fileChange', this.threadGroups);
this.deleteExistJmx();
let bytes = this.countStrToBit(data.jmx);
this.fileList.push({
name: this.test.name + ".jmx",
size: bytes,
type: "JMX",
updateTime: new Date().getTime(),
});
this.tableData.push({
name: this.test.name + ".jmx",
size: (bytes / 1024).toFixed(2) + ' KB',
type: "JMX",
updateTime: new Date().getTime(),
});
this.test.jmx = data.jmx;
this.$success(this.$t('test_track.case.import.success'));
this.loadApiAutomationVisible = false;
})
},
handleDelete(file) {
this.$alert(this.$t('load_test.delete_file_confirm') + file.name + "", '', {
confirmButtonText: this.$t('commons.confirm'),
@ -465,6 +318,12 @@ export default {
if (i > -1) {
this.uploadList.splice(i, 1);
}
let jmxIndex = this.threadGroups.findIndex(tg => tg.handler === file.name);
while (jmxIndex !== -1) {
this.threadGroups.splice(jmxIndex, 1);
jmxIndex = this.threadGroups.findIndex(tg => tg.handler === file.name);
}
},
handleDeleteThreadGroup(tg) {
this.$alert(this.$t('load_test.delete_threadgroup_confirm') + tg.attributes.testname + "", '', {
@ -490,74 +349,23 @@ export default {
return this.fileList;//
},
loadJMX() {
this.loadFileVisible = true;
this.loadType = "jmx";
this.getProjectFiles();
this.$refs.existFiles.open('jmx');
},
loadFile() {
this.loadFileVisible = true;
this.loadType = "resource";
this.getProjectFiles();
this.$refs.existFiles.open('resource');
},
loadApiAutomation() {
this.loadApiAutomationVisible = true;
this.getProjectScenarios();
this.$refs.existScenarios.open();
},
getProjectFiles() {
this.projectLoadingResult = this.$get('/performance/project/' + this.loadType + '/' + getCurrentProjectID() + "/" + this.currentPage + "/" + this.pageSize, res => {
let data = res.data;
this.total = data.itemCount;
this.existFiles = data.listObject;
})
fileChange(threadGroups) {
this.$emit('fileChange', threadGroups);
},
getProjectScenarios() {
let condition = {
projectId: getCurrentProjectID(),
filters: {status: ["Prepare", "Underway", "Completed"]}
}
this.projectLoadingResult = this.$post('/api/automation/list/' + this.currentPage + "/" + this.pageSize, condition, res => {
let data = res.data;
this.total = data.itemCount;
this.apiScenarios = data.listObject;
})
},
validConfig() {
let newJmxNum = 0, oldJmxNum = 0, newCsvNum = 0, oldCsvNum = 0, newJarNum = 0, oldJarNum = 0;
if (this.uploadList.length > 0) {
this.uploadList.forEach(f => {
if (f.name.toLowerCase().endsWith(".jmx")) {
newJmxNum++;
}
if (f.name.toLowerCase().endsWith(".csv")) {
newCsvNum++;
}
if (f.name.toLowerCase().endsWith(".jar")) {
newJarNum++;
}
});
}
if (this.fileList.length > 0) {
this.fileList.forEach(f => {
if (f.name.toLowerCase().endsWith(".jmx")) {
oldJmxNum++;
}
if (f.name.toLowerCase().endsWith(".csv")) {
oldCsvNum++;
}
if (f.name.toLowerCase().endsWith(".jar")) {
oldJarNum++;
}
});
}
if (newCsvNum + oldCsvNum + newJarNum + oldJarNum > this.fileNumLimit - 1) {
if (this.uploadList.length + this.fileList.length > this.fileNumLimit) {
this.handleExceed();
return false;
}
if (newJmxNum + oldJmxNum !== 1) {
this.$error(this.$t('load_test.jmx_is_null'));
return false;
}
if (this.threadGroups.filter(tg => tg.attributes.enabled == 'true').length === 0) {
this.$error(this.$t('load_test.threadgroup_at_least_one'));
return false;

View File

@ -128,8 +128,9 @@
<script>
import echarts from "echarts";
import MsChart from "@/business/components/common/chart/MsChart";
import {findTestPlan, findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
const HANDLER = "handler";
const TARGET_LEVEL = "TargetLevel";
const RAMP_UP = "RampUp";
const ITERATE_RAMP_UP = "iterateRampUpTime";
@ -181,7 +182,6 @@ export default {
resourcePools: [],
activeNames: ["0"],
threadGroups: [],
serializeThreadgroups: false,
resourcePoolResourceLength: 1
}
},
@ -262,6 +262,9 @@ export default {
case DELETED:
this.threadGroups[i].deleted = item.value;
break;
case HANDLER:
this.threadGroups[i].handler = item.value;
break;
default:
break;
}
@ -281,21 +284,17 @@ export default {
},
getJmxContent() {
if (this.testId) {
let threadGroups = [];
this.$get('/performance/get-jmx-content/' + this.testId, (response) => {
if (response.data) {
let testPlan = findTestPlan(response.data);
testPlan.elements.forEach(e => {
if (e.attributes.name === 'TestPlan.serialize_threadgroups') {
this.serializeThreadgroups = Boolean(e.elements[0].text);
}
});
this.threadGroups = findThreadGroup(response.data);
this.threadGroups.forEach(tg => {
response.data.forEach(d => {
threadGroups = threadGroups.concat(findThreadGroup(d.jmx, d.name));
threadGroups.forEach(tg => {
tg.options = {};
});
this.$emit('fileChange', this.threadGroups);
this.getLoadConfig();
}
});
this.threadGroups = threadGroups;
this.$emit('fileChange', threadGroups);
this.getLoadConfig();
});
}
},
@ -523,6 +522,7 @@ export default {
let result = [];
for (let i = 0; i < this.threadGroups.length; i++) {
result.push([
{key: HANDLER, value: this.threadGroups[i].handler},
{key: TARGET_LEVEL, value: this.threadGroups[i].threadNumber},
{key: RAMP_UP, value: this.threadGroups[i].rampUpTime},
{key: STEPS, value: this.threadGroups[i].step},

View File

@ -21,12 +21,13 @@ let travel = function (elements, threadGroups) {
}
}
export function findThreadGroup(jmxContent) {
export function findThreadGroup(jmxContent, handler) {
let jmxJson = JSON.parse(xml2json(jmxContent));
let threadGroups = [];
travel(jmxJson.elements, threadGroups);
threadGroups.forEach(tg => {
tg.deleted = 'false';
tg.handler = handler;
tg.enabled = tg.attributes.enabled;
})
return threadGroups;

View File

@ -499,7 +499,7 @@ export default {
scenario_name: 'Scenario Name',
upload_jmx: 'Upload JMX',
exist_jmx: 'Existed Files',
other_resource: 'Other Files',
other_resource: 'Resource Files',
upload_file: 'Upload Files',
load_exist_file: 'Load Project Files',
load_exist_jmx: 'Load Project JMX',

View File

@ -501,7 +501,7 @@ export default {
scenario_name: "场景名称",
upload_jmx: '上传 JMX 文件',
exist_jmx: '已存在的文件',
other_resource: '其他资源',
other_resource: '资源文件',
upload_file: '上传新文件',
load_exist_file: '加载已有文件',
load_exist_jmx: '加载已有 JMX 文件',

View File

@ -498,7 +498,7 @@ export default {
scenario_name: '場景名稱',
upload_jmx: '上傳 JMX文件',
exist_jmx: '已存在的文件',
other_resource: '其他資源',
other_resource: '資源文件',
upload_file: '上傳新文件',
load_exist_file: '加載已有文件',
load_exist_jmx: '加載已有 JMX 文件',