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) {
return MSR.post<ModuleTreeNode[]>({ url: GetApiCaseModuleUrl, data });
return MSR.post<ModuleTreeNode[]>({ url: GetApiCaseModuleUrl, data }, { ignoreCancelToken: true });
}
// 计划详情-接口用例-获取模块数量
export function getApiCaseModuleCount(data: PlanDetailApiCaseQueryParams) {

View File

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

View File

@ -1,15 +1,14 @@
<template>
<div class="p-[16px]">
<MsAdvanceFilter
ref="msAdvanceFilterRef"
v-model:keyword="keyword"
:filter-config-list="[]"
:custom-fields-config-list="[]"
:count="props.modulesCount[props.activeModule] || 0"
:name="moduleNamePath"
:view-type="ViewTypeEnum.PLAN_API_CASE"
:filter-config-list="filterConfigList"
:search-placeholder="t('common.searchByIdName')"
@keyword-search="loadCaseList()"
@adv-search="loadCaseList()"
@refresh="handleRefreshAll"
@adv-search="handleAdvSearch"
@refresh="handleRefreshAll()"
/>
<a-spin :loading="tableLoading" class="w-full">
<MsBaseTable
@ -17,12 +16,12 @@
class="mt-[16px]"
v-bind="propsRes"
:action-config="batchActions"
:not-show-table-filter="isAdvancedSearchMode"
v-on="propsEvent"
@batch-action="handleTableBatch"
@drag-change="handleDragChange"
@selected-change="handleTableSelect"
@filter-change="getModuleCount"
@module-change="loadCaseList(false)"
>
<template #num="{ record }">
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
@ -137,7 +136,8 @@
import { computed, ref } from '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 MsPopconfirm from '@/components/pure/ms-popconfirm/index.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 BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import { getAssociatedProjectOptions } from '@/api/modules/case-management/featureCase';
import {
associateBugToApiCase,
batchDisassociateApiCase,
@ -165,6 +166,7 @@
batchMoveApiCase,
batchRunApiCase,
disassociateApiCase,
getApiCaseModule,
getApiCaseReport,
getApiCaseReportStep,
getPlanDetailApiCaseList,
@ -180,7 +182,9 @@
import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common';
import type { ProjectListItem } from '@/models/setting/project';
import type { PlanDetailApiCaseItem, PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan';
import { FilterType, ViewTypeEnum } from '@/enums/advancedFilterEnum';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { ReportEnum } from '@/enums/reportEnum';
@ -188,11 +192,13 @@
import { TableKeyEnum } from '@/enums/tableEnum';
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<{
modulesCount: Record<string, number>; //
moduleName: string;
moduleParentId: string;
activeModule: string;
offspringIds: string[];
@ -200,12 +206,14 @@
moduleTree: ModuleTreeNode[];
canEdit: boolean;
selectedProtocols: string[];
allProtocolList: string[];
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
(e: 'getModuleCount', params: PlanDetailApiCaseQueryParams): void;
(e: 'refresh'): void;
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
(e: 'initModules'): void;
}>();
@ -216,9 +224,6 @@
const appStore = useAppStore();
const keyword = ref('');
const moduleNamePath = computed(() => {
return props.activeModule === 'all' ? t('apiTestManagement.allApi') : props.moduleName;
});
const hasOperationPermission = computed(
() => hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ASSOCIATION']) && props.canEdit
@ -391,16 +396,13 @@
tableKey: TableKeyEnum.TEST_PLAN_DETAIL_API_CASE,
showSetting: true,
heightUsed: 445,
showSubdirectory: true,
draggable: { type: 'handle' },
draggableCondition: true,
selectable: hasOperationPermission.value,
});
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getPlanDetailApiCaseList,
tableProps.value
);
const { propsRes, propsEvent, viewId, advanceFilter, setAdvanceFilter, loadList, setLoadListParams, resetSelector } =
useTable(getPlanDetailApiCaseList, tableProps.value);
const existedDefect = inject<Ref<number>>('existedDefect', ref(0));
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() {
let moduleIds: string[] = [];
if (props.activeModule !== 'all') {
if (props.activeModule !== 'all' && !isAdvancedSearchMode.value) {
moduleIds = [props.activeModule];
const getAllChildren = await tableStore.getSubShow(TableKeyEnum.TEST_PLAN_DETAIL_API_CASE);
if (getAllChildren) {
@ -467,7 +471,7 @@
const selectModules = await getModuleIds();
const commonParams = {
testPlanId: props.planId,
protocols: props.selectedProtocols,
protocols: isAdvancedSearchMode.value ? props.allProtocolList : props.selectedProtocols,
...(props.treeType === 'COLLECTION' ? { collectionId: collectionId.value } : { moduleIds: selectModules }),
};
if (isBatch) {
@ -475,6 +479,8 @@
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
viewId: viewId.value,
combineSearch: advanceFilter,
},
projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '',
...commonParams,
@ -506,10 +512,12 @@
const tableParams = await getTableParams(false);
setLoadListParams({
...tableParams,
viewId: viewId.value,
combineSearch: advanceFilter,
projectId: props.activeModule !== 'all' && props.treeType === 'MODULE' ? props.moduleParentId : '',
});
loadList();
if (refreshTreeCount) {
if (refreshTreeCount && !isAdvancedSearchMode.value) {
emit('getModuleCount', {
...tableParams,
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], () => {
if (isAdvancedSearchMode.value) return;
loadCaseList();
});
onBeforeMount(() => {
initAnotherModules();
initProjectList();
});
async function getModuleCount() {
const tableParams = await getTableParams(false);
emit('getModuleCount', {

View File

@ -120,6 +120,7 @@
const selectedKeys = useVModel(props, 'selectedKeys', emit);
const treeFolderAllRef = ref<InstanceType<typeof TreeFolderAll>>();
const selectedProtocols = computed<string[]>(() => treeFolderAllRef.value?.selectedProtocols ?? []);
const allProtocolList = computed<string[]>(() => treeFolderAllRef.value?.allProtocolList ?? []);
//
async function initModules() {
@ -149,7 +150,7 @@
return e;
});
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({
allProtocolList,
initModules,
setActiveFolder,
});

View File

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