feat(测试计划): 测试计划详情用例关联缺陷联调

This commit is contained in:
xinxin.wu 2024-08-30 11:37:16 +08:00 committed by Craftsman
parent 11fe18e5d0
commit e759d15c15
12 changed files with 372 additions and 105 deletions

View File

@ -7,7 +7,11 @@ import {
ApiScenarioReportDetailStepUrl, ApiScenarioReportDetailStepUrl,
ApiScenarioReportDetailUrl, ApiScenarioReportDetailUrl,
archivedPlanUrl, archivedPlanUrl,
AssociatedBugToApiCaseUrl,
AssociatedBugToScenarioCaseUrl,
BatchAddBugToCaseUrl,
batchArchivedPlanUrl, batchArchivedPlanUrl,
BatchAssociatedBugToCaseUrl,
batchCopyPlanUrl, batchCopyPlanUrl,
batchDeletePlanUrl, batchDeletePlanUrl,
BatchDisassociateApiCaseUrl, BatchDisassociateApiCaseUrl,
@ -23,6 +27,8 @@ import {
BatchRunApiScenarioUrl, BatchRunApiScenarioUrl,
BatchRunCaseUrl, BatchRunCaseUrl,
BatchUpdateCaseExecutorUrl, BatchUpdateCaseExecutorUrl,
CancelBugFromApiCaseUrl,
CancelBugFromScenarioCaseUrl,
ConfigScheduleUrl, ConfigScheduleUrl,
copyTestPlanUrl, copyTestPlanUrl,
deletePlanUrl, deletePlanUrl,
@ -56,7 +62,9 @@ import {
GetTestPlanModuleCountUrl, GetTestPlanModuleCountUrl,
GetTestPlanModuleUrl, GetTestPlanModuleUrl,
GetTestPlanUsersUrl, GetTestPlanUsersUrl,
GetUnAssociatedApiBugUrl,
GetUnAssociatedListUrl, GetUnAssociatedListUrl,
GetUnAssociatedScenarioBugUrl,
MoveTestPlanModuleUrl, MoveTestPlanModuleUrl,
planDetailBugPageUrl, planDetailBugPageUrl,
PlanDetailExecuteHistoryUrl, PlanDetailExecuteHistoryUrl,
@ -439,7 +447,39 @@ export function testPlanAssociateModuleCount(data: TableQueryParams) {
export function getExecuteUserOption(projectId: string, keyword?: string) { export function getExecuteUserOption(projectId: string, keyword?: string) {
return MSR.get({ url: `${GetTestPlanExecutorOptionsUrl}/${projectId}`, params: { keyword } }); return MSR.get({ url: `${GetTestPlanExecutorOptionsUrl}/${projectId}`, params: { keyword } });
} }
// 获取测试计划未关联抽屉缺陷列表 // 获取测试计划-功能用例-未关联抽屉缺陷列表
export function getTestPlanBugPage(data: TableQueryParams) { export function getTestPlanBugPage(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetUnAssociatedListUrl, data }); return MSR.post<CommonList<CaseManagementTable>>({ url: GetUnAssociatedListUrl, data });
} }
// 获取测试计划-接口用例-未关联抽屉缺陷列表
export function getTestPlanApiBugPage(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetUnAssociatedApiBugUrl, data });
}
// 获取测试计划-接口用例-未关联抽屉缺陷列表
export function getTestPlanScenarioBugPage(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetUnAssociatedScenarioBugUrl, data });
}
// 测试计划-用例详情-关联缺陷
export function associateBugToApiCase(data: TableQueryParams) {
return MSR.post({ url: AssociatedBugToApiCaseUrl, data });
}
// 测试计划-用例详情-关联缺陷
export function associateBugToScenarioCase(data: TableQueryParams) {
return MSR.post({ url: AssociatedBugToScenarioCaseUrl, data });
}
// 测试计划-接口用例-取消关联联缺陷
export function cancelBugFromApiCase(id: string) {
return MSR.get({ url: `${CancelBugFromApiCaseUrl}/${id}` });
}
// 测试计划-场景用例-取消关联联缺陷
export function cancelBugFromScenarioCase(id: string) {
return MSR.get({ url: `${CancelBugFromScenarioCaseUrl}/${id}` });
}
// 测试计划-详情-批量关联缺陷
export function batchAssociatedBugToCase(data: TableQueryParams) {
return MSR.post({ url: BatchAssociatedBugToCaseUrl, data });
}
// 测试计划-详情-批量新建缺陷
export function batchAddBugToCase(data: TableQueryParams) {
return MSR.post({ url: BatchAddBugToCaseUrl, data });
}

