fix(接口测试): 报告bug修复和补充部分细节

This commit is contained in:
xinxin.wu 2024-03-25 16:11:38 +08:00 committed by Craftsman
parent 44ab9a2cd8
commit 1c64577431
10 changed files with 322 additions and 97 deletions

View File

@ -3,7 +3,7 @@
<div class="relative mr-4">
<div class="absolute bottom-0 left-[30%] top-[35%] text-center">
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
<div class="text-[18px] font-medium">4</div>
<div class="text-[18px] font-medium">{{ totalCount }}</div>
</div>
<MsChart width="110px" height="110px" :options="props.options" />
</div>
@ -38,6 +38,12 @@
options: Record<string, any>;
legendData: LegendData[];
}>();
const totalCount = computed(() => {
return props.legendData.reduce((prev, item) => {
return prev + item.count;
}, 0);
});
</script>
<style scoped lang="less">

View File

@ -56,8 +56,8 @@
<div class="flex items-center">
<span class="count"> {{ getExcuteRate() }} %</span>
<a-divider direction="vertical" class="!h-[16px]" :margin="8"></a-divider>
<span>{{ getRequestEacuteCount }}</span>
<span class="mx-1 text-[var(--color-text-4)]">/ {{ getRequestTotalCount || 0 }}</span>
<span>{{ getIndicators(getRequestEacuteCount) }}</span>
<span class="mx-1 text-[var(--color-text-4)]">/ {{ getIndicators(getRequestTotalCount) }}</span>
</div>
</div>
<div class="time-card-item-rote">
@ -70,8 +70,17 @@
>{{ detail.assertionPassRate === 'Calculating' ? '-' : detail.assertionPassRate || '0.00' }}%</span
>
<a-divider direction="vertical" class="!h-[16px]" :margin="8"></a-divider>
<span>{{ addCommasToNumber(detail.assertionSuccessCount || 0) }}</span>
<span class="mx-1 text-[var(--color-text-4)]">/ {{ detail.assertionCount || 0 }}</span>
<span>{{
getIndicators(detail.assertionSuccessCount) === '-'
? '-'
: addCommasToNumber(detail.assertionSuccessCount || 0)
}}</span>
<span class="mx-1 text-[var(--color-text-4)]"
>/
{{
getIndicators(detail.assertionCount) === '-' ? '-' : addCommasToNumber(detail.assertionCount) || 0
}}</span
>
</div>
</div>
</div>
@ -80,20 +89,13 @@
<!-- 报告步骤分析结束 -->
<!-- 报告明细开始 -->
<div class="report-info">
<div class="mb-4 flex h-[36px] items-center justify-between">
<div class="flex items-center">
<div class="mr-2 font-medium leading-[36px]">{{ t('report.detail.api.reportDetail') }}</div>
<a-radio-group v-model:model-value="activeTab" type="button" size="small">
<a-radio v-for="item of methods" :key="item.value" :value="item.value">
{{ t(item.label) }}
</a-radio>
</a-radio-group>
</div>
<a-select v-model="condition" class="w-[240px]" :placeholder="t('report.detail.api.filterPlaceholder')">
<a-option :key="1" :value="1"> 1 </a-option>
</a-select>
</div>
<TiledList show-type="CASE" :active-type="activeTab" :report-detail="detail || []" />
<reportInfoHeader v-model:keyword="cascaderKeywords" v-model:active-tab="activeTab" />
<TiledList
:key-words="cascaderKeywords"
show-type="CASE"
:active-type="activeTab"
:report-detail="detail || []"
/>
</div>
<!-- 报告明细结束 -->
</div>
@ -104,6 +106,7 @@
import dayjs from 'dayjs';
import SetReportChart from './case/setReportChart.vue';
import reportInfoHeader from './step/reportInfoHeaders.vue';
import TiledList from './tiledList.vue';
import { useI18n } from '@/hooks/useI18n';
@ -111,6 +114,8 @@
import type { LegendData, ReportDetail } from '@/models/apiTest/report';
import { getIndicators } from '../utils';
const { t } = useI18n();
const props = defineProps<{
detailInfo?: ReportDetail;
@ -156,6 +161,8 @@
console: '',
});
const cascaderKeywords = ref<string>('');
const getTotalTime = computed(() => {
if (detail.value) {
const { endTime, startTime } = detail.value;
@ -166,16 +173,6 @@
}
return '-';
});
const methods = ref([
{
label: t('report.detail.api.tiledDisplay'),
value: 'tiled',
},
{
label: t('report.detail.api.tabDisplay'),
value: 'tab',
},
]);
const legendData = ref<LegendData[]>([]);
const charOptions = ref({
@ -237,7 +234,6 @@
},
});
const activeTab = ref<'tiled' | 'tab'>('tiled');
const condition = ref('');
function getExcuteRate() {
return 100 - Number(detail.value.requestPendingRate)
@ -301,7 +297,7 @@
return {
...item,
label: t(item.label),
count: detail.value[item.value] || 0,
count: detail.value[item.value] === 'Calculating' ? '-' : detail.value[item.value] || 0,
rote: detail.value[item.rateKey] === 'Calculating' ? '-' : detail.value[item.rateKey],
};
});

