feat(测试计划): 测试计划新建缺陷抽屉页面调整&关联缺陷页面调整

This commit is contained in:
xinxin.wu 2024-08-26 18:06:09 +08:00 committed by Craftsman
parent bbe09529d9
commit bf1bc4a677
19 changed files with 427 additions and 197 deletions

View File

@ -56,6 +56,7 @@ import {
GetTestPlanModuleCountUrl,
GetTestPlanModuleUrl,
GetTestPlanUsersUrl,
GetUnAssociatedListUrl,
MoveTestPlanModuleUrl,
planDetailBugPageUrl,
PlanDetailExecuteHistoryUrl,
@ -438,3 +439,7 @@ export function testPlanAssociateModuleCount(data: TableQueryParams) {
export function getExecuteUserOption(projectId: string, keyword?: string) {
return MSR.get({ url: `${GetTestPlanExecutorOptionsUrl}/${projectId}`, params: { keyword } });
}
// 获取测试计划未关联抽屉缺陷列表
export function getTestPlanBugPage(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetUnAssociatedListUrl, data });
}

View File

@ -155,3 +155,5 @@ export const EditPlanMinderUrl = '/test-plan/mind/data/edit';
export const TestPlanAssociationUrl = '/test-plan/association/api/case/module/count';
// 获取执行人下拉选项
export const GetTestPlanExecutorOptionsUrl = '/test-plan-execute/user-option';
// 获取测试计划未关联缺陷列表
export const GetUnAssociatedListUrl = '/test-plan/functional/case/associate/bug/page';

View File