View File

@ -157,3 +157,19 @@ export const TestPlanAssociationUrl = '/test-plan/association/api/case/module/co
export const GetTestPlanExecutorOptionsUrl = '/test-plan-execute/user-option'; export const GetTestPlanExecutorOptionsUrl = '/test-plan-execute/user-option';
// 获取测试计划未关联缺陷列表 // 获取测试计划未关联缺陷列表
export const GetUnAssociatedListUrl = '/test-plan/functional/case/associate/bug/page'; export const GetUnAssociatedListUrl = '/test-plan/functional/case/associate/bug/page';
// 获取测试计划未关联缺陷列表
export const GetUnAssociatedApiBugUrl = '/test-plan/api/case/associate/bug/page';
// 获取测试计划未关联缺陷列表
export const GetUnAssociatedScenarioBugUrl = '/test-plan/api/scenario/associate/bug/page';
// 测试计划-接口用例-单个用例关联缺陷
export const AssociatedBugToApiCaseUrl = '/test-plan/api/case/associate/bug';
// 测试计划-场景用例-单个用例关联缺陷
export const AssociatedBugToScenarioCaseUrl = '/test-plan/api/scenario/associate/bug';
// 测试计划-接口用例-取消关联联缺陷
export const CancelBugFromApiCaseUrl = '/test-plan/api/case/disassociate/bug';
// 测试计划-场景用例-取消关联联缺陷
export const CancelBugFromScenarioCaseUrl = '/test-plan/api/scenario/disassociate/bug';
// 测试计划-详情-用例列表-批量关联缺陷
export const BatchAssociatedBugToCaseUrl = '/test-plan/functional/case/batch/associate-bug';
// 测试计划-详情-用例列表-批量新建缺陷
export const BatchAddBugToCaseUrl = '/test-plan/functional/case/batch/add-bug';

View File