View File

@ -28,31 +28,31 @@
<!-- 总数 -->
<div class="countItem">
<span class="mr-2 text-[var(--color-text-4)]"> {{ t('report.detail.stepTotal') }}</span>
{{ detail.stepTotal || 0 }}
{{ getTotal() || 0 }}
</div>
<!-- 通过 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--success-6))]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.successCount') }}</div>
{{ detail.successCount || 0 }}
{{ getIndicators(detail.successCount) }}
</div>
<!-- 误报 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--warning-6))]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.fakeErrorCount') }}</div>
{{ detail.fakeErrorCount || 0 }}
{{ getIndicators(detail.fakeErrorCount) }}
</div>
<!-- 失败 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[rgb(var(--danger-6))]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.errorCount') }}</div>
{{ detail.errorCount || 0 }}
{{ getIndicators(detail.errorCount) }}
</div>
<!-- 未执行 -->
<div class="countItem">
<div class="mb-[2px] mr-[4px] h-[6px] w-[6px] rounded-full bg-[var(--color-text-input-border)]"></div>
<div class="mr-2 text-[var(--color-text-4)]">{{ t('report.detail.pendingCount') }}</div>
{{ detail.pendingCount || 0 }}
{{ getIndicators(detail.pendingCount) }}
</div>
</div>
<StepProgress :report-detail="detail" height="8px" radius="var(--border-radius-mini)" />
@ -88,12 +88,19 @@
</div>
<div class="flex items-center">
<span class="text-[18px] font-medium text-[var(--color-text-1)]"
>{{ detail.assertionPassRate || 0 }} <span>%</span></span
>{{ getIndicators(detail.assertionPassRate) }} <span>%</span></span
>
<a-divider direction="vertical" :margin="0" class="!mx-2 h-[16px]"></a-divider>
<span class="text-[var(--color-text-1)]">{{ addCommasToNumber(detail.assertionSuccessCount || 0) }}</span>
<span class="text-[var(--color-text-1)]">{{
getIndicators(detail.assertionSuccessCount) === '-'
? '-'
: addCommasToNumber(detail.assertionSuccessCount || 0)
}}</span>
<span class="text-[var(--color-text-4)]"
><span class="mx-1">/</span> {{ addCommasToNumber(detail.assertionCount) || 0 }}</span
><span class="mx-1">/</span>
{{
getIndicators(detail.assertionCount) === '-' ? '-' : addCommasToNumber(detail.assertionCount) || 0
}}</span
>
</div>
</div>
@ -106,7 +113,7 @@
<div class="relative mr-4">
<div class="absolute bottom-0 left-[30%] top-[35%] text-center">
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
<div class="text-[18px] font-medium">4</div>
<div class="text-[18px] font-medium">{{ getTotal() }}</div>
</div>
<MsChart width="110px" height="110px" :options="charOptions" />
</div>
@ -127,20 +134,8 @@
<!-- 报告步骤分析和请求分析结束 -->
<!-- 报告明细开始 -->
<div class="report-info">
<div class="mb-4 flex h-[36px] items-center justify-between">
<div class="flex items-center">
<div class="mr-2 font-medium leading-[36px]">{{ t('report.detail.api.reportDetail') }}</div>
<a-radio-group v-model:model-value="activeTab" type="button" size="small">
<a-radio v-for="item of methods" :key="item.value" :value="item.value">
{{ t(item.label) }}
</a-radio>
</a-radio-group>
</div>
<a-select v-model="condition" class="w-[240px]" :placeholder="t('report.detail.api.filterPlaceholder')">
<a-option :key="1" :value="1"> 1 </a-option>
</a-select>
</div>
<TiledList show-type="API" :active-type="activeTab" :report-detail="detail || []" />
<reportInfoHeader v-model:keyword="cascaderKeywords" v-model:active-tab="activeTab" />
<TiledList :key-words="cascaderKeywords" show-type="API" :active-type="activeTab" :report-detail="detail || []" />
</div>
<!-- 报告明细结束 -->
</div>
@ -151,6 +146,7 @@
import dayjs from 'dayjs';
import MsChart from '@/components/pure/chart/index.vue';
import reportInfoHeader from './step/reportInfoHeaders.vue';
import StepProgress from './stepProgress.vue';
import TiledList from './tiledList.vue';
@ -159,6 +155,8 @@
import type { LegendData, ReportDetail } from '@/models/apiTest/report';
import { getIndicators } from '../utils';
const { t } = useI18n();
const props = defineProps<{
detailInfo?: ReportDetail;
@ -204,7 +202,7 @@
console: '',
});
const timeUnits = ['ms', 'sec', 'min', 'hr'];
const cascaderKeywords = ref<string>('');
const getTotalTime = computed(() => {
if (detail.value) {
@ -216,16 +214,10 @@
}
return '-';
});
const methods = ref([
{
label: t('report.detail.api.tiledDisplay'),
value: 'tiled',
},
{
label: t('report.detail.api.tabDisplay'),
value: 'tab',
},
]);
function getTotal() {
const { errorCount, successCount, fakeErrorCount, pendingCount } = detail.value;
return errorCount + successCount + fakeErrorCount + pendingCount;
}
const legendData = ref<LegendData[]>([]);
const charOptions = ref({
@ -287,7 +279,6 @@
},
});
const activeTab = ref<'tiled' | 'tab'>('tiled');
const condition = ref('');
function initOptionsData() {
const tempArr = [
@ -334,7 +325,7 @@
return {
...item,
label: t(item.label),
count: detail.value[item.value] || 0,
count: detail.value[item.value] === 'Calculating' ? '-' : detail.value[item.value] || 0,
rote: detail.value[item.rateKey] === 'Calculating' ? '-' : detail.value[item.rateKey],
};
});