@ -16,7 +16,7 @@
</a-button>
<template #content>
<a-doption v-if="hasAnyPermission(props.linkBugPermission || []) && props.bugCount" value="linkBug">
<a-doption v-if="hasAnyPermission(props.linkBugPermission || []) && props.existedDefect" value="linkBug">
{{ t('caseManagement.featureCase.linkDefect') }}
</a-doption>
<a-doption v-if="hasAnyPermission(['PROJECT_BUG:READ+ADD'])" value="newBug">
@ -28,8 +28,6 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BugCountPopover from './bugCountPopover.vue';
import { useI18n } from '@/hooks/useI18n';
@ -42,6 +40,7 @@
const props = defineProps<{
resourceId: string; // id: id/id/id
bugCount: number; //
existedDefect: number; //
canEdit: boolean;
bugList?: CaseBugItem[];
linkBugPermission?: string[];

View File

@ -62,6 +62,7 @@
v-model:visible="showLinkBugDrawer"
:case-id="activeCase.id"
:drawer-loading="drawerLoading"
:load-api="AssociatedBugApiTypeEnum.FUNCTIONAL_BUG_LIST"
@save="saveHandler"
/>
</template>
@ -83,13 +84,14 @@
import { hasAnyPermission } from '@/utils/permission';
import { TableQueryParams } from '@/models/common';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { BugManagementRouteEnum } from '@/enums/routeEnum';
const AddDefectDrawer = defineAsyncComponent(
() => import('@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue')
);
const LinkDefectDrawer = defineAsyncComponent(
() => import('@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue')
() => import('@/views/case-management/components/linkDefectDrawer.vue')
);
const props = defineProps<{

View File

@ -121,6 +121,7 @@
v-model:visible="showLinkDefectDrawer"
:case-id="selectNode?.data?.caseId ?? ''"
:drawer-loading="linkDrawerLoading"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST"
:show-selector-all="false"
@save="associateSuccessHandler"
/>
@ -182,8 +183,8 @@
import BugList from './bugList.vue';
import AddStep from '@/views/case-management/caseManagementFeature/components/addStep.vue';
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
import LinkDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue';
import ReviewCommentList from '@/views/case-management/caseManagementFeature/components/tabContent/tabComment/reviewCommentList.vue';
import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue';
import ExecuteForm from '@/views/test-plan/testPlan/detail/featureCase/components/executeForm.vue';
import ExecuteSubmit from '@/views/test-plan/testPlan/detail/featureCase/detail/executeSubmit.vue';
@ -201,6 +202,7 @@
import type { TableQueryParams } from '@/models/common';
import { ModuleTreeNode } from '@/models/common';
import type { ExecuteFeatureCaseFormParams, ExecuteHistoryItem } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { LastExecuteResults } from '@/enums/caseEnum';
import { MinderEventName, MinderKeyEnum } from '@/enums/minderEnum';

View File

@ -0,0 +1,7 @@
export enum AssociatedBugApiTypeEnum {
FUNCTIONAL_BUG_LIST = 'FUNCTIONAL_BUG_LIST', // 功能用例关联缺陷
TEST_PLAN_BUG_LIST = 'TEST_PLAN_BUG_LIST', // 测试计划关联缺陷
BUG_TOTAL_LIST = 'BUG_TOTAL_LIST', // 批量关联总缺陷列表
}
export default {};

View File

@ -16,7 +16,9 @@
:placeholder="t('bugManagement.edit.defaultSystemTemplate')"
/>
</template>
<BugDetail ref="bugDetailRef" v-model:template-id="bugTemplateId" :bug-id="bugId" @save-params="saveParams" />
<div class="h-[calc(100vh-168px)] w-full">
<BugDetail ref="bugDetailRef" v-model:template-id="bugTemplateId" :bug-id="bugId" @save-params="saveParams" />
</div>
</MsCard>
</template>

View File

@ -1,7 +1,7 @@
<template>
<a-form ref="formRef" :model="form" layout="vertical">
<div class="flex flex-row">
<div class="left mt-[16px] w-[calc(100%-428px)] grow">
<a-form ref="formRef" class="h-full" :model="form" layout="vertical">
<div class="flex h-full">
<div :class="`${props.isDrawer ? 'w-[calc(100%-332px)]' : 'w-[calc(100%-428px)]'} left grow`">
<!-- 平台默认模板不展示缺陷名称, 描述 -->
<a-form-item
v-if="!isPlatformDefaultTemplate"
@ -132,7 +132,7 @@
</MsFileList>
</div>
<a-divider class="ml-[16px]" direction="vertical" />
<div class="right mt-[16px] w-[428px] grow pr-[24px]">
<div :class="`${props.isDrawer ? 'w-[332px]' : 'w-[428px]'} right grow`">
<div class="min-w-[250px] overflow-auto">
<a-skeleton v-if="isLoading" :loading="isLoading" :animation="true">
<a-space direction="vertical" class="w-full" size="large">
@ -235,8 +235,9 @@
import { convertToFileByBug } from './utils';
const props = defineProps<{
bugId?: string;
templateId: string;
templateId: string; // id
bugId?: string; // id
isDrawer?: boolean; //
}>();
const emit = defineEmits<{

View File

@ -1,207 +1,148 @@
<template>
<MsDrawer
v-model:visible="showDrawer"
v-model:visible="showBugDrawer"
:mask="true"
:title="t('caseManagement.featureCase.createDefect')"
:ok-text="t('common.confirm')"
:ok-loading="drawerLoading"
:width="800"
:width="850"
:mask-closable="true"
unmount-on-close
:show-continue="true"
no-content-padding
@continue="handleDrawerConfirm(true)"
@confirm="handleDrawerConfirm"
@cancel="handleDrawerCancel"
>
<a-form ref="formRef" :model="form" layout="vertical">
<a-form-item
field="title"
asterisk-position="end"
:label="t('bugManagement.bugName')"
:rules="[{ required: true, message: t('bugManagement.edit.nameIsRequired') }]"
:placeholder="t('bugManagement.edit.pleaseInputBugName')"
>
<a-input v-model="form.title" :max-length="255" />
</a-form-item>
<a-form-item
field="handleUserId"
:label="t('caseManagement.featureCase.updateUser')"
:rules="[{ required: true, message: t('bugManagement.edit.handleManIsRequired') }]"
asterisk-position="end"
class="w-[240px]"
>
<MsSelect
v-model:modelValue="form.handleUserId"
mode="static"
:placeholder="t('common.pleaseSelect')"
:options="handleUserOptions"
:search-keys="['label']"
<template #tbutton>
<div class="font-normal">
<a-select
v-model="bugTemplateId"
class="w-[240px]"
:options="templateOption"
allow-search
/>
</a-form-item>
<a-form-item :label="t('bugManagement.edit.content')">
<MsRichText
v-model:raw="form.description"
:upload-image="handleUploadImage"
:preview-url="`${EditorPreviewFileUrl}/${appStore.currentProjectId}`"
/>
</a-form-item>
</a-form>
:placeholder="t('bugManagement.edit.defaultSystemTemplate')"
>
<template #prefix>
<span class="text-[var(--color-text-brand)]">{{ t('system.orgTemplate.defectTemplates') }}</span>
</template>
</a-select>
</div>
</template>
<div class="h-[calc(100vh-122px)] w-full p-[16px]">
<BugDetail
ref="bugDetailRef"
v-model:template-id="bugTemplateId"
is-drawer
:bug-id="bugId"
@save-params="saveParams"
/>
</div>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FormInstance, Message, SelectOptionData, ValidatedError } from '@arco-design/web-vue';
import { Message } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import MsSelect from '@/components/business/ms-select/index';
import BugDetail from '@/views/bug-management/edit.vue';
import {
createOrUpdateBug,
editorUploadFile,
getCustomOptionHeader,
getTemplateDetailInfo,
getTemplateOption,
} from '@/api/modules/bug-management/index';
import { EditorPreviewFileUrl } from '@/api/requrls/bug-management';
import { createOrUpdateBug, getTemplateOption } from '@/api/modules/bug-management';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { TemplateOption } from '@/models/common';
import { BugEditFormObject } from '@/models/bug-management';
const { t } = useI18n();
const appStore = useAppStore();
interface TemplateOption {
label: string;
value: string;
}
const props = defineProps<{
visible: boolean;
caseId: string;
bugId?: string;
extraParams?: Record<string, any>;
}>();
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'success'): void;
}>();
const { t } = useI18n();
const templateOptions = ref<TemplateOption[]>([]);
const defaultTemplateId = ref<string>('');
// TODO
const initForm: any = {
title: '',
templateId: '',
projectId: appStore.currentProjectId,
description: '',
customFields: [],
};
const form = ref({ ...initForm });
const showDrawer = computed({
get() {
return props.visible;
},
set(value) {
emit('update:visible', value);
},
const showBugDrawer = defineModel<boolean>('visible', {
required: true,
});
const formRef = ref<FormInstance | null>(null);
const templateCustomFields = ref([]);
function handleDrawerCancel() {
formRef.value?.resetFields();
form.value = { ...initForm };
showDrawer.value = false;
}
const bugDetailRef = ref<InstanceType<typeof BugDetail>>();
const drawerLoading = ref(false);
const isEdit = computed(() => props.bugId);
const drawerLoading = ref<boolean>(false);
const bugTemplateId = ref<string>('');
function handleDrawerConfirm(isContinue: boolean) {
form.value.templateId = defaultTemplateId.value;
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
drawerLoading.value = true;
templateCustomFields.value.forEach((item: any) => {
if (item.key === 'handleUser') {
item.value = form.value.handleUserId;
}
});
delete form.value.handleUserId;
try {
await createOrUpdateBug({
request: { ...form.value, customFields: templateCustomFields.value, ...props.extraParams },
fileList: [],
});
emit('success');
Message.success(t('caseManagement.featureCase.quicklyCreateDefectSuccess'));
if (!isContinue) {
handleDrawerCancel();
}
form.value = { ...initForm };
} catch (error) {
console.log(error);
} finally {
drawerLoading.value = false;
}
}
});
}
const templateOption = ref<TemplateOption[]>([]);
const handleUserOptions = ref<SelectOptionData[]>([]);
async function getThreePartiesOptions() {
const res = await getCustomOptionHeader(appStore.currentProjectId);
handleUserOptions.value = res.handleUserOption.map((e) => {
return {
value: e.value,
label: e.text,
};
});
}
async function initBugTemplate() {
const getTemplateOptions = async () => {
try {
templateOptions.value = await getTemplateOption(appStore.currentProjectId);
form.value.templateId = templateOptions.value.find((item) => item.enableDefault)?.id as string;
defaultTemplateId.value = templateOptions.value.find((item) => item.enableDefault)?.id as string;
const result = await getTemplateDetailInfo({ id: form.value.templateId, projectId: appStore.currentProjectId });
templateCustomFields.value = result.customFields.map((item: any) => {
drawerLoading.value = true;
const res = await getTemplateOption(appStore.currentProjectId);
templateOption.value = res.map((item) => {
if (item.enableDefault && !isEdit.value) {
//
bugTemplateId.value = item.id;
}
return {
id: item.fieldId,
key: item.fieldKey,
name: item.fieldName,
type: item.type,
value: (Array.isArray(item.defaultValue) ? JSON.stringify(item.defaultValue) : item.defaultValue) || '',
label: item.name,
value: item.id,
};
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
drawerLoading.value = false;
}
};
function handleDrawerCancel() {
bugDetailRef.value?.resetForm();
showBugDrawer.value = false;
}
async function handleUploadImage(file: File) {
const { data } = await editorUploadFile({
fileList: [file],
});
return data;
}
async function saveParams(isContinue: boolean, params: { request: BugEditFormObject; fileList: File[] }) {
try {
drawerLoading.value = true;
const { request, fileList } = params;
await createOrUpdateBug({ request: { ...request, ...props.extraParams }, fileList });
watch(
() => showDrawer.value,
(val) => {
if (val) {
initBugTemplate();
getThreePartiesOptions();
Message.success(props.bugId ? t('common.updateSuccess') : t('common.createSuccess'));
if (isContinue) {
bugDetailRef.value?.resetForm();
} else {
handleDrawerCancel();
emit('success');
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
drawerLoading.value = false;
}
);
}
const handleDrawerConfirm = async (isContinue = false) => {
bugDetailRef.value?.saveHandler(isContinue);
};
const initDefaultFields = async () => {
await getTemplateOptions();
};
onBeforeMount(() => {
initDefaultFields();
});
</script>
<style scoped lang="less">
:deep(.halo-rich-text-editor .ProseMirror) {
min-height: 400px !important;
}
</style>
<style scoped></style>

View File

@ -106,15 +106,11 @@
</div>
</template>
</ms-base-table>
<AddDefectDrawer
v-model:visible="showDrawer"
:case-id="props.caseId"
:extra-params="{ caseId: props.caseId }"
@success="getFetch()"
/>
<AddDefectDrawer v-model:visible="showDrawer" :extra-params="{ caseId: props.caseId }" @success="getFetch()" />
<LinkDefectDrawer
v-model:visible="showLinkDrawer"
:case-id="props.caseId"
:load-api="AssociatedBugApiTypeEnum.FUNCTIONAL_BUG_LIST"
:drawer-loading="drawerLoading"
@save="saveHandler"
/>
@ -135,9 +131,9 @@
import useTable from '@/components/pure/ms-table/useTable';
import AddDefectDrawer from './addDefectDrawer.vue';
import BugList from './bugList.vue';
import LinkDefectDrawer from './linkDefectDrawer.vue';
import BugNamePopover from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/bugNamePopover.vue';
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue';
import { getBugList, getCustomOptionHeader } from '@/api/modules/bug-management';
import {
@ -148,11 +144,11 @@
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { BugListItem, BugOptionItem } from '@/models/bug-management';
import type { TableQueryParams } from '@/models/common';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { makeColumns } from '@/views/case-management/caseManagementFeature/components/utils';
@ -360,6 +356,7 @@
getFetch();
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
cancelLoading.value = false;
@ -386,6 +383,7 @@
getFetch();
showLinkDrawer.value = false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
drawerLoading.value = false;

View File

@ -4,7 +4,7 @@
:mask="true"
:title="t('caseManagement.featureCase.linkDefect')"
:ok-text="t('caseManagement.featureCase.associated')"
:ok-disabled="propsRes.selectedKeys.size === 0"
:ok-disabled="currentCaseTable.propsRes.value.selectedKeys.size === 0"
:width="1200"
:mask-closable="true"
unmount-on-close
@ -29,15 +29,15 @@
</div>
<ms-base-table
ref="tableRef"
v-bind="propsRes"
v-bind="currentCaseTable.propsRes.value"
:action-config="{
baseAction: [],
moreAction: [],
}"
v-on="propsEvent"
v-on="currentCaseTable.propsEvent.value"
>
<template #name="{ record }">
<BugNamePopover :name="record.name" :content="record.content" />
<BugNamePopover :name="record.name || record.title" :content="record.content || record.description || ''" />
</template>
</ms-base-table>
</MsDrawer>
@ -52,10 +52,13 @@
import useTable from '@/components/pure/ms-table/useTable';
import BugNamePopover from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/bugNamePopover.vue';
import { getBugList } from '@/api/modules/bug-management';
import { getDrawerDebugPage } from '@/api/modules/case-management/featureCase';
import { getTestPlanBugPage } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import debounce from 'lodash-es/debounce';
@ -63,13 +66,21 @@
const { t } = useI18n();
const appStore = useAppStore();
const getModuleTreeApiMap: Record<string, any> = {
[AssociatedBugApiTypeEnum.FUNCTIONAL_BUG_LIST]: getDrawerDebugPage, // -
[AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST]: getTestPlanBugPage, // -
[AssociatedBugApiTypeEnum.BUG_TOTAL_LIST]: getBugList, //
};
const currentProjectId = computed(() => appStore.currentProjectId);
const props = withDefaults(
defineProps<{
visible: boolean;
caseId: string;
loadApi: AssociatedBugApiTypeEnum; //
drawerLoading: boolean;
visible: boolean;
caseId?: string; // id
isBatch?: boolean;
showSelectorAll?: boolean;
}>(),
{
@ -140,8 +151,8 @@
},
];
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
getDrawerDebugPage,
const getTotalBugTable = useTable(
getModuleTreeApiMap[AssociatedBugApiTypeEnum.BUG_TOTAL_LIST],
{
scroll: { x: '100%' },
columns,
@ -163,6 +174,33 @@
}
);
const getSingleBugTable = useTable(
getModuleTreeApiMap[props.loadApi],
{
scroll: { x: '100%' },
columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT,
selectable: true,
showSelectorAll: props.showSelectorAll,
heightUsed: 340,
},
(record) => {
return {
...record,
tags: (record.tags || []).map((item: string, i: number) => {
return {
id: `${record.id}-${i}`,
name: item,
};
}),
};
}
);
const currentCaseTable = computed(() => {
return props.isBatch ? getTotalBugTable : getSingleBugTable;
});
const keyword = ref<string>('');
const showDrawer = computed({
@ -175,7 +213,7 @@
});
function handleDrawerConfirm() {
const { excludeKeys, selectedKeys, selectorStatus } = propsRes.value;
const { excludeKeys, selectedKeys, selectorStatus } = currentCaseTable.value.propsRes.value;
const params = {
excludeIds: [...excludeKeys],
selectIds: selectorStatus === 'all' ? [] : [...selectedKeys],
@ -192,12 +230,16 @@
}
function handleDrawerCancel() {
resetSelector();
currentCaseTable.value.resetSelector();
}
function getFetch() {
setLoadListParams({ keyword: keyword.value, projectId: currentProjectId.value, sourceId: props.caseId });
loadList();
currentCaseTable.value.setLoadListParams({
keyword: keyword.value,
projectId: currentProjectId.value,
sourceId: props.caseId,
});
currentCaseTable.value.loadList();
}
const searchList = debounce(() => {
@ -208,7 +250,7 @@
() => props.visible,
(val) => {
if (val) {
resetSelector();
currentCaseTable.value.resetSelector();
getFetch();
}
}

View File

@ -43,6 +43,10 @@
:bug-list="record.bugList"
:resource-id="record.id"
:bug-count="record.bugCount || 0"
:existed-defect="existedDefect"
@load-list="loadList"
@associated="associatedDefect(false, record.id)"
@create="newDefect(record.id)"
/>
</template>
<template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }">
@ -104,6 +108,16 @@
:batch-move="batchMoveApiCase"
@load-list="resetCaseList"
/>
<!-- TODO 等待联调 -->
<AddDefectDrawer v-model:visible="showCreateBugDrawer" :extra-params="{ caseId: associatedCaseId }" />
<!-- TODO 等待联调 -->
<LinkDefectDrawer
v-model:visible="showLinkBugDrawer"
:case-id="associatedCaseId"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST"
:is-batch="isBatchAssociate"
:drawer-loading="drawerLoading"
/>
</div>
</template>
@ -128,6 +142,8 @@
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import CaseAndScenarioReportDrawer from '@/views/api-test/components/caseAndScenarioReportDrawer.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue';
import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import {
@ -151,6 +167,7 @@
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type { PlanDetailApiCaseItem, PlanDetailApiCaseQueryParams } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { ReportEnum } from '@/enums/reportEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
@ -394,6 +411,16 @@
eventTag: 'disassociate',
permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'],
},
{
label: 'caseManagement.featureCase.linkDefect',
eventTag: 'linkDefect',
permission: ['PROJECT_BUG:READ'],
},
{
label: 'testPlan.featureCase.noBugDataNewBug',
eventTag: 'newBug',
permission: ['PROJECT_BUG:READ+ADD'],
},
],
};
});
@ -625,6 +652,28 @@
});
}
const showLinkBugDrawer = ref(false);
const associatedCaseId = ref<string>();
const existedDefect = inject<Ref<number>>('existedDefect', ref(0));
const isBatchAssociate = ref(false);
//
function associatedDefect(isBatch: boolean, caseId?: string) {
isBatchAssociate.value = isBatch;
associatedCaseId.value = caseId;
showLinkBugDrawer.value = true;
}
const drawerLoading = ref(false);
const showCreateBugDrawer = ref<boolean>(false);
//
function newDefect(caseId?: string) {
associatedCaseId.value = caseId;
showCreateBugDrawer.value = true;
}
//
const batchUpdateParams = ref();
const batchMoveModalVisible = ref(false);
@ -650,16 +699,17 @@
case 'move':
batchMoveModalVisible.value = true;
break;
case 'linkDefect':
associatedDefect(true);
break;
case 'newBug':
newDefect();
break;
default:
break;
}
}
const showLinkDrawer = ref(false);
const showCreateDrawer = ref(false);
const drawerLoading = ref(false);
//
function toDetail(record: PlanDetailApiCaseItem) {
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, {

View File

@ -55,6 +55,10 @@
:bug-list="record.bugList"
:resource-id="record.id"
:bug-count="record.bugCount || 0"
:existed-defect="existedDefect"
@load-list="loadList"
@associated="associatedDefect(false, record.id)"
@create="newDefect(record.id)"
/>
</template>
<template v-if="props.canEdit" #operation="{ record }">
@ -103,6 +107,17 @@
:batch-move="batchMoveApiScenario"
@load-list="resetCaseList"
/>
<!-- TODO 等待联调 -->
<AddDefectDrawer v-model:visible="showCreateBugDrawer" :extra-params="{ caseId: associatedCaseId }" />
<!-- TODO 等待联调 -->
<LinkDefectDrawer
v-model:visible="showLinkBugDrawer"
:case-id="associatedCaseId"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST"
:is-batch="isBatchAssociate"
:drawer-loading="drawerLoading"
/>
</div>
</template>
@ -126,6 +141,8 @@
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import CaseAndScenarioReportDrawer from '@/views/api-test/components/caseAndScenarioReportDrawer.vue';
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue';
import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import {
@ -149,6 +166,7 @@
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type { PlanDetailApiScenarioItem, PlanDetailApiScenarioQueryParams } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { ReportEnum } from '@/enums/reportEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
@ -376,6 +394,16 @@
eventTag: 'disassociate',
permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'],
},
{
label: 'caseManagement.featureCase.linkDefect',
eventTag: 'linkDefect',
permission: ['PROJECT_BUG:READ'],
},
{
label: 'testPlan.featureCase.noBugDataNewBug',
eventTag: 'newBug',
permission: ['PROJECT_BUG:READ+ADD'],
},
],
};
});
@ -613,6 +641,25 @@
const batchUpdateParams = ref();
const batchMoveModalVisible = ref(false);
const existedDefect = inject<Ref<number>>('existedDefect', ref(0));
const isBatchAssociate = ref(false);
const showLinkBugDrawer = ref<boolean>(false);
const associatedCaseId = ref<string>();
const drawerLoading = ref<boolean>(false);
//
function associatedDefect(isBatch: boolean, caseId?: string) {
isBatchAssociate.value = isBatch;
associatedCaseId.value = caseId;
showLinkBugDrawer.value = true;
}
const showCreateBugDrawer = ref<boolean>(false);
//
function newDefect(caseId?: string) {
associatedCaseId.value = caseId;
showCreateBugDrawer.value = true;
}
//
async function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
tableSelected.value = params?.selectedIds || [];
@ -634,6 +681,12 @@
case 'move':
batchMoveModalVisible.value = true;
break;
case 'linkDefect':
associatedDefect(true);
break;
case 'newBug':
newDefect();
break;
default:
break;
}

