feat(测试计划): 测试计划详情-接口用例页面联调和取消关联

This commit is contained in:
teukkk 2024-06-07 19:23:06 +08:00 committed by Craftsman
parent b3283e3d78
commit 5ad2775eb8
8 changed files with 129 additions and 47 deletions

View File

@ -7,6 +7,7 @@ import {
batchArchivedPlanUrl, batchArchivedPlanUrl,
batchCopyPlanUrl, batchCopyPlanUrl,
batchDeletePlanUrl, batchDeletePlanUrl,
BatchDisassociateApiCaseUrl,
BatchDisassociateCaseUrl, BatchDisassociateCaseUrl,
BatchEditTestPlanUrl, BatchEditTestPlanUrl,
batchMovePlanUrl, batchMovePlanUrl,
@ -17,15 +18,19 @@ import {
deletePlanUrl, deletePlanUrl,
DeleteScheduleTaskUrl, DeleteScheduleTaskUrl,
DeleteTestPlanModuleUrl, DeleteTestPlanModuleUrl,
DisassociateApiCaseUrl,
DisassociateCaseUrl, DisassociateCaseUrl,
dragPlanOnGroupUrl, dragPlanOnGroupUrl,
ExecuteHistoryUrl, ExecuteHistoryUrl,
ExecutePlanUrl, ExecutePlanUrl,
followPlanUrl, followPlanUrl,
GenerateReportUrl, GenerateReportUrl,
GetApiCaseModuleCountUrl,
GetApiCaseModuleUrl,
GetAssociatedBugUrl, GetAssociatedBugUrl,
GetFeatureCaseModuleCountUrl, GetFeatureCaseModuleCountUrl,
GetFeatureCaseModuleUrl, GetFeatureCaseModuleUrl,
GetPlanDetailApiCaseListUrl,
GetPlanDetailFeatureCaseListUrl, GetPlanDetailFeatureCaseListUrl,
getStatisticalCountUrl, getStatisticalCountUrl,
GetTestPlanCaseListUrl, GetTestPlanCaseListUrl,
@ -59,6 +64,7 @@ import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type { import type {
AddTestPlanParams, AddTestPlanParams,
AssociateCaseRequestType, AssociateCaseRequestType,
BatchApiCaseParams,
BatchExecuteFeatureCaseParams, BatchExecuteFeatureCaseParams,
BatchFeatureCaseParams, BatchFeatureCaseParams,
BatchUpdateCaseExecutorParams, BatchUpdateCaseExecutorParams,
@ -70,6 +76,8 @@ import type {
FollowPlanParams, FollowPlanParams,
PassRateCountDetail, PassRateCountDetail,
PlanDetailApiCaseItem, PlanDetailApiCaseItem,
PlanDetailApiCaseQueryParams,
PlanDetailApiCaseTreeParams,
PlanDetailApiScenarioItem, PlanDetailApiScenarioItem,
PlanDetailBugItem, PlanDetailBugItem,
PlanDetailExecuteHistoryItem, PlanDetailExecuteHistoryItem,
@ -253,9 +261,25 @@ export function testPlanCancelBug(id: string) {
export function executeHistory(data: ExecuteHistoryType) { export function executeHistory(data: ExecuteHistoryType) {
return MSR.post<ExecuteHistoryItem[]>({ url: ExecuteHistoryUrl, data }); return MSR.post<ExecuteHistoryItem[]>({ url: ExecuteHistoryUrl, data });
} }
// 计划详情-接口用例列表 TODO 联调 // 计划详情-接口用例列表
export function getPlanDetailApiCaseList(data: PlanDetailFeatureCaseListQueryParams) { export function getPlanDetailApiCaseList(data: PlanDetailApiCaseQueryParams) {
return MSR.post<CommonList<PlanDetailApiCaseItem>>({ url: GetPlanDetailFeatureCaseListUrl, data }); return MSR.post<CommonList<PlanDetailApiCaseItem>>({ url: GetPlanDetailApiCaseListUrl, data });
}
// 计划详情-接口用例模块树
export function getApiCaseModule(data: PlanDetailApiCaseTreeParams) {
return MSR.post<ModuleTreeNode[]>({ url: GetApiCaseModuleUrl, data });
}
// 计划详情-接口用例-获取模块数量
export function getApiCaseModuleCount(data: PlanDetailApiCaseQueryParams) {
return MSR.post({ url: GetApiCaseModuleCountUrl, data });
}
// 计划详情-接口用例列表-取消关联用例
export function disassociateApiCase(data: DisassociateCaseParams) {
return MSR.post({ url: DisassociateApiCaseUrl, data });
}
// 计划详情-接口用例列表-批量取消关联用例
export function batchDisassociateApiCase(data: BatchApiCaseParams) {
return MSR.post({ url: BatchDisassociateApiCaseUrl, data });
} }
// 计划详情-接口场景列表 TODO 联调 // 计划详情-接口场景列表 TODO 联调
export function getPlanDetailApiScenarioList(data: PlanDetailFeatureCaseListQueryParams) { export function getPlanDetailApiScenarioList(data: PlanDetailFeatureCaseListQueryParams) {

View File

@ -96,3 +96,13 @@ export const ConfigScheduleUrl = '/test-plan/schedule-config';
export const ExecutePlanUrl = '/test-plan-execute/start'; export const ExecutePlanUrl = '/test-plan-execute/start';
// 测试计划-删除定时任务 // 测试计划-删除定时任务
export const DeleteScheduleTaskUrl = 'test-plan/schedule-config-delete'; export const DeleteScheduleTaskUrl = 'test-plan/schedule-config-delete';
// 计划详情-接口用例列表
export const GetPlanDetailApiCaseListUrl = '/test-plan/api/case/page';
// 计划详情-接口用例模块树
export const GetApiCaseModuleUrl = '/test-plan/api/case/tree';
// 计划详情-接口用例列表-取消关联用例
export const DisassociateApiCaseUrl = '/test-plan/api/case/disassociate';
// 计划详情-接口用例列表-批量取消关联用例
export const BatchDisassociateApiCaseUrl = '/test-plan/api/case/batch/disassociate';
// 计划详情-接口用例-获取模块数量
export const GetApiCaseModuleCountUrl = '/test-plan/api/case/module/count';

View File

@ -257,24 +257,48 @@ export interface BatchMoveParams extends TableQueryParams {
targetId?: string | number; targetId?: string | number;
} }
// TODO: 联调 // 计划详情-接口用例
export interface PlanDetailApiCaseQueryParams extends TableQueryParams, TestPlanBaseParams {
apiDefinitionId?: string;
protocols: string[];
moduleIds?: string[];
versionId?: string;
refId?: string;
collectionId?: string;
}
export interface PlanDetailApiCaseTreeParams {
testPlanId: string;
treeType: 'MODULE' | 'COLLECTION';
}
export interface PlanDetailApiCaseItem { export interface PlanDetailApiCaseItem {
id: string; id: string;
num: string; num: number;
name: string; name: string;
moduleId: string; moduleId: string;
versionName: string;
createUser: string; createUser: string;
createUserName: string; createUserName: string;
lastExecResult: LastExecuteResults; lastExecResult: LastExecuteResults;
lastExecTime: number; lastExecTime: number;
lastExecResultReportId: string;
executeUser: string; executeUser: string;
executeUserName: string; executeUserName: string;
bugCount: number; priority: string;
customFields: customFieldsItem[]; // 自定义字段集合 path: string;
caseId: string; projectId: string;
projectName: string;
environmentId: string;
environmentName: string;
testPlanCollectionId: string;
collectEnvironmentId: string;
}
export interface BatchApiCaseParams extends BatchActionQueryParams {
testPlanId: string; testPlanId: string;
lastExecResultReportId: string; moduleIds?: string[];
collectionId?: string;
protocols: string[];
} }
// TODO: 联调 // TODO: 联调

View File

@ -21,7 +21,11 @@
<a-dsubmenu> <a-dsubmenu>
<template #default>{{ t('ms.paramsInput.protocol') }}</template> <template #default>{{ t('ms.paramsInput.protocol') }}</template>
<template #content> <template #content>
<a-checkbox :model-value="isCheckedAll" :indeterminate="indeterminate" @change="handleChangeAll" <a-checkbox
class="checkbox-all"
:model-value="isCheckedAll"
:indeterminate="indeterminate"
@change="handleChangeAll"
>{{ t('common.all') }} >{{ t('common.all') }}
</a-checkbox> </a-checkbox>
<a-checkbox-group direction="vertical" :model-value="selectedProtocols" @change="handleGroupChange"> <a-checkbox-group direction="vertical" :model-value="selectedProtocols" @change="handleGroupChange">
@ -34,7 +38,11 @@
</template> </template>
<!-- 没有 展开请求的开关 --> <!-- 没有 展开请求的开关 -->
<template v-else> <template v-else>
<a-checkbox :model-value="isCheckedAll" :indeterminate="indeterminate" @change="handleChangeAll" <a-checkbox
class="checkbox-all"
:model-value="isCheckedAll"
:indeterminate="indeterminate"
@change="handleChangeAll"
>{{ t('common.all') }} >{{ t('common.all') }}
</a-checkbox> </a-checkbox>
<a-checkbox-group direction="vertical" :model-value="selectedProtocols" @change="handleGroupChange"> <a-checkbox-group direction="vertical" :model-value="selectedProtocols" @change="handleGroupChange">
@ -141,7 +149,17 @@
.arco-dropdown { .arco-dropdown {
padding: 8px; padding: 8px;
.arco-dropdown-list .arco-dropdown-option { .arco-dropdown-list .arco-dropdown-option {
margin: 0; width: 107px;
}
.checkbox-all {
border-bottom: 1px solid var(--color-text-n8);
}
.arco-checkbox {
padding: 6px 12px;
line-height: 24px;
}
.arco-switch {
margin-left: 8px;
} }
} }
.api-expend { .api-expend {

View File

@ -84,7 +84,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeMount, 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';
@ -105,8 +105,8 @@
import { import {
associationCaseToPlan, associationCaseToPlan,
batchDisassociateCase, batchDisassociateApiCase,
disassociateCase, disassociateApiCase,
getPlanDetailApiCaseList, getPlanDetailApiCaseList,
sortFeatureCase, sortFeatureCase,
} from '@/api/modules/test-plan/testPlan'; } from '@/api/modules/test-plan/testPlan';
@ -118,7 +118,7 @@
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common'; import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type { PlanDetailApiCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan'; import type { PlanDetailApiCaseItem, PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum'; import { LastExecuteResults } from '@/enums/caseEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -139,10 +139,11 @@
moduleTree: ModuleTreeNode[]; moduleTree: ModuleTreeNode[];
repeatCase: boolean; repeatCase: boolean;
canEdit: boolean; canEdit: boolean;
selectedProtocols: string[];
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'getModuleCount', params: PlanDetailFeatureCaseListQueryParams): void; (e: 'getModuleCount', params: PlanDetailApiCaseQueryParams): void;
(e: 'refresh'): void; (e: 'refresh'): void;
(e: 'initModules'): void; (e: 'initModules'): void;
}>(); }>();
@ -352,6 +353,8 @@
testPlanId: props.planId, testPlanId: props.planId,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleIds: selectModules, moduleIds: selectModules,
protocols: props.selectedProtocols,
collectionId: props.activeModule,
}; };
if (isBatch) { if (isBatch) {
return { return {
@ -379,12 +382,9 @@
pageSize: propsRes.value.msPagination?.pageSize, pageSize: propsRes.value.msPagination?.pageSize,
}); });
} }
watch( watch([() => props.activeModule, () => props.selectedProtocols], () => {
() => props.activeModule, loadCaseList();
() => { });
loadCaseList();
}
);
async function getModuleCount() { async function getModuleCount() {
const tableParams = await getTableParams(false); const tableParams = await getTableParams(false);
@ -400,7 +400,7 @@
const reportId = ref(''); const reportId = ref('');
function showReport(record: PlanDetailApiCaseItem) { function showReport(record: PlanDetailApiCaseItem) {
reportVisible.value = true; reportVisible.value = true;
reportId.value = record.lastExecResultReportId; // TODO reportId.value = record.lastExecResultReportId;
} }
const tableSelected = ref<(string | number)[]>([]); // const tableSelected = ref<(string | number)[]>([]); //
@ -439,7 +439,7 @@
try { try {
// TODO // TODO
await associationCaseToPlan({ await associationCaseToPlan({
functionalSelectIds: [record.caseId], functionalSelectIds: [record.id],
testPlanId: props.planId, testPlanId: props.planId,
}); });
Message.success(t('ms.case.associate.associateSuccess')); Message.success(t('ms.case.associate.associateSuccess'));
@ -456,8 +456,7 @@
async function handleDisassociateCase(record: PlanDetailApiCaseItem, done?: () => void) { async function handleDisassociateCase(record: PlanDetailApiCaseItem, done?: () => void) {
try { try {
disassociateLoading.value = true; disassociateLoading.value = true;
// TODO await disassociateApiCase({ testPlanId: props.planId, id: record.id });
await disassociateCase({ testPlanId: props.planId, id: record.id });
if (done) { if (done) {
done(); done();
} }
@ -486,8 +485,7 @@
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
const tableParams = await getTableParams(true); const tableParams = await getTableParams(true);
// TODO await batchDisassociateApiCase({
await batchDisassociateCase({
selectIds: tableSelected.value as string[], selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll, selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [], excludeIds: batchParams.value?.excludeIds || [],
@ -525,10 +523,6 @@
} }
} }
onBeforeMount(() => {
loadCaseList();
});
defineExpose({ defineExpose({
resetSelector, resetSelector,
loadCaseList, loadCaseList,

View File

@ -15,6 +15,7 @@
:all-count="allCount" :all-count="allCount"
:show-expand-api="false" :show-expand-api="false"
@set-active-folder="setActiveFolder" @set-active-folder="setActiveFolder"
@selected-protocols-change="selectedProtocolsChange"
/> />
<a-divider class="my-[8px]" /> <a-divider class="my-[8px]" />
<a-spin class="min-h-[200px] w-full" :loading="loading"> <a-spin class="min-h-[200px] w-full" :loading="loading">
@ -48,7 +49,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeMount, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
@ -56,7 +57,7 @@
import type { MsTreeNodeData } from '@/components/business/ms-tree/types'; import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import TreeFolderAll from '@/views/api-test/components/treeFolderAll.vue'; import TreeFolderAll from '@/views/api-test/components/treeFolderAll.vue';
import { getFeatureCaseModule } from '@/api/modules/test-plan/testPlan'; import { getApiCaseModule } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { mapTree } from '@/utils'; import { mapTree } from '@/utils';
@ -65,10 +66,12 @@
const props = defineProps<{ const props = defineProps<{
modulesCount?: Record<string, number>; // modulesCount?: Record<string, number>; //
selectedKeys: string[]; // key selectedKeys: string[]; // key
treeType: 'MODULE' | 'COLLECTION';
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'folderNodeSelect', ids: string[], _offspringIds: string[], nodeName?: string): void; (e: 'folderNodeSelect', ids: string[], _offspringIds: string[], nodeName?: string): void;
(e: 'init', params: ModuleTreeNode[]): void; (e: 'init', params: ModuleTreeNode[]): void;
(e: 'changeProtocol', selectedProtocols: string[]): void;
}>(); }>();
const route = useRoute(); const route = useRoute();
@ -102,8 +105,7 @@
async function initModules() { async function initModules() {
try { try {
loading.value = true; loading.value = true;
// TODO const res = await getApiCaseModule({ testPlanId: route.query.id as string, treeType: props.treeType });
const res = await getFeatureCaseModule(route.query.id as string);
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => { folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
return { return {
...node, ...node,
@ -130,10 +132,6 @@
emit('folderNodeSelect', _selectedKeys as string[], offspringIds, node.name); emit('folderNodeSelect', _selectedKeys as string[], offspringIds, node.name);
} }
onBeforeMount(() => {
initModules();
});
// //
watch( watch(
() => props.modulesCount, () => props.modulesCount,
@ -148,6 +146,11 @@
} }
); );
function selectedProtocolsChange() {
emit('changeProtocol', selectedProtocols.value);
initModules();
}
defineExpose({ defineExpose({
initModules, initModules,
}); });

View File

@ -5,8 +5,10 @@
ref="caseTreeRef" ref="caseTreeRef"
:modules-count="modulesCount" :modules-count="modulesCount"
:selected-keys="selectedKeys" :selected-keys="selectedKeys"
:tree-type="props.treeType"
@folder-node-select="handleFolderNodeSelect" @folder-node-select="handleFolderNodeSelect"
@init="initModuleTree" @init="initModuleTree"
@change-protocol="handleProtocolChange"
/> />
</template> </template>
<template #second> <template #second>
@ -20,6 +22,7 @@
:offspring-ids="offspringIds" :offspring-ids="offspringIds"
:module-tree="moduleTree" :module-tree="moduleTree"
:can-edit="props.canEdit" :can-edit="props.canEdit"
:selected-protocols="selectedProtocols"
@get-module-count="getModuleCount" @get-module-count="getModuleCount"
@refresh="emit('refresh')" @refresh="emit('refresh')"
@init-modules="initModules" @init-modules="initModules"
@ -36,14 +39,15 @@
import CaseTable from './components/caseTable.vue'; import CaseTable from './components/caseTable.vue';
import CaseTree from './components/caseTree.vue'; import CaseTree from './components/caseTree.vue';
import { getFeatureCaseModuleCount } from '@/api/modules/test-plan/testPlan'; import { getApiCaseModuleCount } from '@/api/modules/test-plan/testPlan';
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan'; import type { PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan';
const props = defineProps<{ const props = defineProps<{
repeatCase: boolean; repeatCase: boolean;
canEdit: boolean; canEdit: boolean;
treeType: 'MODULE' | 'COLLECTION';
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -54,10 +58,13 @@
const planId = ref(route.query.id as string); const planId = ref(route.query.id as string);
const modulesCount = ref<Record<string, any>>({}); const modulesCount = ref<Record<string, any>>({});
async function getModuleCount(params: PlanDetailFeatureCaseListQueryParams) { const selectedProtocols = ref<string[]>([]);
function handleProtocolChange(val: string[]) {
selectedProtocols.value = val;
}
async function getModuleCount(params: PlanDetailApiCaseQueryParams) {
try { try {
// TODO modulesCount.value = await getApiCaseModuleCount(params);
modulesCount.value = await getFeatureCaseModuleCount(params);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);

View File

@ -108,9 +108,11 @@
@refresh="initDetail" @refresh="initDetail"
/> />
<BugManagement v-if="activeTab === 'defectList'" :can-edit="detail.status !== 'ARCHIVED'" @refresh="initDetail" /> <BugManagement v-if="activeTab === 'defectList'" :can-edit="detail.status !== 'ARCHIVED'" @refresh="initDetail" />
<!-- TODO 切换模块视图 -->
<ApiCase <ApiCase
v-if="activeTab === 'apiCase'" v-if="activeTab === 'apiCase'"
ref="apiCaseRef" ref="apiCaseRef"
tree-type="MODULE"
:repeat-case="detail.repeatCase" :repeat-case="detail.repeatCase"
:can-edit="detail.status !== 'ARCHIVED'" :can-edit="detail.status !== 'ARCHIVED'"
@refresh="initDetail" @refresh="initDetail"