This commit is contained in:
fit2-zhao 2021-03-24 20:11:32 +08:00
commit 0948132bae
25 changed files with 234 additions and 92 deletions

View File

@ -6,11 +6,11 @@ ARG MS_VERSION=dev
RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter/lib/junit
COPY backend/target/backend-1.7.jar /opt/apps
COPY backend/target/backend-1.8.jar /opt/apps
COPY backend/target/classes/jmeter/ /opt/jmeter/
ENV JAVA_APP_JAR=/opt/apps/backend-1.7.jar
ENV JAVA_APP_JAR=/opt/apps/backend-1.8.jar
ENV AB_OFF=true

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>metersphere-server</artifactId>
<groupId>io.metersphere</groupId>
<version>1.7</version>
<version>1.8</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,6 +5,7 @@ import io.metersphere.api.service.*;
import io.metersphere.base.domain.ApiDefinitionExecResult;
import io.metersphere.base.domain.ApiScenarioReport;
import io.metersphere.base.domain.ApiTestReport;
import io.metersphere.base.domain.TestPlanReport;
import io.metersphere.commons.constants.*;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
@ -14,6 +15,7 @@ import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.SystemParameterService;
import io.metersphere.track.service.TestPlanReportService;
import io.metersphere.track.service.TestPlanService;
import io.metersphere.track.service.TestPlanTestCaseService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

View File

