feat(测试计划): 测试计划详情-功能用例列表-批量执行和批量修改执行人

This commit is contained in:
teukkk 2024-05-16 15:25:14 +08:00 committed by 刘瑞斌
parent 1d3331f5ff
commit 7a1b2f96c6
9 changed files with 198 additions and 28 deletions

View File

@ -9,6 +9,8 @@ import {
batchDeletePlanUrl, batchDeletePlanUrl,
BatchDisassociateCaseUrl, BatchDisassociateCaseUrl,
batchMovePlanUrl, batchMovePlanUrl,
BatchRunCaseUrl,
BatchUpdateCaseExecutorUrl,
copyTestPlanUrl, copyTestPlanUrl,
deletePlanUrl, deletePlanUrl,
DeleteTestPlanModuleUrl, DeleteTestPlanModuleUrl,
@ -36,7 +38,9 @@ import { ModuleTreeNode } from '@/models/common';
import type { import type {
AddTestPlanParams, AddTestPlanParams,
AssociateCaseRequestType, AssociateCaseRequestType,
BatchExecuteFeatureCaseParams,
BatchFeatureCaseParams, BatchFeatureCaseParams,
BatchUpdateCaseExecutorParams,
DisassociateCaseParams, DisassociateCaseParams,
FollowPlanParams, FollowPlanParams,
PassRateCountDetail, PassRateCountDetail,
@ -166,6 +170,14 @@ export function disassociateCase(data: DisassociateCaseParams) {
export function batchDisassociateCase(data: BatchFeatureCaseParams) { export function batchDisassociateCase(data: BatchFeatureCaseParams) {
return MSR.post({ url: BatchDisassociateCaseUrl, data }); return MSR.post({ url: BatchDisassociateCaseUrl, data });
} }
// 计划详情-功能用例列表-批量执行
export function batchExecuteCase(data: BatchExecuteFeatureCaseParams) {
return MSR.post({ url: BatchRunCaseUrl, data });
}
// 计划详情-功能用例列表-批量更新执行人
export function batchUpdateCaseExecutor(data: BatchUpdateCaseExecutorParams) {
return MSR.post({ url: BatchUpdateCaseExecutorUrl, data });
}
// 计划详情-功能用例-执行 // 计划详情-功能用例-执行
export function runFeatureCase(data: RunFeatureCaseParams) { export function runFeatureCase(data: RunFeatureCaseParams) {
return MSR.post({ url: RunFeatureCaseUrl, data }); return MSR.post({ url: RunFeatureCaseUrl, data });

View File

@ -54,3 +54,7 @@ export const DisassociateCaseUrl = '/test-plan/functional/case/disassociate';
export const BatchDisassociateCaseUrl = '/test-plan/functional/case/batch/disassociate'; export const BatchDisassociateCaseUrl = '/test-plan/functional/case/batch/disassociate';
// 计划详情-功能用例-执行 // 计划详情-功能用例-执行
export const RunFeatureCaseUrl = '/test-plan/functional/case/run'; export const RunFeatureCaseUrl = '/test-plan/functional/case/run';
// 计划详情-功能用例-批量执行
export const BatchRunCaseUrl = '/test-plan/functional/case/batch/run';
// 计划详情-功能用例-批量更新执行人
export const BatchUpdateCaseExecutorUrl = '/test-plan/functional/case/batch/update/executor';

View File

@ -162,6 +162,30 @@ export interface DisassociateCaseParams {
export interface BatchFeatureCaseParams extends BatchActionQueryParams { export interface BatchFeatureCaseParams extends BatchActionQueryParams {
testPlanId: string; testPlanId: string;
moduleIds?: string[]; moduleIds?: string[];
projectId: string;
}
export interface ExecuteFeatureCaseFormParams {
lastExecResult: LastExecuteResults;
content?: string;
commentIds?: string[];
planCommentFileIds?: string[];
}
export interface RunFeatureCaseParams extends ExecuteFeatureCaseFormParams {
projectId: string;
id: string;
testPlanId: string;
caseId: string;
notifier?: string;
}
export interface BatchExecuteFeatureCaseParams extends BatchFeatureCaseParams, ExecuteFeatureCaseFormParams {
notifier?: string;
}
export interface BatchUpdateCaseExecutorParams extends BatchFeatureCaseParams {
userId: string;
} }
export interface PassRateCountDetail { export interface PassRateCountDetail {
@ -180,19 +204,4 @@ export interface PassRateCountDetail {
apiScenarioCount: number; apiScenarioCount: number;
} }
export interface ExecuteFeatureCaseFormParams {
lastExecResult: LastExecuteResults;
content?: string;
commentIds?: string[];
planCommentFileIds?: string[];
}
export interface RunFeatureCaseParams extends ExecuteFeatureCaseFormParams {
projectId: string;
id: string;
testPlanId: string;
caseId: string;
notifier?: string;
}
export default {}; export default {};

View File

@ -47,7 +47,12 @@
<span v-else class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.lastExecResult" /></span> <span v-else class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.lastExecResult" /></span>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']" type="text" class="!mr-0"> <MsButton
v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']"
type="text"
class="!mr-0"
@click="toCaseDetail(record)"
>
{{ t('common.execute') }} {{ t('common.execute') }}
</MsButton> </MsButton>
<a-divider v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" direction="vertical" :margin="8"></a-divider> <a-divider v-permission="['PROJECT_TEST_PLAN:READ+ASSOCIATION']" direction="vertical" :margin="8"></a-divider>
@ -74,13 +79,79 @@
</MsButton> </MsButton>
</template> </template>
</MsBaseTable> </MsBaseTable>
<!-- 批量执行 -->
<a-modal
v-model:visible="batchExecuteModalVisible"
title-align="start"
body-class="p-0"
:width="800"
:cancel-button-props="{ disabled: batchLoading }"
:ok-loading="batchLoading"
:ok-text="t('caseManagement.caseReview.commitResult')"
@before-ok="handleBatchExecute"
@close="resetBatchForm"
>
<template #title>
{{ t('testPlan.testPlanIndex.batchExecution') }}
<div class="text-[var(--color-text-4)]">
{{
t('testPlan.testPlanIndex.selectedCount', {
count: batchParams.currentSelectCount || tableSelected.length,
})
}}
</div>
</template>
<ExecuteForm v-model:form="batchExecuteForm" />
</a-modal>
<!-- 批量修改执行人 -->
<a-modal
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="batchLoading"
:options="userOptions"
:search-keys="['label']"
allow-search
/>
</a-form-item>
</a-form>
</a-modal>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeMount, ref } from 'vue'; import { computed, onBeforeMount, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue'; import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue';
@ -89,12 +160,17 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue'; import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue'; import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import MsSelect from '@/components/business/ms-select';
import ExecuteForm from '@/views/test-plan/testPlan/detail/featureCase/components/executeForm.vue';
import { import {
batchDisassociateCase, batchDisassociateCase,
batchExecuteCase,
batchUpdateCaseExecutor,
disassociateCase, disassociateCase,
getPlanDetailFeatureCaseList, getPlanDetailFeatureCaseList,
} from '@/api/modules/test-plan/testPlan'; } from '@/api/modules/test-plan/testPlan';
import { defaultExecuteForm } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore'; import useTableStore from '@/hooks/useTableStore';
@ -102,7 +178,11 @@
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan'; import type {
ExecuteFeatureCaseFormParams,
PlanDetailFeatureCaseItem,
PlanDetailFeatureCaseListQueryParams,
} from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum'; import { LastExecuteResults } from '@/enums/caseEnum';
import { TestPlanRouteEnum } from '@/enums/routeEnum'; import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -124,6 +204,7 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'getModuleCount', params: PlanDetailFeatureCaseListQueryParams): void; (e: 'getModuleCount', params: PlanDetailFeatureCaseListQueryParams): void;
(e: 'executeDone'): void;
}>(); }>();
const { t } = useI18n(); const { t } = useI18n();
@ -270,14 +351,6 @@
eventTag: 'disassociate', eventTag: 'disassociate',
permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'], permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'],
}, },
{
isDivider: true,
},
{
label: 'common.delete',
eventTag: 'delete',
danger: true,
},
], ],
}; };
@ -362,16 +435,79 @@
}); });
} }
//
const batchLoading = ref(false);
const batchExecuteModalVisible = ref(false);
const batchExecuteForm = ref<ExecuteFeatureCaseFormParams>({ ...defaultExecuteForm });
async function handleBatchExecute() {
try {
batchLoading.value = true;
await batchExecuteCase({
...batchParams.value,
...tableParams.value,
...batchExecuteForm.value,
notifier: batchExecuteForm.value?.commentIds?.join(';'),
});
Message.success(t('common.updateSuccess'));
resetSelector();
loadList();
emit('executeDone');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
batchLoading.value = false;
}
}
//
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[]>([]); // TODO
async function handleBatchUpdateExecutor() {
batchUpdateExecutorFormRef.value?.validate(async (errors) => {
if (!errors) {
try {
batchLoading.value = true;
await batchUpdateCaseExecutor({
...batchParams.value,
...tableParams.value,
...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: '' };
}
// //
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) { function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
tableSelected.value = params?.selectedIds || []; tableSelected.value = params?.selectedIds || [];
batchParams.value = params; batchParams.value = params;
switch (event.eventTag) { switch (event.eventTag) {
case 'execute': case 'execute':
batchExecuteModalVisible.value = true;
break; break;
case 'disassociate': case 'disassociate':
handleBatchDisassociateCase(); handleBatchDisassociateCase();
break; break;
case 'changeExecutor':
batchUpdateExecutorModalVisible.value = true;
break;
default: default:
break; break;
} }

View File

@ -38,7 +38,7 @@
import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils'; import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils';
const form = defineModel<ExecuteFeatureCaseFormParams>('form', { const form = defineModel<ExecuteFeatureCaseFormParams>('form', {
default: () => ({ ...defaultExecuteForm }), required: true,
}); });
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();

View File

@ -18,6 +18,7 @@
:offspring-ids="offspringIds" :offspring-ids="offspringIds"
:module-tree="moduleTree" :module-tree="moduleTree"
@get-module-count="getModuleCount" @get-module-count="getModuleCount"
@execute-done="emit('executeDone')"
></CaseTable> ></CaseTable>
</template> </template>
</MsSplitBox> </MsSplitBox>
@ -36,6 +37,10 @@
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan'; import type { PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
const emit = defineEmits<{
(e: 'executeDone'): void;
}>();
const route = useRoute(); const route = useRoute();
const planId = ref(route.query.id as string); const planId = ref(route.query.id as string);

View File

@ -88,7 +88,7 @@
</MsCard> </MsCard>
<!-- special-height的174: 上面卡片高度158 + mt的16 --> <!-- special-height的174: 上面卡片高度158 + mt的16 -->
<MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding> <MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding>
<FeatureCase v-if="activeTab === 'featureCase'" /> <FeatureCase v-if="activeTab === 'featureCase'" @execute-done="getStatistics" />
<!-- TODO 先不上 --> <!-- TODO 先不上 -->
<!-- <BugManagement v-if="activeTab === 'defectList'" :plan-id="detail.id" /> --> <!-- <BugManagement v-if="activeTab === 'defectList'" :plan-id="detail.id" /> -->
</MsCard> </MsCard>

View File

@ -90,6 +90,8 @@ export default {
'testPlan.featureCase.bugCount': 'Bug count', 'testPlan.featureCase.bugCount': 'Bug count',
'testPlan.featureCase.executor': 'Executor', 'testPlan.featureCase.executor': 'Executor',
'testPlan.featureCase.changeExecutor': 'Change executor', 'testPlan.featureCase.changeExecutor': 'Change executor',
'testPlan.featureCase.batchChangeExecutor': 'Batch modification executor',
'testPlan.featureCase.requestExecutorRequired': 'Executor cannot be empty',
'testPlan.featureCase.sort': 'sort', 'testPlan.featureCase.sort': 'sort',
'testPlan.featureCase.executionHistory': 'Execution History', 'testPlan.featureCase.executionHistory': 'Execution History',
'testPlan.featureCase.noBugDataTooltip': 'No related defects, please', 'testPlan.featureCase.noBugDataTooltip': 'No related defects, please',

View File

@ -88,6 +88,8 @@ export default {
'testPlan.featureCase.bugCount': '缺陷数', 'testPlan.featureCase.bugCount': '缺陷数',
'testPlan.featureCase.executor': '执行人', 'testPlan.featureCase.executor': '执行人',
'testPlan.featureCase.changeExecutor': '修改执行人', 'testPlan.featureCase.changeExecutor': '修改执行人',
'testPlan.featureCase.batchChangeExecutor': '批量修改执行人',
'testPlan.featureCase.requestExecutorRequired': '执行人不能为空',
'testPlan.featureCase.sort': '排序', 'testPlan.featureCase.sort': '排序',
'testPlan.featureCase.executionHistory': '执行历史', 'testPlan.featureCase.executionHistory': '执行历史',
'testPlan.featureCase.noBugDataTooltip': '暂无可关联缺陷,请 ', 'testPlan.featureCase.noBugDataTooltip': '暂无可关联缺陷,请 ',