feat(接口测试): 高级筛选-场景

This commit is contained in:
teukkk 2024-10-10 16:34:35 +08:00 committed by Craftsman
parent 86bed46177
commit bb098c9b2f
7 changed files with 226 additions and 123 deletions

View File

@ -29,6 +29,7 @@ export enum FilterType {
export enum ViewTypeEnum { export enum ViewTypeEnum {
FUNCTIONAL_CASE = 'functional-case', FUNCTIONAL_CASE = 'functional-case',
API_DEFINITION = 'api-definition', API_DEFINITION = 'api-definition',
API_SCENARIO = 'api-scenario',
REVIEW_FUNCTIONAL_CASE = 'review-functional-case', REVIEW_FUNCTIONAL_CASE = 'review-functional-case',
API_CASE = 'api-case', API_CASE = 'api-case',
API_MOCK = 'api-mock', API_MOCK = 'api-mock',

View File

@ -176,10 +176,6 @@ export interface ExecutePageParams extends TableQueryParams {
id: string; id: string;
} }
export interface ApiScenarioBatchParam extends BatchActionQueryParams {
moduleIds: string[];
}
// 场景-执行历史-请求参数 // 场景-执行历史-请求参数
export interface ExecuteHistoryItem { export interface ExecuteHistoryItem {
id: string; id: string;

View File

@ -761,7 +761,13 @@
{ {
title: 'case.caseEnvironment', title: 'case.caseEnvironment',
dataIndex: 'environmentName', dataIndex: 'environmentName',
type: FilterType.INPUT, type: FilterType.SELECT,
selectProps: {
labelKey: 'name',
valueKey: 'id',
multiple: true,
options: appStore.envList,
},
}, },
{ {
title: 'common.tag', title: 'common.tag',

View File

@ -1,3 +1,5 @@
import { useI18n } from '@/hooks/useI18n';
import { type CsvVariable, Scenario, ScenarioStepConfig } from '@/models/apiTest/scenario'; import { type CsvVariable, Scenario, ScenarioStepConfig } from '@/models/apiTest/scenario';
import { import {
ApiScenarioStatus, ApiScenarioStatus,
@ -7,6 +9,15 @@ import {
WhileConditionType, WhileConditionType,
} from '@/enums/apiEnum'; } from '@/enums/apiEnum';
const { t } = useI18n();
// 场景状态选项
export const scenarioStatusOptions = [
{ label: t('apiTestManagement.processing'), value: ApiScenarioStatus.UNDERWAY },
{ label: t('apiTestManagement.deprecate'), value: ApiScenarioStatus.DEPRECATED },
{ label: t('apiTestManagement.done'), value: ApiScenarioStatus.COMPLETED },
];
// 循环控制器 // 循环控制器
export const defaultLoopController = { export const defaultLoopController = {
loopType: ScenarioStepLoopTypeEnum.LOOP_COUNT, loopType: ScenarioStepLoopTypeEnum.LOOP_COUNT,

View File

@ -446,6 +446,7 @@
}); });
defineExpose({ defineExpose({
refresh, refresh,
setActiveFolder,
initModuleCount, initModuleCount,
}); });
</script> </script>

View File

@ -1,35 +1,27 @@
<template> <template>
<div :class="['p-[16px]', props.class]"> <div :class="['p-[16px]', props.class]">
<div class="mb-[16px] flex items-center justify-between"> <MsAdvanceFilter
<div class="flex items-center"></div> ref="msAdvanceFilterRef"
<div class="items-right flex gap-[8px]"> v-model:keyword="keyword"
<a-input-search :view-type="ViewTypeEnum.API_SCENARIO"
v-model:model-value="keyword" :filter-config-list="filterConfigList"
:placeholder="t('api_scenario.table.searchPlaceholder')" :search-placeholder="t('api_scenario.table.searchPlaceholder')"
allow-clear @keyword-search="loadScenarioList(true)"
class="mr-[8px] w-[240px]" @adv-search="handleAdvSearch"
@clear="loadScenarioList(true)" @refresh="loadScenarioList(true)"
@search="loadScenarioList(true)" />
@press-enter="loadScenarioList"
/>
<a-button type="outline" class="arco-btn-outline--secondary !p-[8px]" @click="loadScenarioList(true)">
<template #icon>
<icon-refresh class="text-[var(--color-text-4)]" />
</template>
</a-button>
</div>
</div>
<ms-base-table <ms-base-table
class="mt-[16px]"
v-bind="propsRes" v-bind="propsRes"
:action-config="batchActions" :action-config="batchActions"
:first-column-width="44" :first-column-width="44"
no-disable no-disable
filter-icon-align-left filter-icon-align-left
:not-show-table-filter="isAdvancedSearchMode"
v-on="propsEvent" v-on="propsEvent"
@selected-change="handleTableSelect" @selected-change="handleTableSelect"
@batch-action="handleTableBatch" @batch-action="handleTableBatch"
@drag-change="changeHandler" @drag-change="changeHandler"
@module-change="loadScenarioList(false)"
> >
<template #num="{ record }"> <template #num="{ record }">
<div> <div>
@ -481,6 +473,8 @@
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
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 MsCronSelect from '@/components/pure/ms-cron-select/index.vue'; import MsCronSelect from '@/components/pure/ms-cron-select/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -524,15 +518,10 @@
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { Environment } from '@/models/apiTest/management'; import { Environment } from '@/models/apiTest/management';
import { import { ApiScenarioScheduleConfig, ApiScenarioTableItem, ApiScenarioUpdateDTO } from '@/models/apiTest/scenario';
ApiScenarioBatchParam, import { DragSortParams, ModuleTreeNode } from '@/models/common';
ApiScenarioBatchParams,
ApiScenarioScheduleConfig,
ApiScenarioTableItem,
ApiScenarioUpdateDTO,
} from '@/models/apiTest/scenario';
import { DragSortParams } from '@/models/common';
import { ResourcePoolItem } from '@/models/setting/resourcePool'; import { ResourcePoolItem } from '@/models/setting/resourcePool';
import { FilterType, ViewTypeEnum } from '@/enums/advancedFilterEnum';
import { ApiScenarioStatus } from '@/enums/apiEnum'; import { ApiScenarioStatus } from '@/enums/apiEnum';
import { CacheTabTypeEnum } from '@/enums/cacheTabEnum'; import { CacheTabTypeEnum } from '@/enums/cacheTabEnum';
import { TagUpdateTypeEnum } from '@/enums/commonEnum'; import { TagUpdateTypeEnum } from '@/enums/commonEnum';
@ -541,20 +530,22 @@
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { casePriorityOptions } from '@/views/api-test/components/config'; import { casePriorityOptions } from '@/views/api-test/components/config';
import { scenarioStatusOptions } from '@/views/api-test/scenario/components/config';
const props = defineProps<{ const props = defineProps<{
class?: string; class?: string;
activeModule: string; activeModule: string;
offspringIds: string[]; offspringIds: string[];
moduleTree: ModuleTreeNode[]; //
readOnly?: boolean; // readOnly?: boolean; //
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'openScenario', record: ApiScenarioTableItem, action?: 'copy' | 'execute'): void; (e: 'openScenario', record: ApiScenarioTableItem, action?: 'copy' | 'execute'): void;
(e: 'refreshModuleTree', params: any): void; (e: 'refreshModuleTree', params: any): void;
(e: 'createScenario'): void; (e: 'createScenario'): void;
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
}>(); }>();
const memberOptions = ref<{ label: string; value: string }[]>([]);
const appStore = useAppStore(); const appStore = useAppStore();
const cacheStore = useCacheStore(); const cacheStore = useCacheStore();
@ -807,31 +798,31 @@
width: operationWidth(215, 200), width: operationWidth(215, 200),
}, },
]; ];
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable( const { propsRes, propsEvent, viewId, advanceFilter, setAdvanceFilter, loadList, setLoadListParams, resetSelector } =
getScenarioPage, useTable(
{ getScenarioPage,
columns: props.readOnly ? columns : [], {
scroll: { x: '100%' }, columns: props.readOnly ? columns : [],
tableKey: TableKeyEnum.API_SCENARIO, scroll: { x: '100%' },
showSetting: !props.readOnly, tableKey: TableKeyEnum.API_SCENARIO,
selectable: hasAnyPermission([ showSetting: !props.readOnly,
'PROJECT_API_SCENARIO:READ+UPDATE', selectable: hasAnyPermission([
'PROJECT_API_SCENARIO:READ+EXECUTE', 'PROJECT_API_SCENARIO:READ+UPDATE',
'PROJECT_API_SCENARIO:READ+DELETE', 'PROJECT_API_SCENARIO:READ+EXECUTE',
]), 'PROJECT_API_SCENARIO:READ+DELETE',
showSelectAll: !props.readOnly, ]),
draggable: hasAnyPermission(['PROJECT_API_SCENARIO:READ+UPDATE']) ? { type: 'handle', width: 32 } : undefined, showSelectAll: !props.readOnly,
heightUsed: 282, draggable: hasAnyPermission(['PROJECT_API_SCENARIO:READ+UPDATE']) ? { type: 'handle', width: 32 } : undefined,
showSubdirectory: true, heightUsed: 282,
paginationSize: 'mini', paginationSize: 'mini',
}, },
(item) => ({ (item) => ({
...item, ...item,
requestPassRate: item.requestPassRate ? `${item.requestPassRate}%` : '-', requestPassRate: item.requestPassRate ? `${item.requestPassRate}%` : '-',
createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'), createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'), updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
}) })
); );
const batchActions = { const batchActions = {
baseAction: [ baseAction: [
{ {
@ -907,29 +898,153 @@
const tableStore = useTableStore(); const tableStore = useTableStore();
async function loadScenarioList(refreshTreeCount?: boolean) { const msAdvanceFilterRef = ref<InstanceType<typeof MsAdvanceFilter>>();
const isAdvancedSearchMode = computed(() => msAdvanceFilterRef.value?.isAdvancedSearchMode);
async function getModuleIds() {
let moduleIds: string[] = []; let moduleIds: string[] = [];
if (props.activeModule && props.activeModule !== 'all') { if (props.activeModule !== 'all' && !isAdvancedSearchMode.value) {
moduleIds = [props.activeModule]; moduleIds = [props.activeModule];
const getAllChildren = await tableStore.getSubShow(TableKeyEnum.API_SCENARIO); const getAllChildren = await tableStore.getSubShow(TableKeyEnum.API_SCENARIO);
if (getAllChildren) { if (getAllChildren) {
moduleIds = [props.activeModule, ...props.offspringIds]; moduleIds = [props.activeModule, ...props.offspringIds];
} }
} }
memberOptions.value = await getProjectOptions(appStore.currentProjectId, keyword.value); return moduleIds;
memberOptions.value = memberOptions.value.map((e: any) => ({ label: e.name, value: e.id })); }
async function loadScenarioList(refreshTreeCount?: boolean) {
const moduleIds = await getModuleIds();
const params = { const params = {
keyword: keyword.value, keyword: keyword.value,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleIds, moduleIds,
viewId: viewId.value,
combineSearch: advanceFilter,
}; };
setLoadListParams(params); setLoadListParams(params);
await loadList(); await loadList();
if (refreshTreeCount) { if (refreshTreeCount && !isAdvancedSearchMode.value) {
emit('refreshModuleTree', params); emit('refreshModuleTree', params);
} }
} }
const filterConfigList = computed<FilterFormItem[]>(() => [
{
title: 'caseManagement.featureCase.tableColumnID',
dataIndex: 'num',
type: FilterType.INPUT,
},
{
title: 'apiScenario.table.columns.name',
dataIndex: 'name',
type: FilterType.INPUT,
},
{
title: 'common.belongModule',
dataIndex: 'moduleId',
type: FilterType.TREE_SELECT,
treeSelectData: props.moduleTree,
treeSelectProps: {
fieldNames: {
title: 'name',
key: 'id',
children: 'children',
},
multiple: true,
treeCheckable: true,
treeCheckStrictly: true,
},
},
{
title: 'apiScenario.table.columns.level',
dataIndex: 'priority',
type: FilterType.SELECT,
selectProps: {
multiple: true,
options: casePriorityOptions,
},
},
{
title: 'apiScenario.table.columns.status',
dataIndex: 'status',
type: FilterType.SELECT,
selectProps: {
multiple: true,
options: scenarioStatusOptions,
},
},
{
title: 'apiScenario.table.columns.runResult',
dataIndex: 'lastReportStatus',
type: FilterType.SELECT,
selectProps: {
multiple: true,
options: statusList.value,
},
},
{
title: 'common.tag',
dataIndex: 'tags',
type: FilterType.TAGS_INPUT,
numberProps: {
min: 0,
precision: 0,
},
},
{
title: 'apiScenario.table.columns.scenarioEnv',
dataIndex: 'environmentName',
type: FilterType.SELECT,
selectProps: {
labelKey: 'name',
valueKey: 'id',
multiple: true,
options: appStore.envList,
},
},
{
title: 'apiScenario.table.columns.steps',
dataIndex: 'stepTotal',
type: FilterType.NUMBER,
numberProps: {
min: 0,
precision: 0,
},
},
{
title: 'apiScenario.table.columns.passRate',
dataIndex: 'requestPassRate',
type: FilterType.NUMBER,
},
{
title: 'common.creator',
dataIndex: 'createUser',
type: FilterType.MEMBER,
},
{
title: 'common.createTime',
dataIndex: 'createTime',
type: FilterType.DATE_PICKER,
},
{
title: 'apiScenario.table.columns.updateUser',
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 loadScenarioList(false); //
};
async function handleStatusChange(record: ApiScenarioUpdateDTO) { async function handleStatusChange(record: ApiScenarioUpdateDTO) {
try { try {
await updateScenarioStatus(record.id, record.status); await updateScenarioStatus(record.id, record.status);
@ -952,19 +1067,26 @@
const tableSelected = ref<(string | number)[]>([]); const tableSelected = ref<(string | number)[]>([]);
const batchParams = ref<ApiScenarioBatchParam>(); const batchParams = ref<BatchActionQueryParams>();
const batchOptionParams = ref<any>();
const batchOptionParams = ref<ApiScenarioBatchParams>({ async function getBatchConditionParams() {
projectId: appStore.currentProjectId, const selectModules = await getModuleIds();
selectIds: [], return {
selectAll: false, condition: {
condition: {}, keyword: keyword.value,
}); filter: propsRes.value.filter,
viewId: viewId.value,
combineSearch: advanceFilter,
},
projectId: appStore.currentProjectId,
moduleIds: selectModules,
};
}
/** /**
* 删除接口 * 删除接口
*/ */
function deleteScenario(record?: ApiScenarioTableItem, isBatch?: boolean, params?: ApiScenarioBatchParam) { function deleteScenario(record?: ApiScenarioTableItem, isBatch?: boolean, params?: BatchActionQueryParams) {
let title = t('api_scenario.table.deleteScenarioTipTitle', { name: characterLimit(record?.name) }); let title = t('api_scenario.table.deleteScenarioTipTitle', { name: characterLimit(record?.name) });
let selectIds = [record?.id || '']; let selectIds = [record?.id || ''];
if (isBatch) { if (isBatch) {
@ -997,14 +1119,13 @@
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
if (isBatch) { if (isBatch) {
const batchConditionParams = await getBatchConditionParams();
await batchRecycleScenario({ await batchRecycleScenario({
selectIds, selectIds,
selectAll: !!params?.selectAll, selectAll: !!params?.selectAll,
excludeIds: params?.excludeIds || [], excludeIds: params?.excludeIds || [],
condition: { ...params?.condition, keyword: keyword.value, filter: propsRes.value.filter },
projectId: appStore.currentProjectId,
moduleIds: params?.moduleIds || [],
deleteAll: true, deleteAll: true,
...batchConditionParams,
}); });
} else { } else {
await recycleScenario(record?.id as string); await recycleScenario(record?.id as string);
@ -1206,17 +1327,13 @@
if (!errors) { if (!errors) {
try { try {
batchUpdateLoading.value = true; batchUpdateLoading.value = true;
const batchConditionParams = await getBatchConditionParams();
const batchEditParam = { const batchEditParam = {
selectIds: batchParams.value?.selectedIds || [], selectIds: batchParams.value?.selectedIds || [],
selectAll: !!batchParams.value?.selectAll, selectAll: !!batchParams.value?.selectAll,
excludeIds: batchParams.value?.excludeIds || [], excludeIds: batchParams.value?.excludeIds || [],
condition: { ...batchConditionParams,
keyword: keyword.value,
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: batchParams.value?.moduleIds || [],
type: batchForm.value?.attr, type: batchForm.value?.attr,
priority: '', priority: '',
status: '', status: '',
@ -1271,16 +1388,12 @@
} else if (isBatchCopy.value) { } else if (isBatchCopy.value) {
optionType = 'batchCopy'; optionType = 'batchCopy';
} }
const batchConditionParams = await getBatchConditionParams();
const res = await batchOptionScenario(optionType, { const res = await batchOptionScenario(optionType, {
selectIds: batchParams.value?.selectedIds || [], selectIds: batchParams.value?.selectedIds || [],
selectAll: !!batchParams.value?.selectAll, selectAll: !!batchParams.value?.selectAll,
excludeIds: batchParams.value?.excludeIds || [], excludeIds: batchParams.value?.excludeIds || [],
condition: { ...batchConditionParams,
keyword: keyword.value,
filter: propsRes.value.filter,
},
projectId: appStore.currentProjectId,
moduleIds: batchParams.value?.moduleIds || [],
targetModuleId: selectedBatchOptModuleKey.value, targetModuleId: selectedBatchOptModuleKey.value,
}); });
@ -1330,24 +1443,7 @@
async function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) { async function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
tableSelected.value = params?.selectedIds || []; tableSelected.value = params?.selectedIds || [];
// batchParams.value = { ...params };
let moduleIds: string[] = [];
const getAllChildren = await tableStore.getSubShow(TableKeyEnum.API_SCENARIO);
if (getAllChildren) {
moduleIds = [...props.offspringIds];
}
if (props.activeModule !== 'all') {
moduleIds.push(props.activeModule);
}
batchParams.value = { ...params, moduleIds: [] };
batchParams.value.moduleIds = moduleIds;
if (batchParams.value.condition) {
batchParams.value.condition.filter = { ...propsRes.value.filter };
batchParams.value.condition.keyword = keyword.value;
} else {
batchParams.value.condition = { filter: { ...propsRes.value.filter }, keyword: keyword.value };
}
switch (event.eventTag) { switch (event.eventTag) {
case 'delete': case 'delete':
deleteScenario(undefined, true, batchParams.value); deleteScenario(undefined, true, batchParams.value);
@ -1373,22 +1469,7 @@
moveModalVisible.value = true; moveModalVisible.value = true;
break; break;
case 'execute': case 'execute':
batchOptionParams.value = { batchOptionParams.value = await getBatchConditionParams();
projectId: appStore.currentProjectId,
selectIds: [],
selectAll: false,
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
},
};
//
batchOptionParams.value.selectAll = params.selectAll;
batchOptionParams.value.excludeIds = params.excludeIds;
batchOptionParams.value.selectIds = params.selectedIds?.length ? params.selectedIds : [];
batchOptionParams.value.moduleIds = moduleIds;
batchOptionParams.value.condition = batchParams.value?.condition || {};
showBatchExecute.value = true; showBatchExecute.value = true;
break; break;

View File

@ -1,6 +1,6 @@
<template> <template>
<MsCard no-content-padding simple> <MsCard no-content-padding simple>
<MsSplitBox :size="300" :max="0.5"> <MsSplitBox :not-show-first="isAdvancedSearchMode" :size="300" :max="0.5">
<template #first> <template #first>
<div class="flex h-full flex-col"> <div class="flex h-full flex-col">
<div class="flex-1 p-[16px]"> <div class="flex-1 p-[16px]">
@ -75,11 +75,13 @@
> >
<ScenarioTable <ScenarioTable
ref="apiTableRef" ref="apiTableRef"
:module-tree="moduleTree"
:active-module="activeModule" :active-module="activeModule"
:offspring-ids="offspringIds" :offspring-ids="offspringIds"
@refresh-module-tree="refreshTree" @refresh-module-tree="refreshTree"
@open-scenario="openScenarioTab" @open-scenario="openScenarioTab"
@create-scenario="() => newTab()" @create-scenario="() => newTab()"
@handle-adv-search="handleAdvSearch"
/> />
</MsCacheWrapper> </MsCacheWrapper>
</keep-alive> </keep-alive>
@ -501,6 +503,11 @@
} }
const scenarioModuleTreeRef = ref<InstanceType<typeof scenarioModuleTree>>(); const scenarioModuleTreeRef = ref<InstanceType<typeof scenarioModuleTree>>();
const isAdvancedSearchMode = ref(false);
function handleAdvSearch(isStartAdvance: boolean) {
isAdvancedSearchMode.value = isStartAdvance;
scenarioModuleTreeRef.value?.setActiveFolder('all');
}
function handleModuleInit(tree: any, _protocol: string, pathMap: Record<string, any>) { function handleModuleInit(tree: any, _protocol: string, pathMap: Record<string, any>) {
moduleTree.value = tree; moduleTree.value = tree;