@ -23,16 +23,18 @@
import type { MsTableColumn, MsTableProps } from '@/components/pure/ms-table/type'; import type { MsTableColumn, MsTableProps } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import { testPlanCancelBug } from '@/api/modules/test-plan/testPlan'; import { cancelBugFromApiCase, cancelBugFromScenarioCase, testPlanCancelBug } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import type { CaseBugItem } from '@/models/testPlan/testPlan'; import type { CaseBugItem } from '@/models/testPlan/testPlan';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
const props = defineProps<{ const props = defineProps<{
bugList?: CaseBugItem[]; // bugList?: CaseBugItem[]; //
canEdit: boolean; canEdit: boolean;
bugCount: number; bugCount: number;
caseType: CaseLinkEnum;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -85,11 +87,17 @@
propsRes.value.data = props.bugList || []; propsRes.value.data = props.bugList || [];
}); });
const cancelBugMap: Record<string, (id: string) => Promise<any>> = {
FUNCTIONAL: testPlanCancelBug,
API: cancelBugFromApiCase,
SCENARIO: cancelBugFromScenarioCase,
};
// //
async function cancelLink(id: string) { async function cancelLink(id: string) {
try { try {
setLoading(true); setLoading(true);
await testPlanCancelBug(id); await cancelBugMap[props.caseType](id);
Message.success(t('common.unLinkSuccess')); Message.success(t('common.unLinkSuccess'));
emit('loadList'); emit('loadList');
} catch (error) { } catch (error) {

View File

@ -1,14 +1,21 @@
<template> <template>
<div class="flex items-center"> <div class="flex items-center" @click="selectedStyle">
<BugCountPopover :bug-list="bugList || []" :bug-count="bugCount" :can-edit="props.canEdit" @load-list="loadList" /> <BugCountPopover
:case-type="props.caseType"
:bug-list="bugList || []"
:bug-count="bugCount"
:can-edit="props.canEdit"
@load-list="loadList"
/>
<a-dropdown <a-dropdown
v-if="hasAllPermission(['PROJECT_BUG:READ', ...(props.linkBugPermission || [])])" v-if="hasAllPermission(['PROJECT_BUG:READ', ...(props.linkBugPermission || [])])"
position="bl" position="bl"
@select="handleSelect" @select="handleSelect"
@popup-visible-change="popupVisibleChange"
> >
<a-button <a-button
v-permission="['PROJECT_BUG:READ+ADD']" v-permission="['PROJECT_BUG:READ+ADD']"
class="arco-btn-outline--secondary ml-[8px] !p-[4px]" :class="`${isSelected ? 'selected-class' : 'operation-button'} arco-btn-outline--secondary ml-[8px] !p-[4px]`"
type="outline" type="outline"
size="small" size="small"
> >
@ -34,6 +41,7 @@
import { hasAllPermission, hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import type { CaseBugItem } from '@/models/testPlan/testPlan'; import type { CaseBugItem } from '@/models/testPlan/testPlan';
import { CaseLinkEnum } from '@/enums/caseEnum';
const { t } = useI18n(); const { t } = useI18n();
@ -41,6 +49,7 @@
resourceId: string; // id: id/id/id resourceId: string; // id: id/id/id
bugCount: number; // bugCount: number; //
existedDefect: number; // existedDefect: number; //
caseType: CaseLinkEnum; //
canEdit: boolean; canEdit: boolean;
bugList?: CaseBugItem[]; bugList?: CaseBugItem[];
linkBugPermission?: string[]; linkBugPermission?: string[];
@ -63,6 +72,17 @@
} }
} }
const isSelected = ref<boolean>(false);
function selectedStyle() {
isSelected.value = true;
}
function popupVisibleChange(val: boolean) {
if (!val) {
isSelected.value = false;
}
}
function loadList() { function loadList() {
emit('loadList'); emit('loadList');
} }
@ -70,7 +90,11 @@
<style scoped lang="less"> <style scoped lang="less">
:deep(.arco-btn-outline--secondary) { :deep(.arco-btn-outline--secondary) {
&:hover { border-color: var(--color-text-n8) !important;
}
.selected-class {
opacity: 1;
&.arco-btn-outline--secondary {
border-color: rgb(var(--primary-5)) !important; border-color: rgb(var(--primary-5)) !important;
} }
} }

View File

@ -382,6 +382,13 @@ export default function useTableProps<T>(
} }
); );
watch(
() => props?.showSelectorAll,
(val) => {
propsRes.value.showSelectorAll = val;
}
);
// 事件触发组 // 事件触发组
const propsEvent = ref({ const propsEvent = ref({
// 排序触发 // 排序触发

View File

@ -2,6 +2,8 @@ export enum AssociatedBugApiTypeEnum {
FUNCTIONAL_BUG_LIST = 'FUNCTIONAL_BUG_LIST', // 功能用例关联缺陷 FUNCTIONAL_BUG_LIST = 'FUNCTIONAL_BUG_LIST', // 功能用例关联缺陷
TEST_PLAN_BUG_LIST = 'TEST_PLAN_BUG_LIST', // 测试计划关联缺陷 TEST_PLAN_BUG_LIST = 'TEST_PLAN_BUG_LIST', // 测试计划关联缺陷
BUG_TOTAL_LIST = 'BUG_TOTAL_LIST', // 批量关联总缺陷列表 BUG_TOTAL_LIST = 'BUG_TOTAL_LIST', // 批量关联总缺陷列表
API_BUG_LIST = 'API_BUG_LIST', // 接口用例缺陷列表
SCENARIO_BUG_LIST = 'SCENARIO_BUG_LIST', // 场景用例缺陷列表
} }
export default {}; export default {};

View File

@ -48,16 +48,21 @@
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type'; import type { MsTableColumn, MsTableProps } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import BugNamePopover from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/bugNamePopover.vue'; import BugNamePopover from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/bugNamePopover.vue';
import { getBugList } from '@/api/modules/bug-management'; import { getBugList } from '@/api/modules/bug-management';
import { getDrawerDebugPage } from '@/api/modules/case-management/featureCase'; import { getDrawerDebugPage } from '@/api/modules/case-management/featureCase';
import { getTestPlanBugPage } from '@/api/modules/test-plan/testPlan'; import {
getTestPlanApiBugPage,
getTestPlanBugPage,
getTestPlanScenarioBugPage,
} from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { BugListItem } from '@/models/bug-management';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum'; import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -67,9 +72,11 @@
const appStore = useAppStore(); const appStore = useAppStore();
const getModuleTreeApiMap: Record<string, any> = { const getModuleTreeApiMap: Record<string, any> = {
[AssociatedBugApiTypeEnum.FUNCTIONAL_BUG_LIST]: getDrawerDebugPage, // - [AssociatedBugApiTypeEnum.FUNCTIONAL_BUG_LIST]: getDrawerDebugPage, // --
[AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST]: getTestPlanBugPage, // - [AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST]: getTestPlanBugPage, // --
[AssociatedBugApiTypeEnum.BUG_TOTAL_LIST]: getBugList, // [AssociatedBugApiTypeEnum.BUG_TOTAL_LIST]: getBugList, //
[AssociatedBugApiTypeEnum.API_BUG_LIST]: getTestPlanApiBugPage, // -
[AssociatedBugApiTypeEnum.SCENARIO_BUG_LIST]: getTestPlanScenarioBugPage, // -
}; };
const currentProjectId = computed(() => appStore.currentProjectId); const currentProjectId = computed(() => appStore.currentProjectId);
@ -151,16 +158,18 @@
}, },
]; ];
const getTotalBugTable = useTable( const totalCaseProps = ref<Partial<MsTableProps<BugListItem>>>({
getModuleTreeApiMap[AssociatedBugApiTypeEnum.BUG_TOTAL_LIST],
{
scroll: { x: '100%' }, scroll: { x: '100%' },
columns, columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT, tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT,
selectable: true, selectable: true,
showSelectorAll: props.showSelectorAll, showSelectorAll: props.showSelectorAll,
heightUsed: 340, heightUsed: 340,
}, });
const getTotalBugTable = useTable(
getModuleTreeApiMap[AssociatedBugApiTypeEnum.BUG_TOTAL_LIST],
totalCaseProps.value,
(record) => { (record) => {
return { return {
...record, ...record,
@ -174,17 +183,7 @@
} }
); );
const getSingleBugTable = useTable( const getSingleBugTable = useTable(getModuleTreeApiMap[props.loadApi], totalCaseProps.value, (record) => {
getModuleTreeApiMap[props.loadApi],
{
scroll: { x: '100%' },
columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT,
selectable: true,
showSelectorAll: props.showSelectorAll,
heightUsed: 340,
},
(record) => {
return { return {
...record, ...record,
tags: (record.tags || []).map((item: string, i: number) => { tags: (record.tags || []).map((item: string, i: number) => {
@ -194,6 +193,15 @@
}; };
}), }),
}; };
});
watch(
() => props.showSelectorAll,
(val) => {
totalCaseProps.value.showSelectorAll = val;
},
{
immediate: true,
} }
); );

