提交 b602eae0 编写于 作者: S shiziyuan9527

Merge branch 'dev' of https://github.com/metersphere/metersphere into dev

......@@ -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);
......
<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>
<template v-slot:default="scope">
<el-link type="info" @click="link(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-api-report-status :row="row"/>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
<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>
<template v-slot:default="scope">
<el-link type="info" @click="link(scope.row)">{{ scope.row.name }}</el-link>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column width="250" :label="$t('commons.update_time')">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="$t('commons.status')">
<template v-slot:default="{row}">
<ms-api-report-status :row="row"/>
</template>
</el-table-column>
</el-table>
</el-dialog>
</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();
......
<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: "場景配寘",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册