feat(测试计划): 脑图失败重试&报告展示重试结果

This commit is contained in:
baiqi 2024-07-10 16:56:17 +08:00 committed by 刘瑞斌
parent 420a826697
commit fe28bcc27d
11 changed files with 149 additions and 52 deletions

View File

@ -183,7 +183,11 @@
</a-form-item> </a-form-item>
<a-form-item class="hidden-item"> <a-form-item class="hidden-item">
<div class="flex items-center gap-[8px]"> <div class="flex items-center gap-[8px]">
<a-switch v-model:model-value="configForm.retryOnFail" size="small"></a-switch> <a-switch
v-model:model-value="configForm.retryOnFail"
:disabled="configForm.level === 2 && configForm.extended"
size="small"
></a-switch>
<div>{{ t('ms.minders.failRetry') }}</div> <div>{{ t('ms.minders.failRetry') }}</div>
</div> </div>
</a-form-item> </a-form-item>
@ -201,6 +205,7 @@
:step="1" :step="1"
:min="1" :min="1"
:precision="0" :precision="0"
:disabled="configForm.level === 2 && configForm.extended"
size="small" size="small"
class="w-[120px]" class="w-[120px]"
></a-input-number> ></a-input-number>
@ -218,6 +223,7 @@
:step="100" :step="100"
:min="0" :min="0"
:precision="0" :precision="0"
:disabled="configForm.level === 2 && configForm.extended"
size="small" size="small"
class="w-[120px]" class="w-[120px]"
></a-input-number> ></a-input-number>

View File