View File

@ -47,6 +47,12 @@
showTooltip: true,
width: 200,
},
{
title: 'testPlan.caseType',
dataIndex: 'type',
showTooltip: true,
width: 200,
},
];
const { propsRes, propsEvent } = useTable(undefined, {

View File

@ -66,7 +66,9 @@
@change="() => handleEditLastExecResult(record)"
>
<template #label>
<span class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.lastExecResult" /></span>
<span class="text-[var(--color-text-2)]">
<ExecuteResult :execute-result="record.lastExecResult" />
</span>
</template>
<a-option v-for="item in Object.values(executionResultMap)" :key="item.key" :value="item.key">
<ExecuteResult :execute-result="item.key" />
@ -77,11 +79,15 @@
</span>
</template>
<template #bugCount="{ record }">
<BugCountPopover
:bug-list="record.bugList"
:bug-count="record.bugCount"
<MsBugOperation
:can-edit="props.canEdit"
:bug-list="record.bugList"
:resource-id="record.id"
:bug-count="record.bugCount || 0"
:existed-defect="existedDefect"
@load-list="loadList"
@associated="associatedDefect(false, record.id)"
@create="newDefect(record.id)"
/>
</template>
<template v-if="props.canEdit" #operation="{ record }">
@ -167,6 +173,22 @@
:batch-move="batchMoveFeatureCase"
@load-list="resetCaseList"
/>
<!-- TODO 等待联调 -->
<AddDefectDrawer
v-model:visible="showCreateBugDrawer"
:extra-params="{ caseId: associatedCaseId }"
@success="refreshList"
/>
<!-- TODO 等待联调 -->
<LinkDefectDrawer
v-model:visible="showLinkBugDrawer"
:case-id="associatedCaseId"
:drawer-loading="drawerLoading"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST"
:is-batch="isBatchAssociate"
@save="saveHandler"
/>
</div>
</template>
@ -188,11 +210,13 @@
MsTableProps,
} from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import BugCountPopover from '@/components/business/ms-bug-operation/bugCountPopover.vue';
import MsBugOperation from '@/components/business/ms-bug-operation/index.vue';
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import { getMinderOperationParams } from '@/components/business/ms-minders/caseReviewMinder/utils';
import MsTestPlanFeatureCaseMinder from '@/components/business/ms-minders/testPlanFeatureCaseMinder/index.vue';
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue';
import BatchApiMoveModal from '@/views/test-plan/testPlan/components/batchApiMoveModal.vue';
import BatchUpdateExecutorModal from '@/views/test-plan/testPlan/components/batchUpdateExecutorModal.vue';
import ExecuteForm from '@/views/test-plan/testPlan/detail/featureCase/components/executeForm.vue';
@ -216,8 +240,9 @@
import { characterLimit } from '@/utils';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import { DragSortParams, ModuleTreeNode, TableQueryParams } from '@/models/common';
import type { ExecuteFeatureCaseFormParams, PlanDetailFeatureCaseItem } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { LastExecuteResults } from '@/enums/caseEnum';
import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
@ -419,6 +444,19 @@
};
}
);
const existedDefect = inject<Ref<number>>('existedDefect', ref(0));
function getLinkAction() {
return existedDefect.value
? [
{
label: 'caseManagement.featureCase.linkDefect',
eventTag: 'linkDefect',
permission: ['PROJECT_BUG:READ'],
},
]
: [];
}
const batchActions = computed(() => {
return {
@ -441,6 +479,12 @@
eventTag: 'disassociate',
permission: ['PROJECT_TEST_PLAN:READ+ASSOCIATION'],
},
...getLinkAction(),
{
label: 'testPlan.featureCase.noBugDataNewBug',
eventTag: 'newBug',
permission: ['PROJECT_BUG:READ+ADD'],
},
],
};
});
@ -748,6 +792,49 @@
batchExecuteForm.value = { ...defaultExecuteForm };
}
const showLinkBugDrawer = ref<boolean>(false);
const associatedCaseId = ref<string>();
const drawerLoading = ref<boolean>(false);
// TODO
function saveHandler(params: TableQueryParams) {
try {
drawerLoading.value = true;
Message.success(t('caseManagement.featureCase.associatedSuccess'));
resetCaseList();
initModules();
emit('refresh');
showLinkBugDrawer.value = false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
drawerLoading.value = false;
}
}
function refreshList() {
resetCaseList();
initModules();
emit('refresh');
}
const isBatchAssociate = ref(false);
//
function associatedDefect(isBatch: boolean, caseId?: string) {
isBatchAssociate.value = isBatch;
associatedCaseId.value = caseId;
showLinkBugDrawer.value = true;
}
const showCreateBugDrawer = ref<boolean>(false);
//
function newDefect(caseId?: string) {
associatedCaseId.value = caseId;
showCreateBugDrawer.value = true;
}
//
const batchUpdateExecutorModalVisible = ref(false);
const batchMoveModalVisible = ref(false);
@ -766,6 +853,12 @@
case 'move':
batchMoveModalVisible.value = true;
break;
case 'linkDefect':
associatedDefect(true);
break;
case 'newBug':
newDefect();
break;
default:
break;
}

