Merge branch 'dev' of https://github.com/metersphere/metersphere into dev
This commit is contained in:
commit
b602eae04c
|
@ -61,6 +61,11 @@ public class APITestController {
|
|||
apiTestService.update(request, files);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/copy")
|
||||
public void copy(@RequestBody SaveAPITestRequest request) {
|
||||
apiTestService.copy(request);
|
||||
}
|
||||
|
||||
@GetMapping("/get/{testId}")
|
||||
public ApiTestWithBLOBs get(@PathVariable String testId) {
|
||||
return apiTestService.get(testId);
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.io.ByteArrayInputStream;
|
|||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -69,6 +70,26 @@ public class APITestService {
|
|||
saveFile(test.getId(), files);
|
||||
}
|
||||
|
||||
public void copy(SaveAPITestRequest request) {
|
||||
// copy test
|
||||
ApiTestWithBLOBs copy = get(request.getId());
|
||||
copy.setId(UUID.randomUUID().toString());
|
||||
copy.setName(copy.getName() + " Copy");
|
||||
copy.setCreateTime(System.currentTimeMillis());
|
||||
copy.setUpdateTime(System.currentTimeMillis());
|
||||
copy.setStatus(APITestStatus.Saved.name());
|
||||
copy.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
|
||||
apiTestMapper.insert(copy);
|
||||
// copy test file
|
||||
ApiTestFile apiTestFile = getFileByTestId(request.getId());
|
||||
if (apiTestFile != null) {
|
||||
FileMetadata fileMetadata = fileService.copyFile(apiTestFile.getFileId());
|
||||
apiTestFile.setTestId(copy.getId());
|
||||
apiTestFile.setFileId(fileMetadata.getId());
|
||||
apiTestFileMapper.insert(apiTestFile);
|
||||
}
|
||||
}
|
||||
|
||||
public ApiTestWithBLOBs get(String id) {
|
||||
return apiTestMapper.selectByPrimaryKey(id);
|
||||
}
|
||||
|
|
|
@ -3,22 +3,20 @@ package io.metersphere.service;
|
|||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.FileContentMapper;
|
||||
import io.metersphere.base.mapper.FileMetadataMapper;
|
||||
import io.metersphere.base.mapper.ApiTestFileMapper;
|
||||
import io.metersphere.base.mapper.LoadTestFileMapper;
|
||||
import io.metersphere.commons.constants.FileType;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class FileService {
|
||||
@Resource
|
||||
|
@ -26,8 +24,6 @@ public class FileService {
|
|||
@Resource
|
||||
private LoadTestFileMapper loadTestFileMapper;
|
||||
@Resource
|
||||
private ApiTestFileMapper ApiTestFileMapper;
|
||||
@Resource
|
||||
private FileContentMapper fileContentMapper;
|
||||
|
||||
public byte[] loadFileAsBytes(String id) {
|
||||
|
@ -50,17 +46,6 @@ public class FileService {
|
|||
return fileMetadataMapper.selectByExample(example);
|
||||
}
|
||||
|
||||
public FileMetadata getApiFileMetadataByTestId(String testId) {
|
||||
ApiTestFileExample ApiTestFileExample = new ApiTestFileExample();
|
||||
ApiTestFileExample.createCriteria().andTestIdEqualTo(testId);
|
||||
final List<ApiTestFile> loadTestFiles = ApiTestFileMapper.selectByExample(ApiTestFileExample);
|
||||
|
||||
if (CollectionUtils.isEmpty(loadTestFiles)) {
|
||||
return null;
|
||||
}
|
||||
return fileMetadataMapper.selectByPrimaryKey(loadTestFiles.get(0).getFileId());
|
||||
}
|
||||
|
||||
public FileContent getFileContent(String fileId) {
|
||||
return fileContentMapper.selectByPrimaryKey(fileId);
|
||||
}
|
||||
|
@ -101,6 +86,21 @@ public class FileService {
|
|||
return fileMetadata;
|
||||
}
|
||||
|
||||
public FileMetadata copyFile(String fileId) {
|
||||
FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(fileId);
|
||||
FileContent fileContent = getFileContent(fileId);
|
||||
if (fileMetadata != null && fileContent != null) {
|
||||
fileMetadata.setId(UUID.randomUUID().toString());
|
||||
fileMetadata.setCreateTime(System.currentTimeMillis());
|
||||
fileMetadata.setUpdateTime(System.currentTimeMillis());
|
||||
fileMetadataMapper.insert(fileMetadata);
|
||||
|
||||
fileContent.setFileId(fileMetadata.getId());
|
||||
fileContentMapper.insert(fileContent);
|
||||
}
|
||||
return fileMetadata;
|
||||
}
|
||||
|
||||
private FileType getFileType(String filename) {
|
||||
int s = filename.lastIndexOf(".") + 1;
|
||||
String type = filename.substring(s);
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
<template>
|
||||
<div class="relate_report">
|
||||
<el-button type="success" plain @click="search">{{$t('api_report.title')}}</el-button>
|
||||
|
||||
<el-dialog :title="$t('api_report.title')" :visible.sync="reportVisible">
|
||||
<el-table :data="tableData" v-loading="result.loading">
|
||||
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
|
||||
|
@ -26,7 +23,6 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -49,7 +45,7 @@
|
|||
},
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
open() {
|
||||
this.reportVisible = true;
|
||||
|
||||
let url = "/api/report/list/" + this.testId;
|
||||
|
@ -69,7 +65,4 @@
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.relate_report {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -27,7 +27,19 @@
|
|||
|
||||
<el-button type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
|
||||
|
||||
<ms-api-report-dialog :test-id="id" v-if="test.status === 'Completed'"/>
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<el-button class="el-dropdown-link more" icon="el-icon-more" plain/>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="report" :disabled="test.status !== 'Completed'">
|
||||
{{$t('api_report.title')}}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="performance" :disabled="create">
|
||||
{{$t('api_test.create_performance_test')}}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<ms-api-report-dialog :test-id="id" ref="reportDialog"/>
|
||||
</el-row>
|
||||
</el-header>
|
||||
<ms-api-scenario-config :scenarios="test.scenarioDefinition" ref="config"/>
|
||||
|
@ -72,7 +84,7 @@
|
|||
},
|
||||
|
||||
methods: {
|
||||
init: function () {
|
||||
init() {
|
||||
this.result = this.$get("/project/listAll", response => {
|
||||
this.projects = response.data;
|
||||
})
|
||||
|
@ -87,7 +99,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
getTest: function (id) {
|
||||
getTest(id) {
|
||||
this.result = this.$get("/api/get/" + id, response => {
|
||||
if (response.data) {
|
||||
let item = response.data;
|
||||
|
@ -103,7 +115,7 @@
|
|||
}
|
||||
});
|
||||
},
|
||||
save: function (callback) {
|
||||
save(callback) {
|
||||
this.change = false;
|
||||
let url = this.create ? "/api/create" : "/api/update";
|
||||
this.result = this.$request(this.getOptions(url), () => {
|
||||
|
@ -111,7 +123,7 @@
|
|||
if (callback) callback();
|
||||
});
|
||||
},
|
||||
saveTest: function () {
|
||||
saveTest() {
|
||||
this.save(() => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
if (this.create) {
|
||||
|
@ -121,7 +133,7 @@
|
|||
}
|
||||
})
|
||||
},
|
||||
runTest: function () {
|
||||
runTest() {
|
||||
this.result = this.$post("/api/run", {id: this.test.id}, (response) => {
|
||||
this.$success(this.$t('api_test.running'));
|
||||
this.$router.push({
|
||||
|
@ -129,7 +141,7 @@
|
|||
})
|
||||
});
|
||||
},
|
||||
saveRunTest: function () {
|
||||
saveRunTest() {
|
||||
this.change = false;
|
||||
|
||||
this.save(() => {
|
||||
|
@ -137,11 +149,11 @@
|
|||
this.runTest();
|
||||
})
|
||||
},
|
||||
cancel: function () {
|
||||
cancel() {
|
||||
// console.log(this.test.toJMX().xml)
|
||||
this.$router.push('/api/test/list/all');
|
||||
},
|
||||
getOptions: function (url) {
|
||||
getOptions(url) {
|
||||
let formData = new FormData();
|
||||
let request = {
|
||||
id: this.test.id,
|
||||
|
@ -166,6 +178,23 @@
|
|||
'Content-Type': undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
handleCommand(command) {
|
||||
switch (command) {
|
||||
case "report":
|
||||
this.$refs.reportDialog.open();
|
||||
break;
|
||||
case "performance":
|
||||
this.$store.commit('setTest', {
|
||||
projectId: this.test.projectId,
|
||||
name: this.test.name,
|
||||
jmx: this.test.toJMX()
|
||||
})
|
||||
this.$router.push({
|
||||
path: "/performance/test/create"
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -199,4 +228,8 @@
|
|||
.test-project {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.test-container .more {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,16 +3,17 @@
|
|||
<ms-main-container>
|
||||
<el-card class="table-card" v-loading="result.loading">
|
||||
<template v-slot:header>
|
||||
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="search" :title="$t('commons.test')"
|
||||
<ms-table-header :is-tester-permission="true" :condition.sync="condition" @search="search"
|
||||
:title="$t('commons.test')"
|
||||
@create="create" :createTip="$t('load_test.create')"/>
|
||||
</template>
|
||||
<el-table :data="tableData" class="table-content">
|
||||
<el-table-column :label="$t('commons.name')" width="150" show-overflow-tooltip>
|
||||
<el-table-column :label="$t('commons.name')" width="250" show-overflow-tooltip>
|
||||
<template v-slot:default="scope">
|
||||
<el-link type="info" @click="handleEdit(scope.row)">{{ scope.row.name }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="projectName" :label="$t('load_test.project_name')" width="150" show-overflow-tooltip/>
|
||||
<el-table-column prop="projectName" :label="$t('load_test.project_name')" width="200" show-overflow-tooltip/>
|
||||
<el-table-column prop="userName" :label="$t('api_test.creator')" width="150" show-overflow-tooltip/>
|
||||
<el-table-column width="250" :label="$t('commons.create_time')">
|
||||
<template v-slot:default="scope">
|
||||
|
@ -31,7 +32,7 @@
|
|||
</el-table-column>
|
||||
<el-table-column width="150" :label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)" @deleteClick="handleDelete(scope.row)"/>
|
||||
<ms-table-operators :buttons="buttons" :row="scope.row"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -49,9 +50,13 @@
|
|||
import MsContainer from "../../common/components/MsContainer";
|
||||
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||
import MsApiTestStatus from "./ApiTestStatus";
|
||||
import MsTableOperators from "../../common/components/MsTableOperators";
|
||||
|
||||
export default {
|
||||
components: {MsApiTestStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination, MsTableOperator},
|
||||
components: {
|
||||
MsTableOperators,
|
||||
MsApiTestStatus, MsMainContainer, MsContainer, MsTableHeader, MsTablePagination, MsTableOperator
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
|
@ -62,7 +67,19 @@
|
|||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
loading: false
|
||||
loading: false,
|
||||
buttons: [
|
||||
{
|
||||
tip: this.$t('commons.edit'), icon: "el-icon-edit",
|
||||
exec: this.handleEdit
|
||||
}, {
|
||||
tip: this.$t('commons.copy'), icon: "el-icon-copy-document", type: "success",
|
||||
exec: this.handleCopy
|
||||
}, {
|
||||
tip: this.$t('commons.delete'), icon: "el-icon-delete", type: "danger",
|
||||
exec: this.handleDelete
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -111,6 +128,12 @@
|
|||
}
|
||||
});
|
||||
},
|
||||
handleCopy(test) {
|
||||
this.result = this.$post("/api/copy", {id: test.id}, () => {
|
||||
this.$success(this.$t('commons.delete_success'));
|
||||
this.search();
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.projectId = this.$route.params.projectId;
|
||||
this.search();
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<span>
|
||||
<ms-table-operator-button v-for="(btn, index) in buttons" :key="index" :isTesterPermission="isTesterPermission(btn)"
|
||||
:tip="btn.tip" :icon="btn.icon" :type="btn.type"
|
||||
@exec="click(btn)" @click.stop="clickStop(btn)"/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsTableOperatorButton from "./MsTableOperatorButton";
|
||||
|
||||
export default {
|
||||
name: "MsTableOperators",
|
||||
components: {MsTableOperatorButton},
|
||||
props: {
|
||||
row: Object,
|
||||
buttons: Array
|
||||
},
|
||||
methods: {
|
||||
click(btn) {
|
||||
if (btn.exec instanceof Function) {
|
||||
btn.exec(this.row);
|
||||
}
|
||||
},
|
||||
clickStop(btn) {
|
||||
if (btn.stop instanceof Function) {
|
||||
btn.stop(this.row);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isTesterPermission() {
|
||||
return function (btn) {
|
||||
return btn.isTesterPermission !== false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -115,7 +115,23 @@
|
|||
|
||||
this.listProjects();
|
||||
},
|
||||
mounted() {
|
||||
this.importAPITest();
|
||||
},
|
||||
methods: {
|
||||
importAPITest() {
|
||||
let apiTest = this.$store.state.api.test;
|
||||
if (apiTest && apiTest.name) {
|
||||
this.testPlan.projectId = apiTest.projectId;
|
||||
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");
|
||||
}
|
||||
},
|
||||
listProjects() {
|
||||
this.result = this.$get(this.listProjectPath, response => {
|
||||
this.projects = response.data;
|
||||
|
|
|
@ -3,7 +3,24 @@ import Vuex from 'vuex'
|
|||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const API = {
|
||||
state: {
|
||||
test: {}
|
||||
},
|
||||
mutations: {
|
||||
setTest(state, test) {
|
||||
state.test = test;
|
||||
},
|
||||
clearTest(state) {
|
||||
state.test = {};
|
||||
}
|
||||
},
|
||||
actions: {},
|
||||
getters: {}
|
||||
}
|
||||
|
||||
export default new Vuex.Store({})
|
||||
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
api: API
|
||||
}
|
||||
})
|
||||
|
|
|
@ -11,6 +11,7 @@ export default {
|
|||
'save_success': 'Saved successfully',
|
||||
'delete_success': 'Deleted successfully',
|
||||
'modify_success': 'Modify Success',
|
||||
'copy_success': 'Copy Success',
|
||||
'delete_cancel': 'Deleted Cancel',
|
||||
'confirm': 'Confirm',
|
||||
'cancel': 'Cancel',
|
||||
|
@ -266,6 +267,7 @@ export default {
|
|||
copied: "copied",
|
||||
key: "Key",
|
||||
value: "Value",
|
||||
create_performance_test: "Create Performance Test",
|
||||
scenario: {
|
||||
config: "Scenario Config",
|
||||
input_name: "Please enter the scenario name",
|
||||
|
|
|
@ -10,6 +10,7 @@ export default {
|
|||
'save': '保存',
|
||||
'save_success': '保存成功',
|
||||
'delete_success': '删除成功',
|
||||
'copy_success': '复制成功',
|
||||
'modify_success': '修改成功',
|
||||
'delete_cancel': '已取消删除',
|
||||
'confirm': '确定',
|
||||
|
@ -263,6 +264,7 @@ export default {
|
|||
copied: "已拷贝",
|
||||
key: "键",
|
||||
value: "值",
|
||||
create_performance_test: "创建性能测试",
|
||||
scenario: {
|
||||
config: "场景配置",
|
||||
input_name: "请输入场景名称",
|
||||
|
|
|
@ -10,6 +10,7 @@ export default {
|
|||
'save': '保存',
|
||||
'save_success': '保存成功',
|
||||
'delete_success': '刪除成功',
|
||||
'copy_success': '複製成功',
|
||||
'modify_success': '修改成功',
|
||||
'delete_cancel': '已取消刪除',
|
||||
'confirm': '確定',
|
||||
|
@ -264,6 +265,7 @@ export default {
|
|||
copied: "已拷貝",
|
||||
key: "鍵",
|
||||
value: "值",
|
||||
create_performance_test: "創建性能測試",
|
||||
scenario: {
|
||||
creator: "創建人",
|
||||
config: "場景配寘",
|
||||
|
|
Loading…
Reference in New Issue