@ -5,7 +5,7 @@
:class="[props.class, props.noContent ? 'no-content' : '']" :class="[props.class, props.noContent ? 'no-content' : '']"
@change="(val) => handleTabClick(val as string)" @change="(val) => handleTabClick(val as string)"
> >
<a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="item.label"> <a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="`${item.label}`">
<template v-if="props.showBadge" #title> <template v-if="props.showBadge" #title>
<a-badge <a-badge
v-if="props.getTextFunc(item.value) !== ''" v-if="props.getTextFunc(item.value) !== ''"
@ -27,8 +27,11 @@
<div <div
v-for="item of props.contentTabList" v-for="item of props.contentTabList"
:key="item.value" :key="item.value"
class="ms-tab--button-item" class="ms-tab-button-item"
:class="item.value === tempActiveKey ? 'ms-tab--button-item--active' : ''" :class="[
item.value === tempActiveKey ? 'ms-tab-button-item--active' : '',
props.buttonSize === 'small' ? 'ms-tab--button-item--small' : '',
]"
@click="handleTabClick(item.value)" @click="handleTabClick(item.value)"
> >
{{ item.label }} {{ item.label }}
@ -40,23 +43,28 @@
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
mode?: 'origin' | 'button'; mode?: 'origin' | 'button';
contentTabList: { label: string; value: string }[]; contentTabList: { label: string | number; value: string | number }[];
class?: string; class?: string;
getTextFunc?: (value: any) => string; getTextFunc?: (value: any) => string;
noContent?: boolean; noContent?: boolean;
showBadge?: boolean; showBadge?: boolean;
changeInterceptor?: (newVal: string, oldVal: string, done: () => void) => void; changeInterceptor?: (newVal: string | number, oldVal: string | number, done: () => void) => void;
buttonSize?: 'small' | 'default';
}>(), }>(),
{ {
mode: 'origin', mode: 'origin',
showBadge: true, showBadge: true,
getTextFunc: (value: any) => value, getTextFunc: (value: any) => value,
class: '', class: '',
buttonSize: 'default',
} }
); );
const emit = defineEmits<{
(e: 'change', value: string | number): void;
}>();
// tab // tab
const activeKey = defineModel<string>('activeKey', { const activeKey = defineModel<string | number>('activeKey', {
default: '', default: '',
}); });
// //
@ -69,7 +77,7 @@
} }
); );
function handleTabClick(value: string) { function handleTabClick(value: string | number) {
if (value === activeKey.value) { if (value === activeKey.value) {
return; return;
} }
@ -85,6 +93,7 @@
// //
activeKey.value = value; activeKey.value = value;
} }
emit('change', activeKey.value);
} }
</script> </script>
@ -107,7 +116,7 @@
@apply flex; @apply flex;
border-radius: var(--border-radius-small); border-radius: var(--border-radius-small);
.ms-tab--button-item { .ms-tab-button-item {
@apply cursor-pointer; @apply cursor-pointer;
padding: 4px 12px; padding: 4px 12px;
@ -128,7 +137,12 @@
color: rgb(var(--primary-5)); color: rgb(var(--primary-5));
} }
} }
.ms-tab--button-item--active { .ms-tab--button-item--small {
padding: 1px 12px;
font-size: 12px;
line-height: 20px;
}
.ms-tab-button-item--active {
z-index: 2; z-index: 2;
border: 1px solid rgb(var(--primary-5)) !important; border: 1px solid rgb(var(--primary-5)) !important;
color: rgb(var(--primary-5)); color: rgb(var(--primary-5));

View File

@ -1,33 +1,43 @@
<template> <template>
<div class="flex flex-col" @click.stop="() => {}"> <div class="flex flex-col" @click.stop="() => {}">
<div class="response-header"> <div class="response-header">
<div v-if="isShowLoopControl" class="flex w-full items-center justify-start bg-white p-4" @click.stop="() => {}"> <div
<a-pagination v-if="isShowLoopControl"
v-model:page-size="controlPageSize" class="flex w-full items-center justify-start bg-white pb-[8px] pt-[16px]"
v-model:current="controlCurrent" @click.stop="() => {}"
:total="controlTotal" >
size="mini" <MsTab
show-total v-if="isFailedRetry"
:show-jumper="controlTotal > 5" v-model:activeKey="controlCurrent"
:content-tab-list="controlTotalList"
mode="button"
button-size="small"
@change="loadControlLoop"
/>
<loopPagination
v-else
v-model:current-loop="controlCurrent"
:loop-total="controlTotal"
class="!mb-0"
@change="loadControlLoop" @change="loadControlLoop"
/> />
<!-- <loopPagination v-model:current-loop="controlCurrent" :loop-total="controlTotal" /> -->
</div> </div>
<div class="flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4"> <div class="flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4">
<div class="font-medium"> <div class="font-medium">
<span <span
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }" :class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }"
@click.stop="setActiveType('ResContent')" @click.stop="setActiveType('ResContent')"
>{{ t('report.detail.api.resContent') }}</span
> >
{{ t('report.detail.api.resContent') }}
</span>
<span <span
v-if="total > 0" v-if="total > 0"
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'SubRequest' }" :class="{ 'text-[rgb(var(--primary-5))]': activeType === 'SubRequest' }"
@click.stop="setActiveType('SubRequest')" @click.stop="setActiveType('SubRequest')"
> >
<a-divider direction="vertical" :margin="8"></a-divider> <a-divider direction="vertical" :margin="8"></a-divider>
{{ t('report.detail.api.subRequest') }}</span {{ t('report.detail.api.subRequest') }}
> </span>
</div> </div>
<div class="flex flex-row gap-6 text-center"> <div class="flex flex-row gap-6 text-center">
<a-popover position="left" content-class="response-popover-content"> <a-popover position="left" content-class="response-popover-content">
@ -121,7 +131,9 @@
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import MsTab from '@/components/pure/ms-tab/index.vue';
import result from '@/views/api-test/components/requestComposition/response/result.vue'; import result from '@/views/api-test/components/requestComposition/response/result.vue';
import loopPagination from '@/views/api-test/scenario/components/common/loopPagination.vue';
import { reportCaseStepDetail, reportStepDetail } from '@/api/modules/api-test/report'; import { reportCaseStepDetail, reportStepDetail } from '@/api/modules/api-test/report';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -337,7 +349,24 @@
} }
return 0; return 0;
}); });
const controlPageSize = ref(1); const controlTotalList = computed(() => {
return Array.from({ length: controlTotal.value }, (v, k) => {
if (k === 0) {
return {
value: k + 1,
label: t('apiTestDebug.first'),
};
}
return {
value: k + 1,
label: `${t('apiTestDebug.retry')} ${k}`,
};
});
});
const isFailedRetry = computed(() => {
return !!props.stepItem?.stepChildren?.some((item) => item.requestName.includes('MsRetry_')); //
});
/** /**
* 循环次数控制器 * 循环次数控制器
*/ */

View File