View File

@ -223,6 +223,7 @@
:case-id="activeCaseId"
:drawer-loading="drawerLoading"
:show-selector-all="false"
:load-api="AssociatedBugApiTypeEnum.TEST_PLAN_BUG_LIST"
@save="associateSuccessHandler"
/>
<AddDefectDrawer
@ -255,9 +256,9 @@
import BugList from './bug/index.vue';
import ExecuteSubmit from './executeSubmit.vue';
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
import LinkDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue';
import CaseTabDetail from '@/views/case-management/caseManagementFeature/components/tabContent/tabDetail.vue';
import EditCaseDetailDrawer from '@/views/case-management/caseReview/components/editCaseDetailDrawer.vue';
import LinkDefectDrawer from '@/views/case-management/components/linkDefectDrawer.vue';
import ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
import { getBugList } from '@/api/modules/bug-management';
@ -276,6 +277,7 @@
import type { TableQueryParams } from '@/models/common';
import type { ExecuteHistoryItem, PlanDetailFeatureCaseItem, TestPlanDetail } from '@/models/testPlan/testPlan';
import { AssociatedBugApiTypeEnum } from '@/enums/associateBugEnum';
import { LastExecuteResults } from '@/enums/caseEnum';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';

View File

@ -180,6 +180,7 @@
import Plan from './plan/index.vue';
import CreateAndEditPlanDrawer from '@/views/test-plan/testPlan/createAndEditPlanDrawer.vue';
import { getBugList } from '@/api/modules/bug-management';
import {
archivedPlan,
followPlanRequest,
@ -235,11 +236,33 @@
console.log(error);
}
}
const createdBugCount = ref<number>(0);
async function initBugList() {
if (!hasAnyPermission(['PROJECT_BUG:READ'])) {
return;
}
const res = await getBugList({
current: 1,
pageSize: 10,
sort: {},
filter: {},
keyword: '',
combine: {},
searchMode: 'AND',
projectId: appStore.currentProjectId,
});
createdBugCount.value = res.total || 0;
}
provide('existedDefect', createdBugCount);
async function initDetail() {
try {
loading.value = true;
detail.value = await getTestPlanDetail(planId.value);
getStatistics();
initBugList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);

View File

@ -152,4 +152,5 @@ export default {
'testPlan.planStartToEndTimeTip': 'The test plan timed out',
'testPlan.planConfigReport': 'Configuration Report',
'testPlan.planAutomaticGeneration': 'Automatic generation',
'testPlan.caseType': 'Use Case types',
};

View File

@ -141,4 +141,5 @@ export default {
'testPlan.planStartToEndTimeTip': '测试计划已超时',
'testPlan.planConfigReport': '自定义报告',
'testPlan.planAutomaticGeneration': '自动生成',
'testPlan.caseType': '用例类型',
};