feat(测试计划): 高级筛选-计划详情页-接口用例

This commit is contained in:
teukkk 2024-10-11 18:32:40 +08:00 committed by Craftsman
parent 754f107737
commit b08ce1508b
5 changed files with 250 additions and 30 deletions

View File

@ -311,7 +311,7 @@ export function getPlanDetailApiCaseList(data: PlanDetailApiCaseQueryParams) {
} }
// 计划详情-接口用例模块树 // 计划详情-接口用例模块树
export function getApiCaseModule(data: PlanDetailApiCaseTreeParams) { export function getApiCaseModule(data: PlanDetailApiCaseTreeParams) {
return MSR.post<ModuleTreeNode[]>({ url: GetApiCaseModuleUrl, data }); return MSR.post<ModuleTreeNode[]>({ url: GetApiCaseModuleUrl, data }, { ignoreCancelToken: true });
} }
// 计划详情-接口用例-获取模块数量 // 计划详情-接口用例-获取模块数量
export function getApiCaseModuleCount(data: PlanDetailApiCaseQueryParams) { export function getApiCaseModuleCount(data: PlanDetailApiCaseQueryParams) {

View File

@ -180,6 +180,7 @@
defineExpose({ defineExpose({
selectedProtocols, selectedProtocols,
allProtocolList,
}); });
</script> </script>

View File

@ -1,15 +1,14 @@
<template> <template>
<div class="p-[16px]"> <div class="p-[16px]">
<MsAdvanceFilter <MsAdvanceFilter
ref="msAdvanceFilterRef"
v-model:keyword="keyword" v-model:keyword="keyword"
:filter-config-list="[]" :view-type="ViewTypeEnum.PLAN_API_CASE"
:custom-fields-config-list="[]" :filter-config-list="filterConfigList"
:count="props.modulesCount[props.activeModule] || 0"
:name="moduleNamePath"
:search-placeholder="t('common.searchByIdName')" :search-placeholder="t('common.searchByIdName')"
@keyword-search="loadCaseList()" @keyword-search="loadCaseList()"
@adv-search="loadCaseList()" @adv-search="handleAdvSearch"
@refresh="handleRefreshAll" @refresh="handleRefreshAll()"
/> />
<a-spin :loading="tableLoading" class="w-full"> <a-spin :loading="tableLoading" class="w-full">
<MsBaseTable <MsBaseTable
@ -17,12 +16,12 @@
class="mt-[16px]" class="mt-[16px]"
v-bind="propsRes" v-bind="propsRes"
:action-config="batchActions" :action-config="batchActions"
:not-show-table-filter="isAdvancedSearchMode"
v-on="propsEvent" v-on="propsEvent"
@batch-action="handleTableBatch" @batch-action="handleTableBatch"
@drag-change="handleDragChange" @drag-change="handleDragChange"
@selected-change="handleTableSelect" @selected-change="handleTableSelect"
@filter-change="getModuleCount" @filter-change="getModuleCount"
@module-change="loadCaseList(false)"
> >
<template #num="{ record }"> <template #num="{ record }">
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton> <MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
@ -137,7 +136,8 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter'; import MsAdvanceFilter from '@/components/pure/ms-advance-filter/index.vue';
import { FilterFormItem, FilterResult } from '@/components/pure/ms-advance-filter/type';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue'; import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -158,6 +158,7 @@
import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue'; import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue';
import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue'; import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import { getAssociatedProjectOptions } from '@/api/modules/case-management/featureCase';
import { import {
associateBugToApiCase, associateBugToApiCase,
batchDisassociateApiCase, batchDisassociateApiCase,
@ -165,6 +166,7 @@
batchMoveApiCase, batchMoveApiCase,
batchRunApiCase, batchRunApiCase,
disassociateApiCase, disassociateApiCase,
getApiCaseModule,
getApiCaseReport, getApiCaseReport,
getApiCaseReportStep, getApiCaseReportStep,
getPlanDetailApiCaseList, getPlanDetailApiCaseList,
@ -180,7 +182,9 @@
import { hasAllPermission, hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common'; import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common';
import type { ProjectListItem } from '@/models/setting/project';
import type { PlanDetailApiCaseItem, PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan'; import type { PlanDetailApiCaseItem, PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan';
import { FilterType, ViewTypeEnum } from '@/enums/advancedFilterEnum';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum'; import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { CaseLinkEnum } from '@/enums/caseEnum'; import { CaseLinkEnum } from '@/enums/caseEnum';
import { ReportEnum } from '@/enums/reportEnum'; import { ReportEnum } from '@/enums/reportEnum';
@ -188,11 +192,13 @@
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { casePriorityOptions, lastReportStatusListOptions } from '@/views/api-test/components/config'; import {
casePriorityOptions,
caseStatusOptions,
lastReportStatusListOptions,
} from '@/views/api-test/components/config';
const props = defineProps<{ const props = defineProps<{
modulesCount: Record<string, number>; //
moduleName: string;
moduleParentId: string; moduleParentId: string;
activeModule: string; activeModule: string;
offspringIds: string[]; offspringIds: string[];
@ -200,12 +206,14 @@
moduleTree: ModuleTreeNode[]; moduleTree: ModuleTreeNode[];
canEdit: boolean; canEdit: boolean;
selectedProtocols: string[]; selectedProtocols: string[];
allProtocolList: string[];
treeType: 'MODULE' | 'COLLECTION'; treeType: 'MODULE' | 'COLLECTION';
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'getModuleCount', params: PlanDetailApiCaseQueryParams): void; (e: 'getModuleCount', params: PlanDetailApiCaseQueryParams): void;
(e: 'refresh'): void; (e: 'refresh'): void;
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
(e: 'initModules'): void; (e: 'initModules'): void;
}>(); }>();
@ -216,9 +224,6 @@
const appStore = useAppStore(); const appStore = useAppStore();
const keyword = ref(''); const keyword = ref('');
const moduleNamePath = computed(() => {
return props.activeModule === 'all' ? t('apiTestManagement.allApi') : props.moduleName;
});
const hasOperationPermission = computed( const hasOperationPermission = computed(
() => hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ASSOCIATION']) && props.canEdit () => hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ASSOCIATION']) && props.canEdit
@ -391,16 +396,13 @@
tableKey: TableKeyEnum.TEST_PLAN_DETAIL_API_CASE, tableKey: TableKeyEnum.TEST_PLAN_DETAIL_API_CASE,
showSetting: true, showSetting: true,
heightUsed: 445, heightUsed: 445,
showSubdirectory: true,
draggable: { type: 'handle' }, draggable: { type: 'handle' },
draggableCondition: true, draggableCondition: true,
selectable: hasOperationPermission.value, selectable: hasOperationPermission.value,
}); });
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable( const { propsRes, propsEvent, viewId, advanceFilter, setAdvanceFilter, loadList, setLoadListParams, resetSelector } =
getPlanDetailApiCaseList, useTable(getPlanDetailApiCaseList, tableProps.value);
tableProps.value
);
const existedDefect = inject<Ref<number>>('existedDefect', ref(0)); const existedDefect = inject<Ref<number>>('existedDefect', ref(0));
const tableRef = ref<InstanceType<typeof MsBaseTable>>(); const tableRef = ref<InstanceType<typeof MsBaseTable>>();
@ -449,9 +451,11 @@
}; };
}); });
const msAdvanceFilterRef = ref<InstanceType<typeof MsAdvanceFilter>>();
const isAdvancedSearchMode = computed(() => msAdvanceFilterRef.value?.isAdvancedSearchMode);
async function getModuleIds() { async function getModuleIds() {
let moduleIds: string[] = []; let moduleIds: string[] = [];
if (props.activeModule !== 'all') { if (props.activeModule !== 'all' && !isAdvancedSearchMode.value) {
moduleIds = [props.activeModule]; moduleIds = [props.activeModule];
const getAllChildren = await tableStore.getSubShow(TableKeyEnum.TEST_PLAN_DETAIL_API_CASE); const getAllChildren = await tableStore.getSubShow(TableKeyEnum.TEST_PLAN_DETAIL_API_CASE);
if (getAllChildren) { if (getAllChildren) {
@ -467,7 +471,7 @@
const selectModules = await getModuleIds(); const selectModules = await getModuleIds();
const commonParams = { const commonParams = {
testPlanId: props.planId, testPlanId: props.planId,
protocols: props.selectedProtocols, protocols: isAdvancedSearchMode.value ? props.allProtocolList : props.selectedProtocols,
...(props.treeType === 'COLLECTION' ? { collectionId: collectionId.value } : { moduleIds: selectModules }), ...(props.treeType === 'COLLECTION' ? { collectionId: collectionId.value } : { moduleIds: selectModules }),
}; };
if (isBatch) { if (isBatch) {
@ -475,6 +479,8 @@
condition: { condition: {
keyword: keyword.value, keyword: keyword.value,
filter: propsRes.value.filter, filter: propsRes.value.filter,
viewId: viewId.value,
combineSearch: advanceFilter,
}, },
projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '', projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '',
...commonParams, ...commonParams,
@ -506,10 +512,12 @@
const tableParams = await getTableParams(false); const tableParams = await getTableParams(false);
setLoadListParams({ setLoadListParams({
...tableParams, ...tableParams,
viewId: viewId.value,
combineSearch: advanceFilter,
projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '', projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '',
}); });
loadList(); loadList();
if (refreshTreeCount) { if (refreshTreeCount && !isAdvancedSearchMode.value) {
emit('getModuleCount', { emit('getModuleCount', {
...tableParams, ...tableParams,
current: propsRes.value.msPagination?.current, current: propsRes.value.msPagination?.current,
@ -518,10 +526,213 @@
} }
} }
const projectList = ref<ProjectListItem[]>([]);
async function initProjectList() {
try {
projectList.value = await getAssociatedProjectOptions(appStore.currentOrgId, CaseLinkEnum.API);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
const anotherTree = ref<ModuleTreeNode[]>([]);
async function initAnotherModules() {
try {
const res = await getApiCaseModule({
testPlanId: props.planId,
treeType: props.treeType === 'MODULE' ? 'COLLECTION' : 'MODULE',
});
anotherTree.value = res;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
const filterConfigList = computed<FilterFormItem[]>(() => [
{
title: 'caseManagement.featureCase.tableColumnID',
dataIndex: 'num',
type: FilterType.INPUT,
},
{
title: 'case.caseName',
dataIndex: 'name',
type: FilterType.INPUT,
},
{
title: 'ms.minders.testSet',
dataIndex: 'testPlanCollectionId',
type: FilterType.SELECT,
selectProps: {
labelKey: 'name',
valueKey: 'id',
multiple: true,
options: props.treeType !== 'MODULE' ? props.moduleTree : anotherTree.value,
},
},
{
title: 'common.belongModule',
dataIndex: 'moduleId',
type: FilterType.TREE_SELECT,
treeSelectData: props.treeType === 'MODULE' ? props.moduleTree : anotherTree.value,
treeSelectProps: {
fieldNames: {
title: 'name',
key: 'id',
children: 'children',
},
multiple: true,
treeCheckable: true,
treeCheckStrictly: true,
},
},
{
title: 'common.belongProject',
dataIndex: 'projectName',
type: FilterType.SELECT,
selectProps: {
labelKey: 'name',
valueKey: 'id',
multiple: true,
options: projectList.value,
},
},
{
title: 'apiTestManagement.protocol',
dataIndex: 'protocol',
type: FilterType.SELECT,
selectProps: {
multiple: true,
options: props.allProtocolList.map((item) => ({ label: item, value: item })),
},
},
{
title: 'case.caseLevel',
dataIndex: 'priority',
type: FilterType.SELECT,
selectProps: {
multiple: true,
options: casePriorityOptions,
},
},
{
title: 'common.executionResult',
dataIndex: 'lastExecResult',
type: FilterType.SELECT,
selectProps: {
multiple: true,
options: lastReportStatusListOptions.value,
},
},
{
title: 'apiTestManagement.apiStatus',
dataIndex: 'status',
type: FilterType.SELECT,
selectProps: {
multiple: true,
options: caseStatusOptions.map((item) => ({ label: t(item.label), value: item.value })),
},
},
{
title: 'case.apiParamsChange',
dataIndex: 'apiChange',
type: FilterType.BOOLEAN,
selectProps: {
options: [
{ label: t('case.withoutChanges'), value: false },
{ label: t('case.withChanges'), value: true },
],
},
},
{
title: 'testPlan.featureCase.bugCount',
dataIndex: 'bugCount',
type: FilterType.NUMBER,
numberProps: {
min: 0,
precision: 0,
},
},
{
title: 'apiTestManagement.path',
dataIndex: 'path',
type: FilterType.INPUT,
},
{
title: 'case.passRate',
dataIndex: 'passRate',
type: FilterType.NUMBER,
numberProps: {
min: 0,
},
},
{
title: 'report.detail.api.executeEnv',
dataIndex: 'environmentName',
type: FilterType.SELECT,
selectProps: {
labelKey: 'name',
valueKey: 'id',
multiple: true,
options: appStore.envList,
},
},
{
title: 'common.tag',
dataIndex: 'tags',
type: FilterType.TAGS_INPUT,
numberProps: {
min: 0,
precision: 0,
},
},
{
title: 'testPlan.featureCase.executor',
dataIndex: 'executeUser',
type: FilterType.MEMBER,
},
{
title: 'common.creator',
dataIndex: 'createUser',
type: FilterType.MEMBER,
},
{
title: 'common.createTime',
dataIndex: 'createTime',
type: FilterType.DATE_PICKER,
},
{
title: 'common.updateUserName',
dataIndex: 'updateUser',
type: FilterType.MEMBER,
},
{
title: 'common.updateTime',
dataIndex: 'updateTime',
type: FilterType.DATE_PICKER,
},
]);
//
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
resetSelector();
emit('handleAdvSearch', isStartAdvance);
keyword.value = '';
setAdvanceFilter(filter, id);
await loadCaseList(); //
};
watch([() => props.activeModule, () => props.selectedProtocols], () => { watch([() => props.activeModule, () => props.selectedProtocols], () => {
if (isAdvancedSearchMode.value) return;
loadCaseList(); loadCaseList();
}); });
onBeforeMount(() => {
initAnotherModules();
initProjectList();
});
async function getModuleCount() { async function getModuleCount() {
const tableParams = await getTableParams(false); const tableParams = await getTableParams(false);
emit('getModuleCount', { emit('getModuleCount', {

View File

@ -120,6 +120,7 @@
const selectedKeys = useVModel(props, 'selectedKeys', emit); const selectedKeys = useVModel(props, 'selectedKeys', emit);
const treeFolderAllRef = ref<InstanceType<typeof TreeFolderAll>>(); const treeFolderAllRef = ref<InstanceType<typeof TreeFolderAll>>();
const selectedProtocols = computed<string[]>(() => treeFolderAllRef.value?.selectedProtocols ?? []); const selectedProtocols = computed<string[]>(() => treeFolderAllRef.value?.selectedProtocols ?? []);
const allProtocolList = computed<string[]>(() => treeFolderAllRef.value?.allProtocolList ?? []);
// //
async function initModules() { async function initModules() {
@ -149,7 +150,7 @@
return e; return e;
}); });
activeFolder.value = node.id; activeFolder.value = node.id;
emit('folderNodeSelect', _selectedKeys as string[], offspringIds, node.name, getNodeParentId(node)); emit('folderNodeSelect', _selectedKeys as string[], offspringIds, getNodeParentId(node));
} }
// //
@ -176,6 +177,7 @@
}); });
defineExpose({ defineExpose({
allProtocolList,
initModules, initModules,
setActiveFolder, setActiveFolder,
}); });

View File

@ -1,5 +1,5 @@
<template> <template>
<MsSplitBox> <MsSplitBox :not-show-first="isAdvancedSearchMode">
<template #first> <template #first>
<div class="p-[16px]"> <div class="p-[16px]">
<a-radio-group v-model:model-value="treeType" size="medium" class="mb-[16px] w-full" type="button"> <a-radio-group v-model:model-value="treeType" size="medium" class="mb-[16px] w-full" type="button">
@ -22,8 +22,7 @@
ref="caseTableRef" ref="caseTableRef"
:plan-id="planId" :plan-id="planId"
:tree-type="treeType" :tree-type="treeType"
:modules-count="modulesCount" :all-protocol-list="allProtocolList"
:module-name="moduleName"
:module-parent-id="moduleParentId" :module-parent-id="moduleParentId"
:active-module="activeFolderId" :active-module="activeFolderId"
:offspring-ids="offspringIds" :offspring-ids="offspringIds"
@ -32,6 +31,7 @@
:selected-protocols="selectedProtocols" :selected-protocols="selectedProtocols"
@get-module-count="getModuleCount" @get-module-count="getModuleCount"
@refresh="emit('refresh')" @refresh="emit('refresh')"
@handle-adv-search="handleAdvSearch"
@init-modules="initModules" @init-modules="initModules"
></CaseTable> ></CaseTable>
</template> </template>
@ -80,17 +80,15 @@
const caseTableRef = ref<InstanceType<typeof CaseTable>>(); const caseTableRef = ref<InstanceType<typeof CaseTable>>();
const activeFolderId = ref<string>('all'); const activeFolderId = ref<string>('all');
const moduleName = ref<string>('');
const moduleParentId = ref<string>(''); const moduleParentId = ref<string>('');
const offspringIds = ref<string[]>([]); const offspringIds = ref<string[]>([]);
const selectedKeys = computed({ const selectedKeys = computed({
get: () => [activeFolderId.value], get: () => [activeFolderId.value],
set: (val) => val, set: (val) => val,
}); });
function handleFolderNodeSelect(ids: string[], _offspringIds: string[], name?: string, parentId?: string) { function handleFolderNodeSelect(ids: string[], _offspringIds: string[], parentId?: string) {
[activeFolderId.value] = ids; [activeFolderId.value] = ids;
offspringIds.value = [..._offspringIds]; offspringIds.value = [..._offspringIds];
moduleName.value = name ?? '';
moduleParentId.value = parentId ?? ''; moduleParentId.value = parentId ?? '';
caseTableRef.value?.resetSelector(); caseTableRef.value?.resetSelector();
} }
@ -101,10 +99,18 @@
} }
const caseTreeRef = ref<InstanceType<typeof CaseTree>>(); const caseTreeRef = ref<InstanceType<typeof CaseTree>>();
const allProtocolList = computed<string[]>(() => caseTreeRef.value?.allProtocolList ?? []);
function initModules() { function initModules() {
caseTreeRef.value?.initModules(); caseTreeRef.value?.initModules();
} }
const isAdvancedSearchMode = ref(false);
function handleAdvSearch(isStartAdvance: boolean) {
isAdvancedSearchMode.value = isStartAdvance;
caseTreeRef.value?.setActiveFolder('all');
}
const treeType = ref<'MODULE' | 'COLLECTION'>('COLLECTION'); const treeType = ref<'MODULE' | 'COLLECTION'>('COLLECTION');
function getCaseTableList() { function getCaseTableList() {
nextTick(() => { nextTick(() => {