@ -403,17 +403,24 @@ public class ApiDefinitionService {
private void importMsCase(ApiDefinitionImport apiImport, SqlSession sqlSession, ApiTestCaseMapper apiTestCaseMapper) {
List<ApiTestCaseWithBLOBs> cases = apiImport.getCases();
List<String> caseNames = apiTestCaseService.listPorjectAllCaseName(SessionUtils.getCurrentProjectId());
Set<String> existCaseName = new HashSet<>();
caseNames.forEach(item -> {
existCaseName.add(item);
});
if (CollectionUtils.isNotEmpty(cases)) {
int batchCount = 0;
cases.forEach(item -> {
item.setId(UUID.randomUUID().toString());
item.setCreateTime(System.currentTimeMillis());
item.setUpdateTime(System.currentTimeMillis());
item.setCreateUserId(SessionUtils.getUserId());
item.setUpdateUserId(SessionUtils.getUserId());
item.setProjectId(SessionUtils.getCurrentProjectId());
item.setNum(getNextNum(item.getApiDefinitionId()));
apiTestCaseMapper.insert(item);
if(!existCaseName.contains(item.getName())) {
item.setId(UUID.randomUUID().toString());
item.setCreateTime(System.currentTimeMillis());
item.setUpdateTime(System.currentTimeMillis());
item.setCreateUserId(SessionUtils.getUserId());
item.setUpdateUserId(SessionUtils.getUserId());
item.setProjectId(SessionUtils.getCurrentProjectId());
item.setNum(getNextNum(item.getApiDefinitionId()));
apiTestCaseMapper.insert(item);
}
});
if (batchCount % 300 == 0) {
sqlSession.flushStatements();
@ -628,12 +635,7 @@ public class ApiDefinitionService {
}
if (!CollectionUtils.isEmpty(apiImport.getCases())) {
for (int i = 0; i < apiImport.getCases().size(); i++) {
importMsCase(apiImport, sqlSession, apiTestCaseMapper);
if (i % 300 == 0) {
sqlSession.flushStatements();
}
}
importMsCase(apiImport, sqlSession, apiTestCaseMapper);
}
sqlSession.flushStatements();
}

View File

@ -80,6 +80,10 @@ public class ApiTestCaseService {
private static final String BODY_FILE_DIR = FileUtils.BODY_FILE_DIR;
public List<String> listPorjectAllCaseName(String projectId) {
return extApiTestCaseMapper.listPorjectAllCaseName(projectId);
}
public List<ApiTestCaseResult> list(ApiTestCaseRequest request) {
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
List<ApiTestCaseResult> returnList = extApiTestCaseMapper.list(request);

View File

@ -12,6 +12,8 @@ import java.util.List;
public interface ExtApiTestCaseMapper {
List<String> listPorjectAllCaseName(@Param("projectId") String projectId);
List<ApiTestCaseResult> list(@Param("request") ApiTestCaseRequest request);
List<ApiTestCaseDTO> listSimple(@Param("request") ApiTestCaseRequest request);

View File

@ -391,6 +391,8 @@
<select id="getNextNum" resultType="io.metersphere.base.domain.ApiTestCase">
SELECT * FROM api_test_case WHERE api_test_case.api_definition_id = #{definitionId} ORDER BY num DESC LIMIT 1;
</select>
<select id="listPorjectAllCaseName" resultType="java.lang.String">
select name from api_test_case where project_id = #{projectId}
</select>
</mapper>

View File

@ -120,8 +120,8 @@ public class TestCaseController {
@PostMapping(value = "/add", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public void addTestCase(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file") List<MultipartFile> files) {
testCaseService.save(request, files);
public String addTestCase(@RequestPart("request") EditTestCaseRequest request, @RequestPart(value = "file") List<MultipartFile> files) {
return testCaseService.save(request, files);
}
@PostMapping(value = "/edit", consumes = {"multipart/form-data"})

View File

@ -191,4 +191,7 @@ CREATE TABLE IF NOT EXISTS test_case_test
DEFAULT CHARSET = utf8mb4 COMMENT ='测试用例和关联用例的关系表';
alter table test_case
modify test_id varchar(2000) null;
modify test_id varchar(2000) null;
-- update history data
update test_case set review_status = 'Prepare' where review_status is null;

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>metersphere-server</artifactId>
<groupId>io.metersphere</groupId>
<version>1.7</version>
<version>1.8</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -122,6 +122,9 @@ export default {
this.$nextTick(() => {
this.isActive = true;
})
},
setJsonImport(data) {
this.importJson = data;
}
}
}

View File

@ -267,7 +267,14 @@
let lastOrganizationId = this.currentUser.lastOrganizationId;
let userRole = this.currentUser.userRoles.filter(r => r.sourceId === lastOrganizationId);
if (userRole.length > 0) {
if (userRole[0].roleId === "org_admin") {
let isOrg_admin = false;
userRole.forEach(row=>{
if(row.roleId === "org_admin" ){
isOrg_admin = true;
return;
}
});
if (isOrg_admin) {
this.result = this.$post(url, this.condition, response => {
let data = response.data;
this.items = data.listObject;

View File

@ -8,6 +8,7 @@
@setTreeNodes="setTreeNodes"
@exportTestCase="exportTestCase"
@saveAsEdit="editTestCase"
@createCase="handleCaseSimpleCreate($event, 'add')"
@refreshAll="refreshAll"
:type="'edit'"
ref="nodeTree"
@ -44,7 +45,7 @@
:tree-nodes="treeNodes"
:project-id="projectId"
v-if="activeDom === 'right'"
ref="testCaseList"/>
ref="minder"/>
</ms-tab-button>
</el-tab-pane>
<el-tab-pane
@ -58,6 +59,8 @@
:currentTestCaseInfo="item.testCaseInfo"
@refresh="refreshTable"
@setModuleOptions="setModuleOptions"
@caseEdit="handleCaseCreateOrEdit($event,'edit')"
@caseCreate="handleCaseCreateOrEdit($event,'add')"
:read-only="testCaseReadOnly"
:tree-nodes="treeNodes"
:select-node="selectNode"
@ -155,6 +158,11 @@ export default {
}
}
},
activeName(newVal, oldVal) {
if (oldVal !== 'default' && newVal === 'default' && this.$refs.minder) {
this.$refs.minder.refresh();
}
}
},
computed: {
checkRedirectID: function () {
@ -282,7 +290,9 @@ export default {
this.selectParentNodes = pNodes;
},
refreshTable() {
this.$refs.testCaseList.initTableData();
if ( this.$refs.testCaseList) {
this.$refs.testCaseList.initTableData();
}
},
editTestCase(testCase) {
this.type="edit"
@ -293,11 +303,20 @@ export default {
}
this.addTab({name: 'edit', testCaseInfo: testCase});
},
handleCaseCreateOrEdit(data, type) {
if (this.$refs.minder) {
this.$refs.minder.addCase(data, type);
}
},
handleCaseSimpleCreate(data, type) {
this.handleCaseCreateOrEdit(data, type);
if (this.$refs.minder) {
this.$refs.minder.refresh();
}
},
copyTestCase(testCase) {
this.type="copy"
this.testCaseReadOnly = false;
let item = {};
testCase.isCopy = true;
this.addTab({name: 'edit', testCaseInfo: testCase});
},

View File

@ -125,6 +125,7 @@ export default {
this.$emit('saveAsEdit', this.testCaseForm);
} else {
this.$emit('refresh');
this.$emit('createCase', this.testCaseForm);
}
})
} else {

View File

@ -9,7 +9,7 @@
id="inputDelay"
type="primary"
:content="$t('commons.save')"
size="small" @exec="saveCase"
size="small" @click="saveCase"
icon=""
title="ctrl + s"/>
<el-dropdown v-else split-button type="primary" class="ms-api-buttion" @click="handleCommand"
@ -62,14 +62,14 @@
</el-row>
<el-row>
<el-col :span="7">
<el-form-item label="评审状态" :label-width="formLabelWidth" prop="reviewStatus">
<el-select size="small" v-model="form.reviewStatus" class="ms-case-input">
<el-option v-for="item in options" :key="item.id" :label="item.label" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<!-- <el-col :span="7">-->
<!-- <el-form-item label="评审状态" :label-width="formLabelWidth" prop="reviewStatus">-->
<!-- <el-select size="small" v-model="form.reviewStatus" class="ms-case-input">-->
<!-- <el-option v-for="item in options" :key="item.id" :label="item.label" :value="item.id">-->
<!-- </el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="7">
<el-form-item :label="$t('commons.tag')" :label-width="formLabelWidth" prop="tag">
<ms-input-tag :currentScenario="form" v-if="showInputTag" ref="tag" class="ms-case-input"/>
@ -88,8 +88,6 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="7">
<el-form-item :label="$t('test_track.case.priority')" :label-width="formLabelWidth" prop="priority">
<el-select :disabled="readOnly" v-model="form.priority" clearable
@ -101,17 +99,17 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="14">
</el-row>
<el-row>
<el-col :span="7">
<el-form-item :label="$t('test_track.case.relate_test')" :label-width="formLabelWidth">
<el-cascader :options="sysList" filterable placeholder="请选择要关联的测试" show-all-levels
v-model="form.selected" :props="props"
class="ms-case" @change="clearInput" ref="cascade"></el-cascader>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-col :span="7">
<el-form-item label="关联需求" :label-width="formLabelWidth" prop="demandId">
<el-select filterable :disabled="readOnly" v-model="form.demandId" @visible-change="visibleChange"
placeholder="请选择要关联的需求" class="ms-case-input">
@ -125,7 +123,7 @@
</el-form-item>
</el-col>
<el-col :span="10" :offset="1">
<el-col :span="7">
<el-form-item label="需求ID/名称" :label-width="formLabelWidth" prop="demandName"
v-if="form.demandId=='other'">
<el-input v-model="form.demandName"></el-input>
@ -291,7 +289,7 @@
<script>
import {TokenKey, WORKSPACE_ID} from '@/common/js/constants';
import MsDialogFooter from '../../../common/components/MsDialogFooter'
import {getCurrentUser, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {getCurrentUser, handleCtrlSEvent, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {Message} from "element-ui";
import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment";
import {buildNodePath} from "../../../api/definition/model/NodeTree";
@ -363,6 +361,7 @@ export default {
{max: 255, message: this.$t('test_track.length_less_than') + '255', trigger: 'blur'}
],
module: [{required: true, message: this.$t('test_track.case.input_module'), trigger: 'change'}],
demandName: [{required: true, message: this.$t('test_track.case.input_demand_name'), trigger: 'change'}],
maintainer: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
priority: [{required: true, message: this.$t('test_track.case.input_priority'), trigger: 'change'}],
method: [{required: true, message: this.$t('test_track.case.input_method'), trigger: 'change'}],
@ -404,7 +403,7 @@ export default {
type: String
},
computed: {
projectId() {
projectIds() {
return this.$store.state.projectId
},
},
@ -431,7 +430,8 @@ export default {
}
},
created() {
this.loadOptions()
this.loadOptions();
this.addListener(); // ctrl s
},
methods: {
clearInput() {
@ -455,7 +455,7 @@ export default {
},
getTestOptions(val) {
this.form.type = val
this.projectId = this.projectId
this.projectId = this.projectIds
this.testOptions = [];
let url = '';
if (this.form.type === 'testcase' || this.form.type === 'automation') {
@ -556,7 +556,7 @@ export default {
/*
this.form.selected=[["automation", "3edaaf31-3fa4-4a53-9654-320205c2953a"],["automation", "3aa58bd1-c986-448c-8060-d32713dbd4eb"]]
*/
this.projectId = this.projectId;
this.projectId = this.projectIds;
if (window.history && window.history.pushState) {
history.pushState(null, null, document.URL);
window.addEventListener('popstate', this.close);
@ -718,7 +718,7 @@ export default {
let param = this.buildParam();
if (this.validate(param)) {
let option = this.getOption(param);
this.result = this.$request(option, () => {
this.result = this.$request(option, (response) => {
this.$success(this.$t('commons.save_success'));
if (this.operationType == 'add' && this.isCreateContinue) {
this.form.name = '';
@ -737,6 +737,13 @@ export default {
}
this.dialogFormVisible = false;
this.$emit("refresh");
if (this.type === 'add' || this.type === 'copy') {
param.id = response.data;
this.$emit("caseCreate", param);
this.close();
} else {
this.$emit("caseEdit", param);
}
});
}
} else {
@ -974,6 +981,15 @@ export default {
/// todo:
return file.size > 0;
},
addListener() {
document.addEventListener("keydown", this.createCtrlSHandle);
},
removeListener() {
document.removeEventListener("keydown", this.createCtrlSHandle);
},
createCtrlSHandle(event) {
handleCtrlSEvent(event, this.saveCase);
},
}
}
</script>

View File

@ -35,6 +35,7 @@
<test-case-create
:tree-nodes="treeNodes"
@saveAsEdit="saveAsEdit"
@createCase="createCase"
@refresh="refresh"
ref="testCaseCreate"
></test-case-create>
@ -98,6 +99,9 @@ export default {
saveAsEdit(data) {
this.$emit('saveAsEdit', data);
},
createCase(data) {
this.$emit('createCase', data);
},
refresh() {
this.$emit("refreshTable");
},

View File

@ -6,12 +6,17 @@
:tags="tags"
:distinct-tags="tags"
@save="save"
ref="minder"
/>
</template>
<script>
import MsModuleMinder from "@/business/components/common/components/MsModuleMinder";
import {getTestCaseDataMap} from "@/business/components/track/common/minder/minderUtils";
import {
appendChild,
getTestCaseDataMap,
parseCase, updateNode
} from "@/business/components/track/common/minder/minderUtils";
export default {
name: "TestCaseMinder",
components: {MsModuleMinder},
@ -48,6 +53,7 @@ name: "TestCaseMinder",
}
},
save(data) {
console.log(data);
let saveCases = [];
let deleteCases = [];
this.buildSaveCase(data.root, saveCases, deleteCases, undefined);
@ -130,7 +136,22 @@ name: "TestCaseMinder",
throw new Error(tip);
}
},
addCase(data, type) {
let nodeData = parseCase(data, new Map());
let minder = window.minder;
let jsonImport = minder.exportJson();
if (type === 'edit') {
updateNode(jsonImport.root, nodeData);
} else {
appendChild(data.nodeId, jsonImport.root, nodeData);
}
this.$refs.minder.setJsonImport(jsonImport);
},
refresh() {
if (this.$refs.minder) {
this.$refs.minder.reload();
}
}
}
}
</script>

View File

@ -47,13 +47,13 @@ name: "TestPlanMinder",
if (this.projectId) {
this.result = this.$get('/test/plan/case/list/minder/' + this.planId, response => {
this.dataMap = getTestCaseDataMap(response.data, true, (data, item) => {
if (item.stats === 'Pass') {
if (item.status === 'Pass') {
data.resource.push(this.$t('test_track.plan_view.pass'));
} else if (item.reviewStatus === 'Failure') {
} else if (item.status === 'Failure') {
data.resource.push(this.$t('test_track.plan_view.failure'));
} else if (item.reviewStatus === 'Blocking') {
} else if (item.status === 'Blocking') {
data.resource.push(this.$t('test_track.plan_view.blocking'));
} else if (item.reviewStatus === 'Skip') {
} else if (item.status === 'Skip') {
data.resource.push(this.$t('test_track.plan_view.skip'));
} else {
data.resource.push(this.$t('test_track.plan.plan_status_prepare'));

View File

@ -1,44 +1,55 @@
import {getUUID} from "@/common/js/utils";
export function getTestCaseDataMap(testCase, isDisable, setParamCallback) {
let dataMap = new Map();
if (testCase) {
testCase.forEach(item => {
item.steps = JSON.parse(item.steps);
// if (item.tags && item.tags.length > 0) {
// item.tags = JSON.parse(item.tags);
// }
let mapItem = dataMap.get(item.nodeId);
let nodeItem = {
data: {
id: item.id,
text: item.name,
priority: Number.parseInt(item.priority.substring(item.priority.length - 1 )) + 1,
resource: ["用例"],
type: item.type,
method: item.method,
maintainer: item.maintainer
}
}
if (setParamCallback) {
setParamCallback(nodeItem.data, item);
}
if (isDisable) {
nodeItem.data.disable = true;
// 用例节点可以打标签
nodeItem.data.allowDisabledTag = true;
}
parseChildren(nodeItem, item, isDisable);
if (mapItem) {
mapItem.push(nodeItem);
} else {
mapItem = [];
mapItem.push(nodeItem);
dataMap.set(item.nodeId, mapItem);
}
parseCase(item, dataMap, isDisable, setParamCallback);
})
}
return dataMap;
}
export function parseCase(item, dataMap, isDisable, setParamCallback) {
if (item.steps) {
item.steps = JSON.parse(item.steps);
} else {
item.steps = [];
}
// if (item.tags && item.tags.length > 0) {
// item.tags = JSON.parse(item.tags);
// }
let mapItem = dataMap.get(item.nodeId);
let nodeItem = {
data: {
id: item.id,
text: item.name,
priority: Number.parseInt(item.priority.substring(item.priority.length - 1 )) + 1,
resource: ["用例"],
type: item.type,
method: item.method,
maintainer: item.maintainer
}
}
if (setParamCallback) {
setParamCallback(nodeItem.data, item);
}
if (isDisable) {
nodeItem.data.disable = true;
// 用例节点可以打标签
nodeItem.data.allowDisabledTag = true;
}
parseChildren(nodeItem, item, isDisable);
if (mapItem) {
mapItem.push(nodeItem);
} else {
mapItem = [];
mapItem.push(nodeItem);
dataMap.set(item.nodeId, mapItem);
}
return nodeItem;
}
function parseChildren(nodeItem, item, isDisable) {
nodeItem.children = [];
let children = [];
@ -63,7 +74,8 @@ function _parseChildren(children, k, v, isDisable) {
data: {
text: k,
resource: v ? [v] : []
}
},
children: []
}
if (isDisable) {
node.data.disable = true;
@ -72,3 +84,40 @@ function _parseChildren(children, k, v, isDisable) {
return node;
}
}
export function appendChild(appendPid, root, node) {
if (root.data.id === appendPid) {
root.children.push(node);
return;
}
if (!root.children) {
root.children = [];
}
let children = root.children;
for (const index in children) {
let item = children[index];
if (item.data.id === appendPid) {
item.data.expandState = "expand";
item.children.push(node);
return;
} else {
appendChild(appendPid, item, node);
}
}
}
export function updateNode(root, node) {
if (!root.children) {
root.children = [];
}
let children = root.children;
for (const index in children) {
let item = children[index];
if (item.data.id === node.data.id) {
children[index] = node;
return;
} else {
updateNode(item, node);
}
}
}

View File

@ -86,7 +86,7 @@
show-overflow-tooltip
:key="index">
<template v-slot:default="scope">
<el-progress :percentage="scope.row.passRate.substring(0, scope.row.passRate.length-1)"></el-progress>
<el-progress :percentage="calPassRate(scope)"></el-progress>
</template>
</el-table-column>
<el-table-column
@ -293,6 +293,10 @@ export default {
this.initTableData();
},
methods: {
calPassRate(scope) {
let passRate = scope.row.passRate.substring(0, scope.row.passRate.length-1);
return Number.parseInt(passRate, 10);
},
customHeader() {
this.$refs.headerCustom.open(this.tableLabel)
},

@ -1 +1 @@
Subproject commit 07951ba17aef6f29e50cfd68e40de3266f9a60cd
Subproject commit 2115bd28a90854d2b6276a90878934715498c584

View File

@ -1166,6 +1166,7 @@ export default {
input_type: "Please select type",
input_method: "Please select method",
input_prerequisite: "Please select prerequisite",
input_demand_name: "Please input demand id or name",
delete_confirm: "Confirm delete test case",
delete: "Delete case",
save_create_continue: "Save and create continue",

View File

@ -1170,6 +1170,7 @@ export default {
input_type: "请选择用例类型",
input_method: "请选择测试方式",
input_prerequisite: "请输入前置条件",
input_demand_name: "请输入需求ID或名称",
delete_confirm: "确认删除测试用例",
delete: "删除用例",
save_create_continue: "保存并继续创建",

View File

@ -1168,6 +1168,7 @@ export default {
input_type: "請選擇用例類型",
input_method: "請選擇測試方式",
input_prerequisite: "請輸入前置條件",
input_demand_name: "請輸入請求ID或名稱",
delete_confirm: "確認刪除測試用例",
delete: "刪除用例",
save_create_continue: "保存並繼續創建",

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>io.metersphere</groupId>
<artifactId>metersphere-server</artifactId>
<version>1.7</version>
<version>1.8</version>
<packaging>pom</packaging>
<parent>