feat(测试计划): 测试计划详情-接口用例-批量修改执行人&拖拽排序

This commit is contained in:
teukkk 2024-06-11 17:08:39 +08:00 committed by 刘瑞斌
parent 49345ac62a
commit b29118595d
13 changed files with 308 additions and 235 deletions

View File

@ -12,6 +12,7 @@ import {
BatchEditTestPlanUrl,
batchMovePlanUrl,
BatchRunCaseUrl,
BatchUpdateApiCaseExecutorUrl,
BatchUpdateCaseExecutorUrl,
ConfigScheduleUrl,
copyTestPlanUrl,
@ -44,6 +45,7 @@ import {
PlanDetailExecuteHistoryUrl,
planPassRateUrl,
RunFeatureCaseUrl,
SortApiCaseUrl,
SortFeatureCaseUrl,
TestPlanAndGroupCopyUrl,
TestPlanApiAssociatedPageUrl,
@ -67,6 +69,7 @@ import type {
BatchApiCaseParams,
BatchExecuteFeatureCaseParams,
BatchFeatureCaseParams,
BatchUpdateApiCaseExecutorParams,
BatchUpdateCaseExecutorParams,
CreateTask,
DisassociateCaseParams,
@ -84,6 +87,7 @@ import type {
PlanDetailFeatureCaseItem,
PlanDetailFeatureCaseListQueryParams,
RunFeatureCaseParams,
SortApiCaseParams,
SortFeatureCaseParams,
TestPlanBaseParams,
TestPlanDetail,
@ -273,6 +277,10 @@ export function getApiCaseModule(data: PlanDetailApiCaseTreeParams) {
export function getApiCaseModuleCount(data: PlanDetailApiCaseQueryParams) {
return MSR.post({ url: GetApiCaseModuleCountUrl, data });
}
// 计划详情-接口用例列表-拖拽排序
export const sortApiCase = (data: SortApiCaseParams) => {
return MSR.post({ url: SortApiCaseUrl, data });
};
// 计划详情-接口用例列表-取消关联用例
export function disassociateApiCase(data: DisassociateCaseParams) {
return MSR.post({ url: DisassociateApiCaseUrl, data });
@ -281,10 +289,18 @@ export function disassociateApiCase(data: DisassociateCaseParams) {
export function batchDisassociateApiCase(data: BatchApiCaseParams) {
return MSR.post({ url: BatchDisassociateApiCaseUrl, data });
}
// 计划详情-接口用例列表-批量更新执行人
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 });
}
// 计划详情-接口场景列表-批量更新执行人 TODO 联调
export function batchUpdateApiScenarioExecutor(data: BatchUpdateApiCaseExecutorParams) {
return MSR.post({ url: BatchUpdateApiCaseExecutorUrl, data });
}
// 计划详情-执行历史 TODO 联调
export function getPlanDetailExecuteHistory(data: PlanDetailFeatureCaseListQueryParams) {
return MSR.post<CommonList<PlanDetailExecuteHistoryItem>>({ url: PlanDetailExecuteHistoryUrl, data });

View File

@ -100,9 +100,13 @@ 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 GetApiCaseModuleCountUrl = '/test-plan/api/case/module/count';
// 计划详情-接口用例列表-拖拽排序
export const SortApiCaseUrl = '/test-plan/api/case/sort';
// 计划详情-接口用例列表-取消关联用例
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';
// 计划详情-接口用例列表-批量更新执行人
export const BatchUpdateApiCaseExecutorUrl = '/test-plan/api/case/batch/update/executor';

View File

@ -269,7 +269,7 @@ export interface PlanDetailApiCaseQueryParams extends TableQueryParams, TestPlan
export interface PlanDetailApiCaseTreeParams {
testPlanId: string;
treeType: 'MODULE' | 'COLLECTION';
treeType: 'MODULE' | 'COLLECTION'; // 视图类型模块是MODULE测试集是COLLECTION
}
export interface PlanDetailApiCaseItem {
@ -281,7 +281,7 @@ export interface PlanDetailApiCaseItem {
createUserName: string;
lastExecResult: LastExecuteResults;
lastExecTime: number;
lastExecResultReportId: string;
lastExecReportId: string; // 报告id
executeUser: string;
executeUserName: string;
priority: string;
@ -290,17 +290,24 @@ export interface PlanDetailApiCaseItem {
projectName: string;
environmentId: string;
environmentName: string;
testPlanCollectionId: string;
collectEnvironmentId: string;
testPlanCollectionId: string; // 测试集id
}
export interface BatchApiCaseParams extends BatchActionQueryParams {
testPlanId: string;
moduleIds?: string[];
collectionId?: string;
collectionId?: string; // 测试集id
protocols: string[];
}
export interface BatchUpdateApiCaseExecutorParams extends BatchApiCaseParams {
userId: string; // 执行人id
}
export interface SortApiCaseParams extends DragSortParams {
testCollectionId: string; // 测试集id
}
// TODO: 联调
export interface PlanDetailApiScenarioItem {
id: string;

View File

@ -8,7 +8,13 @@
>
<template #expandLeft>
<a-dropdown v-model:popup-visible="visible" :hide-on-select="false">
<MsButton type="icon" status="secondary" class="!mr-[4px] p-[4px]" @click="visible = !visible">
<MsButton
v-show="typeof isExpandAll !== 'undefined'"
type="icon"
status="secondary"
class="!mr-[4px] p-[4px]"
@click="visible = !visible"
>
<MsIcon :type="`${showExpandApi ? 'icon-icon_more_outlined' : 'icon-icon_protocol'}`" />
</MsButton>
<template #content>
@ -83,7 +89,8 @@
}>();
const isExpandAll = defineModel<boolean>('isExpandAll', {
required: true,
required: false,
default: undefined,
});
const isExpandApi = defineModel<boolean>('isExpandApi', {
required: false,

View File

@ -0,0 +1,124 @@
<template>
<a-modal
v-model:visible="visible"
title-align="start"
body-class="p-0"
:cancel-button-props="{ disabled: batchLoading }"
:ok-loading="batchLoading"
:ok-button-props="{ disabled: batchUpdateExecutorDisabled }"
:ok-text="t('common.update')"
@before-ok="handleBatchUpdateExecutor"
@close="resetBatchForm"
>
<template #title>
{{ t('testPlan.featureCase.batchChangeExecutor') }}
<div class="text-[var(--color-text-4)]">
{{
t('testPlan.testPlanIndex.selectedCount', {
count: props.count,
})
}}
</div>
</template>
<a-form ref="batchUpdateExecutorFormRef" :model="batchUpdateExecutorForm" layout="vertical">
<a-form-item
field="userId"
:label="t('testPlan.featureCase.executor')"
:rules="[{ required: true, message: t('testPlan.featureCase.requestExecutorRequired') }]"
asterisk-position="end"
class="mb-0"
>
<MsSelect
v-model:modelValue="batchUpdateExecutorForm.userId"
mode="static"
:placeholder="t('common.pleaseSelect')"
:loading="executorLoading"
:options="userOptions"
:search-keys="['label']"
allow-search
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import MsSelect from '@/components/business/ms-select';
import { GetTestPlanUsers } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { ReviewUserItem } from '@/models/caseManagement/caseReview';
import type { BatchUpdateApiCaseExecutorParams, BatchUpdateCaseExecutorParams } from '@/models/testPlan/testPlan';
const props = defineProps<{
count: number;
params?: BatchUpdateCaseExecutorParams | BatchUpdateApiCaseExecutorParams;
batchUpdateExecutor: (...args: any) => Promise<any>; //
}>();
const emit = defineEmits<{
(e: 'loadList'): void;
}>();
const visible = defineModel<boolean>('visible', {
required: true,
});
const { t } = useI18n();
const appStore = useAppStore();
const batchUpdateExecutorFormRef = ref<FormInstance>();
const batchLoading = ref(false);
const batchUpdateExecutorForm = ref<{ userId: string }>({ userId: '' });
const batchUpdateExecutorDisabled = computed(() => !batchUpdateExecutorForm.value.userId.length);
const userOptions = ref<SelectOptionData[]>([]);
const executorLoading = ref(false);
async function initUserOptions() {
try {
executorLoading.value = true;
const res = await GetTestPlanUsers(appStore.currentProjectId, '');
userOptions.value = res.map((e: ReviewUserItem) => ({ label: e.name, value: e.id }));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
executorLoading.value = false;
}
}
async function handleBatchUpdateExecutor(done: (closed: boolean) => void) {
batchUpdateExecutorFormRef.value?.validate(async (errors) => {
if (!errors) {
try {
batchLoading.value = true;
await props.batchUpdateExecutor({
...props.params,
...batchUpdateExecutorForm.value,
});
Message.success(t('common.updateSuccess'));
emit('loadList');
done(true);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
done(false);
} finally {
batchLoading.value = false;
done(false);
}
}
});
}
function resetBatchForm() {
batchUpdateExecutorForm.value = { userId: '' };
}
onBeforeMount(() => {
initUserOptions();
});
</script>

View File

@ -27,7 +27,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" />
@ -62,24 +62,17 @@
{{ t('common.cancelLink') }}
</MsButton>
</MsPopconfirm>
<a-divider
v-if="props.repeatCase"
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
direction="vertical"
:margin="8"
></a-divider>
<MsButton
v-if="props.repeatCase"
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
type="text"
class="!mr-0"
@click="handleCopyCase(record)"
>
{{ t('common.copy') }}
</MsButton>
</template>
</MsBaseTable>
<ReportDrawer v-model:visible="reportVisible" :report-id="reportId" />
<!-- 批量修改执行人 -->
<BatchUpdateExecutorModal
v-model:visible="batchUpdateExecutorModalVisible"
:count="batchParams.currentSelectCount || tableSelected.length"
:params="batchUpdateExecutorParams"
:batch-update-executor="batchUpdateApiCaseExecutor"
@load-list="resetSelectorAndCaseList"
/>
</div>
</template>
@ -101,14 +94,15 @@
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import BatchUpdateExecutorModal from '@/views/test-plan/testPlan/components/batchUpdateExecutorModal.vue';
import ReportDrawer from '@/views/test-plan/testPlan/detail/reportDrawer.vue';
import {
associationCaseToPlan,
batchDisassociateApiCase,
batchUpdateApiCaseExecutor,
disassociateApiCase,
getPlanDetailApiCaseList,
sortFeatureCase,
sortApiCase,
} from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
@ -124,11 +118,7 @@
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,9 +127,9 @@
offspringIds: string[];
planId: string;
moduleTree: ModuleTreeNode[];
repeatCase: boolean;
canEdit: boolean;
selectedProtocols: string[];
treeType: 'MODULE' | 'COLLECTION';
}>();
const emit = defineEmits<{
@ -188,7 +178,7 @@
},
{
title: 'case.caseLevel',
dataIndex: 'caseLevel',
dataIndex: 'priority',
slotName: 'caseLevel',
filterConfig: {
options: casePriorityOptions,
@ -242,7 +232,7 @@
},
{
title: 'report.detail.api.executeEnv',
dataIndex: 'executeEnv',
dataIndex: 'environmentName',
width: 150,
showInTable: false,
showDrag: true,
@ -288,21 +278,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,
@ -347,6 +327,7 @@
}
return moduleIds;
}
const collectionId = computed(() => (props.activeModule === 'all' ? '' : props.activeModule));
async function getTableParams(isBatch: boolean) {
const selectModules = await getModuleIds();
const commonParams = {
@ -354,7 +335,7 @@
projectId: appStore.currentProjectId,
moduleIds: selectModules,
protocols: props.selectedProtocols,
collectionId: props.activeModule,
collectionId: collectionId.value,
};
if (isBatch) {
return {
@ -372,6 +353,20 @@
};
}
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);
@ -400,7 +395,7 @@
const reportId = ref('');
function showReport(record: PlanDetailApiCaseItem) {
reportVisible.value = true;
reportId.value = record.lastExecResultReportId;
reportId.value = record.lastExecReportId;
}
const tableSelected = ref<(string | number)[]>([]); //
@ -421,11 +416,15 @@
loadList();
}
function resetSelectorAndCaseList() {
resetSelector();
loadList();
}
//
async function handleDragChange(params: DragSortParams) {
try {
// TODO
await sortFeatureCase({ ...params, testPlanId: props.planId });
await sortApiCase({ ...params, testCollectionId: collectionId.value });
Message.success(t('caseManagement.featureCase.sortSuccess'));
loadCaseList();
} catch (error) {
@ -434,23 +433,6 @@
}
}
//
async function handleCopyCase(record: PlanDetailApiCaseItem) {
try {
// TODO
await associationCaseToPlan({
functionalSelectIds: [record.id],
testPlanId: props.planId,
});
Message.success(t('ms.case.associate.associateSuccess'));
resetCaseList();
emit('refresh');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
const disassociateLoading = ref(false);
async function handleDisassociateCase(record: PlanDetailApiCaseItem, done?: () => void) {
@ -504,10 +486,15 @@
});
}
//
const batchUpdateExecutorModalVisible = ref(false);
const batchUpdateExecutorParams = ref();
//
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
async function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
tableSelected.value = params?.selectedIds || [];
batchParams.value = { ...params, selectIds: params?.selectedIds };
const tableParams = await getTableParams(true);
switch (event.eventTag) {
case 'execute':
break;
@ -515,6 +502,13 @@
handleBatchDisassociateCase();
break;
case 'changeExecutor':
batchUpdateExecutorParams.value = {
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...tableParams,
};
batchUpdateExecutorModalVisible.value = true;
break;
case 'move':
break;

View File

@ -88,7 +88,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;

View File

@ -15,6 +15,7 @@
<CaseTable
ref="caseTableRef"
:plan-id="planId"
:tree-type="props.treeType"
:modules-count="modulesCount"
:module-name="moduleName"
:repeat-case="props.repeatCase"
@ -97,8 +98,10 @@
}
function getCaseTableList() {
initModules();
caseTableRef.value?.loadCaseList();
nextTick(() => {
initModules();
caseTableRef.value?.loadCaseList();
});
}
defineExpose({

View File

@ -65,24 +65,17 @@
{{ t('common.cancelLink') }}
</MsButton>
</MsPopconfirm>
<a-divider
v-if="props.repeatCase"
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
direction="vertical"
:margin="8"
></a-divider>
<MsButton
v-if="props.repeatCase"
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
type="text"
class="!mr-0"
@click="handleCopyCase(record)"
>
{{ t('common.copy') }}
</MsButton>
</template>
</MsBaseTable>
<ReportDrawer v-model:visible="reportVisible" :report-id="reportId" />
<!-- 批量修改执行人 -->
<BatchUpdateExecutorModal
v-model:visible="batchUpdateExecutorModalVisible"
:count="batchParams.currentSelectCount || tableSelected.length"
:params="batchUpdateExecutorParams"
:batch-update-executor="batchUpdateApiScenarioExecutor"
@load-list="resetSelectorAndCaseList"
/>
</div>
</template>
@ -104,11 +97,12 @@
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import BatchUpdateExecutorModal from '@/views/test-plan/testPlan/components/batchUpdateExecutorModal.vue';
import ReportDrawer from '@/views/test-plan/testPlan/detail/reportDrawer.vue';
import {
associationCaseToPlan,
batchDisassociateCase,
batchUpdateApiScenarioExecutor,
disassociateCase,
getPlanDetailApiScenarioList,
sortFeatureCase,
@ -142,7 +136,6 @@
offspringIds: string[];
planId: string;
moduleTree: ModuleTreeNode[];
repeatCase: boolean;
canEdit: boolean;
}>();
@ -421,6 +414,11 @@
loadList();
}
function resetSelectorAndCaseList() {
resetSelector();
loadList();
}
//
async function handleDragChange(params: DragSortParams) {
try {
@ -434,23 +432,6 @@
}
}
//
async function handleCopyCase(record: PlanDetailApiScenarioItem) {
try {
// TODO
await associationCaseToPlan({
functionalSelectIds: [record.caseId],
testPlanId: props.planId,
});
Message.success(t('ms.case.associate.associateSuccess'));
resetCaseList();
emit('refresh');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
const disassociateLoading = ref(false);
async function handleDisassociateCase(record: PlanDetailApiScenarioItem, done?: () => void) {
@ -506,10 +487,15 @@
});
}
//
const batchUpdateExecutorModalVisible = ref(false);
const batchUpdateExecutorParams = ref();
//
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
async function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
tableSelected.value = params?.selectedIds || [];
batchParams.value = { ...params, selectIds: params?.selectedIds };
const tableParams = await getTableParams(true);
switch (event.eventTag) {
case 'execute':
break;
@ -517,6 +503,13 @@
handleBatchDisassociateCase();
break;
case 'changeExecutor':
batchUpdateExecutorParams.value = {
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...tableParams,
};
batchUpdateExecutorModalVisible.value = true;
break;
case 'move':
break;

View File

@ -78,21 +78,6 @@
{{ t('common.cancelLink') }}
</MsButton>
</MsPopconfirm>
<a-divider
v-if="props.repeatCase"
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
direction="vertical"
:margin="8"
></a-divider>
<MsButton
v-if="props.repeatCase"
v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']"
type="text"
class="!mr-0"
@click="handleCopyCase(record)"
>
{{ t('common.copy') }}
</MsButton>
</template>
</MsBaseTable>
<!-- 批量执行 -->
@ -120,54 +105,20 @@
<ExecuteForm v-model:form="batchExecuteForm" />
</a-modal>
<!-- 批量修改执行人 -->
<a-modal
<BatchUpdateExecutorModal
v-model:visible="batchUpdateExecutorModalVisible"
title-align="start"
body-class="p-0"
:cancel-button-props="{ disabled: batchLoading }"
:ok-loading="batchLoading"
:ok-button-props="{ disabled: batchUpdateExecutorDisabled }"
:ok-text="t('common.update')"
@before-ok="handleBatchUpdateExecutor"
@close="resetBatchForm"
>
<template #title>
{{ t('testPlan.featureCase.batchChangeExecutor') }}
<div class="text-[var(--color-text-4)]">
{{
t('testPlan.testPlanIndex.selectedCount', {
count: batchParams.currentSelectCount || tableSelected.length,
})
}}
</div>
</template>
<a-form ref="batchUpdateExecutorFormRef" :model="batchUpdateExecutorForm" layout="vertical">
<a-form-item
field="userId"
:label="t('testPlan.featureCase.executor')"
:rules="[{ required: true, message: t('testPlan.featureCase.requestExecutorRequired') }]"
asterisk-position="end"
class="mb-0"
>
<MsSelect
v-model:modelValue="batchUpdateExecutorForm.userId"
mode="static"
:placeholder="t('common.pleaseSelect')"
:loading="executorLoading"
:options="userOptions"
:search-keys="['label']"
allow-search
/>
</a-form-item>
</a-form>
</a-modal>
:count="batchParams.currentSelectCount || tableSelected.length"
:params="batchUpdateExecutorParams"
:batch-update-executor="batchUpdateCaseExecutor"
@load-list="resetSelectorAndCaseList"
/>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import { Message } from '@arco-design/web-vue';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import MsButton from '@/components/pure/ms-button/index.vue';
@ -182,18 +133,16 @@
import useTable from '@/components/pure/ms-table/useTable';
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import MsSelect from '@/components/business/ms-select';
import BugCountPopover from './bugCountPopover.vue';
import BatchUpdateExecutorModal from '@/views/test-plan/testPlan/components/batchUpdateExecutorModal.vue';
import ExecuteForm from '@/views/test-plan/testPlan/detail/featureCase/components/executeForm.vue';
import {
associationCaseToPlan,
batchDisassociateCase,
batchExecuteCase,
batchUpdateCaseExecutor,
disassociateCase,
getPlanDetailFeatureCaseList,
GetTestPlanUsers,
runFeatureCase,
sortFeatureCase,
} from '@/api/modules/test-plan/testPlan';
@ -205,7 +154,6 @@
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { ReviewUserItem } from '@/models/caseManagement/caseReview';
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type {
ExecuteFeatureCaseFormParams,
@ -231,7 +179,6 @@
offspringIds: string[];
planId: string;
moduleTree: ModuleTreeNode[];
repeatCase: boolean;
canEdit: boolean;
}>();
@ -492,6 +439,11 @@
loadList();
}
function resetSelectorAndCaseList() {
resetSelector();
loadList();
}
//
async function handleDragChange(params: DragSortParams) {
try {
@ -504,22 +456,6 @@
}
}
//
async function handleCopyCase(record: PlanDetailFeatureCaseItem) {
try {
await associationCaseToPlan({
functionalSelectIds: [record.caseId],
testPlanId: props.planId,
});
Message.success(t('ms.case.associate.associateSuccess'));
resetCaseList();
emit('refresh');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
async function handleEditLastExecResult(record: PlanDetailFeatureCaseItem) {
try {
@ -619,61 +555,19 @@
}
}
//
const batchUpdateExecutorFormRef = ref<FormInstance>();
const batchUpdateExecutorModalVisible = ref(false);
const batchUpdateExecutorForm = ref<{ userId: string }>({ userId: '' });
const batchUpdateExecutorDisabled = computed(() => !batchUpdateExecutorForm.value.userId.length);
const userOptions = ref<SelectOptionData[]>([]);
const executorLoading = ref(false);
async function initUserOptions() {
try {
executorLoading.value = true;
const res = await GetTestPlanUsers(appStore.currentProjectId, '');
userOptions.value = res.map((e: ReviewUserItem) => ({ label: e.name, value: e.id }));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
executorLoading.value = false;
}
}
async function handleBatchUpdateExecutor() {
batchUpdateExecutorFormRef.value?.validate(async (errors) => {
if (!errors) {
try {
batchLoading.value = true;
const tableParams = await getTableParams(true);
await batchUpdateCaseExecutor({
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...tableParams,
...batchUpdateExecutorForm.value,
});
Message.success(t('common.updateSuccess'));
resetSelector();
loadList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
batchLoading.value = false;
}
}
});
}
function resetBatchForm() {
batchExecuteForm.value = { ...defaultExecuteForm };
batchUpdateExecutorForm.value = { userId: '' };
}
//
const batchUpdateExecutorModalVisible = ref(false);
const batchUpdateExecutorParams = ref();
//
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
async function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
tableSelected.value = params?.selectedIds || [];
batchParams.value = { ...params, selectIds: params?.selectedIds };
const tableParams = await getTableParams(true);
switch (event.eventTag) {
case 'execute':
batchExecuteModalVisible.value = true;
@ -682,6 +576,12 @@
handleBatchDisassociateCase();
break;
case 'changeExecutor':
batchUpdateExecutorParams.value = {
selectIds: tableSelected.value as string[],
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...tableParams,
};
batchUpdateExecutorModalVisible.value = true;
break;
default:
@ -707,7 +607,6 @@
onBeforeMount(() => {
loadCaseList();
initUserOptions();
});
defineExpose({

View File

@ -19,6 +19,16 @@
</a-tooltip>
</template>
<template #headerRight>
<a-switch
v-model="treeType"
size="small"
type="line"
checked-value="COLLECTION"
unchecked-value="MODULE"
class="mr-[4px]"
@change="loadActiveTabList"
/>
<span class="mr-[14px]">{{ t('testPlan.testPlanDetail.moduleView') }}</span>
<MsButton
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+ASSOCIATION']) && detail.status !== 'ARCHIVED'"
type="button"
@ -108,11 +118,10 @@
@refresh="initDetail"
/>
<BugManagement v-if="activeTab === 'defectList'" :can-edit="detail.status !== 'ARCHIVED'" @refresh="initDetail" />
<!-- TODO 切换模块视图 -->
<ApiCase
v-if="activeTab === 'apiCase'"
ref="apiCaseRef"
tree-type="MODULE"
:tree-type="treeType"
:repeat-case="detail.repeatCase"
:can-edit="detail.status !== 'ARCHIVED'"
@refresh="initDetail"
@ -198,6 +207,7 @@
const detail = ref<TestPlanDetail>({
...testPlanDefaultDetail,
});
const treeType = ref<'MODULE' | 'COLLECTION'>('MODULE');
const countDetail = ref<PassRateCountDetail>({ ...defaultDetailCount });
@ -419,8 +429,7 @@
const featureCaseRef = ref<InstanceType<typeof FeatureCase>>();
const apiCaseRef = ref<InstanceType<typeof ApiCase>>();
const apiScenarioRef = ref<InstanceType<typeof ApiScenario>>();
function handleSuccess() {
initDetail();
function loadActiveTabList() {
switch (activeTab.value) {
case 'featureCase':
featureCaseRef.value?.getCaseTableList();
@ -435,6 +444,10 @@
return '';
}
}
function handleSuccess() {
initDetail();
loadActiveTabList();
}
onBeforeMount(() => {
initDetail();

View File

@ -89,6 +89,7 @@ export default {
'testPlan.planForm.pickCases': 'Select cases',
'testPlan.testPlanDetail.executed': 'Executed',
'testPlan.testPlanDetail.generateReport': 'Generate report',
'testPlan.testPlanDetail.moduleView': 'Module View',
'testPlan.testPlanDetail.successfullyGenerated': 'Successfully generated',
'testPlan.bugManagement.bug': 'Defect list',
'testPlan.bugManagement.bugName': 'name',

View File

@ -84,6 +84,7 @@ export default {
'testPlan.planForm.pickCases': '选择用例',
'testPlan.testPlanDetail.executed': '已执行',
'testPlan.testPlanDetail.generateReport': '生成报告',
'testPlan.testPlanDetail.moduleView': '模块视图',
'testPlan.testPlanDetail.successfullyGenerated': '生成成功',
'testPlan.bugManagement.bug': '缺陷列表',
'testPlan.bugManagement.bugName': '名称',