View File

@ -39,14 +39,15 @@
</template> </template>
<template #bugCount="{ record }"> <template #bugCount="{ record }">
<MsBugOperation <MsBugOperation
:case-type="CaseLinkEnum.API"
:can-edit="props.canEdit" :can-edit="props.canEdit"
:bug-list="record.bugList" :bug-list="record.bugList"
:resource-id="record.id" :resource-id="record.id"
:bug-count="record.bugCount || 0" :bug-count="record.bugCount || 0"
:existed-defect="existedDefect" :existed-defect="existedDefect"
@load-list="loadList" @load-list="refreshDetailAndList()"
@associated="associatedDefect(false, record.id)" @associated="associateAndCreateDefect(true, false, record)"
@create="newDefect(record.id)" @create="associateAndCreateDefect(false, false, record)"
/> />
</template> </template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }"> <template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }">
@ -109,14 +110,20 @@
@load-list="resetCaseList" @load-list="resetCaseList"
/> />
<!-- TODO 等待联调 --> <!-- TODO 等待联调 -->
<AddDefectDrawer v-model:visible="showCreateBugDrawer" :extra-params="{ caseId: associatedCaseId }" /> <AddDefectDrawer
v-model:visible="showCreateBugDrawer"
:extra-params="{ caseId: associatedCaseId, testPlanId: props.planId, testPlanCaseId }"
@success="refreshDetailAndList()"
/>
<!-- TODO 等待联调 --> <!-- TODO 等待联调 -->
<LinkDefectDrawer <LinkDefectDrawer
v-model:visible="showLinkBugDrawer" v-model:visible="showLinkBugDrawer"
:case-id="associatedCaseId" :case-id="testPlanCaseId"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST" :load-api="AssociatedBugApiTypeEnum.API_BUG_LIST"
:is-batch="isBatchAssociate" :is-batch="isBatchAssociate"
:drawer-loading="drawerLoading" :drawer-loading="drawerLoading"
:show-selector-all="!isBatchAssociate"
@save="saveApiBugHandler"
/> />
</div> </div>
</template> </template>
@ -147,6 +154,8 @@
import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue'; import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import { import {
associateBugToApiCase,
batchAssociatedBugToCase,
batchDisassociateApiCase, batchDisassociateApiCase,
batchMoveApiCase, batchMoveApiCase,
batchRunApiCase, batchRunApiCase,
@ -165,9 +174,10 @@
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common'; import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common';
import type { PlanDetailApiCaseItem, PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan'; import type { PlanDetailApiCaseItem, PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum'; import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { ReportEnum } from '@/enums/reportEnum'; import { ReportEnum } from '@/enums/reportEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum'; import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -547,6 +557,11 @@
loadList(); loadList();
} }
function refreshDetailAndList() {
emit('refresh');
loadCaseList();
}
// //
async function handleDragChange(params: DragSortParams) { async function handleDragChange(params: DragSortParams) {
try { try {
@ -654,24 +669,61 @@
const showLinkBugDrawer = ref(false); const showLinkBugDrawer = ref(false);
const associatedCaseId = ref<string>(); const associatedCaseId = ref<string>();
const testPlanCaseId = ref<string>();
const existedDefect = inject<Ref<number>>('existedDefect', ref(0)); const existedDefect = inject<Ref<number>>('existedDefect', ref(0));
const showCreateBugDrawer = ref<boolean>(false);
const isBatchAssociate = ref(false); const isBatchAssociate = ref(false);
// //
function associatedDefect(isBatch: boolean, caseId?: string) { function associateAndCreateDefect(isAssociate: boolean, isBatch: boolean, record?: PlanDetailApiCaseItem) {
isBatchAssociate.value = isBatch; isBatchAssociate.value = isBatch;
associatedCaseId.value = caseId; if (record) {
const { id, apiTestCaseId } = record;
associatedCaseId.value = apiTestCaseId;
testPlanCaseId.value = id;
}
if (isAssociate) {
showLinkBugDrawer.value = true; showLinkBugDrawer.value = true;
} else {
showCreateBugDrawer.value = true;
}
} }
const drawerLoading = ref(false); const drawerLoading = ref(false);
const showCreateBugDrawer = ref<boolean>(false); //
async function saveApiBugHandler(params: TableQueryParams) {
try {
drawerLoading.value = true;
const tableParams = await getTableParams(true);
if (isBatchAssociate.value) {
await batchAssociatedBugToCase({
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...tableParams,
bugIds: params.selectIds,
projectId: appStore.currentProjectId,
});
} else {
await associateBugToApiCase({
...params,
caseId: associatedCaseId.value,
testPlanId: props.planId,
testPlanCaseId: testPlanCaseId.value,
projectId: appStore.currentProjectId,
});
}
// Message.success(t('caseManagement.featureCase.associatedSuccess'));
function newDefect(caseId?: string) { showLinkBugDrawer.value = false;
associatedCaseId.value = caseId; resetSelectorAndCaseList();
showCreateBugDrawer.value = true; emit('refresh');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
drawerLoading.value = false;
}
} }
// //
@ -700,10 +752,10 @@
batchMoveModalVisible.value = true; batchMoveModalVisible.value = true;
break; break;
case 'linkDefect': case 'linkDefect':
associatedDefect(true); associateAndCreateDefect(true, true);
break; break;
case 'newBug': case 'newBug':
newDefect(); associateAndCreateDefect(false, true);
break; break;
default: default:
break; break;

View File

@ -51,14 +51,15 @@
</template> </template>
<template #bugCount="{ record }"> <template #bugCount="{ record }">
<MsBugOperation <MsBugOperation
:case-type="CaseLinkEnum.SCENARIO"
:can-edit="props.canEdit" :can-edit="props.canEdit"
:bug-list="record.bugList" :bug-list="record.bugList"
:resource-id="record.id" :resource-id="record.id"
:bug-count="record.bugCount || 0" :bug-count="record.bugCount || 0"
:existed-defect="existedDefect" :existed-defect="existedDefect"
@load-list="loadList" @load-list="refreshListAndDetail()"
@associated="associatedDefect(false, record.id)" @associated="associateAndCreateDefect(true, false, record)"
@create="newDefect(record.id)" @create="associateAndCreateDefect(false, false, record)"
/> />
</template> </template>
<template v-if="props.canEdit" #operation="{ record }"> <template v-if="props.canEdit" #operation="{ record }">
@ -109,14 +110,20 @@
/> />
<!-- TODO 等待联调 --> <!-- TODO 等待联调 -->
<AddDefectDrawer v-model:visible="showCreateBugDrawer" :extra-params="{ caseId: associatedCaseId }" /> <AddDefectDrawer
v-model:visible="showCreateBugDrawer"
:extra-params="{ caseId: associatedCaseId, testPlanId: props.planId, testPlanCaseId }"
@success="refreshListAndDetail()"
/>
<!-- TODO 等待联调 --> <!-- TODO 等待联调 -->
<LinkDefectDrawer <LinkDefectDrawer
v-model:visible="showLinkBugDrawer" v-model:visible="showLinkBugDrawer"
:case-id="associatedCaseId" :case-id="testPlanCaseId"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST" :load-api="AssociatedBugApiTypeEnum.SCENARIO_BUG_LIST"
:is-batch="isBatchAssociate" :is-batch="isBatchAssociate"
:drawer-loading="drawerLoading" :drawer-loading="drawerLoading"
:show-selector-all="!isBatchAssociate"
@save="saveScenarioBugHandler"
/> />
</div> </div>
</template> </template>
@ -146,6 +153,8 @@
import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue'; import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import { import {
associateBugToScenarioCase,
batchAssociatedBugToCase,
batchDisassociateApiScenario, batchDisassociateApiScenario,
batchMoveApiScenario, batchMoveApiScenario,
batchRunApiScenario, batchRunApiScenario,
@ -164,9 +173,10 @@
import { characterLimit } from '@/utils'; import { characterLimit } from '@/utils';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common'; import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common';
import type { PlanDetailApiScenarioItem, PlanDetailApiScenarioQueryParams } from '@/models/testPlan/testPlan'; import type { PlanDetailApiScenarioItem, PlanDetailApiScenarioQueryParams } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum'; import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { ReportEnum } from '@/enums/reportEnum'; import { ReportEnum } from '@/enums/reportEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum'; import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -489,6 +499,11 @@
loadCaseList(); loadCaseList();
} }
function refreshListAndDetail() {
loadCaseList();
emit('refresh');
}
async function getModuleCount() { async function getModuleCount() {
const tableParams = await getTableParams(false); const tableParams = await getTableParams(false);
emit('getModuleCount', { emit('getModuleCount', {
@ -645,19 +660,59 @@
const isBatchAssociate = ref(false); const isBatchAssociate = ref(false);
const showLinkBugDrawer = ref<boolean>(false); const showLinkBugDrawer = ref<boolean>(false);
const associatedCaseId = ref<string>(); const associatedCaseId = ref<string>();
const testPlanCaseId = ref<string>();
const showCreateBugDrawer = ref<boolean>(false);
const drawerLoading = ref<boolean>(false); const drawerLoading = ref<boolean>(false);
// //
function associatedDefect(isBatch: boolean, caseId?: string) { function associateAndCreateDefect(isAssociate: boolean, isBatch: boolean, record?: PlanDetailApiScenarioItem) {
isBatchAssociate.value = isBatch; isBatchAssociate.value = isBatch;
associatedCaseId.value = caseId; if (record) {
const { id, apiScenarioId } = record;
associatedCaseId.value = apiScenarioId;
testPlanCaseId.value = id;
}
if (isAssociate) {
showLinkBugDrawer.value = true; showLinkBugDrawer.value = true;
} else {
showCreateBugDrawer.value = true;
}
} }
const showCreateBugDrawer = ref<boolean>(false); //
// async function saveScenarioBugHandler(params: TableQueryParams) {
function newDefect(caseId?: string) { try {
associatedCaseId.value = caseId; drawerLoading.value = true;
showCreateBugDrawer.value = true; const tableParams = await getTableParams(true);
if (isBatchAssociate.value) {
await batchAssociatedBugToCase({
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...tableParams,
bugIds: params.selectIds,
projectId: appStore.currentProjectId,
});
} else {
await associateBugToScenarioCase({
...params,
caseId: associatedCaseId.value,
testPlanId: props.planId,
testPlanCaseId: testPlanCaseId.value,
projectId: appStore.currentProjectId,
});
}
Message.success(t('caseManagement.featureCase.associatedSuccess'));
showLinkBugDrawer.value = false;
resetSelectorAndCaseList();
emit('refresh');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
drawerLoading.value = false;
}
} }
// //
@ -682,10 +737,10 @@
batchMoveModalVisible.value = true; batchMoveModalVisible.value = true;
break; break;
case 'linkDefect': case 'linkDefect':
associatedDefect(true); associateAndCreateDefect(true, true);
break; break;
case 'newBug': case 'newBug':
newDefect(); associateAndCreateDefect(false, true);
break; break;
default: default:
break; break;

View File

@ -1,14 +1,17 @@
<template> <template>
<a-popover position="br" content-class="case-count-popover" @popup-visible-change="popupChange"> <a-popover position="br" content-class="case-count-popover" @popup-visible-change="popupChange">
<div class="one-line-text cursor-pointer px-0 text-[rgb(var(--primary-5))]">{{ <div class="one-line-text cursor-pointer px-0 text-[rgb(var(--primary-5))]">
props.bugItem.relateCases?.length ?? 0 {{ props.bugItem.relateCases?.length ?? 0 }}
}}</div> </div>
<template #content> <template #content>
<div class="w-[500px]"> <div class="w-[500px]">
<MsBaseTable v-bind="propsRes" v-on="propsEvent"> <MsBaseTable v-bind="propsRes" v-on="propsEvent">
<template #num="{ record }"> <template #num="{ record }">
<MsButton size="mini" type="text" @click="goCaseDetail(record.id)">{{ record.num }}</MsButton> <MsButton size="mini" type="text" @click="goCaseDetail(record.id)">{{ record.num }}</MsButton>
</template> </template>
<template #type="{ record }">
<span>{{ getCaseType(record.type) || '-' }}</span>
</template>
</MsBaseTable> </MsBaseTable>
</div> </div>
</template> </template>
@ -21,9 +24,11 @@
import type { MsTableColumn } from '@/components/pure/ms-table/type'; import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage'; import useOpenNewPage from '@/hooks/useOpenNewPage';
import type { PlanDetailBugItem } from '@/models/testPlan/testPlan'; import type { PlanDetailBugItem } from '@/models/testPlan/testPlan';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { CaseManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -32,6 +37,7 @@
}>(); }>();
const { openNewPage } = useOpenNewPage(); const { openNewPage } = useOpenNewPage();
const { t } = useI18n();
const columns: MsTableColumn = [ const columns: MsTableColumn = [
{ {
@ -50,11 +56,25 @@
{ {
title: 'testPlan.caseType', title: 'testPlan.caseType',
dataIndex: 'type', dataIndex: 'type',
slotName: 'type',
showTooltip: true, showTooltip: true,
width: 200, width: 200,
}, },
]; ];
function getCaseType(type: CaseLinkEnum) {
switch (type) {
case CaseLinkEnum.API:
return t('testPlan.testPlanIndex.apiCase');
case CaseLinkEnum.FUNCTIONAL:
return t('testPlan.testPlanIndex.functionalUseCase');
case CaseLinkEnum.SCENARIO:
return t('testPlan.testPlanIndex.apiScenarioCase');
default:
break;
}
}
const { propsRes, propsEvent } = useTable(undefined, { const { propsRes, propsEvent } = useTable(undefined, {
columns, columns,
size: 'mini', size: 'mini',

View File

@ -80,14 +80,15 @@
</template> </template>
<template #bugCount="{ record }"> <template #bugCount="{ record }">
<MsBugOperation <MsBugOperation
:case-type="CaseLinkEnum.FUNCTIONAL"
:can-edit="props.canEdit" :can-edit="props.canEdit"
:bug-list="record.bugList" :bug-list="record.bugList"
:resource-id="record.id" :resource-id="record.id"
:bug-count="record.bugCount || 0" :bug-count="record.bugCount || 0"
:existed-defect="existedDefect" :existed-defect="existedDefect"
@load-list="loadList" @load-list="refreshList()"
@associated="associatedDefect(false, record.id)" @associated="associateAndCreateDefect(true, false, record)"
@create="newDefect(record.id)" @create="associateAndCreateDefect(false, false, record)"
/> />
</template> </template>
<template v-if="props.canEdit" #operation="{ record }"> <template v-if="props.canEdit" #operation="{ record }">
@ -177,17 +178,18 @@
<!-- TODO 等待联调 --> <!-- TODO 等待联调 -->
<AddDefectDrawer <AddDefectDrawer
v-model:visible="showCreateBugDrawer" v-model:visible="showCreateBugDrawer"
:extra-params="{ caseId: associatedCaseId }" :extra-params="{ caseId: associatedCaseId, testPlanId: props.planId, testPlanCaseId }"
@success="refreshList" @success="refreshList()"
/> />
<!-- TODO 等待联调 --> <!-- TODO 等待联调 -->
<LinkDefectDrawer <LinkDefectDrawer
v-model:visible="showLinkBugDrawer" v-model:visible="showLinkBugDrawer"
:case-id="associatedCaseId" :case-id="testPlanCaseId"
:drawer-loading="drawerLoading" :drawer-loading="drawerLoading"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST" :load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST"
:show-selector-all="!isBatchAssociate"
:is-batch="isBatchAssociate" :is-batch="isBatchAssociate"
@save="saveHandler" @save="saveFunctionBugHandler"
/> />
</div> </div>
</template> </template>
@ -222,6 +224,8 @@
import ExecuteForm from '@/views/test-plan/testPlan/detail/featureCase/components/executeForm.vue'; import ExecuteForm from '@/views/test-plan/testPlan/detail/featureCase/components/executeForm.vue';
import { import {
associateBugToPlan,
batchAssociatedBugToCase,
batchDisassociateCase, batchDisassociateCase,
batchExecuteCase, batchExecuteCase,
batchMoveFeatureCase, batchMoveFeatureCase,
@ -243,7 +247,7 @@
import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common'; import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common';
import type { ExecuteFeatureCaseFormParams, PlanDetailFeatureCaseItem } from '@/models/testPlan/testPlan'; import type { ExecuteFeatureCaseFormParams, PlanDetailFeatureCaseItem } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum'; import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { LastExecuteResults } from '@/enums/caseEnum'; import { CaseLinkEnum, LastExecuteResults } from '@/enums/caseEnum';
import { TestPlanRouteEnum } from '@/enums/routeEnum'; import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -801,17 +805,38 @@
const showLinkBugDrawer = ref<boolean>(false); const showLinkBugDrawer = ref<boolean>(false);
const associatedCaseId = ref<string>(); const associatedCaseId = ref<string>();
const testPlanCaseId = ref<string>();
const drawerLoading = ref<boolean>(false); const drawerLoading = ref<boolean>(false);
const isBatchAssociate = ref(false);
// TODO //
function saveHandler(params: TableQueryParams) { async function saveFunctionBugHandler(params: TableQueryParams) {
try { try {
drawerLoading.value = true; drawerLoading.value = true;
const tableParams = await getTableParams(true);
if (isBatchAssociate.value) {
await batchAssociatedBugToCase({
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...tableParams,
projectId: appStore.currentProjectId,
bugIds: params.selectIds,
});
} else {
await associateBugToPlan({
...params,
projectId: appStore.currentProjectId,
caseId: associatedCaseId.value,
testPlanId: props.planId,
testPlanCaseId: testPlanCaseId.value,
});
}
Message.success(t('caseManagement.featureCase.associatedSuccess')); Message.success(t('caseManagement.featureCase.associatedSuccess'));
resetCaseList();
initModules();
emit('refresh');
showLinkBugDrawer.value = false; showLinkBugDrawer.value = false;
resetCaseList();
emit('refresh');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -822,25 +847,25 @@
function refreshList() { function refreshList() {
resetCaseList(); resetCaseList();
initModules();
emit('refresh'); emit('refresh');
} }
const isBatchAssociate = ref(false);
//
function associatedDefect(isBatch: boolean, caseId?: string) {
isBatchAssociate.value = isBatch;
associatedCaseId.value = caseId;
showLinkBugDrawer.value = true;
}
const showCreateBugDrawer = ref<boolean>(false); const showCreateBugDrawer = ref<boolean>(false);
//
function newDefect(caseId?: string) { // /
function associateAndCreateDefect(isAssociate: boolean, isBatch: boolean, record?: PlanDetailFeatureCaseItem) {
isBatchAssociate.value = isBatch;
if (record) {
const { id, caseId } = record;
associatedCaseId.value = caseId; associatedCaseId.value = caseId;
testPlanCaseId.value = id;
}
if (isAssociate) {
showLinkBugDrawer.value = true;
} else {
showCreateBugDrawer.value = true; showCreateBugDrawer.value = true;
} }
}
// //
const batchUpdateExecutorModalVisible = ref(false); const batchUpdateExecutorModalVisible = ref(false);
@ -861,10 +886,10 @@
batchMoveModalVisible.value = true; batchMoveModalVisible.value = true;
break; break;
case 'linkDefect': case 'linkDefect':
associatedDefect(true); associateAndCreateDefect(true, true);
break; break;
case 'newBug': case 'newBug':
newDefect(); associateAndCreateDefect(false, true);
break; break;
default: default:
break; break;

View File

@ -597,4 +597,14 @@
:deep(.arco-tabs-content) { :deep(.arco-tabs-content) {
@apply hidden; @apply hidden;
} }
:deep(.arco-table-tr) {
.operation-button {
opacity: 0;
}
&:hover {
.operation-button {
opacity: 1;
}
}
}
</style> </style>