feat(用例管理): 新增用例详情执行评论tab

This commit is contained in:
guoyuqi 2024-05-13 10:16:09 +08:00 committed by Craftsman
parent 2c0c4c07fa
commit bcd06b5e27
6 changed files with 103 additions and 21 deletions

View File

@ -12,7 +12,9 @@ import io.metersphere.functional.excel.domain.FunctionalCaseExcelData;
import io.metersphere.functional.mapper.*; import io.metersphere.functional.mapper.*;
import io.metersphere.functional.request.*; import io.metersphere.functional.request.*;
import io.metersphere.functional.result.CaseManagementResultCode; import io.metersphere.functional.result.CaseManagementResultCode;
import io.metersphere.plan.domain.TestPlanCaseExecuteHistoryExample;
import io.metersphere.plan.domain.TestPlanFunctionalCaseExample; import io.metersphere.plan.domain.TestPlanFunctionalCaseExample;
import io.metersphere.plan.mapper.TestPlanCaseExecuteHistoryMapper;
import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper; import io.metersphere.plan.mapper.TestPlanFunctionalCaseMapper;
import io.metersphere.project.domain.*; import io.metersphere.project.domain.*;
import io.metersphere.project.dto.ModuleCountDTO; import io.metersphere.project.dto.ModuleCountDTO;
@ -166,6 +168,8 @@ public class FunctionalCaseService {
@Resource @Resource
private CaseReviewHistoryMapper caseReviewHistoryMapper; private CaseReviewHistoryMapper caseReviewHistoryMapper;
@Resource @Resource
private TestPlanCaseExecuteHistoryMapper testPlanCaseExecuteHistoryMapper;
@Resource
private FunctionalCaseCommentMapper functionalCaseCommentMapper; private FunctionalCaseCommentMapper functionalCaseCommentMapper;
@Resource @Resource
private ProjectService projectService; private ProjectService projectService;
@ -420,9 +424,12 @@ public class FunctionalCaseService {
FunctionalCaseCommentExample functionalCaseCommentExample = new FunctionalCaseCommentExample(); FunctionalCaseCommentExample functionalCaseCommentExample = new FunctionalCaseCommentExample();
functionalCaseCommentExample.createCriteria().andCaseIdEqualTo(functionalCaseDetailDTO.getId()); functionalCaseCommentExample.createCriteria().andCaseIdEqualTo(functionalCaseDetailDTO.getId());
long caseComment = functionalCaseCommentMapper.countByExample(functionalCaseCommentExample); long caseComment = functionalCaseCommentMapper.countByExample(functionalCaseCommentExample);
long commentCount = caseComment + reviewComment; //获取关联测试计划的执行评论数量
TestPlanCaseExecuteHistoryExample testPlanCaseExecuteHistoryExample = new TestPlanCaseExecuteHistoryExample();
testPlanCaseExecuteHistoryExample.createCriteria().andCaseIdEqualTo(functionalCaseDetailDTO.getId());
long testPlanExecuteComment = testPlanCaseExecuteHistoryMapper.countByExample(testPlanCaseExecuteHistoryExample);
long commentCount = caseComment + reviewComment + testPlanExecuteComment;
functionalCaseDetailDTO.setCommentCount((int) commentCount); functionalCaseDetailDTO.setCommentCount((int) commentCount);
//获取变更历史数量数量 //获取变更历史数量数量
OperationHistoryExample operationHistoryExample = new OperationHistoryExample(); OperationHistoryExample operationHistoryExample = new OperationHistoryExample();
List<String> types = List.of(OperationLogType.ADD.name(), OperationLogType.IMPORT.name(), OperationLogType.UPDATE.name()); List<String> types = List.of(OperationLogType.ADD.name(), OperationLogType.IMPORT.name(), OperationLogType.UPDATE.name());

View File

@ -52,6 +52,7 @@ import {
GetDependOnRelationUrl, GetDependOnRelationUrl,
GetDetailCaseReviewUrl, GetDetailCaseReviewUrl,
GetFileIsUpdateUrl, GetFileIsUpdateUrl,
GetPlanExecuteCommentListUrl,
GetRecycleCaseListUrl, GetRecycleCaseListUrl,
GetRecycleCaseModulesCountUrl, GetRecycleCaseModulesCountUrl,
GetReviewCommentListUrl, GetReviewCommentListUrl,
@ -435,4 +436,9 @@ export function getLinkedCaseTestPlanList(data: TableQueryParams) {
return MSR.post<CommonList<AssociateFunctionalCaseItem>>({ url: GetAssociatedTestPlanUrl, data }); return MSR.post<CommonList<AssociateFunctionalCaseItem>>({ url: GetAssociatedTestPlanUrl, data });
} }
// 获取执行评论
export function getTestPlanExecuteCommentList(caseId: string) {
return MSR.get<CommentItem[]>({ url: `${GetPlanExecuteCommentListUrl}/${caseId}` });
}
export default {}; export default {};

View File

@ -154,3 +154,6 @@ export const associatedProjectOptionsUrl = '/project/list/options';
// 获取详情已关联测试计划列表 // 获取详情已关联测试计划列表
export const GetAssociatedTestPlanUrl = '/functional/case/test/has/associate/plan/page'; export const GetAssociatedTestPlanUrl = '/functional/case/test/has/associate/plan/page';
// 评审评论
export const GetPlanExecuteCommentListUrl = '/functional/case/test/plan/comment';

View File

@ -4,7 +4,7 @@
<a-radio-group v-model="activeComment" type="button"> <a-radio-group v-model="activeComment" type="button">
<a-radio value="caseComment">{{ t('caseManagement.featureCase.caseComment') }}</a-radio> <a-radio value="caseComment">{{ t('caseManagement.featureCase.caseComment') }}</a-radio>
<a-radio value="reviewComment">{{ t('caseManagement.featureCase.reviewComment') }}</a-radio> <a-radio value="reviewComment">{{ t('caseManagement.featureCase.reviewComment') }}</a-radio>
<!-- <a-radio value="executiveComment">{{ t('caseManagement.featureCase.executiveReview') }}</a-radio> --> <a-radio value="executiveComment">{{ t('caseManagement.featureCase.executiveReview') }}</a-radio>
</a-radio-group> </a-radio-group>
</div> </div>
<div> <div>
@ -21,13 +21,20 @@
</div> </div>
<!-- 评审评论 --> <!-- 评审评论 -->
<div v-show="activeComment === 'reviewComment'" class="flex flex-1 flex-col overflow-hidden"> <div
v-show="activeComment === 'reviewComment' || activeComment === 'executiveComment'"
class="flex flex-1 flex-col overflow-hidden"
>
<div class="review-history-list"> <div class="review-history-list">
<div v-for="item of reviewCommentList" :key="item.id" class="review-history-list-item"> <div v-for="item of reviewCommentList" :key="item.id" class="review-history-list-item">
<div class="flex items-center"> <div class="flex items-center">
<MSAvatar :avatar="item.userLogo" /> <MSAvatar :avatar="item.userLogo" />
<div class="ml-[8px] flex items-center"> <div class="ml-[8px] flex items-center">
<div class="font-medium text-[var(--color-text-1)]">{{ item.userName }}</div> <a-tooltip :content="item.userName" :mouse-enter-delay="300">
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{
item.userName
}}</div>
</a-tooltip>
<a-divider direction="vertical" margin="8px"></a-divider> <a-divider direction="vertical" margin="8px"></a-divider>
<div v-if="item.status === 'PASS'" class="flex items-center"> <div v-if="item.status === 'PASS'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" /> <MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
@ -45,24 +52,53 @@
<MsIcon type="icon-icon_resubmit_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" /> <MsIcon type="icon-icon_resubmit_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
{{ t('caseManagement.caseReview.reReview') }} {{ t('caseManagement.caseReview.reReview') }}
</div> </div>
<div v-if="item.status === 'PASSED'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.featureCase.execute.success') }}
</div>
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.featureCase.execute.blocked') }}
</div>
<div v-if="item.status === 'FAILED'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.featureCase.execute.failed') }}
</div>
</div> </div>
</div> </div>
<div class="markdown-body" style="margin-left: 48px" v-html="item.contentText"></div> <div class="markdown-body" style="margin-left: 48px" v-html="item.contentText"></div>
<div class="ml-[48px] mt-[8px] text-[var(--color-text-4)]"> <div class="ml-[48px] mt-[8px] flex text-[var(--color-text-4)]">
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }} {{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
<a-tooltip :content="item.reviewName" :mouse-enter-delay="300"> <div v-if="activeComment === 'reviewComment'">
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all"> <a-tooltip :content="item.reviewName" :mouse-enter-delay="300">
{{ characterLimit(item.reviewName) }} <span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
</span> {{ characterLimit(item.reviewName) }}
</span>
<span <span
v-else v-else
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]" class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
@click="review(item)" @click="review(item)"
> >
{{ characterLimit(item.reviewName) }} {{ characterLimit(item.reviewName) }}
</span> </span>
</a-tooltip> </a-tooltip>
</div>
<div v-if="activeComment === 'executiveComment'">
<a-tooltip :content="item.testPlanName" :mouse-enter-delay="300">
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
{{ characterLimit(item.testPlanName) }}
</span>
<span
v-else
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
@click="toPlan(item)"
>
{{ characterLimit(item.testPlanName) }}
</span>
</a-tooltip>
</div>
</div> </div>
</div> </div>
<MsEmpty v-if="reviewCommentList.length === 0" /> <MsEmpty v-if="reviewCommentList.length === 0" />
@ -88,15 +124,15 @@
editorUploadFile, editorUploadFile,
getCommentList, getCommentList,
getReviewCommentList, getReviewCommentList,
getTestPlanExecuteCommentList,
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase'; import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useFeatureCaseStore from '@/store/modules/case/featureCase'; import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { CaseManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum, TestPlanRouteEnum } from '@/enums/routeEnum';
const featureCaseStore = useFeatureCaseStore(); const featureCaseStore = useFeatureCaseStore();
const router = useRouter(); const router = useRouter();
@ -133,6 +169,16 @@
} }
} }
//
async function initTestPlanExecuteCommentList() {
try {
const result = await getTestPlanExecuteCommentList(props.caseId);
reviewCommentList.value = result;
} catch (error) {
console.log(error);
}
}
async function getAllCommentList() { async function getAllCommentList() {
switch (activeComment.value) { switch (activeComment.value) {
case 'caseComment': case 'caseComment':
@ -144,7 +190,7 @@
featureCaseStore.getCaseCounts(props.caseId); featureCaseStore.getCaseCounts(props.caseId);
break; break;
case 'executiveComment': case 'executiveComment':
await initCommentList(); await initTestPlanExecuteCommentList();
featureCaseStore.getCaseCounts(props.caseId); featureCaseStore.getCaseCounts(props.caseId);
break; break;
default: default:
@ -204,6 +250,20 @@
}); });
} }
//
function toPlan(record: CommentItem) {
router.push({
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL,
query: {
...route.query,
id: record.testPlanId,
},
state: {
params: JSON.stringify(record.moduleName),
},
});
}
watch( watch(
() => activeComment.value, () => activeComment.value,
(val) => { (val) => {

View File

@ -277,4 +277,7 @@ export default {
'caseManagement.featureCase.deleteFileTip': 'caseManagement.featureCase.deleteFileTip':
'After deletion, the file cannot be restored. Please operate with caution!', 'After deletion, the file cannot be restored. Please operate with caution!',
'caseManagement.featureCase.nameNotNull': 'The name can not be null!', 'caseManagement.featureCase.nameNotNull': 'The name can not be null!',
'caseManagement.featureCase.execute.success': 'SUCCESS',
'caseManagement.featureCase.execute.failed': 'ERROR',
'caseManagement.featureCase.execute.blocked': 'BLOCKED',
}; };

View File

@ -272,4 +272,7 @@ export default {
'caseManagement.featureCase.deleteFile': '确认删除文件 {name} 吗', 'caseManagement.featureCase.deleteFile': '确认删除文件 {name} 吗',
'caseManagement.featureCase.deleteFileTip': '删除后,文件无法恢复,请谨慎操作!', 'caseManagement.featureCase.deleteFileTip': '删除后,文件无法恢复,请谨慎操作!',
'caseManagement.featureCase.nameNotNull': '用例名称不能为空!', 'caseManagement.featureCase.nameNotNull': '用例名称不能为空!',
'caseManagement.featureCase.execute.success': '成功',
'caseManagement.featureCase.execute.failed': '失败',
'caseManagement.featureCase.execute.blocked': '阻塞',
}; };