@ -213,4 +213,6 @@ export default {
'apiTestDebug.standardAdditionsTip': 'apiTestDebug.standardAdditionsTip':
'Writing format: parameter name, type, required, parameter value; multiple records separated by newlines', 'Writing format: parameter name, type, required, parameter value; multiple records separated by newlines',
'apiTestDebug.quickAdditions': 'Quick', 'apiTestDebug.quickAdditions': 'Quick',
'apiTestDebug.first': 'First',
'apiTestDebug.retry': 'Retry',
}; };

View File

@ -199,4 +199,6 @@ export default {
'apiTestDebug.standardAdditions': '标准添加', 'apiTestDebug.standardAdditions': '标准添加',
'apiTestDebug.standardAdditionsTip': '书写格式:参数名,类型,必填,参数值;多条记录换行分隔', 'apiTestDebug.standardAdditionsTip': '书写格式:参数名,类型,必填,参数值;多条记录换行分隔',
'apiTestDebug.quickAdditions': '快捷添加', 'apiTestDebug.quickAdditions': '快捷添加',
'apiTestDebug.first': '首次',
'apiTestDebug.retry': '重试',
}; };

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="mb-4 flex h-[36px] items-center justify-between"> <div class="flex h-[36px] items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<div class="mr-2 font-medium leading-[36px]">{{ t('report.detail.api.reportDetail') }}</div> <div class="mr-2 font-medium leading-[36px]">{{ t('report.detail.api.reportDetail') }}</div>
<a-radio-group v-model:model-value="innerActiveTab" type="button" size="small"> <a-radio-group v-model:model-value="innerActiveTab" type="button" size="small">

View File

