fix(测试跟踪): 功能用例批量编辑性能优化

--bug=1014258 --user=陈建星 【测试跟踪】功能列表用例条数过多时,批量编辑接口性能需要优化 https://www.tapd.cn/55049933/s/1187303
This commit is contained in:
chenjianxing 2022-06-21 11:17:17 +08:00 committed by jianxing
parent bf0b53bfb1
commit 5f1f8c2730
6 changed files with 250 additions and 95 deletions

View File

@ -157,4 +157,6 @@ public interface ExtTestCaseMapper {
List<CustomFieldResourceCompatibleDTO> getForCompatibleCustomField(String projectId, int offset, int pageSize);
List<Map<String, Object>> moduleExtraNodeCount(@Param("nodeIds") List<String> nodeIds);
int bathUpdateByCondition(@Param("request") QueryTestCaseRequest condition, @Param("record") TestCaseWithBLOBs testCaseWithBLOBs);
}

View File

@ -1094,6 +1094,124 @@
WHERE ref_id = #{refId,jdbcType=VARCHAR}
</update>
<update id="bathUpdateByCondition">
update test_case
<set>
<if test="record.id != null">
id = #{record.id,jdbcType=VARCHAR},
</if>
<if test="record.nodeId != null">
node_id = #{record.nodeId,jdbcType=VARCHAR},
</if>
<if test="record.testId != null">
test_id = #{record.testId,jdbcType=VARCHAR},
</if>
<if test="record.nodePath != null">
node_path = #{record.nodePath,jdbcType=VARCHAR},
</if>
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.name != null">
`name` = #{record.name,jdbcType=VARCHAR},
</if>
<if test="record.type != null">
`type` = #{record.type,jdbcType=VARCHAR},
</if>
<if test="record.maintainer != null">
maintainer = #{record.maintainer,jdbcType=VARCHAR},
</if>
<if test="record.priority != null">
priority = #{record.priority,jdbcType=VARCHAR},
</if>
<if test="record.method != null">
`method` = #{record.method,jdbcType=VARCHAR},
</if>
<if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT},
</if>
<if test="record.updateTime != null">
update_time = #{record.updateTime,jdbcType=BIGINT},
</if>
<if test="record.sort != null">
sort = #{record.sort,jdbcType=INTEGER},
</if>
<if test="record.num != null">
num = #{record.num,jdbcType=INTEGER},
</if>
<if test="record.otherTestName != null">
other_test_name = #{record.otherTestName,jdbcType=VARCHAR},
</if>
<if test="record.reviewStatus != null">
review_status = #{record.reviewStatus,jdbcType=VARCHAR},
</if>
<if test="record.tags != null">
tags = #{record.tags,jdbcType=VARCHAR},
</if>
<if test="record.demandId != null">
demand_id = #{record.demandId,jdbcType=VARCHAR},
</if>
<if test="record.demandName != null">
demand_name = #{record.demandName,jdbcType=VARCHAR},
</if>
<if test="record.status != null">
`status` = #{record.status,jdbcType=VARCHAR},
</if>
<if test="record.stepModel != null">
step_model = #{record.stepModel,jdbcType=VARCHAR},
</if>
<if test="record.customNum != null">
custom_num = #{record.customNum,jdbcType=VARCHAR},
</if>
<if test="record.createUser != null">
create_user = #{record.createUser,jdbcType=VARCHAR},
</if>
<if test="record.originalStatus != null">
original_status = #{record.originalStatus,jdbcType=VARCHAR},
</if>
<if test="record.deleteTime != null">
delete_time = #{record.deleteTime,jdbcType=BIGINT},
</if>
<if test="record.deleteUserId != null">
delete_user_id = #{record.deleteUserId,jdbcType=VARCHAR},
</if>
<if test="record.order != null">
`order` = #{record.order,jdbcType=BIGINT},
</if>
<if test="record.casePublic != null">
case_public = #{record.casePublic,jdbcType=BIT},
</if>
<if test="record.versionId != null">
version_id = #{record.versionId,jdbcType=VARCHAR},
</if>
<if test="record.refId != null">
ref_id = #{record.refId,jdbcType=VARCHAR},
</if>
<if test="record.latest != null">
latest = #{record.latest,jdbcType=BIT},
</if>
<if test="record.prerequisite != null">
prerequisite = #{record.prerequisite,jdbcType=LONGVARCHAR},
</if>
<if test="record.remark != null">
remark = #{record.remark,jdbcType=LONGVARCHAR},
</if>
<if test="record.steps != null">
steps = #{record.steps,jdbcType=LONGVARCHAR},
</if>
<if test="record.stepDescription != null">
step_description = #{record.stepDescription,jdbcType=LONGVARCHAR},
</if>
<if test="record.expectedResult != null">
expected_result = #{record.expectedResult,jdbcType=LONGVARCHAR},
</if>
<if test="record.customFields != null">
custom_fields = #{record.customFields,jdbcType=LONGVARCHAR},
</if>
</set>
<include refid="queryWhereCondition"/>
</update>
<select id="moduleExtraNodeCount" resultType="java.util.Map">
select parent_id as moduleId, count(id) as countNum from minder_extra_node
where parent_id in

