feat(测试跟踪): 功能用例公共用例库(X-Pack) 多版本支持

--story=1004645 --user=王孝刚 功能用例公共用例库(X-Pack) 多版本支持
https://www.tapd.cn/55049933/s/1091704
This commit is contained in:
wxg0103 2022-01-11 10:27:25 +08:00 committed by 刘瑞斌
parent 4cfe63bee8
commit 9b3df16605
9 changed files with 362 additions and 84 deletions

View File

@ -232,8 +232,40 @@
</select> </select>
<select id="publicList" resultType="io.metersphere.track.dto.TestCaseDTO"> <select id="publicList" resultType="io.metersphere.track.dto.TestCaseDTO">
select SELECT test_case.versionName,
deleteUser.name AS delete_user_id,test_case.delete_time, test_case.versionId,
test_case.createName,
test_case.projectName,
test_case.id,
test_case.node_id,
test_case.node_path,
test_case.project_id,
test_case.`name`,
test_case.`type`,
test_case.maintainer,
test_case.priority,
test_case.`method`,
test_case.create_time,
test_case.update_time,
test_case.test_id,
test_case.sort,
test_case.num,
test_case.other_test_name,
test_case.review_status,
test_case.tags,
test_case.demand_id,
test_case.demand_name,
test_case.`status`,
test_case.custom_num,
test_case.step_model,
test_case.create_user,
test_case.custom_fields,
test_case.case_public ,
test_case.ref_id
from
(select
project_version.name as versionName,
project_version.id as versionId,
<if test="request.selectFields != null and request.selectFields.size() > 0"> <if test="request.selectFields != null and request.selectFields.size() > 0">
<foreach collection="request.selectFields" item="field" separator=","> <foreach collection="request.selectFields" item="field" separator=",">
${field} ${field}
@ -246,11 +278,13 @@
test_case.other_test_name, test_case.review_status, test_case.tags, test_case.other_test_name, test_case.review_status, test_case.tags,
test_case.demand_id, test_case.demand_name, test_case.`status`, test_case.demand_id, test_case.demand_name, test_case.`status`,
test_case.custom_num, test_case.step_model, test_case.create_user,u.name as createName, test_case.custom_num, test_case.step_model, test_case.create_user,u.name as createName,
test_case.custom_fields,test_case.case_public , project.name as projectName test_case.custom_fields,test_case.case_public , project.name as projectName ,test_case.ref_id
</if> </if>
from test_case left join user u on test_case.create_user=u.id from test_case left join user u on test_case.create_user=u.id
left join user deleteUser on test_case.delete_user_id=deleteUser.id left join (select id,workspace_id,NAME from project where workspace_id =#{request.workspaceId})
left join project on test_case.project_id = project.id project on test_case.project_id = project.id
left join project_version on project.id = project_version.project_id and project_version.id =
test_case.version_id
<where> <where>
<include refid="filters"/> <include refid="filters"/>
<if test="request.combine != null"> <if test="request.combine != null">
@ -306,7 +340,71 @@
</if> </if>
</where> </where>
<include refid="io.metersphere.base.mapper.ext.ExtBaseMapper.orders"/> <include refid="io.metersphere.base.mapper.ext.ExtBaseMapper.orders"/>) test_case right join
(select
max(test_case.update_time) update_time
from test_case left join user u on test_case.create_user=u.id
left join (select id,workspace_id,NAME from project where workspace_id =#{request.workspaceId})
project on test_case.project_id = project.id
left join project_version on project.id = project_version.project_id and project_version.id =
test_case.version_id
<where>
<include refid="filters"/>
<if test="request.combine != null">
<include refid="combine">
<property name="condition" value="request.combine"/>
<property name="name" value="request.name"/>
<property name="objectKey" value="request.combine.tags"/>
</include>
</if>
<if test="request.statusIsNot != null">
and (test_case.status is null or test_case.status != #{request.statusIsNot})
</if>
<if test="request.notEqStatus != null">
and (test_case.status is null or test_case.status != #{request.notEqStatus})
</if>
<if test="request.name != null">
and (test_case.name like CONCAT('%', #{request.name},'%')
or test_case.num like CONCAT('%', #{request.name},'%')
or test_case.tags like CONCAT('%', #{request.name},'%')
or test_case.custom_num like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.ids != null">
and test_case.id in
<foreach collection="request.ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="request.relevanceCreateTime >0">
and test_case.id in (select test_case_id from test_case_test where test_case_test.create_time >=
#{request.createTime})
</if>
<if test="request.createTime >0">
and test_case.create_time >= #{request.createTime}
</if>
<if test="request.nodeIds != null and request.nodeIds.size() > 0">
and test_case.node_id in
<foreach collection="request.nodeIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
</foreach>
</if>
<if test="request.workspaceId != null">
AND test_case.project_id in (select id from project where workspace_id=#{request.workspaceId})
</if>
and test_case.case_public = TRUE
<include refid="filters"/>
<if test="request.caseCoverage == 'uncoverage' ">
and test_case.id not in (select distinct test_case_test.test_case_id from test_case_test)
</if>
<if test="request.caseCoverage == 'coverage' ">
and test_case.id in (select distinct test_case_test.test_case_id from test_case_test)
</if>
</where>
group by test_case.ref_id
<include refid="io.metersphere.base.mapper.ext.ExtBaseMapper.orders"/>) tmp on test_case.update_time = tmp.update_time
</select> </select>
<select id="moduleCount" resultType="java.lang.Integer"> <select id="moduleCount" resultType="java.lang.Integer">
@ -758,7 +856,7 @@
<update id="deletePublic"> <update id="deletePublic">
update test_case update test_case
set case_public = false set case_public = false
where id = #{request.id} where ref_id = #{request.refId}
</update> </update>
<update id="reduction"> <update id="reduction">
@ -790,7 +888,7 @@
</select> </select>
<select id="countByWorkSpaceId" resultType="java.lang.Integer"> <select id="countByWorkSpaceId" resultType="java.lang.Integer">
select count(id) select count(distinct ref_id)
from test_case from test_case
where project_id in ( where project_id in (
select id select id

View File

@ -250,13 +250,12 @@ public class TestCaseController {
return testCaseService.deleteTestCaseToGc(testCaseId); return testCaseService.deleteTestCaseToGc(testCaseId);
} }
@PostMapping("/deletePublic/{testCaseId}") @PostMapping("/deletePublic/{refId}")
@MsAuditLog(module = "track_test_case", type = OperLogConstants.GC, beforeEvent = "#msClass.getLogDetails(#testCaseId)", msClass = TestCaseService.class) @MsAuditLog(module = "track_test_case", type = OperLogConstants.GC, beforeEvent = "#msClass.getLogDetails(#testCaseId)", msClass = TestCaseService.class)
@SendNotice(taskType = NoticeConstants.TaskType.TRACK_TEST_CASE_TASK, event = NoticeConstants.Event.DELETE, target = "#targetClass.getTestCase(#testCaseId)", targetClass = TestCaseService.class, @SendNotice(taskType = NoticeConstants.TaskType.TRACK_TEST_CASE_TASK, event = NoticeConstants.Event.DELETE, target = "#targetClass.getTestCase(#testCaseId)", targetClass = TestCaseService.class,
mailTemplate = "track/TestCaseDelete", subject = "测试用例通知") mailTemplate = "track/TestCaseDelete", subject = "测试用例通知")
public int deletePublic(@PathVariable String testCaseId) { public int deletePublic(@PathVariable String refId) {
checkPermissionService.checkTestCaseOwner(testCaseId); return testCaseService.deleteTestCasePublic(refId);
return testCaseService.deleteTestCasePublic(testCaseId);
} }
@ -346,6 +345,15 @@ public class TestCaseController {
testCaseService.deleteToGcBatch(request.getIds()); testCaseService.deleteToGcBatch(request.getIds());
} }
@PostMapping("/batch/movePublic/deleteToGc")
@RequiresPermissions(PermissionConstants.PROJECT_TRACK_CASE_READ_DELETE)
@MsAuditLog(module = "track_test_case", type = OperLogConstants.BATCH_DEL, beforeEvent = "#msClass.getLogDetails(#request.ids)", msClass = TestCaseService.class)
@SendNotice(taskType = NoticeConstants.TaskType.TRACK_TEST_CASE_TASK, target = "#targetClass.findByBatchRequest(#request)", targetClass = TestCaseService.class,
event = NoticeConstants.Event.DELETE, mailTemplate = "track/TestCaseDelete", subject = "测试用例通知")
public void deleteToGcBatchPublic(@RequestBody TestCaseBatchRequest request) {
testCaseService.deleteToGcBatchPublic(request.getIds());
}
@PostMapping("/reduction") @PostMapping("/reduction")
@MsAuditLog(module = "track_test_case", type = OperLogConstants.GC, beforeEvent = "#msClass.getLogDetails(#testCaseId)", msClass = TestCaseService.class) @MsAuditLog(module = "track_test_case", type = OperLogConstants.GC, beforeEvent = "#msClass.getLogDetails(#testCaseId)", msClass = TestCaseService.class)
public void reduction(@RequestBody TestCaseBatchRequest request) { public void reduction(@RequestBody TestCaseBatchRequest request) {

View File

@ -16,6 +16,7 @@ import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtIssuesMapper; import io.metersphere.base.mapper.ext.ExtIssuesMapper;
import io.metersphere.base.mapper.ext.ExtProjectVersionMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper; import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.constants.TestCaseReviewStatus; import io.metersphere.commons.constants.TestCaseReviewStatus;
@ -155,6 +156,8 @@ public class TestCaseService {
private MinderExtraNodeService minderExtraNodeService; private MinderExtraNodeService minderExtraNodeService;
@Resource @Resource
private ProjectVersionService projectVersionService; private ProjectVersionService projectVersionService;
@Resource
private ExtProjectVersionMapper extProjectVersionMapper;
private void setNode(TestCaseWithBLOBs testCase) { private void setNode(TestCaseWithBLOBs testCase) {
if (StringUtils.isEmpty(testCase.getNodeId()) || "default-module".equals(testCase.getNodeId())) { if (StringUtils.isEmpty(testCase.getNodeId()) || "default-module".equals(testCase.getNodeId())) {
@ -1371,28 +1374,47 @@ public class TestCaseService {
TestCaseExample exampleList = new TestCaseExample(); TestCaseExample exampleList = new TestCaseExample();
exampleList.createCriteria().andIdIn(request.getIds()); exampleList.createCriteria().andIdIn(request.getIds());
List<TestCaseWithBLOBs> list = testCaseMapper.selectByExampleWithBLOBs(exampleList); List<TestCaseWithBLOBs> list = testCaseMapper.selectByExampleWithBLOBs(exampleList);
for (TestCaseWithBLOBs item : list) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
Long nextOrder = ServiceUtils.getNextOrder(request.getProjectId(), extTestCaseMapper::getLastOrder);
int nextNum = getNextNum(request.getProjectId());
try {
for (int i = 0; i < list.size(); i++) {
TestCaseWithBLOBs batchCopy = new TestCaseWithBLOBs(); TestCaseWithBLOBs batchCopy = new TestCaseWithBLOBs();
BeanUtils.copyBean(batchCopy, item); BeanUtils.copyBean(batchCopy, list.get(i));
checkTestCaseExist(batchCopy); checkTestCaseExist(batchCopy);
batchCopy.setId(UUID.randomUUID().toString()); String id = UUID.randomUUID().toString();
batchCopy.setName("copy_" + item.getName()); batchCopy.setId(id);
batchCopy.setName(ServiceUtils.getCopyName(batchCopy.getName()));
batchCopy.setCreateTime(System.currentTimeMillis()); batchCopy.setCreateTime(System.currentTimeMillis());
batchCopy.setUpdateTime(System.currentTimeMillis()); batchCopy.setUpdateTime(System.currentTimeMillis());
batchCopy.setNum(getNextNum(SessionUtils.getCurrentProjectId()));
batchCopy.setCustomNum(batchCopy.getNum().toString());
batchCopy.setCreateUser(SessionUtils.getUserId()); batchCopy.setCreateUser(SessionUtils.getUserId());
batchCopy.setMaintainer(SessionUtils.getUserId()); batchCopy.setMaintainer(SessionUtils.getUserId());
batchCopy.setReviewStatus(TestCaseReviewStatus.Prepare.name()); batchCopy.setReviewStatus(TestCaseReviewStatus.Prepare.name());
batchCopy.setStatus(TestCaseReviewStatus.Prepare.name()); batchCopy.setStatus(TestCaseReviewStatus.Prepare.name());
batchCopy.setNodePath(request.getNodePath()); batchCopy.setNodePath(request.getNodePath());
batchCopy.setNodeId(request.getNodeId()); batchCopy.setNodeId(request.getNodeId());
batchCopy.setProjectId(SessionUtils.getCurrentProjectId());
batchCopy.setCasePublic(false); batchCopy.setCasePublic(false);
testCaseMapper.insert(batchCopy); batchCopy.setRefId(id);
if (!(batchCopy.getProjectId()).equals(SessionUtils.getCurrentProjectId())) {
String versionId = extProjectVersionMapper.getDefaultVersion(SessionUtils.getCurrentProjectId());
batchCopy.setVersionId(versionId);
}
batchCopy.setProjectId(SessionUtils.getCurrentProjectId());
batchCopy.setOrder(nextOrder += ServiceUtils.ORDER_STEP);
batchCopy.setCustomNum(String.valueOf(nextNum));
batchCopy.setNum(nextNum++);
mapper.insert(batchCopy);
if (i % 50 == 0)
sqlSession.flushStatements();
}
sqlSession.flushStatements();
} finally {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
} }
} }
public void deleteTestCaseBath(TestCaseBatchRequest request) { public void deleteTestCaseBath(TestCaseBatchRequest request) {
TestCaseExample example = this.getBatchExample(request); TestCaseExample example = this.getBatchExample(request);
deleteTestPlanTestCaseBath(request.getIds()); deleteTestPlanTestCaseBath(request.getIds());
@ -2013,6 +2035,21 @@ public class TestCaseService {
} }
} }
public void deleteToGcBatchPublic(List<String> ids) {
if (CollectionUtils.isNotEmpty(ids)) {
for (String id : ids) {
TestCase testCase = testCaseMapper.selectByPrimaryKey(id);
if ((StringUtils.isNotEmpty(testCase.getMaintainer()) && testCase.getMaintainer() == SessionUtils.getUserId()) ||
(StringUtils.isNotEmpty(testCase.getCreateUser()) && testCase.getCreateUser() == SessionUtils.getUserId())) {
this.deleteTestCasePublic(testCase.getRefId());
}
else {
MSException.throwException(Translator.get("check_owner_case"));
}
}
}
}
public String getCaseLogDetails(TestCaseMinderEditRequest request) { public String getCaseLogDetails(TestCaseMinderEditRequest request) {
if (CollectionUtils.isNotEmpty(request.getData())) { if (CollectionUtils.isNotEmpty(request.getData())) {
List<String> ids = request.getData().stream().map(TestCase::getId).collect(Collectors.toList()); List<String> ids = request.getData().stream().map(TestCase::getId).collect(Collectors.toList());
@ -2237,13 +2274,16 @@ public class TestCaseService {
try { try {
for (int i = 0; i < testCases.size(); i++) { for (int i = 0; i < testCases.size(); i++) {
TestCaseWithBLOBs testCase = testCases.get(i); TestCaseWithBLOBs testCase = testCases.get(i);
testCase.setId(UUID.randomUUID().toString()); String id = UUID.randomUUID().toString();
testCase.setId(id);
testCase.setName(ServiceUtils.getCopyName(testCase.getName())); testCase.setName(ServiceUtils.getCopyName(testCase.getName()));
testCase.setNodeId(request.getNodeId()); testCase.setNodeId(request.getNodeId());
testCase.setNodePath(request.getNodePath()); testCase.setNodePath(request.getNodePath());
testCase.setOrder(nextOrder += ServiceUtils.ORDER_STEP); testCase.setOrder(nextOrder += ServiceUtils.ORDER_STEP);
testCase.setCustomNum(String.valueOf(nextNum)); testCase.setCustomNum(String.valueOf(nextNum));
testCase.setNum(nextNum++); testCase.setNum(nextNum++);
testCase.setCasePublic(false);
testCase.setRefId(id);
mapper.insert(testCase); mapper.insert(testCase);
if (i % 50 == 0) if (i % 50 == 0)
sqlSession.flushStatements(); sqlSession.flushStatements();
@ -2284,9 +2324,9 @@ public class TestCaseService {
} }
} }
public int deleteTestCasePublic(String testCaseId) { public int deleteTestCasePublic(String refId) {
TestCase testCase = new TestCase(); TestCase testCase = new TestCase();
testCase.setId(testCaseId); testCase.setRefId(refId);
return extTestCaseMapper.deletePublic(testCase); return extTestCaseMapper.deletePublic(testCase);
} }
} }

View File

@ -132,6 +132,7 @@
@caseEdit="handleCaseCreateOrEdit($event,'edit')" @caseEdit="handleCaseCreateOrEdit($event,'edit')"
@caseCreate="handleCaseCreateOrEdit($event,'add')" @caseCreate="handleCaseCreateOrEdit($event,'add')"
:read-only="testCaseReadOnly" :read-only="testCaseReadOnly"
@checkout="checkoutPublic($event, item)"
:tree-nodes="treeNodes" :tree-nodes="treeNodes"
:select-node="selectNode" :select-node="selectNode"
:select-condition="condition" :select-condition="condition"
@ -646,6 +647,19 @@ export default {
vh.loading = false; vh.loading = false;
}); });
}); });
},
checkoutPublic(testCase, item) {
Object.assign(item.testCaseInfo, testCase)
// copy
this.$refs.testCaseEditShow[0].changeType("copy");
this.$refs.testCaseEditShow[0].initEdit(item.testCaseInfo, () => {
this.$nextTick(() => {
let vh = this.$refs.testCaseEditShow[0].$refs.versionHistory;
vh.getVersionOptionList(vh.handleVersionOptions);
vh.show = false;
vh.loading = false;
});
});
} }
} }
}; };

View File

@ -23,6 +23,7 @@
ref="versionHistory" ref="versionHistory"
:version-data="versionData" :version-data="versionData"
:current-id="currentTestCaseInfo.id" :current-id="currentTestCaseInfo.id"
:current-project-id="currentProjectId"
@compare="compare" @checkout="checkout" @create="create" @del="del"/> @compare="compare" @checkout="checkout" @create="create" @del="del"/>
<el-dropdown split-button type="primary" class="ms-api-buttion" @click="handleCommand" <el-dropdown split-button type="primary" class="ms-api-buttion" @click="handleCommand"
@command="handleCommand" size="small" style="float: right;margin-right: 20px"> @command="handleCommand" size="small" style="float: right;margin-right: 20px">
@ -150,8 +151,8 @@
:tree-nodes="treeNodes"></test-case-version-diff> :tree-nodes="treeNodes"></test-case-version-diff>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible=false"> </el-button> <el-button @click="dialogVisible=false">{{this.$t('commons.cancel')}}</el-button>
<el-button type="primary"> </el-button> <el-button type="primary">{{this.$t('commons.confirm')}}</el-button>
</span> </span>
</el-dialog> </el-dialog>
</div> </div>
@ -309,7 +310,8 @@ export default {
versionData: [], versionData: [],
dialogVisible: false, dialogVisible: false,
oldData: null, oldData: null,
newData: null newData: null ,
currentProjectId: "" ,
}; };
}, },
props: { props: {
@ -944,6 +946,9 @@ export default {
}, },
getVersionHistory() { getVersionHistory() {
this.$get('/test/case/versions/' + this.currentTestCaseInfo.id, response => { this.$get('/test/case/versions/' + this.currentTestCaseInfo.id, response => {
for (let i = 0; i < response.data.length; i++) {
this.currentProjectId = response.data[i].projectId;
}
this.versionData = response.data; this.versionData = response.data;
this.$refs.versionHistory.loading = false; this.$refs.versionHistory.loading = false;
}); });

View File

@ -18,13 +18,20 @@
<el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="form.id"> <el-link type="primary" style="margin-right: 20px" @click="openHis" v-if="form.id">
{{ $t('operating_log.change_history') }} {{ $t('operating_log.change_history') }}
</el-link> </el-link>
<!-- 版本历史 -->
<ms-version-history v-xpack
ref="versionHistory"
:version-data="versionData"
:current-id="currentTestCaseInfo.id"
:current-project-id="currentProjectId"
@compare="compare" @checkout="checkout" @create="create" @del="del"/>
<ms-table-button v-if="this.path!=='/test/case/add'" <ms-table-button v-if="this.path!=='/test/case/add'"
id="inputDelay" id="inputDelay"
type="primary" type="primary"
:content="$t('commons.copy')" :content="$t('commons.copy')"
size="small" @click="handleCopyPublic" size="small" @click="handleCopyPublic"
icon="" icon=""
:disabled="readOnly"/> />
</div> </div>
<el-form :model="form" :rules="rules" ref="caseFrom" v-loading="result.loading" class="case-form"> <el-form :model="form" :rules="rules" ref="caseFrom" v-loading="result.loading" class="case-form">
<ms-form-divider :title="$t('test_track.plan_view.base_info')"/> <ms-form-divider :title="$t('test_track.plan_view.base_info')"/>
@ -35,13 +42,13 @@
:label="$t('test_track.case.name')" :label="$t('test_track.case.name')"
:label-width="formLabelWidth" :label-width="formLabelWidth"
prop="name"> prop="name">
<el-input :disabled="true" v-model="form.name" size="small" class="ms-case-input"></el-input> <el-input :disabled="readOnly" v-model="form.name" size="small" class="ms-case-input"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="$t('test_track.case.project')" :label-width="formLabelWidth" prop="projectId"> <el-form-item :label="$t('test_track.case.project')" :label-width="formLabelWidth" prop="projectId">
<el-select v-model="form.projectId" filterable clearable :disabled="true"> <el-select v-model="form.projectId" filterable clearable :disabled="readOnly">
<el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option> <el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -49,7 +56,7 @@
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="$t('commons.tag')" :label-width="formLabelWidth" prop="tag"> <el-form-item :label="$t('commons.tag')" :label-width="formLabelWidth" prop="tag">
<ms-input-tag :read-only="true" :currentScenario="form" v-if="showInputTag" ref="tag" :disabled="true" <ms-input-tag :read-only="readOnly" :currentScenario="form" v-if="showInputTag" ref="tag" :disabled="true"
class="ms-case-input"/> class="ms-case-input"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -65,7 +72,7 @@
<el-row v-if="isCustomNum"> <el-row v-if="isCustomNum">
<el-col :span="7"> <el-col :span="7">
<el-form-item label="ID" :label-width="formLabelWidth" prop="customNum"> <el-form-item label="ID" :label-width="formLabelWidth" prop="customNum">
<el-input :disabled="true" v-model.trim="form.customNum" size="small" <el-input :disabled="readOnly" v-model.trim="form.customNum" size="small"
class="ms-case-input"></el-input> class="ms-case-input"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -74,26 +81,26 @@
<ms-form-divider :title="$t('test_track.case.step_info')"/> <ms-form-divider :title="$t('test_track.case.step_info')"/>
<form-rich-text-item :disabled="true" :label-width="formLabelWidth" <form-rich-text-item :disabled="readOnly" :label-width="formLabelWidth"
:title="$t('test_track.case.prerequisite')" :data="form" prop="prerequisite"/> :title="$t('test_track.case.prerequisite')" :data="form" prop="prerequisite"/>
<step-change-item :label-width="formLabelWidth" :form="form"/> <step-change-item :label-width="formLabelWidth" :form="form"/>
<form-rich-text-item :disabled="true" :label-width="formLabelWidth" v-if="form.stepModel === 'TEXT'" <form-rich-text-item :disabled="readOnly" :label-width="formLabelWidth" v-if="form.stepModel === 'TEXT'"
:title="$t('test_track.case.step_desc')" :data="form" prop="stepDescription"/> :title="$t('test_track.case.step_desc')" :data="form" prop="stepDescription"/>
<form-rich-text-item :disabled="true" :label-width="formLabelWidth" v-if="form.stepModel === 'TEXT'" <form-rich-text-item :disabled="readOnly" :label-width="formLabelWidth" v-if="form.stepModel === 'TEXT'"
:title="$t('test_track.case.expected_results')" :data="form" prop="expectedResult"/> :title="$t('test_track.case.expected_results')" :data="form" prop="expectedResult"/>
<test-case-step-item :label-width="formLabelWidth" v-if="form.stepModel === 'STEP' || !form.stepModel" <test-case-step-item :label-width="formLabelWidth" v-if="form.stepModel === 'STEP' || !form.stepModel"
:form="form" :read-only="true"/> :form="form" :read-only="readOnly"/>
<ms-form-divider :title="$t('test_track.case.other_info')"/> <ms-form-divider :title="$t('test_track.case.other_info')"/>
<test-case-edit-other-info :read-only="true" :project-id="projectIds" :form="form" <test-case-edit-other-info :read-only="readOnly" :project-id="projectIds" :form="form"
:label-width="formLabelWidth" :case-id="form.id" ref="otherInfo"/> :label-width="formLabelWidth" :case-id="form.id" ref="otherInfo"/>
<el-row style="margin-top: 10px" v-if="type!=='add'"> <el-row style="margin-top: 10px" v-if="type!=='add'">
<el-col :span="20" :offset="1">{{ $t('test_track.review.comment') }}: <el-col :span="20" :offset="1">{{ $t('test_track.review.comment') }}:
<el-button icon="el-icon-plus" type="mini" @click="openComment" :disabled="true"></el-button> <el-button icon="el-icon-plus" type="mini" @click="openComment" :disabled="readOnly"></el-button>
</el-col> </el-col>
</el-row> </el-row>
<el-row v-if="type!=='add'"> <el-row v-if="type!=='add'">
@ -102,7 +109,7 @@
<review-comment-item v-for="(comment,index) in comments" <review-comment-item v-for="(comment,index) in comments"
:key="index" :key="index"
:comment="comment" :comment="comment"
@refresh="getComments" :disabled="true" api-url="/test/case"/> @refresh="getComments" :disabled="readOnly" api-url="/test/case"/>
<div v-if="comments.length === 0" style="text-align: center"> <div v-if="comments.length === 0" style="text-align: center">
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;"> <i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
<span style="font-size: 15px; color: #8a8b8d;"> <span style="font-size: 15px; color: #8a8b8d;">
@ -120,6 +127,20 @@
</div> </div>
<ms-change-history ref="changeHistory"/> <ms-change-history ref="changeHistory"/>
<el-dialog
:fullscreen="true"
:visible.sync="dialogVisible"
width="100%"
>
<test-case-version-diff :old-data="oldData" :new-data="newData"
:tree-nodes="treeNodes" :is-public="publicEnable"></test-case-version-diff>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible=false">{{this.$t('commons.cancel')}}</el-button>
<el-button type="primary">{{this.$t('commons.confirm')}}</el-button>
</span>
</el-dialog>
</div> </div>
<batch-move ref="testBatchMove" :public-enable="publicEnable" <batch-move ref="testBatchMove" :public-enable="publicEnable"
@copyPublic="copyPublic"/> @copyPublic="copyPublic"/>
@ -166,6 +187,11 @@ import MsChangeHistory from "../../../history/ChangeHistory";
import {getTestTemplate} from "@/network/custom-field-template"; import {getTestTemplate} from "@/network/custom-field-template";
import CustomFiledFormItem from "@/business/components/common/components/form/CustomFiledFormItem"; import CustomFiledFormItem from "@/business/components/common/components/form/CustomFiledFormItem";
import BatchMove from "@/business/components/track/case/components/BatchMove"; import BatchMove from "@/business/components/track/case/components/BatchMove";
import TestCaseVersionDiff from "@/business/components/track/case/version/TestCaseVersionDiff";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const versionHistory = requireComponent.keys().length > 0 ? requireComponent("./version/VersionHistory.vue") : {};
export default { export default {
name: "TestCaseEditShow", name: "TestCaseEditShow",
@ -183,7 +209,9 @@ export default {
TestCaseComment, MsPreviousNextButton, MsInputTag, CaseComment, MsDialogFooter, TestCaseAttachment, TestCaseComment, MsPreviousNextButton, MsInputTag, CaseComment, MsDialogFooter, TestCaseAttachment,
MsTestCaseStepRichText, MsTestCaseStepRichText,
MsChangeHistory, MsChangeHistory,
BatchMove BatchMove,
'MsVersionHistory': versionHistory.default,
TestCaseVersionDiff
}, },
data() { data() {
return { return {
@ -272,7 +300,13 @@ export default {
id: 'id', id: 'id',
label: 'name', label: 'name',
}, },
tabId: getUUID() tabId: getUUID(),
versionData: [],
currentProjectId: "" ,
dialogVisible: false,
oldData: null,
newData: null,
readOnly: true
}; };
}, },
props: { props: {
@ -302,14 +336,6 @@ export default {
isCustomNum() { isCustomNum() {
return this.$store.state.currentProjectIsCustomNum; return this.$store.state.currentProjectIsCustomNum;
}, },
readOnly() {
const {rowClickHasPermission} = this.currentTestCaseInfo;
if (rowClickHasPermission !== undefined) {
return !rowClickHasPermission;
}
return !hasPermission('PROJECT_TRACK_CASE:READ+CREATE') &&
!hasPermission('PROJECT_TRACK_CASE:READ+EDIT');
}
}, },
beforeDestroy() { beforeDestroy() {
@ -390,6 +416,9 @@ export default {
} else { } else {
this.isXpack = false; this.isXpack = false;
} }
if (hasLicense()) {
this.getVersionHistory();
}
}, },
methods: { methods: {
currentUser: () => { currentUser: () => {
@ -512,7 +541,7 @@ export default {
initFuc(testCase); initFuc(testCase);
}); });
}, },
initEdit(testCase) { initEdit(testCase , callback) {
if (window.history && window.history.pushState) { if (window.history && window.history.pushState) {
history.pushState(null, null, document.URL); history.pushState(null, null, document.URL);
window.addEventListener('popstate', this.close); window.addEventListener('popstate', this.close);
@ -553,6 +582,9 @@ export default {
this.customFieldForm = parseCustomField(this.form, this.testCaseTemplate, this.customFieldRules); this.customFieldForm = parseCustomField(this.form, this.testCaseTemplate, this.customFieldRules);
this.reload(); this.reload();
} }
if (callback) {
callback();
}
}, },
handlePre() { handlePre() {
this.index--; this.index--;
@ -706,10 +738,13 @@ export default {
this.close(); this.close();
} }
this.form.id = response.data.id; this.form.id = response.data.id;
this.currentTestCaseInfo.id = response.data.id;
if (callback) { if (callback) {
callback(this); callback(this);
} }
if (hasLicense()) {
this.getVersionHistory();
}
// //
}); });
} }
@ -886,6 +921,73 @@ export default {
}); });
} }
} }
},
getVersionHistory() {
this.$get('/test/case/versions/' + this.currentTestCaseInfo.id, response => {
for (let i = 0; i < response.data.length ; i++) {
this.currentProjectId = response.data[i].projectId
}
this.versionData = response.data;
this.$refs.versionHistory.loading = false;
});
},
setSpecialPropForCompare: function (that) {
that.newData.tags = JSON.parse(that.newData.tags || "");
that.newData.steps = JSON.parse(that.newData.steps || "");
that.oldData.tags = JSON.parse(that.oldData.tags || "");
that.oldData.steps = JSON.parse(that.oldData.steps || "");
that.newData.readOnly = true;
that.oldData.readOnly = true;
},
compare(row) {
this.$get('/test/case/get/' + row.id + "/" + this.currentTestCaseInfo.refId, response => {
let p1 = this.$get('/test/case/get/' + response.data.id);
let p2 = this.$get('/test/case/get/' + this.currentTestCaseInfo.id);
let that = this;
Promise.all([p1, p2]).then(data => {
if (data[0] && data[1]) {
that.newData = data[0].data.data;
that.oldData = data[1].data.data;
this.setSpecialPropForCompare(that);
that.dialogVisible = true;
}
});
});
},
checkout(row) {
this.$refs.versionHistory.loading = true;
let testCase = this.versionData.filter(v => v.versionId === row.id)[0];
if (testCase) {
this.$get('test/case/get/' + testCase.id, response => {
let testCase = response.data;
this.$emit("checkout", testCase);
this.$refs.versionHistory.loading = false;
});
}
},
create(row) {
//
this.form.versionId = row.id;
this.saveCase();
},
del(row) {
let that = this;
this.$alert(this.$t('api_test.definition.request.delete_confirm') + ' ' + row.name + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this.$get('/test/case/delete/' + row.id + '/' + this.form.refId, () => {
this.$success(this.$t('commons.delete_success'));
this.getVersionHistory();
});
} else {
that.$refs.versionHistory.loading = false;
}
}
});
},
changeType(type) {
this.type = type;
} }
} }
} }

