feat(测试计划): 测试计划联调执行历史&补充权限&用例详情缺陷新建和关联&调整bug细节问题
This commit is contained in:
parent
d96d4fc8ac
commit
a66ba037f5
|
@ -8,6 +8,7 @@ import {
|
|||
batchCopyPlanUrl,
|
||||
batchDeletePlanUrl,
|
||||
BatchDisassociateCaseUrl,
|
||||
BatchEditTestPlanUrl,
|
||||
batchMovePlanUrl,
|
||||
BatchRunCaseUrl,
|
||||
BatchUpdateCaseExecutorUrl,
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
DeleteTestPlanModuleUrl,
|
||||
DisassociateCaseUrl,
|
||||
EditCaseLastExecResultUrl,
|
||||
ExecuteHistoryUrl,
|
||||
followPlanUrl,
|
||||
GenerateReportUrl,
|
||||
GetAssociatedBugUrl,
|
||||
|
@ -52,6 +54,8 @@ import type {
|
|||
BatchUpdateCaseExecutorParams,
|
||||
DisassociateCaseParams,
|
||||
EditLastExecResultParams,
|
||||
ExecuteHistoryItem,
|
||||
ExecuteHistoryType,
|
||||
FollowPlanParams,
|
||||
PassRateCountDetail,
|
||||
PlanDetailBugItem,
|
||||
|
@ -85,6 +89,11 @@ export function moveTestPlanModuleTree(data: MoveModules) {
|
|||
return MSR.post({ url: MoveTestPlanModuleUrl, data });
|
||||
}
|
||||
|
||||
// 批量编辑测试计划
|
||||
export function batchEditTestPlan(data: TableQueryParams) {
|
||||
return MSR.post({ url: BatchEditTestPlanUrl, data });
|
||||
}
|
||||
|
||||
// 删除模块
|
||||
export function deletePlanModuleTree(id: string) {
|
||||
return MSR.get({ url: `${DeleteTestPlanModuleUrl}/${id}` });
|
||||
|
@ -226,3 +235,7 @@ export function associateBugToPlan(data: TableQueryParams) {
|
|||
export function testPlanCancelBug(id: string) {
|
||||
return MSR.get({ url: `${TestPlanCancelBugUrl}/${id}` });
|
||||
}
|
||||
// 测试计划-用例详情-执行历史
|
||||
export function executeHistory(data: ExecuteHistoryType) {
|
||||
return MSR.post<ExecuteHistoryItem[]>({ url: ExecuteHistoryUrl, data });
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ export const UpdateTestPlanUrl = '/test-plan/update';
|
|||
export const batchDeletePlanUrl = '/test-plan/batch-delete';
|
||||
// 删除测试计划
|
||||
export const deletePlanUrl = '/test-plan/delete';
|
||||
// 测试计划批量编辑
|
||||
export const BatchEditTestPlanUrl = '/test-plan/batch-edit';
|
||||
// 获取统计数量
|
||||
export const getStatisticalCountUrl = '/test-plan/getCount';
|
||||
// 归档
|
||||
|
@ -29,7 +31,7 @@ export const archivedPlanUrl = '/test-plan/archived';
|
|||
// 批量复制
|
||||
export const batchCopyPlanUrl = '/test-plan/batch-copy';
|
||||
// 批量移动
|
||||
export const batchMovePlanUrl = '/test-plan/batch/move';
|
||||
export const batchMovePlanUrl = '/test-plan/batch-move';
|
||||
// 批量归档
|
||||
export const batchArchivedPlanUrl = '/test-plan/batch-archived';
|
||||
// 计划详情缺陷管理列表
|
||||
|
@ -74,3 +76,5 @@ export const BatchRunCaseUrl = '/test-plan/functional/case/batch/run';
|
|||
export const GetTestPlanUsersUrl = '/test-plan/functional/case/user-option';
|
||||
// 计划详情-功能用例-批量更新执行人
|
||||
export const BatchUpdateCaseExecutorUrl = '/test-plan/functional/case/batch/update/executor';
|
||||
// 计划详情-功能用例-执行历史
|
||||
export const ExecuteHistoryUrl = '/test-plan/functional/case/exec/history';
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
}"
|
||||
:expand-all="isExpandAll"
|
||||
block-node
|
||||
title-tooltip-position="left"
|
||||
title-tooltip-position="top"
|
||||
@select="folderNodeSelect"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
|
@ -212,9 +212,11 @@
|
|||
moduleCountParams?: TableQueryParams; // 获取模块树数量额外的参数
|
||||
hideProjectSelect?: boolean; // 是否隐藏项目选择
|
||||
isHiddenCaseLevel?: boolean;
|
||||
selectorAll: boolean;
|
||||
}>(),
|
||||
{
|
||||
isHiddenCaseLevel: false,
|
||||
selectorAll: false,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -421,6 +423,7 @@
|
|||
selectable: true,
|
||||
showSelectAll: true,
|
||||
heightUsed: 310,
|
||||
showSelectorAll: !props.selectorAll,
|
||||
},
|
||||
(record) => {
|
||||
return {
|
||||
|
|
|
@ -185,6 +185,8 @@ export interface RunFeatureCaseParams extends ExecuteFeatureCaseFormParams {
|
|||
notifier?: string;
|
||||
}
|
||||
|
||||
export type ExecuteHistoryType = Pick<RunFeatureCaseParams, 'id' | 'testPlanId' | 'caseId'>;
|
||||
|
||||
export interface BatchExecuteFeatureCaseParams extends BatchFeatureCaseParams, ExecuteFeatureCaseFormParams {
|
||||
notifier?: string;
|
||||
}
|
||||
|
@ -213,4 +215,19 @@ export interface PassRateCountDetail {
|
|||
apiScenarioCount: number;
|
||||
}
|
||||
|
||||
// 执行历史
|
||||
export interface ExecuteHistoryItem {
|
||||
status: string;
|
||||
content: string;
|
||||
contentText: string;
|
||||
stepsExecResult: string;
|
||||
createUser: string;
|
||||
userName: string;
|
||||
userLogo: string;
|
||||
email: string;
|
||||
steps: string;
|
||||
createTime: string;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import type { BatchEditCaseType, CustomAttributes } from '@/models/caseManagement/featureCase';
|
||||
import type { CustomAttributes } from '@/models/caseManagement/featureCase';
|
||||
import { TableQueryParams } from '@/models/common';
|
||||
|
||||
import Message from '@arco-design/web-vue/es/message';
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
children: 'children',
|
||||
count: 'count',
|
||||
}"
|
||||
title-tooltip-position="left"
|
||||
title-tooltip-position="top"
|
||||
@select="caseNodeSelect"
|
||||
@more-action-select="handleCaseMoreSelect"
|
||||
@more-actions-close="moreActionsClose"
|
||||
|
|
|
@ -97,10 +97,10 @@
|
|||
});
|
||||
|
||||
const innerKeyword = useVModel(props, 'keyword', emit);
|
||||
function searchData() {
|
||||
function searchData(keyword?: string) {
|
||||
setLinkListParams({
|
||||
...props.loadParams,
|
||||
keyword: innerKeyword.value,
|
||||
keyword,
|
||||
projectId: appStore.currentProjectId,
|
||||
condition: {
|
||||
keyword: innerKeyword.value,
|
||||
|
@ -123,7 +123,7 @@
|
|||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
searchData();
|
||||
searchData(innerKeyword.value);
|
||||
});
|
||||
|
||||
watch(
|
||||
|
@ -139,7 +139,7 @@
|
|||
() => props.caseId,
|
||||
(val) => {
|
||||
if (val) {
|
||||
searchData();
|
||||
searchData(innerKeyword.value);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
import { getCaseLevels } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
|
|
@ -73,19 +73,19 @@
|
|||
/>
|
||||
<ms-base-table v-else v-bind="testPlanPropsRes" ref="planTableRef" v-on="testPlanTableEvent">
|
||||
<template #name="{ record }">
|
||||
<div class="one-line-text max-w-[300px]"> {{ record.name }}</div>
|
||||
<a-popover title="" position="right">
|
||||
<span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
|
||||
<template #content>
|
||||
<div class="max-w-[600px] text-[14px] text-[var(--color-text-1)]">
|
||||
{{ record.content }}
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<div class="flex flex-nowrap items-center">
|
||||
<div class="one-line-text">{{ characterLimit(record.name) }}</div>
|
||||
<a-popover title="" position="right" style="width: 480px">
|
||||
<div class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</div>
|
||||
<template #content>
|
||||
<div v-dompurify-html="record.content" class="markdown-body" style="margin-left: 48px"> </div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
<template #handleUserName="{ record }">
|
||||
<a-tooltip :content="record.handleUserName">
|
||||
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) }}</div>
|
||||
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) || '-' }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #testPlanName="{ record }">
|
||||
|
@ -259,7 +259,7 @@
|
|||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
dataIndex: 'num',
|
||||
width: 200,
|
||||
width: 100,
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
|
@ -271,7 +271,7 @@
|
|||
dataIndex: 'name',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 250,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
|
@ -281,7 +281,7 @@
|
|||
dataIndex: 'testPlanName',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
|
@ -291,7 +291,7 @@
|
|||
dataIndex: 'defectState',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
|
@ -305,7 +305,7 @@
|
|||
},
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
|
@ -364,7 +364,7 @@
|
|||
return;
|
||||
}
|
||||
if (showType.value === 'link') {
|
||||
bugTableListRef.value?.searchData();
|
||||
bugTableListRef.value?.searchData(keyword.value);
|
||||
} else {
|
||||
setTestPlanListParams(initTableParams());
|
||||
await testPlanLinkList();
|
||||
|
@ -373,7 +373,7 @@
|
|||
}
|
||||
async function resetFetch() {
|
||||
if (showType.value === 'link') {
|
||||
bugTableListRef.value?.searchData();
|
||||
bugTableListRef.value?.searchData(keyword.value);
|
||||
} else {
|
||||
setTestPlanListParams({ keyword: '', projectId: appStore.currentProjectId, testPlanCaseId: props.caseId });
|
||||
await testPlanLinkList();
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
{{ t('caseManagement.caseReview.fail') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--link-6))]" />
|
||||
{{ t('caseManagement.caseReview.suggestion') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
||||
|
@ -57,11 +57,11 @@
|
|||
{{ t('caseManagement.featureCase.execute.success') }}
|
||||
</div>
|
||||
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.featureCase.execute.blocked') }}
|
||||
</div>
|
||||
<div v-if="item.status === 'FAILED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('caseManagement.featureCase.execute.failed') }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,23 +10,30 @@
|
|||
:type="RequestModuleEnum.CASE_MANAGEMENT"
|
||||
hide-project-select
|
||||
is-hidden-case-level
|
||||
:selector-all="true"
|
||||
@save="saveHandler"
|
||||
>
|
||||
</MsCaseAssociate>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
|
||||
import { RequestModuleEnum } from '@/components/business/ms-case-associate/utils';
|
||||
|
||||
import { getCaseList, getCaseModuleTree } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import type { AssociateCaseRequest } from '@/models/testPlan/testPlan';
|
||||
import type { AssociateCaseRequest, AssociateCaseRequestType } from '@/models/testPlan/testPlan';
|
||||
import { CaseLinkEnum } from '@/enums/caseEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
hasNotAssociatedIds?: string[];
|
||||
saveApi?: (params: AssociateCaseRequestType) => Promise<any>;
|
||||
}>();
|
||||
const innerVisible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
|
@ -36,16 +43,31 @@
|
|||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const route = useRoute();
|
||||
const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
|
||||
const currentProjectId = ref(appStore.currentProjectId);
|
||||
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
const planId = ref(route.query.id as string);
|
||||
|
||||
function saveHandler(params: AssociateCaseRequest) {
|
||||
async function saveHandler(params: AssociateCaseRequest) {
|
||||
try {
|
||||
confirmLoading.value = true;
|
||||
emit('success', { ...params, functionalSelectIds: params.selectIds });
|
||||
if (typeof props.saveApi !== 'function') {
|
||||
emit('success', { ...params, functionalSelectIds: params.selectIds });
|
||||
} else {
|
||||
try {
|
||||
await props.saveApi({
|
||||
functionalSelectIds: params.selectIds,
|
||||
testPlanId: planId.value,
|
||||
});
|
||||
emit('success', { ...params, functionalSelectIds: params.selectIds });
|
||||
Message.success(t('ms.case.associate.associateSuccess'));
|
||||
confirmLoading.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
innerVisible.value = false;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
|
|
@ -1,100 +1,116 @@
|
|||
<template>
|
||||
<MsDialog
|
||||
v-model:visible="isVisible"
|
||||
dialog-size="small"
|
||||
:title="t('testPlan.testPlanIndex.batchEdit', { number: props.batchParams.currentSelectCount })"
|
||||
ok-text="common.update"
|
||||
:confirm="confirmHandler"
|
||||
:close="closeHandler"
|
||||
unmount-on-close
|
||||
:switch-props="{
|
||||
switchName: t('caseManagement.featureCase.appendTag'),
|
||||
switchTooltip: t('caseManagement.featureCase.enableTags'),
|
||||
showSwitch: form.selectedAttrsId === 'tags' ? true : false,
|
||||
enable: form.append,
|
||||
}"
|
||||
>
|
||||
<div class="form">
|
||||
<a-form ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
|
||||
<a-form-item
|
||||
field="selectedAttrsId"
|
||||
:label="t('apiTestManagement.chooseAttr')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.attrRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-select v-model="form.selectedAttrsId" :placeholder="t('common.pleaseSelect')">
|
||||
<a-option v-for="item of attrOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.name) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
<a-modal v-model:visible="isVisible" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
||||
<template #title>
|
||||
{{ t('common.edit') }}
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{
|
||||
t('case.batchModalSubTitle', {
|
||||
count: props.batchParams.currentSelectCount,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<a-form ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
|
||||
<a-form-item
|
||||
field="selectedAttrsId"
|
||||
:label="t('apiTestManagement.chooseAttr')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.attrRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-select v-model="form.selectedAttrsId" :placeholder="t('common.pleaseSelect')">
|
||||
<a-option v-for="item of attrOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.name) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="form.selectedAttrsId === 'tags'"
|
||||
field="tags"
|
||||
:label="t('apiTestManagement.batchUpdate')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
required
|
||||
>
|
||||
<MsTagsInput v-model:modelValue="form.tags" allow-clear></MsTagsInput>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-else
|
||||
field="value"
|
||||
:label="t('apiTestManagement.batchUpdate')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
>
|
||||
<a-select v-model="form.value" :placeholder="t('common.pleaseSelect')" :disabled="form.selectedAttrsId === ''">
|
||||
<a-option v-for="item of valueOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex" :class="[form.selectedAttrsId === 'tags' ? 'justify-between' : 'justify-end']">
|
||||
<div
|
||||
v-if="form.selectedAttrsId === 'tags'"
|
||||
field="values"
|
||||
:label="t('apiTestManagement.batchUpdate')"
|
||||
:validate-trigger="['blur', 'input']"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
required
|
||||
class="flex flex-row items-center justify-center"
|
||||
style="padding-top: 10px"
|
||||
>
|
||||
<MsTagsInput
|
||||
v-model:model-value="form.tags"
|
||||
placeholder="common.tagsInputPlaceholder"
|
||||
allow-clear
|
||||
unique-value
|
||||
retain-input-value
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-else
|
||||
field="value"
|
||||
:label="t('apiTestManagement.batchUpdate')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
>
|
||||
<a-select
|
||||
v-model="form.value"
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
:disabled="form.selectedAttrsId === ''"
|
||||
>
|
||||
<a-option v-for="item of valueOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</MsDialog>
|
||||
<a-switch v-model="form.append" class="mr-1" size="small" type="line" />
|
||||
<span class="flex items-center">
|
||||
<span class="mr-1">{{ t('caseManagement.featureCase.appendTag') }}</span>
|
||||
<span class="mt-[2px]">
|
||||
<a-tooltip>
|
||||
<IconQuestionCircle class="h-[16px] w-[16px] text-[rgb(var(--primary-5))]" />
|
||||
<template #content>
|
||||
<div>{{ t('caseManagement.featureCase.enableTags') }}</div>
|
||||
<div>{{ t('caseManagement.featureCase.closeTags') }}</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<a-button type="secondary" :disabled="batchEditLoading" @click="closeHandler">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button class="ml-3" type="primary" :loading="batchEditLoading" @click="confirmHandler">
|
||||
{{ t('common.update') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance, ValidatedError } from '@arco-design/web-vue';
|
||||
import { FormInstance } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsDialog from '@/components/pure/ms-dialog/index.vue';
|
||||
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
|
||||
import { batchEditTestPlan } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { TableQueryParams } from '@/models/common';
|
||||
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||
|
||||
import Message from '@arco-design/web-vue/es/message';
|
||||
|
||||
const isVisible = ref<boolean>(false);
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
batchParams: BatchActionQueryParams;
|
||||
activeFolder: string;
|
||||
offspringIds: string[];
|
||||
condition?: TableQueryParams;
|
||||
showType: keyof typeof testPlanTypeEnum;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
|
@ -102,7 +118,6 @@
|
|||
(e: 'success'): void;
|
||||
}>();
|
||||
|
||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
||||
const initForm = {
|
||||
selectedAttrsId: '',
|
||||
append: false,
|
||||
|
@ -126,33 +141,33 @@
|
|||
form.value = { ...initForm };
|
||||
}
|
||||
|
||||
async function confirmHandler(enable: boolean | undefined) {
|
||||
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
|
||||
const batchEditLoading = ref(false);
|
||||
function confirmHandler() {
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
const customField = {
|
||||
fieldId: '',
|
||||
value: '',
|
||||
};
|
||||
batchEditLoading.value = true;
|
||||
const { selectedIds, selectAll, excludeIds } = props.batchParams;
|
||||
const params: TableQueryParams = {
|
||||
selectIds: selectedIds || [],
|
||||
selectAll: !!selectAll,
|
||||
excludeIds: excludeIds || [],
|
||||
projectId: currentProjectId.value,
|
||||
append: enable as boolean,
|
||||
tags: form.value.tags,
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||
customField: form.value.selectedAttrsId === 'systemTags' ? {} : customField,
|
||||
condition: {
|
||||
...props.condition,
|
||||
},
|
||||
...form.value,
|
||||
type: props.showType,
|
||||
};
|
||||
await batchEditTestPlan(params);
|
||||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||
closeHandler();
|
||||
emits('success');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
batchEditLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
class="mt-4"
|
||||
:action-config="testPlanBatchActions"
|
||||
filter-icon-align-left
|
||||
:selectable="hasOperationPermission"
|
||||
v-on="propsEvent"
|
||||
@batch-action="handleTableBatch"
|
||||
>
|
||||
|
@ -115,7 +116,7 @@
|
|||
<StatusProgress :status-detail="defaultCountDetailMap[record.id]" height="5px" />
|
||||
</div>
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ `${record.passRate || 0}%` }}
|
||||
{{ `${defaultCountDetailMap[record.id] ? defaultCountDetailMap[record.id].passRate : '-'}%` }}
|
||||
</div>
|
||||
</template>
|
||||
<template #passRateTitleSlot="{ columnConfig }">
|
||||
|
@ -173,10 +174,17 @@
|
|||
|
||||
<template #operation="{ record }">
|
||||
<div class="flex items-center">
|
||||
<MsButton v-if="record.functionalCaseCount > 0" class="!mx-0">{{
|
||||
t('testPlan.testPlanIndex.execution')
|
||||
}}</MsButton>
|
||||
<a-divider v-if="record.functionalCaseCount > 0" direction="vertical" :margin="8"></a-divider>
|
||||
<MsButton
|
||||
v-if="record.functionalCaseCount > 0 && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
||||
class="!mx-0"
|
||||
@click="openDetail(record.id)"
|
||||
>{{ t('testPlan.testPlanIndex.execution') }}</MsButton
|
||||
>
|
||||
<a-divider
|
||||
v-if="record.functionalCaseCount > 0 && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
||||
direction="vertical"
|
||||
:margin="8"
|
||||
></a-divider>
|
||||
|
||||
<MsButton
|
||||
v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']"
|
||||
|
@ -245,6 +253,7 @@
|
|||
:active-folder="props.activeFolder"
|
||||
:offspring-ids="props.offspringIds"
|
||||
:condition="conditionParams"
|
||||
:show-type="showType"
|
||||
@success="successHandler"
|
||||
/>
|
||||
</template>
|
||||
|
@ -289,6 +298,7 @@
|
|||
import { characterLimit } from '@/utils';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import type { TableQueryParams } from '@/models/common';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import type { PassRateCountDetail, planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||
|
@ -319,6 +329,10 @@
|
|||
(e: 'editOrCopy', id: string, isCopy: boolean): void;
|
||||
}>();
|
||||
|
||||
const hasOperationPermission = computed(() =>
|
||||
hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE', 'PROJECT_TEST_PLAN:READ+EXECUTE', 'PROJECT_TEST_PLAN:READ+ADD'])
|
||||
);
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.ID',
|
||||
|
@ -443,11 +457,11 @@
|
|||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.operation',
|
||||
title: hasOperationPermission.value ? 'testPlan.testPlanIndex.operation' : '',
|
||||
slotName: 'operation',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
width: hasOperationPermission.value ? 200 : 50,
|
||||
showInTable: true,
|
||||
showDrag: false,
|
||||
},
|
||||
|
@ -537,37 +551,43 @@
|
|||
{
|
||||
label: 'common.archive',
|
||||
eventTag: 'archive',
|
||||
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
|
||||
},
|
||||
];
|
||||
const copyActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.copy',
|
||||
eventTag: 'copy',
|
||||
permission: ['PROJECT_TEST_PLAN:READ+ADD'],
|
||||
},
|
||||
];
|
||||
|
||||
function getMoreActions(status: planStatusType, useCount: number) {
|
||||
// 有用例数量才可以执行 否则不展示执行
|
||||
const copyAction = useCount > 0 ? copyActions : [];
|
||||
if (status === 'COMPLETED' || status === 'ARCHIVED') {
|
||||
// 单独操作已归档和已完成 不展示归档
|
||||
if (status === 'ARCHIVED' || status === 'PREPARED' || status === 'UNDERWAY') {
|
||||
return [
|
||||
...copyAction,
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
permission: ['PROJECT_TEST_PLAN:READ+DELETE'],
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
...copyAction,
|
||||
...archiveActions,
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
permission: ['PROJECT_TEST_PLAN:READ+DELETE'],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -610,7 +630,6 @@
|
|||
filter: propsRes.value.filter,
|
||||
combine: batchParams.value.condition,
|
||||
};
|
||||
|
||||
return {
|
||||
type: showType.value,
|
||||
moduleIds: props.activeFolder && props.activeFolder !== 'all' ? [props.activeFolder, ...props.offspringIds] : [],
|
||||
|
@ -620,6 +639,7 @@
|
|||
selectIds: batchParams.value.selectedIds || [],
|
||||
keyword: keyword.value,
|
||||
condition: {
|
||||
filter: propsRes.value.filter,
|
||||
keyword: keyword.value,
|
||||
},
|
||||
combine: {
|
||||
|
@ -832,6 +852,7 @@
|
|||
}
|
||||
|
||||
function successHandler() {
|
||||
resetSelector();
|
||||
fetchData();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
:node-more-actions="caseMoreActions"
|
||||
:expand-all="props.isExpandAll"
|
||||
:empty-text="t('testPlan.testPlanIndex.planEmptyContent')"
|
||||
draggable
|
||||
:draggable="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE'])"
|
||||
:virtual-list-props="virtualListProps"
|
||||
block-node
|
||||
:field-names="{
|
||||
|
@ -17,7 +17,7 @@
|
|||
children: 'children',
|
||||
count: 'count',
|
||||
}"
|
||||
title-tooltip-position="left"
|
||||
title-tooltip-position="top"
|
||||
@select="planNodeSelect"
|
||||
@more-action-select="handlePlanMoreSelect"
|
||||
@more-actions-close="moreActionsClose"
|
||||
|
@ -33,6 +33,7 @@
|
|||
</template>
|
||||
<template #extra="nodeData">
|
||||
<MsPopConfirm
|
||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD'])"
|
||||
:visible="addSubVisible"
|
||||
:is-delete="false"
|
||||
:all-names="[]"
|
||||
|
@ -50,6 +51,7 @@
|
|||
</MsButton>
|
||||
</MsPopConfirm>
|
||||
<MsPopConfirm
|
||||
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE'])"
|
||||
:title="t('testPlan.testPlanIndex.rename')"
|
||||
:all-names="[]"
|
||||
:is-delete="false"
|
||||
|
@ -87,6 +89,7 @@
|
|||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { mapTree } from '@/utils';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
|
|
|
@ -287,9 +287,12 @@
|
|||
if (props.planId?.length) {
|
||||
const result = await getTestPlanDetail(props.planId);
|
||||
form.value = cloneDeep(result);
|
||||
let copyName = `copy_${result.name}`;
|
||||
copyName = copyName.length > 255 ? copyName.slice(0, 255) : copyName;
|
||||
form.value.name = copyName;
|
||||
if (props.isCopy) {
|
||||
let copyName = `copy_${result.name}`;
|
||||
copyName = copyName.length > 255 ? copyName.slice(0, 255) : copyName;
|
||||
form.value.name = copyName;
|
||||
}
|
||||
|
||||
form.value.cycle = [result.plannedStartTime as number, result.plannedEndTime as number];
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -534,6 +534,7 @@
|
|||
...tableParams.value,
|
||||
...batchExecuteForm.value,
|
||||
notifier: batchExecuteForm.value?.commentIds?.join(';'),
|
||||
selectIds: batchParams.value.selectedIds,
|
||||
});
|
||||
Message.success(t('common.updateSuccess'));
|
||||
resetSelector();
|
||||
|
@ -636,6 +637,7 @@
|
|||
|
||||
defineExpose({
|
||||
resetSelector,
|
||||
loadCaseList,
|
||||
});
|
||||
|
||||
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_DETAIL_FEATURE_CASE_TABLE, columns, 'drawer', true);
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
class="mx-[8px] w-[240px]"
|
||||
@search="initData"
|
||||
@press-enter="initData"
|
||||
@clear="initData"
|
||||
@clear="resetHandler"
|
||||
/>
|
||||
</div>
|
||||
<BugList
|
||||
|
@ -62,26 +62,10 @@
|
|||
testPlanCaseId: route.query.testPlanCaseId,
|
||||
caseId: props.caseId,
|
||||
}"
|
||||
@link="linkDefect"
|
||||
@new="createDefect"
|
||||
@link="emit('link')"
|
||||
@new="emit('new')"
|
||||
@cancel-link="cancelLink"
|
||||
/>
|
||||
<LinkDefectDrawer
|
||||
v-model:visible="showLinkDrawer"
|
||||
:case-id="props.caseId"
|
||||
:drawer-loading="drawerLoading"
|
||||
@save="saveHandler"
|
||||
/>
|
||||
<AddDefectDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:case-id="props.caseId"
|
||||
::extra-params="{
|
||||
testPlanCaseId: route.query.testPlanCaseId,
|
||||
caseId: props.caseId,
|
||||
testPlanId:props.testPlanId,
|
||||
}"
|
||||
@success="initData()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -92,12 +76,10 @@
|
|||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import AddDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue';
|
||||
import BugList from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/bugList.vue';
|
||||
import LinkDefectDrawer from '@/views/case-management/caseManagementFeature/components/tabContent/tabBug/linkDefectDrawer.vue';
|
||||
|
||||
import { getBugList, getCustomOptionHeader } from '@/api/modules/bug-management';
|
||||
import { associateBugToPlan, associatedBugPage, testPlanCancelBug } from '@/api/modules/test-plan/testPlan';
|
||||
import { associatedBugPage, testPlanCancelBug } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
@ -116,6 +98,12 @@
|
|||
|
||||
const keyword = ref<string>('');
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'link'): void;
|
||||
(e: 'new'): void;
|
||||
(e: 'save', params: TableQueryParams): void;
|
||||
}>();
|
||||
|
||||
const columns = ref<MsTableColumn>([
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
|
@ -188,26 +176,16 @@
|
|||
if (!hasAnyPermission(['FUNCTIONAL_CASE:READ', 'FUNCTIONAL_CASE:READ+UPDATE', 'FUNCTIONAL_CASE:READ+DELETE'])) {
|
||||
return;
|
||||
}
|
||||
bugTableListRef.value?.searchData();
|
||||
}
|
||||
|
||||
const showLinkDrawer = ref<boolean>(false);
|
||||
function linkDefect() {
|
||||
showLinkDrawer.value = true;
|
||||
}
|
||||
|
||||
const showDrawer = ref<boolean>(false);
|
||||
function createDefect() {
|
||||
showDrawer.value = true;
|
||||
bugTableListRef.value?.searchData(keyword.value);
|
||||
}
|
||||
|
||||
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||
switch (value) {
|
||||
case 'associated':
|
||||
linkDefect();
|
||||
emit('link');
|
||||
break;
|
||||
default:
|
||||
createDefect();
|
||||
emit('new');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -232,6 +210,7 @@
|
|||
}
|
||||
|
||||
const cancelLoading = ref<boolean>(false);
|
||||
|
||||
// 取消关联缺陷
|
||||
async function cancelLink(id: string) {
|
||||
cancelLoading.value = true;
|
||||
|
@ -261,25 +240,10 @@
|
|||
}
|
||||
}
|
||||
const route = useRoute();
|
||||
const drawerLoading = ref<boolean>(false);
|
||||
// 关联缺陷
|
||||
async function saveHandler(params: TableQueryParams) {
|
||||
try {
|
||||
drawerLoading.value = true;
|
||||
await associateBugToPlan({
|
||||
...params,
|
||||
caseId: props.caseId,
|
||||
testPlanId: props.testPlanId,
|
||||
testPlanCaseId: route.query.testPlanCaseId as string,
|
||||
});
|
||||
Message.success(t('caseManagement.featureCase.associatedSuccess'));
|
||||
initData();
|
||||
showLinkDrawer.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
drawerLoading.value = false;
|
||||
}
|
||||
|
||||
function resetHandler() {
|
||||
keyword.value = '';
|
||||
initData();
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@ -296,6 +260,10 @@
|
|||
initData();
|
||||
initBugList();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
initData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,84 +1,107 @@
|
|||
<template>
|
||||
<!-- TODO: 待联调 -->
|
||||
<div class="review-history-list">
|
||||
<div v-for="item of executeHistoryList" :key="item.id" class="review-history-list-item">
|
||||
<div class="flex items-center">
|
||||
<MsAvatar :avatar="item.userLogo" />
|
||||
<div class="ml-[8px] flex items-center">
|
||||
<a-tooltip :content="item.userName" :mouse-enter-delay="300">
|
||||
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
|
||||
</a-tooltip>
|
||||
<a-divider direction="vertical" margin="8px"></a-divider>
|
||||
<div v-if="item.status === 'PASS'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.caseReview.pass') }}
|
||||
<a-spin :loading="loading" class="w-full">
|
||||
<div class="execute-history-list">
|
||||
<div v-for="item of executeHistoryList" :key="item.status" class="execute-history-list-item">
|
||||
<div class="flex items-center">
|
||||
<MsAvatar :avatar="item.userLogo" />
|
||||
<div class="ml-[8px] flex items-center">
|
||||
<a-tooltip :content="item.userName" :mouse-enter-delay="300">
|
||||
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
|
||||
</a-tooltip>
|
||||
<a-divider direction="vertical" margin="8px"></a-divider>
|
||||
<div v-if="item.status === 'SUCCESS'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('common.success') }}
|
||||
</div>
|
||||
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_block_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('common.block') }}
|
||||
</div>
|
||||
<div v-if="item.status === 'ERROR'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('common.fail') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="item.status === 'UN_PASS'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('caseManagement.caseReview.fail') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.caseReview.suggestion') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_resubmit_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.caseReview.reReview') }}
|
||||
</div>
|
||||
<div v-if="item.status === 'PASSED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.featureCase.execute.success') }}
|
||||
</div>
|
||||
<div v-if="item.status === 'BLOCKED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.featureCase.execute.blocked') }}
|
||||
</div>
|
||||
<div v-if="item.status === 'FAILED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.featureCase.execute.failed') }}
|
||||
</div>
|
||||
<div class="markdown-body" style="margin-left: 48px" v-html="item.contentText"></div>
|
||||
<div class="ml-[48px] mt-[8px] flex text-[var(--color-text-4)]">
|
||||
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
<div>
|
||||
<a-tooltip :content="item.userName" :mouse-enter-delay="300" :disabled="!item.userName">
|
||||
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
|
||||
{{ characterLimit(item.userName) }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="markdown-body" style="margin-left: 48px" v-html="item.contentText"></div>
|
||||
<div class="ml-[48px] mt-[8px] flex text-[var(--color-text-4)]">
|
||||
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
<div>
|
||||
<a-tooltip :content="item.reviewName" :mouse-enter-delay="300">
|
||||
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
|
||||
{{ characterLimit(item.reviewName) }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-else
|
||||
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
|
||||
>
|
||||
{{ characterLimit(item.reviewName) }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<MsEmpty v-if="executeHistoryList.length === 0" />
|
||||
</div>
|
||||
<MsEmpty v-if="executeHistoryList.length === 0" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
||||
|
||||
import { executeHistory } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import type { ExecuteHistoryItem } from '@/models/testPlan/testPlan';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
}>();
|
||||
|
||||
const executeHistoryList = ref<CommentItem[]>([]);
|
||||
const executeHistoryList = ref<ExecuteHistoryItem[]>([]);
|
||||
|
||||
const route = useRoute();
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
async function initList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
executeHistoryList.value = await executeHistory({
|
||||
caseId: props.caseId,
|
||||
id: route.query.testPlanCaseId as string,
|
||||
testPlanId: route.query.id as string,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initList();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.caseId,
|
||||
(val) => {
|
||||
if (val) {
|
||||
initList();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped lang="less">
|
||||
.execute-history-list {
|
||||
height: calc(100vh - 240px);
|
||||
@apply overflow-auto;
|
||||
.ms-scroll-bar();
|
||||
.execute-history-list-item {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -120,7 +120,33 @@
|
|||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<!-- TODO: 缺陷 -->
|
||||
<MsTag type="danger" theme="light" size="medium" class="ml-4">
|
||||
<MsIcon type="icon-icon_defect" class="!text-[14px] text-[rgb(var(--danger-6))]" size="16" />
|
||||
<span class="ml-1 text-[rgb(var(--danger-6))]"> {{ t('testPlan.featureCase.bug') }}</span>
|
||||
<span class="ml-1 text-[rgb(var(--danger-6))]">{{ bugCount }}</span>
|
||||
</MsTag>
|
||||
<a-dropdown @select="handleSelect">
|
||||
<a-button type="outline" size="mini" class="ml-1">
|
||||
<template #icon> <icon-plus class="text-[12px]" /> </template>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption value="new">{{ t('common.newCreate') }}</a-doption>
|
||||
<a-doption v-if="createdBugCount > 0" value="link">{{ t('common.associated') }}</a-doption>
|
||||
<a-popover v-else title="" position="left">
|
||||
<a-doption :disabled="true" value="link">{{ t('common.associated') }}</a-doption>
|
||||
<template #content>
|
||||
<div class="flex items-center text-[14px]">
|
||||
<span class="text-[var(--color-text-4)]">{{
|
||||
t('testPlan.featureCase.noBugDataTooltip')
|
||||
}}</span>
|
||||
<MsButton type="text" @click="handleSelect('new')">
|
||||
{{ t('testPlan.featureCase.noBugDataNewBug') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<ExecuteSubmit
|
||||
|
@ -134,19 +160,39 @@
|
|||
</div>
|
||||
<BugList
|
||||
v-if="activeTab === 'defectList'"
|
||||
:case-id="caseDetail.id"
|
||||
ref="bugRef"
|
||||
:case-id="activeCaseId"
|
||||
:test-plan-id="route.query.id as string"
|
||||
@link="linkDefect"
|
||||
@new="addBug"
|
||||
/>
|
||||
<ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" />
|
||||
<ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="activeCaseId" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</MsCard>
|
||||
<EditCaseDetailDrawer v-model:visible="editCaseVisible" :case-id="activeCaseId" @load-case="loadCase" />
|
||||
<LinkDefectDrawer
|
||||
v-model:visible="showLinkDrawer"
|
||||
:case-id="activeCaseId"
|
||||
:drawer-loading="drawerLoading"
|
||||
@save="associateSuccessHandler"
|
||||
/>
|
||||
<AddDefectDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:case-id="activeCaseId"
|
||||
::extra-params="{
|
||||
testPlanCaseId: route.query.testPlanCaseId,
|
||||
caseId: activeCaseId,
|
||||
testPlanId:route.query.id as string,
|
||||
}"
|
||||
@success="addSuccess"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
|
@ -154,19 +200,31 @@
|
|||
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||
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 ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
|
||||
|
||||
import { getCaseDetail, getPlanDetailFeatureCaseList, getTestPlanDetail } from '@/api/modules/test-plan/testPlan';
|
||||
import { getBugList } from '@/api/modules/bug-management';
|
||||
import {
|
||||
associateBugToPlan,
|
||||
associatedBugPage,
|
||||
getCaseDetail,
|
||||
getPlanDetailFeatureCaseList,
|
||||
getTestPlanDetail,
|
||||
} from '@/api/modules/test-plan/testPlan';
|
||||
import { testPlanDefaultDetail } from '@/config/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import type { TableQueryParams } from '@/models/common';
|
||||
import type { PlanDetailFeatureCaseItem, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
@ -338,7 +396,6 @@
|
|||
() => activeId.value,
|
||||
() => {
|
||||
loadCaseDetail();
|
||||
// TODO 更新历史列表
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -389,6 +446,95 @@
|
|||
}
|
||||
}
|
||||
|
||||
const bugCount = ref<number>(0);
|
||||
|
||||
const showLinkDrawer = ref<boolean>(false);
|
||||
const drawerLoading = ref<boolean>(false);
|
||||
|
||||
const showDrawer = ref<boolean>(false);
|
||||
|
||||
function addBug() {
|
||||
showDrawer.value = true;
|
||||
}
|
||||
|
||||
function linkDefect() {
|
||||
showLinkDrawer.value = true;
|
||||
}
|
||||
|
||||
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||
switch (value) {
|
||||
case 'new':
|
||||
addBug();
|
||||
break;
|
||||
default:
|
||||
linkDefect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
const bugRef = ref();
|
||||
|
||||
async function getBugTotal() {
|
||||
try {
|
||||
const params = {
|
||||
testPlanCaseId: route.query.testPlanCaseId,
|
||||
caseId: activeCaseId.value,
|
||||
projectId: appStore.currentProjectId,
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
const res = await associatedBugPage(params);
|
||||
bugCount.value = res.total;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function addSuccess() {
|
||||
if (activeTab.value === 'defectList') {
|
||||
bugRef.value?.initData();
|
||||
} else {
|
||||
getBugTotal();
|
||||
}
|
||||
}
|
||||
|
||||
async function associateSuccessHandler(params: TableQueryParams) {
|
||||
try {
|
||||
drawerLoading.value = true;
|
||||
await associateBugToPlan({
|
||||
...params,
|
||||
caseId: activeCaseId.value,
|
||||
testPlanId: route.query.id as string,
|
||||
testPlanCaseId: route.query.testPlanCaseId as string,
|
||||
});
|
||||
Message.success(t('caseManagement.featureCase.associatedSuccess'));
|
||||
showLinkDrawer.value = false;
|
||||
addSuccess();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
drawerLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; // 获取上个页面带过来的表格查询参数
|
||||
if (lastPageParams) {
|
||||
|
@ -404,7 +550,11 @@
|
|||
moduleIds,
|
||||
};
|
||||
}
|
||||
if (activeTab.value === 'detail') {
|
||||
getBugTotal();
|
||||
}
|
||||
getPlanDetail();
|
||||
initBugList();
|
||||
await loadCase();
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -76,4 +76,12 @@
|
|||
function initModuleTree(tree: ModuleTreeNode[]) {
|
||||
moduleTree.value = unref(tree);
|
||||
}
|
||||
|
||||
function getCaseTableList() {
|
||||
caseTableRef.value?.loadCaseList();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getCaseTableList,
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -101,11 +101,21 @@
|
|||
</MsCard>
|
||||
<!-- special-height的174: 上面卡片高度158 + mt的16 -->
|
||||
<MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding>
|
||||
<FeatureCase v-if="activeTab === 'featureCase'" :repeat-case="detail.repeatCase" @refresh="getStatistics" />
|
||||
<FeatureCase
|
||||
v-if="activeTab === 'featureCase'"
|
||||
ref="featureCaseRef"
|
||||
:repeat-case="detail.repeatCase"
|
||||
@refresh="getStatistics"
|
||||
/>
|
||||
<!-- TODO 先不上 -->
|
||||
<!-- <BugManagement v-if="activeTab === 'defectList'" :plan-id="detail.id" /> -->
|
||||
</MsCard>
|
||||
<AssociateDrawer v-model:visible="caseAssociateVisible" :associated-ids="hasSelectedIds" @success="success" />
|
||||
<AssociateDrawer
|
||||
v-model:visible="caseAssociateVisible"
|
||||
:associated-ids="detail.repeatCase ? hasSelectedIds : []"
|
||||
:save-api="associationCaseToPlan"
|
||||
@success="handleSuccess"
|
||||
/>
|
||||
<CreateAndEditPlanDrawer
|
||||
v-model:visible="showPlanDrawer"
|
||||
:plan-id="planId"
|
||||
|
@ -117,7 +127,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
@ -153,12 +163,7 @@
|
|||
import { characterLimit } from '@/utils';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import type {
|
||||
AssociateCaseRequest,
|
||||
PassRateCountDetail,
|
||||
TestPlanDetail,
|
||||
TestPlanItem,
|
||||
} from '@/models/testPlan/testPlan';
|
||||
import type { PassRateCountDetail, TestPlanDetail, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
|
@ -353,6 +358,7 @@
|
|||
function successHandler() {
|
||||
initDetail();
|
||||
}
|
||||
|
||||
const testPlanTree = ref<ModuleTreeNode[]>([]);
|
||||
async function initPlanTree() {
|
||||
try {
|
||||
|
@ -362,19 +368,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
// 关联用例到测试计划
|
||||
async function success(params: AssociateCaseRequest) {
|
||||
try {
|
||||
await associationCaseToPlan({
|
||||
functionalSelectIds: params.selectIds,
|
||||
testPlanId: planId.value,
|
||||
});
|
||||
Message.success(t('ms.case.associate.associateSuccess'));
|
||||
caseAssociateVisible.value = false;
|
||||
initDetail();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
const featureCaseRef = ref<InstanceType<typeof FeatureCase>>();
|
||||
function handleSuccess() {
|
||||
initDetail();
|
||||
featureCaseRef.value?.getCaseTableList();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
isExpandAll ? t('testPlan.testPlanIndex.collapseAll') : t('testPlan.testPlanIndex.expandAll')
|
||||
"
|
||||
>
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="expandHandler">
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" position="top" @click="expandHandler">
|
||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
|
|
|
@ -19,6 +19,7 @@ export default {
|
|||
'testPlan.testPlanIndex.passRate': 'Pass Rate',
|
||||
'testPlan.testPlanIndex.useCount': 'Use cases',
|
||||
'testPlan.testPlanIndex.bugCount': 'bug count',
|
||||
'testPlan.featureCase.bug': 'bug',
|
||||
'testPlan.testPlanIndex.belongModule': 'belong module',
|
||||
'testPlan.testPlanIndex.createTime': 'create time',
|
||||
'testPlan.testPlanIndex.operation': 'operation',
|
||||
|
|
|
@ -88,6 +88,7 @@ export default {
|
|||
'testPlan.bugManagement.defectState': '缺陷状态',
|
||||
'testPlan.bugManagement.caseClassification': '用例分类',
|
||||
'testPlan.featureCase.bugCount': '缺陷数',
|
||||
'testPlan.featureCase.bug': '缺陷',
|
||||
'testPlan.featureCase.executor': '执行人',
|
||||
'testPlan.featureCase.changeExecutor': '修改执行人',
|
||||
'testPlan.featureCase.batchChangeExecutor': '批量修改执行人',
|
||||
|
|
Loading…
Reference in New Issue