feat(用例管理): 新增系统管理员评审时修改提示信息以及修复导入用例文件时的按钮显示问题和消息通知内容显示userId不是user名称问题

--bug=1038000 --user=郭雨琦 https://www.tapd.cn/55049933/bugtrace/bugs/view/1155049933001038000
--bug=1039431 --user=郭雨琦 https://www.tapd.cn/55049933/bugtrace/bugs/view/1155049933001039431
This commit is contained in:
guoyuqi 2024-04-15 20:37:15 +08:00 committed by Craftsman
parent c76c9b014c
commit a6e01dd075
17 changed files with 106 additions and 24 deletions

View File

@ -4,6 +4,7 @@ package io.metersphere.functional.controller;
import com.alibaba.excel.util.StringUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.functional.domain.CaseReviewFunctionalCaseUser;
import io.metersphere.functional.dto.ReviewFunctionalCaseDTO;
import io.metersphere.functional.request.*;
import io.metersphere.functional.service.CaseReviewFunctionalCaseService;
@ -50,6 +51,8 @@ public class CaseReviewFunctionalCaseController {
}
@PostMapping("/page")
@Operation(summary = "用例管理-用例评审-评审列表-评审详情-已关联用例列表")
@RequiresPermissions(PermissionConstants.CASE_REVIEW_READ)
@ -127,12 +130,21 @@ public class CaseReviewFunctionalCaseController {
@GetMapping("/reviewer/status/{reviewId}/{caseId}")
@Operation(summary = "用例管理-用例评审-评审列表-评审详情-评审结果的气泡数据")
@RequiresPermissions(PermissionConstants.CASE_REVIEW_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
@CheckOwner(resourceId = "#reviewId", resourceType = "case_review")
public List<OptionDTO> getUserStatus(@Schema(description = "评审id", requiredMode = Schema.RequiredMode.REQUIRED)
@PathVariable("reviewId") String reviewId, @Schema(description = "用例id", requiredMode = Schema.RequiredMode.REQUIRED)
@PathVariable("caseId") String caseId) {
return caseReviewFunctionalCaseService.getUserStatus(reviewId, caseId);
}
@GetMapping("/reviewer/list/{reviewId}/{caseId}")
@Operation(summary = "用例管理-用例评审-评审列表-评审详情-获取单个用例的评审人")
@RequiresPermissions(PermissionConstants.CASE_REVIEW_READ)
@CheckOwner(resourceId = "#reviewId", resourceType = "case_review")
public List<CaseReviewFunctionalCaseUser> getReviewerList(@Schema(description = "评审id", requiredMode = Schema.RequiredMode.REQUIRED)
@PathVariable("reviewId") String reviewId, @Schema(description = "用例id", requiredMode = Schema.RequiredMode.REQUIRED)
@PathVariable("caseId") String caseId) {
return caseReviewFunctionalCaseService.getReviewerList(reviewId, caseId);
}
}

View File

@ -844,4 +844,10 @@ public class CaseReviewFunctionalCaseService {
extCaseReviewHistoryMapper.batchUpdateAbandoned(null, caseIds);
caseReviewHistoryMapper.batchInsertSelective(historyList);
}
public List<CaseReviewFunctionalCaseUser> getReviewerList(String reviewId, String caseId) {
CaseReviewFunctionalCaseUserExample caseReviewFunctionalCaseUserExample = new CaseReviewFunctionalCaseUserExample();
caseReviewFunctionalCaseUserExample.createCriteria().andCaseIdEqualTo(caseId).andReviewIdEqualTo(reviewId);
return caseReviewFunctionalCaseUserMapper.selectByExample(caseReviewFunctionalCaseUserExample);
}
}

View File