View File

@ -677,12 +677,17 @@ public class TestCaseService {
}
public List<TestCaseDTO> listTestCase(QueryTestCaseRequest request) {
return listTestCase(request, false);
}
public List<TestCaseDTO> listTestCase(QueryTestCaseRequest request, boolean isSampleInfo) {
this.initRequest(request, true);
setDefaultOrder(request);
if (request.getFilters() != null && !request.getFilters().containsKey("status")) {
request.getFilters().put("status", new ArrayList<>(0));
}
List<TestCaseDTO> list = extTestCaseMapper.list(request);
if (!isSampleInfo) {
buildUserInfo(list);
if (StringUtils.isNotBlank(request.getProjectId())) {
buildProjectInfo(request.getProjectId(), list);
@ -691,6 +696,7 @@ public class TestCaseService {
}
buildCustomField(list);
list = this.parseStatus(list);
}
return list;
}
@ -1494,20 +1500,7 @@ public class TestCaseService {
}
public List<TestCaseDTO> findByBatchRequest(TestCaseBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extTestCaseMapper.selectIds(query));
QueryTestCaseRequest condition = request.getCondition();
List<OrderRequest> orderList = new ArrayList<>();
if (condition != null) {
orderList = ServiceUtils.getDefaultSortOrder(condition.getOrders());
}
OrderRequest order = new OrderRequest();
order.setName("sort");
order.setType("desc");
orderList.add(order);
request.setOrders(orderList);
List<TestCaseDTO> testCaseList = extTestCaseMapper.listByTestCaseIds(request);
return testCaseList;
return listTestCase(request.getCondition(), true);
}
private List<TestCaseExcelData> generateTestCaseExcel(TestCaseBatchRequest request) {
@ -1672,42 +1665,29 @@ public class TestCaseService {
* @param request
*/
public void editTestCaseBath(TestCaseBatchRequest request) {
if (request.getCustomField() != null) {
batchEditField(request);
} else if (StringUtils.equals("tags", request.getType())) {
batchEditTag(request);
} else {
// 批量移动
TestCaseWithBLOBs batchEdit = new TestCaseWithBLOBs();
BeanUtils.copyBean(batchEdit, request);
batchEdit.setUpdateTime(System.currentTimeMillis());
bathUpdateByCondition(request, batchEdit);
}
}
private void batchEditTag(TestCaseBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extTestCaseMapper.selectIds(query));
List<String> ids = request.getIds();
if (CollectionUtils.isEmpty(ids)) {
return;
}
if (request.getCustomField() != null) {
List<TestCaseWithBLOBs> testCases = extTestCaseMapper.getCustomFieldsByIds(ids);
testCases.forEach((testCase) -> {
CustomFieldResourceDTO customField = request.getCustomField();
if (StringUtils.equals(customField.getName(), "用例等级")) {
testCase.setPriority(JSONObject.parse(customField.getValue()).toString());
} else if (StringUtils.equals(request.getCustomField().getName(), "用例状态")) {
testCase.setStatus(JSONObject.parse(customField.getValue()).toString());
} else if (StringUtils.equals(customField.getName(), "责任人")) {
testCase.setMaintainer(JSONObject.parse(customField.getValue()).toString());
} else {
customField.setResourceId(testCase.getId());
int row = customFieldTestCaseService.updateByPrimaryKeySelective(customField);
if (row < 1) {
customFieldTestCaseService.insert(customField);
}
}
testCase.setUpdateTime(System.currentTimeMillis());
TestCaseExample example = new TestCaseExample();
example.createCriteria().andIdEqualTo(testCase.getId());
testCaseMapper.updateByExampleSelective(testCase, example);
});
} else if (StringUtils.equals("tags", request.getType())) {
if (request.getTagList().isEmpty()) {
if (CollectionUtils.isEmpty(request.getIds()) || request.getTagList().isEmpty()) {
return;
}
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
TestCaseExample example = new TestCaseExample();
example.createCriteria().andIdIn(ids);
example.createCriteria().andIdIn(request.getIds());
List<TestCase> testCaseList = testCaseMapper.selectByExample(example);
for (TestCase tc : testCaseList) {
String tags = tc.getTags();
@ -1730,15 +1710,55 @@ public class TestCaseService {
if (sqlSession != null && sqlSessionFactory != null) {
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
} else {
// 批量移动
TestCaseWithBLOBs batchEdit = new TestCaseWithBLOBs();
BeanUtils.copyBean(batchEdit, request);
batchEdit.setUpdateTime(System.currentTimeMillis());
TestCaseExample example = new TestCaseExample();
example.createCriteria().andIdIn(request.getIds());
testCaseMapper.updateByExampleSelective(batchEdit, example);
}
private void batchEditField(TestCaseBatchRequest request) {
CustomFieldResourceDTO customField = request.getCustomField();
String name = customField.getName();
String value = JSONObject.parse(customField.getValue()).toString();
TestCaseWithBLOBs testCaseWithBLOBs = new TestCaseWithBLOBs();
if (StringUtils.equalsAnyIgnoreCase(name, "用例等级")) {
testCaseWithBLOBs.setPriority(value);
bathUpdateByCondition(request, testCaseWithBLOBs);
} else if (StringUtils.equals(name, "用例状态")) {
testCaseWithBLOBs.setStatus(value);
bathUpdateByCondition(request, testCaseWithBLOBs);
} else if (StringUtils.equals(name, "责任人")) {
testCaseWithBLOBs.setMaintainer(value);
bathUpdateByCondition(request, testCaseWithBLOBs);
} else {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extTestCaseMapper.selectIds(query));
if (CollectionUtils.isEmpty(request.getIds())) {
return;
}
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
List<TestCaseWithBLOBs> testCases = extTestCaseMapper.getCustomFieldsByIds(request.getIds());
for (int i = 0; i < testCases.size(); i++) {
TestCaseWithBLOBs testCase = testCases.get(i);
customField.setResourceId(testCase.getId());
int row = customFieldTestCaseService.updateByPrimaryKeySelective(customField);
if (row < 1) {
customFieldTestCaseService.insert(customField);
}
testCase.setUpdateTime(System.currentTimeMillis());
TestCaseExample example = new TestCaseExample();
example.createCriteria().andIdEqualTo(testCase.getId());
mapper.updateByExampleSelective(testCase, example);
if (i % 1000 == 0) {
sqlSession.flushStatements();
}
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
}
private int bathUpdateByCondition(TestCaseBatchRequest request, TestCaseWithBLOBs testCaseWithBLOBs) {
return extTestCaseMapper.bathUpdateByCondition(request.getCondition(), testCaseWithBLOBs);
}
public void copyTestCaseBathPublic(TestCaseBatchRequest request) {

View File

@ -9,12 +9,14 @@
@close="handleClose"
v-loading="result.loading"
>
<el-form :model="form" label-position="right" label-width="150px" size="medium" ref="form" :rules="rules">
<el-form :model="form" label-position="right" label-width="180px" size="medium" ref="form" :rules="rules">
<el-form-item :label="$t('test_track.case.batch_update', [size])" prop="type">
<el-select v-model="form.type" style="width: 80%" @change="changeType">
<el-select v-model="form.type" style="width: 100%" @change="changeType">
<el-option v-for="(type, index) in typeArr" :key="index" :value="type.custom ? type.custom : type.id" :label="type.name"/>
</el-select>
</el-form-item>
<el-form-item v-if="form.type === 'projectEnv'" :label="$t('test_track.case.updated_attr_value')">
<env-popover :env-map="projectEnvMap"
:project-ids="projectIds"
@ -27,10 +29,12 @@
@setEnvGroup="setEnvGroup"
ref="envPopover"/>
</el-form-item>
<el-form-item v-else-if="fieldType === 'custom'" :label="$t('test_track.case.updated_attr_value')">
<el-form-item v-else-if="fieldType === 'custom'" prop="customFieldValue" :label="$t('test_track.case.updated_attr_value')">
<custom-filed-component :data="customField" prop="defaultValue"/>
</el-form-item>
<el-form-item v-else-if="form.type === 'tags'" :label="$t('test_track.case.updated_attr_value')">
<el-form-item v-else-if="form.type === 'tags'" prop="tags" :label="$t('test_track.case.updated_attr_value')">
<ms-input-tag :currentScenario="form" v-if="showInputTag" ref="tag" class="ms-case-input"></ms-input-tag>
<el-checkbox v-model="form.appendTag">
追加标签
@ -39,8 +43,9 @@
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item v-else :label="$t('test_track.case.updated_attr_value')" prop="value">
<el-select v-model="form.value" style="width: 80%" :filterable="filterable">
<el-select v-model="form.value" style="width: 100%" :filterable="filterable">
<el-option v-for="(option, index) in options" :key="index" :value="option.id" :label="option.name">
<div v-if="option.email">
<span>{{option.id}}({{option.name}})</span>
@ -48,6 +53,7 @@
</el-option>
</el-select>
</el-form-item>
</el-form>
<template v-slot:footer>
<ms-dialog-footer
@ -89,12 +95,17 @@ export default {
dialogVisible: false,
showConfigButtonWithOutPermission:false,
form: {
appendTag: true
appendTag: true,
customFieldValue: null,
tags: null,
value: null
},
size: 0,
rules: {
type: {required: true, message: this.$t('test_track.case.please_select_attr'), trigger: ['blur','change']},
value: {required: true, message: this.$t('test_track.case.please_select_attr_value'), trigger: ['blur','change']}
value: {required: true, message: this.$t('test_track.case.please_select_attr_value'), trigger: ['blur','change']},
tags: {required: true, message: this.$t('test_track.case.please_select_attr_value'), trigger: ['blur','change']},
customFieldValue: {required: true, message: this.$t('test_track.case.please_select_attr_value'), trigger: ['blur','change']}
},
options: [],
filterable: false,
@ -118,6 +129,11 @@ export default {
return ENV_TYPE;
}
},
watch: {
'customField.defaultValue'() {
this.$set(this.form, 'customFieldValue', this.customField.defaultValue);
}
},
methods: {
submit(form) {
this.$refs[form].validate(async (valid) => {

View File

@ -675,7 +675,7 @@ export default {
if (field.name === '用例等级') {
return row.priority;
} else if (field.name === '责任人') {
return row.maintainer;
return row.maintainerName;
} else if (field.name === '用例状态') {
return row.status;
}
@ -990,6 +990,7 @@ export default {
},
refresh() {
this.$refs.table.clear();
this.condition.selectAll = false;
this.$emit('refresh');
},
refreshAll() {

View File

@ -130,6 +130,7 @@ import MsTablePagination from "@/business/components/common/pagination/TablePagi
import MsDialogHeader from "@/business/components/common/components/MsDialogHeader";
import MsTable from "@/business/components/common/components/table/MsTable";
import TableSelectCountBar from "@/business/components/api/automation/scenario/api/TableSelectCountBar";
import {getVersionFilters} from "@/network/project";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const VersionSelect = requireComponent.keys().length > 0 ? requireComponent("./version/VersionSelect.vue") : {};
@ -215,6 +216,7 @@ export default {
},
projectId() {
this.condition.projectId = this.projectId;
this.getVersionOptions();
this.getProjectNode();
}
},
@ -366,13 +368,9 @@ export default {
this.selectNodeIds = [];
},
getVersionOptions() {
if (hasLicense()) {
this.$get('/project/version/get-project-versions/' + getCurrentProjectID(), response => {
this.versionFilters = response.data.map(u => {
return {text: u.name, value: u.id};
getVersionFilters(this.projectId, (data) => {
this.versionFilters = data;
});
});
}
},
changeVersion(version) {
this.condition.versionId = version || null;