feat(测试计划): 测试计划详情-接口场景部分联调&功能用例调整

This commit is contained in:
teukkk 2024-06-12 11:05:41 +08:00 committed by 刘瑞斌
parent 13ff8addf2
commit 8f3dc0b489
14 changed files with 172 additions and 81 deletions

View File

@ -8,11 +8,13 @@ import {
batchCopyPlanUrl,
batchDeletePlanUrl,
BatchDisassociateApiCaseUrl,
BatchDisassociateApiScenarioUrl,
BatchDisassociateCaseUrl,
BatchEditTestPlanUrl,
batchMovePlanUrl,
BatchRunCaseUrl,
BatchUpdateApiCaseExecutorUrl,
BatchUpdateApiScenarioExecutorUrl,
BatchUpdateCaseExecutorUrl,
ConfigScheduleUrl,
copyTestPlanUrl,
@ -20,6 +22,7 @@ import {
DeleteScheduleTaskUrl,
DeleteTestPlanModuleUrl,
DisassociateApiCaseUrl,
DisassociateApiScenarioUrl,
DisassociateCaseUrl,
dragPlanOnGroupUrl,
ExecuteHistoryUrl,
@ -28,10 +31,13 @@ import {
GenerateReportUrl,
GetApiCaseModuleCountUrl,
GetApiCaseModuleUrl,
GetApiScenarioModuleCountUrl,
GetApiScenarioModuleUrl,
GetAssociatedBugUrl,
GetFeatureCaseModuleCountUrl,
GetFeatureCaseModuleUrl,
GetPlanDetailApiCaseListUrl,
GetPlanDetailApiScenarioListUrl,
GetPlanDetailFeatureCaseListUrl,
GetPlanMinderUrl,
getStatisticalCountUrl,
@ -47,6 +53,7 @@ import {
planPassRateUrl,
RunFeatureCaseUrl,
SortApiCaseUrl,
SortApiScenarioUrl,
SortFeatureCaseUrl,
TestPlanAndGroupCopyUrl,
TestPlanApiAssociatedPageUrl,
@ -83,6 +90,7 @@ import type {
PlanDetailApiCaseQueryParams,
PlanDetailApiCaseTreeParams,
PlanDetailApiScenarioItem,
PlanDetailApiScenarioQueryParams,
PlanDetailBugItem,
PlanDetailExecuteHistoryItem,
PlanDetailFeatureCaseItem,
@ -216,8 +224,8 @@ export function getFeatureCaseModuleCount(data: PlanDetailFeatureCaseListQueryPa
return MSR.post({ url: GetFeatureCaseModuleCountUrl, data });
}
// 计划详情-功能用例模块树
export function getFeatureCaseModule(planId: string) {
return MSR.get<ModuleTreeNode[]>({ url: `${GetFeatureCaseModuleUrl}/${planId}` });
export function getFeatureCaseModule(data: PlanDetailApiCaseTreeParams) {
return MSR.post<ModuleTreeNode[]>({ url: GetFeatureCaseModuleUrl, data });
}
// 计划详情-功能用例列表-取消关联用例
export function disassociateCase(data: DisassociateCaseParams) {
@ -295,13 +303,33 @@ export function batchDisassociateApiCase(data: BatchApiCaseParams) {
export function batchUpdateApiCaseExecutor(data: BatchUpdateApiCaseExecutorParams) {
return MSR.post({ url: BatchUpdateApiCaseExecutorUrl, data });
}
// 计划详情-接口场景列表 TODO 联调
export function getPlanDetailApiScenarioList(data: PlanDetailFeatureCaseListQueryParams) {
return MSR.post<CommonList<PlanDetailApiScenarioItem>>({ url: GetPlanDetailFeatureCaseListUrl, data });
// 计划详情-接口场景列表
export function getPlanDetailApiScenarioList(data: PlanDetailApiScenarioQueryParams) {
return MSR.post<CommonList<PlanDetailApiScenarioItem>>({ url: GetPlanDetailApiScenarioListUrl, data });
}
// 计划详情-接口场景列表-批量更新执行人 TODO 联调
// 计划详情-接口场景模块树
export function getApiScenarioModule(data: PlanDetailApiCaseTreeParams) {
return MSR.post<ModuleTreeNode[]>({ url: GetApiScenarioModuleUrl, data });
}
// 计划详情-接口场景-获取模块数量
export function getApiScenarioModuleCount(data: PlanDetailApiScenarioQueryParams) {
return MSR.post({ url: GetApiScenarioModuleCountUrl, data });
}
// 计划详情-接口场景列表-拖拽排序
export const sortApiScenario = (data: SortApiCaseParams) => {
return MSR.post({ url: SortApiScenarioUrl, data });
};
// 计划详情-接口场景列表-取消关联用例
export function disassociateApiScenario(data: DisassociateCaseParams) {
return MSR.post({ url: DisassociateApiScenarioUrl, data });
}
// 计划详情-接口场景列表-批量取消关联用例
export function batchDisassociateApiScenario(data: BatchApiCaseParams) {
return MSR.post({ url: BatchDisassociateApiScenarioUrl, data });
}
// 计划详情-接口场景列表-批量更新执行人
export function batchUpdateApiScenarioExecutor(data: BatchUpdateApiCaseExecutorParams) {
return MSR.post({ url: BatchUpdateApiCaseExecutorUrl, data });
return MSR.post({ url: BatchUpdateApiScenarioExecutorUrl, data });
}
// 计划详情-执行历史 TODO 联调
export function getPlanDetailExecuteHistory(data: PlanDetailFeatureCaseListQueryParams) {

View File

@ -96,6 +96,7 @@ export const ConfigScheduleUrl = '/test-plan/schedule-config';
export const ExecutePlanUrl = '/test-plan-execute/start';
// 测试计划-删除定时任务
export const DeleteScheduleTaskUrl = 'test-plan/schedule-config-delete';
// 计划详情-接口用例列表
export const GetPlanDetailApiCaseListUrl = '/test-plan/api/case/page';
// 计划详情-接口用例模块树
@ -110,5 +111,21 @@ export const DisassociateApiCaseUrl = '/test-plan/api/case/disassociate';
export const BatchDisassociateApiCaseUrl = '/test-plan/api/case/batch/disassociate';
// 计划详情-接口用例列表-批量更新执行人
export const BatchUpdateApiCaseExecutorUrl = '/test-plan/api/case/batch/update/executor';
// 计划详情-接口场景列表
export const GetPlanDetailApiScenarioListUrl = '/test-plan/api/scenario/page';
// 计划详情-接口场景模块树
export const GetApiScenarioModuleUrl = '/test-plan/api/scenario/tree';
// 计划详情-接口场景-获取模块数量
export const GetApiScenarioModuleCountUrl = '/test-plan/api/scenario/module/count';
// 计划详情-接口场景列表-拖拽排序
export const SortApiScenarioUrl = '/test-plan/api/scenario/sort';
// 计划详情-接口场景列表-取消关联用例
export const DisassociateApiScenarioUrl = '/test-plan/api/scenario/disassociate';
// 计划详情-接口场景列表-批量取消关联用例
export const BatchDisassociateApiScenarioUrl = '/test-plan/api/scenario/batch/disassociate';
// 计划详情-接口场景列表-批量更新执行人
export const BatchUpdateApiScenarioExecutorUrl = '/test-plan/api/scenario/batch/update/executor';
// 测试规划脑图
export const GetPlanMinderUrl = '/test-plan/mind/data';

View File

@ -266,6 +266,7 @@ export interface PlanDetailApiCaseQueryParams extends TableQueryParams, TestPlan
versionId?: string;
refId?: string;
collectionId?: string;
treeType?: 'MODULE' | 'COLLECTION'; // 视图类型模块是MODULE测试集是COLLECTION
}
export interface PlanDetailApiCaseTreeParams {
@ -298,7 +299,7 @@ export interface BatchApiCaseParams extends BatchActionQueryParams {
testPlanId: string;
moduleIds?: string[];
collectionId?: string; // 测试集id
protocols: string[];
protocols?: string[]; // 接口用例传protocols 接口场景不传
}
export interface BatchUpdateApiCaseExecutorParams extends BatchApiCaseParams {
@ -309,24 +310,34 @@ export interface SortApiCaseParams extends DragSortParams {
testCollectionId: string; // 测试集id
}
// TODO: 联调
// 计划详情-接口场景
export interface PlanDetailApiScenarioQueryParams extends TableQueryParams, TestPlanBaseParams {
scenarioId?: string;
moduleIds?: string[];
versionId?: string;
refId?: string;
collectionId?: string;
treeType?: 'MODULE' | 'COLLECTION'; // 视图类型模块是MODULE测试集是COLLECTION
}
export interface PlanDetailApiScenarioItem {
id: string;
num: string;
name: string;
priority: string;
projectId: string;
projectName: string;
environmentId: string;
environmentName: string;
moduleId: string;
versionName: string;
createUser: string;
createUserName: string;
lastExecResult: LastExecuteResults;
lastExecTime: number;
executeUser: string;
executeUserName: string;
bugCount: number;
customFields: customFieldsItem[]; // 自定义字段集合
caseId: string;
testPlanId: string;
lastExecResultReportId: string;
lastExecReportId: string; // 报告id
testPlanCollectionId: string; // 测试集id
}
// 执行历史 TODO 联调

View File

@ -155,7 +155,7 @@
function showReport(record: PlanDetailApiScenarioItem) {
reportVisible.value = true;
apiReportId.value = record.lastExecResultReportId; // TODO
apiReportId.value = record.lastExecReportId;
}
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_REPORT_DETAIL_BUG, columns, 'drawer');

View File

@ -146,7 +146,7 @@
function showReport(record: PlanDetailApiScenarioItem) {
reportVisible.value = true;
apiReportId.value = record.lastExecResultReportId; // TODO
apiReportId.value = record.lastExecReportId;
}
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_REPORT_DETAIL_BUG, columns, 'drawer');

View File

@ -349,6 +349,7 @@
return {
keyword: keyword.value,
filter: propsRes.value.filter,
treeType: props.treeType,
...commonParams,
};
}

View File

@ -18,7 +18,6 @@
:tree-type="props.treeType"
:modules-count="modulesCount"
:module-name="moduleName"
:repeat-case="props.repeatCase"
:active-module="activeFolderId"
:offspring-ids="offspringIds"
:module-tree="moduleTree"
@ -46,7 +45,6 @@
import type { PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan';
const props = defineProps<{
repeatCase: boolean;
canEdit: boolean;
treeType: 'MODULE' | 'COLLECTION';
}>();

View File

@ -30,7 +30,7 @@
<CaseLevel :case-level="filterContent.value" />
</template>
<template #caseLevel="{ record }">
<CaseLevel :case-level="record.caseLevel" />
<CaseLevel :case-level="record.priority" />
</template>
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
<ExecuteResult :execute-result="filterContent.key" />
@ -101,11 +101,11 @@
import ReportDrawer from '@/views/test-plan/testPlan/detail/reportDrawer.vue';
import {
batchDisassociateCase,
batchDisassociateApiScenario,
batchUpdateApiScenarioExecutor,
disassociateCase,
disassociateApiScenario,
getPlanDetailApiScenarioList,
sortFeatureCase,
sortApiScenario,
} from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
@ -116,18 +116,14 @@
import { hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type { PlanDetailApiScenarioItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import type { PlanDetailApiScenarioItem, PlanDetailApiScenarioQueryParams } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { casePriorityOptions } from '@/views/api-test/components/config';
import {
executionResultMap,
getCaseLevels,
getModules,
} from '@/views/case-management/caseManagementFeature/components/utils';
import { executionResultMap, getModules } from '@/views/case-management/caseManagementFeature/components/utils';
const props = defineProps<{
modulesCount: Record<string, number>; //
@ -137,10 +133,11 @@
planId: string;
moduleTree: ModuleTreeNode[];
canEdit: boolean;
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
(e: 'getModuleCount', params: PlanDetailFeatureCaseListQueryParams): void;
(e: 'getModuleCount', params: PlanDetailApiScenarioQueryParams): void;
(e: 'refresh'): void;
(e: 'initModules'): void;
}>();
@ -187,7 +184,7 @@
},
{
title: 'case.caseLevel',
dataIndex: 'caseLevel',
dataIndex: 'priority',
slotName: 'caseLevel',
filterConfig: {
options: casePriorityOptions,
@ -234,7 +231,7 @@
},
{
title: 'report.detail.api.executeEnv',
dataIndex: 'executeEnv',
dataIndex: 'environmentName',
width: 150,
showDrag: true,
},
@ -280,21 +277,11 @@
return {
...record,
lastExecResult: record.lastExecResult ?? LastExecuteResults.PENDING,
caseLevel: getCaseLevels(record.customFields),
moduleId: getModules(record.moduleId, props.moduleTree),
};
}
);
watch(
() => props.canEdit,
(val) => {
tableProps.value.draggableCondition = hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && val;
},
{
immediate: true,
}
);
const tableRef = ref<InstanceType<typeof MsBaseTable>>();
watch(
() => hasOperationPermission.value,
@ -339,12 +326,14 @@
}
return moduleIds;
}
const collectionId = computed(() => (props.activeModule === 'all' ? '' : props.activeModule));
async function getTableParams(isBatch: boolean) {
const selectModules = await getModuleIds();
const commonParams = {
testPlanId: props.planId,
projectId: appStore.currentProjectId,
moduleIds: selectModules,
collectionId: collectionId.value,
};
if (isBatch) {
return {
@ -356,12 +345,27 @@
};
}
return {
treeType: props.treeType,
keyword: keyword.value,
filter: propsRes.value.filter,
...commonParams,
};
}
watch(
[() => props.canEdit, () => props.treeType, () => collectionId.value.length],
() => {
tableProps.value.draggableCondition =
hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) &&
props.canEdit &&
props.treeType === 'COLLECTION' &&
!!collectionId.value.length;
},
{
immediate: true,
}
);
async function loadCaseList() {
const tableParams = await getTableParams(false);
setLoadListParams(tableParams);
@ -393,7 +397,7 @@
const reportId = ref('');
function showReport(record: PlanDetailApiScenarioItem) {
reportVisible.value = true;
reportId.value = record.lastExecResultReportId; // TODO
reportId.value = record.lastExecReportId;
}
const tableSelected = ref<(string | number)[]>([]); //
@ -422,8 +426,7 @@
//
async function handleDragChange(params: DragSortParams) {
try {
// TODO
await sortFeatureCase({ ...params, testPlanId: props.planId });
await sortApiScenario({ ...params, testCollectionId: collectionId.value });
Message.success(t('caseManagement.featureCase.sortSuccess'));
loadCaseList();
} catch (error) {
@ -437,8 +440,7 @@
async function handleDisassociateCase(record: PlanDetailApiScenarioItem, done?: () => void) {
try {
disassociateLoading.value = true;
// TODO
await disassociateCase({ testPlanId: props.planId, id: record.id });
await disassociateApiScenario({ testPlanId: props.planId, id: record.id });
if (done) {
done();
}
@ -467,8 +469,7 @@
onBeforeOk: async () => {
try {
const tableParams = await getTableParams(true);
// TODO
await batchDisassociateCase({
await batchDisassociateApiScenario({
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],

View File

@ -54,7 +54,7 @@
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import { getFeatureCaseModule } from '@/api/modules/test-plan/testPlan';
import { getApiScenarioModule } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import { mapTree } from '@/utils';
@ -63,6 +63,7 @@
const props = defineProps<{
modulesCount?: Record<string, number>; //
selectedKeys: string[]; // key
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
(e: 'folderNodeSelect', ids: string[], _offspringIds: string[], nodeName?: string): void;
@ -83,7 +84,18 @@
const activeFolder = ref<string>('all');
const allCount = ref(0);
const isExpandAll = ref(false);
const isExpandAll = ref<boolean | undefined>(false);
watch(
() => props.treeType,
(val) => {
if (val === 'COLLECTION') {
isExpandAll.value = undefined;
} else {
isExpandAll.value = false;
}
}
);
function setActiveFolder(id: string) {
activeFolder.value = id;
@ -99,8 +111,7 @@
async function initModules() {
try {
loading.value = true;
// TODO
const res = await getFeatureCaseModule(route.query.id as string);
const res = await getApiScenarioModule({ testPlanId: route.query.id as string, treeType: props.treeType });
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
return {
...node,

View File

@ -5,6 +5,7 @@
ref="caseTreeRef"
:modules-count="modulesCount"
:selected-keys="selectedKeys"
:tree-type="props.treeType"
@folder-node-select="handleFolderNodeSelect"
@init="initModuleTree"
/>
@ -13,9 +14,9 @@
<CaseTable
ref="caseTableRef"
:plan-id="planId"
:tree-type="props.treeType"
:modules-count="modulesCount"
:module-name="moduleName"
:repeat-case="props.repeatCase"
:active-module="activeFolderId"
:offspring-ids="offspringIds"
:module-tree="moduleTree"
@ -36,14 +37,14 @@
import CaseTable from './components/scenarioTable.vue';
import CaseTree from './components/scenarioTree.vue';
import { getFeatureCaseModuleCount } from '@/api/modules/test-plan/testPlan';
import { getApiScenarioModuleCount } from '@/api/modules/test-plan/testPlan';
import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import type { PlanDetailApiScenarioQueryParams } from '@/models/testPlan/testPlan';
const props = defineProps<{
repeatCase: boolean;
canEdit: boolean;
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
@ -54,10 +55,9 @@
const planId = ref(route.query.id as string);
const modulesCount = ref<Record<string, any>>({});
async function getModuleCount(params: PlanDetailFeatureCaseListQueryParams) {
async function getModuleCount(params: PlanDetailApiScenarioQueryParams) {
try {
// TODO
modulesCount.value = await getFeatureCaseModuleCount(params);
modulesCount.value = await getApiScenarioModuleCount(params);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -90,8 +90,10 @@
}
function getCaseTableList() {
initModules();
caseTableRef.value?.loadCaseList();
nextTick(() => {
initModules();
caseTableRef.value?.loadCaseList();
});
}
defineExpose({

View File

@ -180,6 +180,7 @@
planId: string;
moduleTree: ModuleTreeNode[];
canEdit: boolean;
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
@ -321,16 +322,6 @@
}
);
watch(
() => props.canEdit,
(val) => {
tableProps.value.draggableCondition = hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) && val;
},
{
immediate: true,
}
);
const batchActions = {
baseAction: [
{
@ -372,12 +363,14 @@
}
return moduleIds;
}
const collectionId = computed(() => (props.activeModule === 'all' ? '' : props.activeModule));
async function getTableParams(isBatch: boolean) {
const selectModules = await getModuleIds();
const commonParams = {
testPlanId: props.planId,
projectId: appStore.currentProjectId,
moduleIds: selectModules,
collectionId: collectionId.value,
};
if (isBatch) {
return {
@ -391,10 +384,25 @@
return {
keyword: keyword.value,
filter: propsRes.value.filter,
treeType: props.treeType,
...commonParams,
};
}
watch(
[() => props.canEdit, () => props.treeType, () => collectionId.value.length],
() => {
tableProps.value.draggableCondition =
hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE']) &&
props.canEdit &&
props.treeType === 'COLLECTION' &&
!!collectionId.value.length;
},
{
immediate: true,
}
);
async function loadCaseList() {
const tableParams = await getTableParams(false);
setLoadListParams(tableParams);

View File

@ -63,6 +63,7 @@
const props = defineProps<{
modulesCount?: Record<string, number>; //
selectedKeys: string[]; // key
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
(e: 'folderNodeSelect', ids: string[], _offspringIds: string[], nodeName?: string): void;
@ -83,7 +84,18 @@
const activeFolder = ref<string>('all');
const allCount = ref(0);
const isExpandAll = ref(false);
const isExpandAll = ref<boolean | undefined>(false);
watch(
() => props.treeType,
(val) => {
if (val === 'COLLECTION') {
isExpandAll.value = undefined;
} else {
isExpandAll.value = false;
}
}
);
function setActiveFolder(id: string) {
activeFolder.value = id;
@ -102,7 +114,7 @@
async function initModules() {
try {
loading.value = true;
const res = await getFeatureCaseModule(route.query.id as string);
const res = await getFeatureCaseModule({ testPlanId: route.query.id as string, treeType: props.treeType });
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
return {
...node,

View File

@ -4,6 +4,7 @@
<div class="p-[16px]">
<CaseTree
ref="caseTreeRef"
:tree-type="props.treeType"
:modules-count="modulesCount"
:selected-keys="selectedKeys"
@folder-node-select="handleFolderNodeSelect"
@ -14,10 +15,10 @@
<template #second>
<CaseTable
ref="caseTableRef"
:tree-type="props.treeType"
:plan-id="planId"
:modules-count="modulesCount"
:module-name="moduleName"
:repeat-case="props.repeatCase"
:active-module="activeFolderId"
:offspring-ids="offspringIds"
:module-tree="moduleTree"
@ -44,8 +45,8 @@
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
const props = defineProps<{
repeatCase: boolean;
canEdit: boolean;
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
@ -91,8 +92,10 @@
}
function getCaseTableList() {
initModules();
caseTableRef.value?.loadCaseList();
nextTick(() => {
initModules();
caseTableRef.value?.loadCaseList();
});
}
defineExpose({

View File

@ -105,7 +105,7 @@
<FeatureCase
v-if="activeTab === 'featureCase'"
ref="featureCaseRef"
:repeat-case="detail.repeatCase"
:tree-type="treeType"
:can-edit="detail.status !== 'ARCHIVED'"
@refresh="initDetail"
/>
@ -114,14 +114,13 @@
v-if="activeTab === 'apiCase'"
ref="apiCaseRef"
:tree-type="treeType"
:repeat-case="detail.repeatCase"
:can-edit="detail.status !== 'ARCHIVED'"
@refresh="initDetail"
/>
<ApiScenario
v-if="activeTab === 'apiScenario'"
ref="apiScenarioRef"
:repeat-case="detail.repeatCase"
:tree-type="treeType"
:can-edit="detail.status !== 'ARCHIVED'"
@refresh="initDetail"
/>