@ -2,10 +2,7 @@ package io.metersphere.functional.controller;
import io.metersphere.functional.constants.CaseReviewPassRule;
import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
import io.metersphere.functional.domain.CaseReviewFunctionalCase;
import io.metersphere.functional.domain.CaseReviewFunctionalCaseExample;
import io.metersphere.functional.domain.CaseReviewHistory;
import io.metersphere.functional.domain.CaseReviewHistoryExample;
import io.metersphere.functional.domain.*;
import io.metersphere.functional.dto.ReviewFunctionalCaseDTO;
import io.metersphere.functional.mapper.CaseReviewFunctionalCaseMapper;
import io.metersphere.functional.mapper.CaseReviewHistoryMapper;
@ -62,6 +59,8 @@ public class CaseReviewFunctionalCaseControllerTests extends BaseTest {
public static final String REVIEW_FUNCTIONAL_CASE_REVIEWER_STATUS = "/case/review/detail/reviewer/status/";
public static final String GET_CASE_REVIEWER_LIST = "/case/review/detail/reviewer/list";
@Resource
private CaseReviewFunctionalCaseMapper caseReviewFunctionalCaseMapper;
@Resource
@ -561,6 +560,20 @@ public class CaseReviewFunctionalCaseControllerTests extends BaseTest {
}
@Test
@Order(13)
public void getReviewerList() throws Exception {
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(GET_CASE_REVIEWER_LIST + "/wx_review_id_1/gyq_case_id_5").header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn();
String returnData = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
List<CaseReviewFunctionalCaseUser> optionDTOS = JSON.parseArray(JSON.toJSONString(resultHolder.getData()), CaseReviewFunctionalCaseUser.class);
Assertions.assertTrue(CollectionUtils.isNotEmpty(optionDTOS));
}
private List<OptionDTO> getOptionDTOS(String reviewId, String caseId) throws Exception {
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(REVIEW_FUNCTIONAL_CASE_REVIEWER_STATUS + "/" + reviewId + "/" + caseId).header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)

View File

@ -2,7 +2,6 @@ package io.metersphere.functional.controller;
import io.metersphere.functional.constants.CaseFileSourceType;
import io.metersphere.functional.constants.CaseReviewPassRule;
import io.metersphere.functional.constants.CaseReviewStatus;
import io.metersphere.functional.constants.FunctionalCaseReviewStatus;
import io.metersphere.functional.domain.*;
import io.metersphere.functional.dto.CaseReviewHistoryDTO;
@ -217,7 +216,7 @@ public class ReviewFunctionalCaseControllerTests extends BaseTest {
Assertions.assertTrue(StringUtils.equalsIgnoreCase(caseReviewFunctionalCases.get(0).getStatus(), FunctionalCaseReviewStatus.UNDER_REVIEWED.toString()));
List<CaseReview> caseReviews1 = getCaseReviews("创建用例评审2");
System.out.println(caseReviews1.get(0).getStatus());
Assertions.assertTrue(StringUtils.equals(caseReviews1.get(0).getStatus(), CaseReviewStatus.UNDERWAY.toString()));
reviewFunctionalCaseRequest = new ReviewFunctionalCaseRequest();
reviewFunctionalCaseRequest.setReviewId(reviewId);
@ -228,12 +227,6 @@ public class ReviewFunctionalCaseControllerTests extends BaseTest {
reviewFunctionalCaseRequest.setNotifier("default-project-member-user-gyq-2");
reviewFunctionalCaseRequest.setReviewPassRule(CaseReviewPassRule.MULTIPLE.toString());
reviewFunctionalCaseService.saveReview(reviewFunctionalCaseRequest, "default-project-member-user-gyq-4");
caseReviewFunctionalCaseExample = new CaseReviewFunctionalCaseExample();
caseReviewFunctionalCaseExample.createCriteria().andReviewIdEqualTo(reviewId).andCaseIdEqualTo("gyqReviewCaseTestTwo");
caseReviewFunctionalCases = caseReviewFunctionalCaseMapper.selectByExample(caseReviewFunctionalCaseExample);
Assertions.assertTrue(StringUtils.equalsIgnoreCase(caseReviewFunctionalCases.get(0).getStatus(), FunctionalCaseReviewStatus.UNDER_REVIEWED.toString()));
try {
reviewFunctionalCaseService.saveReview(reviewFunctionalCaseRequest, "default-project-member-user-gyq-s");

View File

@ -12,7 +12,6 @@ import io.metersphere.system.dto.request.OrganizationRequest;
import io.metersphere.system.dto.sdk.OptionDTO;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.service.OrganizationService;
import io.metersphere.system.utils.PageUtils;
import io.metersphere.system.utils.Pager;
@ -44,7 +43,6 @@ public class OrganizationController {
@PostMapping("/member/list")
@Operation(summary = "系统设置-组织-成员-获取组织成员列表")
@RequiresPermissions(PermissionConstants.ORGANIZATION_MEMBER_READ)
@CheckOwner(resourceId = "#organizationId", resourceType = "organization")
public Pager<List<OrgUserExtend>> getMemberList(@Validated @RequestBody OrganizationRequest organizationRequest) {
Page<Object> page = PageHelper.startPage(organizationRequest.getCurrent(), organizationRequest.getPageSize());
return PageUtils.setPageInfo(page, organizationService.getMemberListByOrg(organizationRequest));

View File

@ -5,12 +5,15 @@ import io.metersphere.functional.domain.CaseReview;
import io.metersphere.load.domain.LoadTest;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.sdk.constants.TemplateScene;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.CustomField;
import io.metersphere.system.domain.Schedule;
import io.metersphere.system.domain.User;
import io.metersphere.system.dto.BugMessageDTO;
import io.metersphere.system.dto.sdk.ApiDefinitionCaseDTO;
import io.metersphere.system.dto.sdk.FunctionalCaseMessageDTO;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.ui.domain.UiScenario;
import io.swagger.v3.oas.annotations.media.Schema;
@ -182,6 +185,8 @@ public class MessageTemplateUtils {
});
// 处理时间格式的数据
handleTime(context);
// 处理人相关的数据
handleUser(context);
StringSubstitutor sub = new StringSubstitutor(context);
return sub.replace(template);
}
@ -199,6 +204,20 @@ public class MessageTemplateUtils {
}
});
}
public static void handleUser(Map<String, Object> context) {
UserMapper userMapper = CommonBeanFactory.getBean(UserMapper.class);
context.forEach((k, v) -> {
if (StringUtils.endsWithIgnoreCase(k, "User")) {
try {
String value = v.toString();
assert userMapper != null;
User user = userMapper.selectByPrimaryKey(value);
context.put(k, user.getName());
} catch (Exception ignore) {
}
}
});
}
public static String getTranslateTemplate(String taskType, String template, Map<String, List<CustomField>> customFielddMap) {
if (StringUtils.equalsIgnoreCase(taskType, NoticeConstants.TaskType.JENKINS_TASK)) {

View File

@ -47,7 +47,7 @@ public class NotificationService {
record.setStatus(NotificationConstants.Status.READ.name());
NotificationExample example = new NotificationExample();
if (StringUtils.isNotBlank(resourceType)) {
example.createCriteria().andResourceTypeEqualTo("%" + resourceType + "%");
example.createCriteria().andResourceTypeLike("%" + resourceType + "%");
}
example.createCriteria().andReceiverEqualTo(userId);
return notificationMapper.updateByExampleSelective(record, example);

View File

@ -13,6 +13,7 @@ import {
EditReviewUrl,
FollowReviewUrl,
GetAssociatedIdsUrl,
getCaseReviewerListUrl,
GetCaseReviewHistoryListUrl,
GetReviewDetailCasePageUrl,
GetReviewDetailModuleCountUrl,
@ -36,6 +37,7 @@ import {
BatchChangeReviewerParams,
BatchMoveReviewParams,
BatchReviewCaseParams,
CaseReviewFunctionalCaseUserItem,
CommitReviewResultParams,
CopyReviewParams,
CopyReviewResponse,
@ -195,3 +197,8 @@ export const getCaseReviewHistoryList = (reviewId: string, caseId: string) => {
export const saveCaseReviewResult = (data: CommitReviewResultParams) => {
return MSR.post({ url: SaveCaseReviewResultUrl, data });
};
// 评审详情-获取用例的评审人
export const getCaseReviewerList = (reviewId: string, caseId: string) => {
return MSR.get<CaseReviewFunctionalCaseUserItem[]>({ url: `${getCaseReviewerListUrl}/${reviewId}/${caseId}` });
};

View File

@ -26,3 +26,4 @@ export const GetReviewDetailModuleCountUrl = '/case/review/detail/module/count';
export const GetReviewDetailModuleTreeUrl = '/case/review/detail/tree'; // 评审详情-已关联用例模块树
export const GetCaseReviewHistoryListUrl = '/review/functional/case/get/list'; // 评审详情-获取用例评审历史
export const SaveCaseReviewResultUrl = '/review/functional/case/save'; // 评审详情-提交评审
export const getCaseReviewerListUrl = '/case/review/detail/reviewer/list/'; // 评审详情-获取用例的评审人

View File

@ -243,3 +243,10 @@ export interface ReviewHistoryItem {
userName: string;
contentText: string;
}
// 评审详情-用例列表项
export interface CaseReviewFunctionalCaseUserItem {
caseId: string;
reviewId: string;
userId: string;
}

View File

@ -71,14 +71,14 @@
/> </a-tooltip></span
></a-checkbox>
<div>
<a-button type="secondary" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button>
<!-- <a-button type="secondary" @click="handleCancel">{{ t('system.plugin.pluginCancel') }}</a-button>-->
<a-button
class="ml-3"
type="primary"
:loading="props.confirmLoading"
:disabled="fileList.length < 1"
@click="saveConfirm"
>{{ t('caseManagement.featureCase.checkTemplate') }}</a-button
>{{ t('caseManagement.featureCase.checkImportFile') }}</a-button
>
</div>
</div>

View File

@ -6,6 +6,7 @@
:ok-text="t('common.confirm')"
:cancel-text="t('common.cancel')"
unmount-on-close
:footer="false"
@close="handleCancel"
>
<template #title> {{ t('caseManagement.featureCase.importingUseCase') }} </template>
@ -20,9 +21,6 @@
<a-progress :percent="props.percent" size="large" />
</div>
</div>
<template #footer>
<a-button type="text" @click="handleCancel">{{ t('common.cancel') }}</a-button>
</template>
</a-modal>
</template>

View File

@ -226,6 +226,7 @@ export default {
'Only xls and xlsx are supported, and the size of each device cannot exceed 100 MB',
'caseManagement.featureCase.onlyXmindTip': 'Only xmind/ type single size up to 100M is supported',
'caseManagement.featureCase.checkTemplate': 'Check template',
'caseManagement.featureCase.checkImportFile': 'Check file',
'caseManagement.featureCase.selectedRecoverCase':
'Check, the original use case will be overwritten when the ID is the same',
'caseManagement.featureCase.notSelectedRecoverCase': 'If the ID already exists, the use case is skipped',

View File

@ -224,6 +224,7 @@ export default {
'caseManagement.featureCase.onlyEXcelTip': '仅支持 xls/xlsx单个大小不超过 100M',
'caseManagement.featureCase.onlyXmindTip': '仅支持 xmind/类型 单个大小不超过 100M',
'caseManagement.featureCase.checkTemplate': '校验模板',
'caseManagement.featureCase.checkImportFile': '校验文件',
'caseManagement.featureCase.selectedRecoverCase': '勾选ID相同时覆盖原用例',
'caseManagement.featureCase.notSelectedRecoverCase': '不勾选ID已存在时跳过该用例',
'caseManagement.featureCase.cancelValidate': '取消校验',

View File

@ -79,20 +79,22 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { FormInstance, Message } from '@arco-design/web-vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import { saveCaseReviewResult } from '@/api/modules/case-management/caseReview';
import { getCaseReviewerList, saveCaseReviewResult } from '@/api/modules/case-management/caseReview';
import { editorUploadFile } from '@/api/modules/case-management/featureCase';
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import { useUserStore } from '@/store';
import useAppStore from '@/store/modules/app';
import { hasAnyPermission } from '@/utils/permission';
import { ReviewPassRule, ReviewResult } from '@/models/caseManagement/caseReview';
import { CaseReviewFunctionalCaseUserItem, ReviewPassRule, ReviewResult } from '@/models/caseManagement/caseReview';
const props = defineProps<{
reviewId: string;
@ -102,9 +104,13 @@
const emit = defineEmits(['done']);
const appStore = useAppStore();
const userStore = useUserStore();
const { t } = useI18n();
const dialogFormRef = ref<FormInstance>();
const caseReviewerList = ref<CaseReviewFunctionalCaseUserItem[]>([]);
const caseResultForm = ref({
result: 'PASS' as ReviewResult,
reason: '',
@ -119,6 +125,8 @@
);
const modalVisible = ref(false);
const singleAdmin = ref(false);
async function handleUploadImage(file: File) {
const { data } = await editorUploadFile({
fileList: [file],
@ -126,6 +134,10 @@
return data;
}
onMounted(async () => {
caseReviewerList.value = await getCaseReviewerList(props.reviewId, props.caseId);
});
//
function submitReview(done?: (close: boolean) => void) {
dialogFormRef.value?.validate(async (errors) => {
@ -143,7 +155,17 @@
};
await saveCaseReviewResult(params);
modalVisible.value = false;
Message.success(t('caseManagement.caseReview.reviewSuccess'));
caseReviewerList.value.forEach((child) => {
if (child.userId === userStore.id) {
singleAdmin.value = true;
}
});
if (userStore.isAdmin && !singleAdmin.value) {
Message.warning(t('caseManagement.caseReview.reviewSuccess.widthAdmin'));
} else {
Message.success(t('caseManagement.caseReview.reviewSuccess'));
}
caseResultForm.value = {
result: 'PASS' as ReviewResult,
reason: '',

View File

@ -134,4 +134,6 @@ export default {
'caseManagement.caseReview.crateCase': 'Create case',
'caseManagement.caseReview.demandCases': 'Requirements association list',
'caseManagement.caseReview.demandSearchPlaceholder': 'Search by name',
'caseManagement.caseReview.reviewSuccess.widthAdmin':
'Submitted successfully! You are not the designated reviewer for the current project. The system will only record your review and will not affect the final review result.',
};

View File

@ -133,4 +133,6 @@ export default {
'caseManagement.caseReview.associateSuccess': '关联成功',
'caseManagement.caseReview.reviewSuccess': '评审成功',
'caseManagement.caseReview.updateCase': '更新用例',
'caseManagement.caseReview.reviewSuccess.widthAdmin':
'提交成功! 您不是当前项目指定的评审人,系统只会记录您的评审,不影响最终评审结果',
};