View File

@ -954,8 +954,8 @@ export default {
}); });
}, },
_handleDeletePublic(testCase) { _handleDeletePublic(testCase) {
let testCaseId = testCase.id; let refId = testCase.refId;
this.$post('/test/case/deletePublic/' + testCaseId, {}, () => { this.$post('/test/case/deletePublic/' + refId, {}, () => {
this.$emit('refreshTable'); this.$emit('refreshTable');
this.initTableData(); this.initTableData();
this.$success(this.$t('commons.delete_success')); this.$success(this.$t('commons.delete_success'));
@ -1082,13 +1082,18 @@ export default {
}, },
handleDeleteBatchToPublic() { handleDeleteBatchToPublic() {
let param = {}; this.$alert(this.$t('test_track.case.delete_confirm') + "", '', {
param.ids = this.$refs.table.selectIds; confirmButtonText: this.$t('commons.confirm'),
param.casePublic = false; callback: (action) => {
param.condition = this.condition; if (action === 'confirm') {
this.page.result = this.$post('/test/case/batch/edit', param, () => { let param = buildBatchParam(this, this.$refs.table.selectIds);
this.$success(this.$t('commons.save_success')); this.$post('/test/case/batch/movePublic/deleteToGc', param, () => {
this.refresh(); this.$refs.table.clear();
this.$emit("refresh");
this.$success(this.$t('commons.delete_success'));
});
}
}
}); });
}, },
handleBatchMove() { handleBatchMove() {
@ -1118,6 +1123,7 @@ export default {
}, },
copyPublic(param) { copyPublic(param) {
param.condition = this.condition; param.condition = this.condition;
param.projectId = this.projectId;
this.page.result = this.$post('/test/case/batch/copy/public', param, () => { this.page.result = this.$post('/test/case/batch/copy/public', param, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.$refs.testBatchMove.close(); this.$refs.testBatchMove.close();

View File

@ -16,17 +16,18 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="$t('test_track.case.module')" :label-width="oldData.formLabelWidth" prop="module"> <el-form-item :label="$t('test_track.case.module')" :label-width="oldData.formLabelWidth" prop="module" v-if="!isPublic">
<ms-select-tree :disabled="oldData.readOnly" :data="treeNodes" :defaultKey="oldData.module" <ms-select-tree :disabled="oldData.readOnly" :data="treeNodes" :defaultKey="oldData.module"
:obj="moduleObj" :obj="moduleObj"
@getValue="setModule" clearable checkStrictly size="small"/> @getValue="setModule" clearable checkStrictly size="small"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="$t('test_track.case.project')" :label-width="oldData.formLabelWidth" prop="projectId" <el-form-item :label="$t('test_track.case.project')" :label-width="oldData.formLabelWidth" prop="projectId"
v-if="publicEnable"> v-if="isPublic" >
<el-select v-model="oldData.projectId" filterable clearable> <el-select v-model="oldData.projectId" filterable clearable :disabled="oldData.readOnly">
<el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option> <el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -45,7 +46,7 @@
ref="oldCustomFieldForm" ref="oldCustomFieldForm"
class="case-form"> class="case-form">
<custom-filed-form-item :form="oldData.customFieldForm" :form-label-width="oldData.formLabelWidth" <custom-filed-form-item :form="oldData.customFieldForm" :form-label-width="oldData.formLabelWidth"
:issue-template="oldData.testCaseTemplate"/> :issue-template="oldData.testCaseTemplate" :is-public="isPublic"/>
</el-form> </el-form>
<el-row v-if="oldData.isCustomNum"> <el-row v-if="oldData.isCustomNum">
@ -91,7 +92,7 @@
:key="index" :key="index"
:comment="comment" :comment="comment"
@refresh="getComments" api-url="/test/case"/> @refresh="getComments" api-url="/test/case"/>
<div v-if="oldData.comments.length === 0" style="text-align: center"> <div v-if="oldData.comments && oldData.comments.length === 0" style="text-align: center">
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;"> <i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
<span style="font-size: 15px; color: #8a8b8d;"> <span style="font-size: 15px; color: #8a8b8d;">
{{ $t('test_track.comment.no_comment') }} {{ $t('test_track.comment.no_comment') }}
@ -121,7 +122,7 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="$t('test_track.case.module')" :label-width="newData.formLabelWidth" prop="module"> <el-form-item :label="$t('test_track.case.module')" :label-width="newData.formLabelWidth" prop="module" v-if="!isPublic">
<ms-select-tree :disabled="newData.readOnly" :data="treeNodes" :defaultKey="newData.module" <ms-select-tree :disabled="newData.readOnly" :data="treeNodes" :defaultKey="newData.module"
:obj="moduleObj" :obj="moduleObj"
@getValue="setModule" clearable checkStrictly size="small"/> @getValue="setModule" clearable checkStrictly size="small"/>
@ -130,8 +131,8 @@
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="$t('test_track.case.project')" :label-width="newData.formLabelWidth" prop="projectId" <el-form-item :label="$t('test_track.case.project')" :label-width="newData.formLabelWidth" prop="projectId"
v-if="publicEnable"> v-if="isPublic" >
<el-select v-model="newData.projectId" filterable clearable> <el-select v-model="newData.projectId" filterable clearable :disabled="newData.readOnly">
<el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option> <el-option v-for="item in projectList" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -150,7 +151,7 @@
ref="newCustomFieldForm" ref="newCustomFieldForm"
class="case-form"> class="case-form">
<custom-filed-form-item :form="newData.customFieldForm" :form-label-width="newData.formLabelWidth" <custom-filed-form-item :form="newData.customFieldForm" :form-label-width="newData.formLabelWidth"
:issue-template="newData.testCaseTemplate"/> :issue-template="newData.testCaseTemplate" :is-public="isPublic"/>
</el-form> </el-form>
<el-row v-if="newData.isCustomNum"> <el-row v-if="newData.isCustomNum">
@ -196,7 +197,7 @@
:key="index" :key="index"
:comment="comment" :comment="comment"
@refresh="getComments" api-url="/test/case"/> @refresh="getComments" api-url="/test/case"/>
<div v-if="newData.comments.length === 0" style="text-align: center"> <div v-if="newData.comments && newData.comments.length === 0" style="text-align: center">
<i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;"> <i class="el-icon-chat-line-square" style="font-size: 15px;color: #8a8b8d;">
<span style="font-size: 15px; color: #8a8b8d;"> <span style="font-size: 15px; color: #8a8b8d;">
{{ $t('test_track.comment.no_comment') }} {{ $t('test_track.comment.no_comment') }}
@ -253,6 +254,12 @@ export default {
type: Object type: Object
}, },
treeNodes: [], treeNodes: [],
isPublic: {
type: Boolean,
default() {
return false;
}
}
}, },
computed: { computed: {
projectIds() { projectIds() {
@ -268,7 +275,6 @@ export default {
data() { data() {
return { return {
path: "/test/case/add", path: "/test/case/add",
isPublic: false,
isXpack: false, isXpack: false,
projectList: [], projectList: [],
result: {}, result: {},
@ -292,7 +298,6 @@ export default {
}, },
versionData: [], versionData: [],
dialogVisible: false, dialogVisible: false,
publicEnable: false,
maintainerOptions: [], maintainerOptions: [],
oldLoading: null, oldLoading: null,
newLoading: null, newLoading: null,

View File

@ -190,7 +190,7 @@ export let CUSTOM_TABLE_HEADER = {
{id: 'name', key: '2', label: 'commons.name'}, {id: 'name', key: '2', label: 'commons.name'},
{id: 'reviewStatus', key: '3', label: 'test_track.case.status'}, {id: 'reviewStatus', key: '3', label: 'test_track.case.status'},
{id: 'tags', key: '4', label: 'commons.tag'}, {id: 'tags', key: '4', label: 'commons.tag'},
{id: 'versionId', key: 'a', label: 'project.version.name', xpack: true}, {id: 'versionId', key: 'b', label: 'project.version.name', xpack: true},
{id: 'nodePath', key: '5', label: 'test_track.case.module'}, {id: 'nodePath', key: '5', label: 'test_track.case.module'},
{id: 'projectName', key: 'a', label: 'test_track.review.review_project'}, {id: 'projectName', key: 'a', label: 'test_track.review.review_project'},
{id: 'updateTime', key: '6', label: 'commons.update_time'}, {id: 'updateTime', key: '6', label: 'commons.update_time'},