View File

@ -0,0 +1,93 @@
<template>
<div class="mb-4 flex h-[36px] items-center justify-between">
<div class="flex items-center">
<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 v-for="item of methods" :key="item.value" :value="item.value">
{{ t(item.label) }}
</a-radio>
</a-radio-group>
</div>
<MsCascader
v-model:model-value="innerKeyword"
mode="native"
option-size="small"
:multiple="false"
class="w-[240px]"
:options="cascaderOptions || []"
:placeholder="t('report.detail.api.filterPlaceholder')"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import MsCascader from '@/components/business/ms-cascader/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { ScenarioStepType } from '@/enums/apiEnum';
import type { CascaderOption } from '@arco-design/web-vue';
const { t } = useI18n();
const props = defineProps<{
activeTab: 'tiled' | 'tab';
keyword: string;
}>();
const emit = defineEmits(['update:activeTab', 'update:keyword']);
const innerActiveTab = useVModel(props, 'activeTab', emit);
const innerKeyword = useVModel(props, 'keyword', emit);
const methods = ref([
{
label: t('report.detail.api.tiledDisplay'),
value: 'tiled',
},
{
label: t('report.detail.api.tabDisplay'),
value: 'tab',
},
]);
const createChildOption = (key: string) => [
{
value: `${key}-SUCCESS`,
label: t(`report.detail.successCount`),
},
{
value: `${key}-FAKE_ERROR`,
label: t(`report.detail.fakeErrorCount`),
},
{
value: `${key}-ERROR`,
label: t(`report.detail.errorCount`),
},
{
value: `${key}-PENDING`,
label: t(`report.detail.pendingCount`),
},
{
value: `${key}-scriptIdentifier`,
label: t(`report.detail.api.scriptError`),
},
];
const cascaderOptions = ref<CascaderOption[]>([
{
value: ScenarioStepType.API_SCENARIO,
label: t('report.detail.api.scenario'),
children: createChildOption(ScenarioStepType.API_SCENARIO),
},
{
value: 'REQUEST',
label: t('report.detail.api.request'),
children: createChildOption(ScenarioStepType.CUSTOM_REQUEST),
},
]);
</script>
<style scoped></style>

View File