@ -128,25 +128,21 @@
<span v-show="step.requestTime !== null" class="resTime"> <span v-show="step.requestTime !== null" class="resTime">
{{ t('report.detail.api.responseTime') }} {{ t('report.detail.api.responseTime') }}
<a-popover position="left" content-class="response-popover-content"> <a-popover position="left" content-class="response-popover-content">
<span class="resTimeCount ml-2" <span class="resTimeCount ml-2">
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-' {{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-' }}
}}{{ {{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms' }}
step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms' </span>
}}</span
>
<template #content> <template #content>
<span v-show="step.requestTime !== null" class="resTime"> <span v-show="step.requestTime !== null" class="resTime">
{{ t('report.detail.api.responseTime') }} {{ t('report.detail.api.responseTime') }}
<span class="resTimeCount ml-2" <span class="resTimeCount ml-2">
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-' {{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-' }}
}}{{ {{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms' }}
step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms' </span>
}}</span </span>
></span
>
</template> </template>
</a-popover></span </a-popover>
> </span>
<span v-show="step.responseSize !== null" class="resSize"> <span v-show="step.responseSize !== null" class="resSize">
{{ t('report.detail.api.responseSize') }} {{ t('report.detail.api.responseSize') }}
<a-popover position="left" content-class="response-popover-content"> <a-popover position="left" content-class="response-popover-content">
@ -157,8 +153,8 @@
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span <span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
> >
</template> </template>
</a-popover></span </a-popover>
> </span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,10 +5,18 @@
'border border-solid border-[var(--color-text-n8)]': props.showType === 'API', 'border border-solid border-[var(--color-text-n8)]': props.showType === 'API',
}" }"
> >
<div v-if="isFailedRetry" class="mb-[8px]">
<MsTab
v-model:activeKey="controlCurrent"
:content-tab-list="controlTotalList"
mode="button"
button-size="small"
/>
</div>
<!-- 步骤树 --> <!-- 步骤树 -->
<StepTree <StepTree
ref="stepTreeRef" ref="stepTreeRef"
v-model:steps="tiledList" v-model:steps="currentTiledList"
v-model:expandedKeys="expandedKeys" v-model:expandedKeys="expandedKeys"
:show-type="props.showType" :show-type="props.showType"
:active-type="props.activeType" :active-type="props.activeType"
@ -36,9 +44,12 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { cloneDeep, debounce } from 'lodash-es'; import { cloneDeep, debounce } from 'lodash-es';
import MsTab from '@/components/pure/ms-tab/index.vue';
import StepDrawer from './step/stepDrawer.vue'; import StepDrawer from './step/stepDrawer.vue';
import StepTree from './step/stepTree.vue'; import StepTree from './step/stepTree.vue';
import { useI18n } from '@/hooks/useI18n';
import type { ReportDetail, ScenarioItemType } from '@/models/apiTest/report'; import type { ReportDetail, ScenarioItemType } from '@/models/apiTest/report';
import { ScenarioStepType } from '@/enums/apiEnum'; import { ScenarioStepType } from '@/enums/apiEnum';
@ -52,6 +63,8 @@
getReportStepDetail?: (...args: any) => Promise<any>; // getReportStepDetail?: (...args: any) => Promise<any>; //
}>(); }>();
const { t } = useI18n();
const tiledList = ref<ScenarioItemType[]>([]); const tiledList = ref<ScenarioItemType[]>([]);
const isExpandAll = ref(false); // const isExpandAll = ref(false); //
@ -82,6 +95,37 @@
originTreeData.value = cloneDeep(tiledList.value); originTreeData.value = cloneDeep(tiledList.value);
} }
const controlCurrent = ref(0);
const isFailedRetry = computed(() => {
// id
return (
props.reportDetail.children.every((item) => item.stepId === props.reportDetail.children[0].stepId) &&
props.reportDetail.children.some((item) => item.requestName?.includes('MsRetry_'))
);
});
const currentTiledList = computed(() => {
if (isFailedRetry.value === false) {
//
return tiledList.value;
}
//
return [tiledList.value[controlCurrent.value]];
});
const controlTotalList = computed(() => {
return Array.from({ length: props.reportDetail.children.length }, (v, k) => {
if (k === 0) {
return {
value: k,
label: t('apiTestDebug.first'),
};
}
return {
value: k,
label: `${t('apiTestDebug.retry')} ${k}`,
};
});
});
watch( watch(
() => props.reportDetail, () => props.reportDetail,
(val) => { (val) => {

View File

@ -9,6 +9,7 @@
show-total show-total
size="mini" size="mini"
class="loop-pagination" class="loop-pagination"
@change="() => emit('change', currentLoop)"
> >
<template #total="{ total }"> <template #total="{ total }">
<div <div
@ -31,6 +32,9 @@
const props = defineProps<{ const props = defineProps<{
loopTotal: number; loopTotal: number;
}>(); }>();
const emit = defineEmits<{
(e: 'change', value: number): void;
}>();
const { t } = useI18n(); const { t } = useI18n();

View File

@ -90,18 +90,18 @@
<template #default="{ detail, loading }"> <template #default="{ detail, loading }">
<div ref="wrapperRef" class="bg-white"> <div ref="wrapperRef" class="bg-white">
<div class="header relative h-[48px] border-b pl-2"> <div class="header relative h-[48px] border-b pl-2">
<div class="max-w-[calc(100%-72px)]" <div class="max-w-[calc(100%-72px)]">
><MsTab <MsTab
v-model:active-key="activeTab" v-model:active-key="activeTab"
:content-tab-list="tabSetting" :content-tab-list="tabSetting"
:get-text-func="getTotal" :get-text-func="getTotal"
class="no-content relative" class="no-content relative"
@change="clickMenu" @change="clickMenu"
/></div> />
</div>
<span class="display-setting h-full text-[var(--color-text-2)]" @click="showMenuSetting">{{ <span class="display-setting h-full text-[var(--color-text-2)]" @click="showMenuSetting">
t('caseManagement.featureCase.detailDisplaySetting') {{ t('caseManagement.featureCase.detailDisplaySetting') }}
}}</span> </span>
</div> </div>
<div> <div>
<div <div
@ -247,9 +247,9 @@
const commentInputIsActive = computed(() => commentInputRef.value?.isActive); const commentInputIsActive = computed(() => commentInputRef.value?.isActive);
const tabSetting = ref<TabItemType[]>([]); const tabSetting = ref<TabItemType[]>([]);
const activeTab = ref<string>('detail'); const activeTab = ref<string | number>('detail');
function clickMenu(key: string) { function clickMenu(key: string | number) {
activeTab.value = key; activeTab.value = key;
featureCaseStore.setActiveTab(key); featureCaseStore.setActiveTab(key);
switch (activeTab.value) { switch (activeTab.value) {

View File

@ -509,7 +509,7 @@
} }
} }
function changeTabInterceptor(newVal: string, oldVal: string, done: () => void) { function changeTabInterceptor(newVal: string | number, oldVal: string | number, done: () => void) {
if (oldVal === 'plan' && minderStore.minderUnsaved) { if (oldVal === 'plan' && minderStore.minderUnsaved) {
openModal({ openModal({
type: 'warning', type: 'warning',