@ -0,0 +1,48 @@
<template>
<MsTag theme="light" :type="getStatus"> {{ t(statusMap[props.status || 'PENDING'].label) }}</MsTag>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
status: string;
}>();
const statusMap = {
PENDING: {
label: 'report.detail.pendingCount',
value: 'PENDING',
type: 'default',
},
FAKE_ERROR: {
label: 'report.detail.fakeErrorCount',
value: 'FAKE_ERROR',
type: 'warning',
},
ERROR: {
label: 'report.failure',
value: 'ERROR',
type: 'danger',
},
SUCCESS: {
label: 'report.detail.successCount',
value: 'SUCCESS',
type: 'success',
},
};
const getStatus = computed(() => {
if (props.status) {
return statusMap[props.status].type;
}
return statusMap.PENDING.type;
});
</script>
<style scoped></style>

View File

@ -4,6 +4,7 @@
<MsTree
ref="treeRef"
v-model:selected-keys="selectedKeys"
v-model:expanded-keys="innerExpandedKeys"
v-model:data="steps"
:expand-all="props.expandAll"
:field-names="{ title: 'name', key: 'stepId', children: 'children' }"
@ -75,9 +76,27 @@
</a-tooltip>
</div>
<div class="flex">
<MsTag class="cursor-pointer" :type="step.status === 'SUCCESS' ? 'success' : 'danger'" theme="light">
{{ step.status === 'SUCCESS' ? t('report.detail.api.pass') : t('report.detail.api.resError') }}
</MsTag>
<stepStatus v-if="step.status" :status="step.status" />
<!-- 脚本报错 -->
<a-popover position="left" content-class="response-popover-content">
<MsTag
v-if="step.scriptIdentifier"
type="primary"
theme="light"
:self-style="{
color: 'rgb(var(--primary-3))',
background: 'rgb(var(--primary-1))',
}"
>
<template #icon>
<MsIcon type="icon-icon_info_outlined" class="mx-1 !text-[rgb(var(--primary-3))]" size="16" />
<span class="!text-[rgb(var(--primary-3))]">{{ t('report.detail.api.scriptErrorTip') }}</span>
</template>
</MsTag>
<template #content>
<div>{{ step.scriptIdentifier }}</div>
</template>
</a-popover>
<span class="statusCode mx-2">
<div class="mr-2"> {{ t('report.detail.api.statusCode') }}</div>
<a-popover position="left" content-class="response-popover-content">
@ -97,14 +116,22 @@
<span class="resTime">
{{ t('report.detail.api.responseTime') }}
<span class="resTimeCount ml-2"
>{{ formatDuration(step.requestTime).split('-')[0]
}}{{ formatDuration(step.requestTime).split('-')[1] }}</span
>{{ step.requestTime ? formatDuration(step.requestTime).split('-')[0] : '-'
}}{{ step.requestTime ? formatDuration(step.requestTime).split('-')[1] : 'ms' }}</span
></span
>
<span class="resSize">
{{ t('report.detail.api.responseSize') }}
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
>
<a-popover position="left" content-class="response-popover-content">
<span class="resSize">
{{ t('report.detail.api.responseSize') }}
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
>
<template #content>
<span class="resSize">
{{ t('report.detail.api.responseSize') }}
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
>
</template>
</a-popover>
</div>
</div>
<div v-if="!step.fold" class="line"></div>
@ -142,6 +169,7 @@
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import MsTree from '@/components/business/ms-tree/index.vue';
import { MsTreeExpandedData } from '@/components/business/ms-tree/types';
import stepStatus from './stepStatus.vue';
import StepDetailContent from '@/views/api-test/components/requestComposition/response/result/index.vue';
import ConditionStatus from '@/views/api-test/report/component/conditionStatus.vue';
@ -160,6 +188,7 @@
console?: string;
environmentName?: string;
reportId?: string;
expandedKeys: (string | number)[];
}>();
const loading = ref(false);
@ -170,6 +199,9 @@
const steps = defineModel<ScenarioItemType[]>('steps', {
required: true,
});
const innerExpandedKeys = defineModel<(string | number)[]>('expandedKeys', {
required: false,
});
/**
* 处理步骤展开折叠

View File

@ -5,17 +5,11 @@
'border border-solid border-[var(--color-text-n8)]': props.showType === 'API',
}"
>
<!-- <a-scrollbar
:style="{
overflow: 'auto',
height: 'calc(100vh - 424px)',
width: '100%',
}"
> -->
<!-- 步骤树 -->
<stepTree
ref="stepTreeRef"
v-model:steps="tiledList"
v-model:expandedKeys="expandedKeys"
:show-type="props.showType"
:active-type="props.activeType"
:expand-all="isExpandAll"
@ -24,7 +18,6 @@
:report-id="props.reportDetail.id"
@detail="showDetail"
/>
<!-- </a-scrollbar> -->
<!-- 步骤抽屉 -->
<StepDrawer
v-model:visible="showStepDrawer"
@ -41,13 +34,11 @@
<script setup lang="ts">
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import { cloneDeep, debounce } from 'lodash-es';
import StepDrawer from './step/stepDrawer.vue';
import StepTree from './step/stepTree.vue';
import { addLevelToTree } from '@/utils';
import type { ReportDetail, ScenarioItemType } from '@/models/apiTest/report';
import { addFoldField } from '../utils';
@ -56,6 +47,7 @@
reportDetail: ReportDetail;
activeType: 'tiled' | 'tab'; // |tab
showType: 'API' | 'CASE'; // |
keyWords: string;
}>();
const tiledList = ref<ScenarioItemType[]>([]);
@ -77,19 +69,74 @@
activeStepIndex.value = item.sort;
}
onMounted(() => {
tiledList.value = addLevelToTree<ScenarioItemType>(tiledList.value) as ScenarioItemType[];
});
const expandedKeys = ref<(string | number)[]>([]);
const originTreeData = ref<ScenarioItemType[]>([]);
watchEffect(() => {
if (props.reportDetail && props.reportDetail.children) {
tiledList.value = props.reportDetail.children || [];
tiledList.value = addLevelToTree<ScenarioItemType>(tiledList.value) as ScenarioItemType[];
tiledList.value.forEach((item) => {
addFoldField(item);
function initStepTree() {
tiledList.value = cloneDeep(props.reportDetail.children) || [];
tiledList.value.forEach((item) => {
addFoldField(item);
});
originTreeData.value = cloneDeep(tiledList.value);
}
watch(
() => props.reportDetail,
(val) => {
if (val && val.children) {
initStepTree();
}
},
{ deep: true, immediate: true }
);
function searchStep() {
const splitLevel = props.keyWords.split('-');
const stepTypeStatus = splitLevel[1];
const stepType = splitLevel[0] !== 'REQUEST' ? ['API', 'API_CASE', 'CUSTOM_REQUEST'] : splitLevel[0];
const search = (_data: ScenarioItemType[]) => {
const result: ScenarioItemType[] = [];
_data.forEach((item) => {
if (
(stepType.includes(item.stepType) && item.status && item.status.includes(stepTypeStatus)) ||
(stepTypeStatus && stepTypeStatus.includes('scriptIdentifier') && item.scriptIdentifier)
) {
result.push({ ...item, expanded: true });
} else if (item.children) {
const filterData = search(item.children);
if (filterData.length) {
result.push({
...item,
expanded: true,
children: filterData,
});
}
}
});
result.forEach((item: any) => expandedKeys.value.push(item.stepId)); //
return result;
};
return search(originTreeData.value);
}
//
const updateDebouncedSearch = debounce(() => {
if (props.keyWords) {
tiledList.value = searchStep();
}
});
}, 300);
watch(
() => props.keyWords,
(val) => {
if (!val) {
initStepTree();
} else {
updateDebouncedSearch();
}
}
);
</script>
<style scoped lang="less">

View File

@ -66,4 +66,8 @@ export default {
'report.detail.api.extract': 'extract',
'report.detail.api.assert': 'assertion',
'report.detail.api.subRequest': 'Sub-request',
'report.detail.api.scriptError': 'Script error',
'report.detail.api.scriptErrorTip': 'Script error',
'report.detail.api.scenario': 'scenario',
'report.detail.api.request': 'request',
};

View File

@ -65,4 +65,8 @@ export default {
'report.detail.api.extract': '提取',
'report.detail.api.assert': '断言',
'report.detail.api.subRequest': '子请求',
'report.detail.api.scriptError': '脚本错误',
'report.detail.api.scriptErrorTip': '脚本报错',
'report.detail.api.scenario': '场景',
'report.detail.api.request': '请求',
};

View File

@ -11,4 +11,8 @@ export function addFoldField(node: ScenarioItemType) {
}
}
// 是否为计算中
export function getIndicators(value: any) {
return value === 'Calculating' ? '-' : value || 0;
}
export default {};