fix: 修复缺陷管理&用例管理&报告相关问题
This commit is contained in:
parent
890429bb6e
commit
1b4b6f0076
|
@ -51,7 +51,8 @@
|
|||
<div>{{ t('apiTestDebug.expressionTip2') }}</div>
|
||||
<div>{{ t('apiTestDebug.expressionTip3') }}</div>
|
||||
</template>
|
||||
<!-- <MsIcon
|
||||
<MsIcon
|
||||
v-if="props.showExtraction"
|
||||
:disabled="props.disabled"
|
||||
type="icon-icon_flashlamp"
|
||||
:size="15"
|
||||
|
@ -61,7 +62,7 @@
|
|||
: 'ms-params-input-suffix-icon'
|
||||
"
|
||||
@click.stop="() => showFastExtraction(record, RequestExtractExpressionEnum.JSON_PATH)"
|
||||
/> -->
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
|
@ -146,7 +147,8 @@
|
|||
<div>{{ t('apiTestDebug.expressionTip2') }}</div>
|
||||
<div>{{ t('apiTestDebug.expressionTip3') }}</div>
|
||||
</template>
|
||||
<!-- <MsIcon
|
||||
<MsIcon
|
||||
v-if="props.showExtraction"
|
||||
type="icon-icon_flashlamp"
|
||||
:disabled="props.disabled"
|
||||
:size="15"
|
||||
|
@ -156,7 +158,7 @@
|
|||
: 'ms-params-input-suffix-icon'
|
||||
"
|
||||
@click.stop="() => showFastExtraction(record, RequestExtractExpressionEnum.X_PATH)"
|
||||
/> -->
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
|
@ -295,6 +297,7 @@
|
|||
<div>{{ t('apiTestDebug.expressionTip3') }}</div>
|
||||
</template>
|
||||
<MsIcon
|
||||
v-if="props.showExtraction"
|
||||
type="icon-icon_flashlamp"
|
||||
:size="15"
|
||||
:class="
|
||||
|
@ -349,7 +352,7 @@
|
|||
import { TableColumnData, TableData } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import { statusCodeOptions } from '@/components/pure/ms-advance-filter';
|
||||
import { EQUAL, statusCodeOptions } from '@/components/pure/ms-advance-filter';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import { TableOperationColumn } from '../../ms-user-group-comp/authTable.vue';
|
||||
import fastExtraction from '@/views/api-test/components/fastExtraction/index.vue';
|
||||
|
@ -389,11 +392,17 @@
|
|||
[key: string]: any;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: Param;
|
||||
response?: string;
|
||||
disabled?: boolean;
|
||||
}>();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: Param;
|
||||
response?: string;
|
||||
disabled?: boolean;
|
||||
showExtraction?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showExtraction: false,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:data', data: ExecuteConditionProcessor): void;
|
||||
|
@ -486,11 +495,9 @@
|
|||
// json默认值
|
||||
const jsonPathDefaultParamItem = {
|
||||
expression: '',
|
||||
condition: '',
|
||||
condition: EQUAL.value,
|
||||
expectedValue: '',
|
||||
enable: true,
|
||||
moreSettingPopoverVisible: false,
|
||||
disable: true,
|
||||
};
|
||||
// xpath默认值
|
||||
const xPathDefaultParamItem = {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
:disabled="props.disabled"
|
||||
:step="100"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
mode="button"
|
||||
@blur="
|
||||
emit('change', {
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
v-model:data="getCurrentItemState"
|
||||
:disabled="props.disabled"
|
||||
:response="props.response"
|
||||
:show-extraction="props.showExtraction"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<!-- 响应时间 -->
|
||||
|
@ -176,6 +177,7 @@
|
|||
assertionConfig?: ExecuteAssertionConfig; // 是否开启全局
|
||||
response?: string; // 响应内容
|
||||
disabled?: boolean;
|
||||
showExtraction?: boolean; // 是否显示提取
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
* @description 用于自己扩展功能的form-create
|
||||
*/
|
||||
import { ref, watch } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import { FieldTypeFormRules } from '@/components/pure/ms-form-create/form-create';
|
||||
import JiraKey from './comp/jiraKey.vue';
|
||||
|
@ -36,7 +37,7 @@
|
|||
api: any; // 表单对象
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:api', 'update', 'update:form-item']);
|
||||
const emit = defineEmits(['update:api', 'update', 'update:formItem', 'change']);
|
||||
|
||||
const fApi = computed({
|
||||
get() {
|
||||
|
@ -47,6 +48,8 @@
|
|||
},
|
||||
});
|
||||
|
||||
const innerFormItem = useVModel(props, 'formItem', emit);
|
||||
|
||||
// 规定好的字段格式
|
||||
const formItems = ref<FormItem[]>([...props.formRule]);
|
||||
// 控制器的选项
|
||||
|
@ -281,15 +284,16 @@
|
|||
},
|
||||
};
|
||||
|
||||
function changeHandler(value: any) {
|
||||
function changeHandler(value: any, defaultValue: any, formRuleItem: FormRuleItem, api: any) {
|
||||
fApi.value.validateField(value);
|
||||
emit('change', defaultValue, formRuleItem, api);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => formRuleList.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
emit('update:form-item', formRuleList.value);
|
||||
innerFormItem.value = val;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -93,12 +93,14 @@
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
const isInit = ref<boolean>(true);
|
||||
watch(
|
||||
() => selectValue.value,
|
||||
(val) => {
|
||||
selectValue.value = val;
|
||||
emit('update:model-value', val);
|
||||
if (!isInit.value) {
|
||||
emit('update:model-value', val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -115,6 +117,7 @@
|
|||
if (props.inputSearch && props.optionMethod) {
|
||||
getLinksItem();
|
||||
}
|
||||
isInit.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -198,8 +198,8 @@ export interface ResponseDocumentAssertion {
|
|||
}
|
||||
// 断言-断言列表的断言子项
|
||||
export interface ResponseAssertionItem {
|
||||
condition: RequestAssertionConditionType;
|
||||
expectedValue: string;
|
||||
condition?: RequestAssertionConditionType;
|
||||
expectedValue?: string;
|
||||
expression: string;
|
||||
enable?: boolean;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@ import {
|
|||
ExecuteRequestFormBodyFormValue,
|
||||
KeyValueParam,
|
||||
RequestTaskResult,
|
||||
ResponseAssertionItem,
|
||||
ResponseDefinition,
|
||||
} from '@/models/apiTest/common';
|
||||
import {
|
||||
RequestAssertionCondition,
|
||||
RequestBodyFormat,
|
||||
RequestCaseStatus,
|
||||
RequestContentTypeEnum,
|
||||
|
@ -157,3 +159,17 @@ export const caseStatusOptions = [
|
|||
{ label: 'apiTestManagement.deprecate', value: RequestCaseStatus.DEPRECATED },
|
||||
{ label: 'apiTestManagement.done', value: RequestCaseStatus.DONE },
|
||||
];
|
||||
|
||||
// 断言 参数表格默认行的值
|
||||
export const defaultAssertParamsItem: ResponseAssertionItem = {
|
||||
expression: '',
|
||||
condition: RequestAssertionCondition.EQUALS,
|
||||
expectedValue: '',
|
||||
enable: true,
|
||||
};
|
||||
|
||||
// 断言xpath & reg
|
||||
export const defaultAssertXpathParamsItem: ResponseAssertionItem = {
|
||||
expression: '',
|
||||
enable: true,
|
||||
};
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
v-model:params="requestVModel.children[0].assertionConfig.assertions"
|
||||
:disabled="props.disabledExceptParam"
|
||||
:is-definition="false"
|
||||
:show-extraction="true"
|
||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||
/>
|
||||
<auth
|
||||
|
|
|
@ -264,6 +264,7 @@
|
|||
:is-definition="props.isDefinition"
|
||||
:response="requestVModel.response?.requestResults[0]?.responseResult.body"
|
||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||
:show-extraction="true"
|
||||
/>
|
||||
<auth
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.AUTH"
|
||||
|
@ -580,6 +581,8 @@
|
|||
import {
|
||||
casePriorityOptions,
|
||||
caseStatusOptions,
|
||||
defaultAssertParamsItem,
|
||||
defaultAssertXpathParamsItem,
|
||||
defaultBodyParamsItem,
|
||||
defaultHeaderParamsItem,
|
||||
defaultKeyValueParamItem,
|
||||
|
@ -1210,6 +1213,39 @@
|
|||
requestName = requestVModel.value.isNew ? saveModalForm.value.name : requestVModel.value.name;
|
||||
requestModuleId = requestVModel.value.isNew ? saveModalForm.value.moduleId : requestVModel.value.moduleId;
|
||||
}
|
||||
|
||||
// 处理断言参数
|
||||
const { assertionConfig } = requestVModel.value.children[0];
|
||||
const assertionList = assertionConfig.assertions.map((assertItem: any) => {
|
||||
const bodyAssertionDataByTypeList = filterKeyValParams(
|
||||
assertItem.bodyAssertionDataByType.assertions,
|
||||
defaultAssertParamsItem,
|
||||
isExecute
|
||||
).validParams;
|
||||
return {
|
||||
...assertItem,
|
||||
bodyAssertionDataByType: {
|
||||
...assertItem.bodyAssertionDataByType,
|
||||
assertions: bodyAssertionDataByTypeList,
|
||||
},
|
||||
regexAssertion: {
|
||||
...assertItem.regexAssertion,
|
||||
assertions: filterKeyValParams(assertItem.regexAssertion.assertions, defaultAssertXpathParamsItem, isExecute)
|
||||
.validParams,
|
||||
},
|
||||
xpathAssertion: {
|
||||
...assertItem.xpathAssertion,
|
||||
assertions: filterKeyValParams(assertItem.xpathAssertion.assertions, defaultAssertXpathParamsItem, isExecute)
|
||||
.validParams,
|
||||
},
|
||||
jsonPathAssertion: {
|
||||
...assertItem.jsonPathAssertion,
|
||||
assertions: filterKeyValParams(assertItem.jsonPathAssertion.assertions, defaultAssertParamsItem, isExecute)
|
||||
.validParams,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: requestVModel.value.id.toString(),
|
||||
reportId: reportId.value,
|
||||
|
@ -1226,7 +1262,10 @@
|
|||
children: [
|
||||
{
|
||||
polymorphicName: 'MsCommonElement', // 协议多态名称,写死MsCommonElement
|
||||
assertionConfig: requestVModel.value.children[0].assertionConfig,
|
||||
assertionConfig: {
|
||||
...assertionConfig,
|
||||
assertions: assertionList,
|
||||
},
|
||||
postProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].postProcessorConfig),
|
||||
preProcessorConfig: filterConditionsSqlValidParams(requestVModel.value.children[0].preProcessorConfig),
|
||||
},
|
||||
|
@ -1658,15 +1697,10 @@
|
|||
}
|
||||
}
|
||||
.url-input-tip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
margin-left: 250px;
|
||||
color: rgb(var(--danger-6));
|
||||
margin-top: 2px 0 250px;
|
||||
font-size: 12px;
|
||||
color: rgb(var(--danger-6));
|
||||
line-height: 16px;
|
||||
margin-top: 2px;
|
||||
@apply flex flex-col flex-nowrap items-center justify-start;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,114 +1,115 @@
|
|||
<template>
|
||||
<div v-if="isShowLoopControl" class="my-4 flex items-center justify-start" @click.stop="() => {}">
|
||||
<a-pagination
|
||||
v-model:page-size="controlPageSize"
|
||||
v-model:current="controlCurrent"
|
||||
:total="controlTotal"
|
||||
size="mini"
|
||||
show-total
|
||||
:show-jumper="controlTotal > 5"
|
||||
@change="loadControlLoop"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-4 flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4">
|
||||
<div class="font-medium">
|
||||
<span
|
||||
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }"
|
||||
@click.stop="setActiveType('ResContent')"
|
||||
>{{ t('report.detail.api.resContent') }}</span
|
||||
>
|
||||
<span
|
||||
v-if="total > 0"
|
||||
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'SubRequest' }"
|
||||
@click.stop="setActiveType('SubRequest')"
|
||||
>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
{{ t('report.detail.api.subRequest') }}</span
|
||||
>
|
||||
<div class="flex h-[calc(100%-8px)] flex-col" @click.stop="() => {}">
|
||||
<div v-if="isShowLoopControl" class="my-4 flex items-center justify-start" @click.stop="() => {}">
|
||||
<a-pagination
|
||||
v-model:page-size="controlPageSize"
|
||||
v-model:current="controlCurrent"
|
||||
:total="controlTotal"
|
||||
size="mini"
|
||||
show-total
|
||||
:show-jumper="controlTotal > 5"
|
||||
@change="loadControlLoop"
|
||||
/>
|
||||
<!-- <loopPagination v-model:current-loop="controlCurrent" :loop-total="controlTotal" /> -->
|
||||
</div>
|
||||
<div class="flex flex-row gap-6 text-center">
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div class="one-line-text max-w-[200px]" :style="{ color: statusCodeColor }">
|
||||
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||
<div :style="{ color: statusCodeColor }">
|
||||
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||
<div class="mt-4 flex w-full items-center justify-between rounded bg-[var(--color-text-n9)] p-4">
|
||||
<div class="font-medium">
|
||||
<span
|
||||
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'ResContent' }"
|
||||
@click.stop="setActiveType('ResContent')"
|
||||
>{{ t('report.detail.api.resContent') }}</span
|
||||
>
|
||||
<span
|
||||
v-if="total > 0"
|
||||
:class="{ 'text-[rgb(var(--primary-5))]': activeType === 'SubRequest' }"
|
||||
@click.stop="setActiveType('SubRequest')"
|
||||
>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
{{ t('report.detail.api.subRequest') }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row gap-6 text-center">
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div class="one-line-text max-w-[200px]" :style="{ color: statusCodeColor }">
|
||||
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||
<div :style="{ color: statusCodeColor }">
|
||||
{{ activeStepDetail?.content?.responseResult.responseCode || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-popover position="left" content-class="w-[400px]">
|
||||
<div class="one-line-text text-[rgb(var(--success-7))]"> {{ timingInfo?.responseTime || 0 }} ms </div>
|
||||
<template #content>
|
||||
<div class="mb-[8px] flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseTime') }}</div>
|
||||
<div class="text-[rgb(var(--success-7))]"> {{ timingInfo?.responseTime }} ms </div>
|
||||
</div>
|
||||
<responseTimeLine v-if="timingInfo" :response-timing="timingInfo" />
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div class="one-line-text text-[rgb(var(--success-7))]">
|
||||
{{ activeStepDetail?.content?.responseResult.responseSize || '-' }} bytes
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseSize') }}</div>
|
||||
<div class="one-line-text text-[rgb(var(--success-7))]">
|
||||
{{ activeStepDetail?.content?.responseResult.responseSize }} bytes
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-popover position="left" content-class="w-[400px]">
|
||||
<div class="one-line-text text-[rgb(var(--success-7))]"> {{ timingInfo?.responseTime || 0 }} ms </div>
|
||||
<template #content>
|
||||
<div class="mb-[8px] flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseTime') }}</div>
|
||||
<div class="text-[rgb(var(--success-7))]"> {{ timingInfo?.responseTime }} ms </div>
|
||||
</div>
|
||||
<responseTimeLine v-if="timingInfo" :response-timing="timingInfo" />
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div class="one-line-text text-[rgb(var(--success-7))]">
|
||||
{{ activeStepDetail?.content?.responseResult.responseSize || '-' }} bytes
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text max-w-[150px]">{{
|
||||
props.environmentName
|
||||
}}</div>
|
||||
<template #content>
|
||||
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text">{{
|
||||
<template #content>
|
||||
<div class="flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.responseSize') }}</div>
|
||||
<div class="one-line-text text-[rgb(var(--success-7))]">
|
||||
{{ activeStepDetail?.content?.responseResult.responseSize }} bytes
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text max-w-[150px]">{{
|
||||
props.environmentName
|
||||
}}</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<template #content>
|
||||
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text">{{
|
||||
props.environmentName
|
||||
}}</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeType === 'SubRequest'" class="my-4 flex justify-start">
|
||||
<MsPagination
|
||||
v-model:page-size="pageSize"
|
||||
v-model:current="current"
|
||||
:total="total"
|
||||
size="mini"
|
||||
@change="loadLoop"
|
||||
/>
|
||||
</div>
|
||||
<!-- 平铺 -->
|
||||
<TiledDisplay
|
||||
v-if="props.mode === 'tiled'"
|
||||
:menu-list="responseCompositionTabList"
|
||||
:request-result="activeStepDetailCopy?.content"
|
||||
:console="props.console"
|
||||
:is-definition="props.isDefinition"
|
||||
:report-id="props.reportId"
|
||||
/>
|
||||
<!-- 响应内容tab -->
|
||||
<a-spin
|
||||
v-else
|
||||
:loading="loading"
|
||||
:class="[props.isResponseModel ? 'h-full w-full' : 'h-[calc(100%-35px)] w-full px-[18px] pb-[18px]']"
|
||||
>
|
||||
<result
|
||||
v-model:active-tab="activeTab"
|
||||
<div v-if="activeType === 'SubRequest'" class="my-4 flex justify-start">
|
||||
<MsPagination
|
||||
v-model:page-size="pageSize"
|
||||
v-model:current="current"
|
||||
:total="total"
|
||||
size="mini"
|
||||
@change="loadLoop"
|
||||
/>
|
||||
</div>
|
||||
<!-- 平铺 -->
|
||||
<TiledDisplay
|
||||
v-if="props.mode === 'tiled'"
|
||||
:menu-list="responseCompositionTabList"
|
||||
:request-result="activeStepDetailCopy?.content"
|
||||
:console="props.console"
|
||||
:is-http-protocol="false"
|
||||
:request-url="activeStepDetail?.content.url"
|
||||
is-definition
|
||||
:is-priority-local-exec="false"
|
||||
:is-definition="props.isDefinition"
|
||||
:report-id="props.reportId"
|
||||
/>
|
||||
</a-spin>
|
||||
<!-- 响应内容tab -->
|
||||
<div v-else class="h-[calc(100%-8px)]">
|
||||
<a-spin :loading="loading" class="h-[calc(100%-8px)] w-full pb-1">
|
||||
<result
|
||||
v-model:active-tab="activeTab"
|
||||
:request-result="activeStepDetailCopy?.content"
|
||||
:console="props.console"
|
||||
:is-http-protocol="false"
|
||||
:request-url="activeStepDetail?.content.url"
|
||||
is-definition
|
||||
:is-priority-local-exec="false"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -116,18 +117,19 @@
|
|||
import { useRoute } from 'vue-router';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||
import TiledDisplay from './tiledDisplay.vue';
|
||||
import result from '@/views/api-test/components/requestComposition/response/result.vue';
|
||||
import responseTimeLine from '@/views/api-test/components/responseTimeLine.vue';
|
||||
import loopPagination from '@/views/api-test/scenario/components/common/customApiDrawer.vue';
|
||||
|
||||
import { reportCaseStepDetail, reportStepDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { findNodeByKey, formatDuration } from '@/utils';
|
||||
|
||||
import type { ReportStepDetail, ReportStepDetailItem, ScenarioItemType } from '@/models/apiTest/report';
|
||||
import { ResponseComposition, ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
const TiledDisplay = defineAsyncComponent(() => import('./tiledDisplay.vue'));
|
||||
const responseTimeLine = defineAsyncComponent(() => import('@/views/api-test/components/responseTimeLine.vue'));
|
||||
const MsPagination = defineAsyncComponent(() => import('@/components/pure/ms-pagination/index'));
|
||||
|
||||
const props = defineProps<{
|
||||
mode: 'tiled' | 'tab'; // 平铺 | tab形式
|
||||
stepItem?: ScenarioItemType; // 步骤详情
|
||||
|
@ -312,10 +314,10 @@
|
|||
);
|
||||
});
|
||||
|
||||
const controlCurrent = ref(1);
|
||||
const controlCurrent = ref<number>(1);
|
||||
const controlTotal = computed(() => {
|
||||
if (props.stepItem?.children) {
|
||||
return props.stepItem.children.length;
|
||||
return props.stepItem.children.length || 0;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="flex min-h-[110px] items-center">
|
||||
<div class="relative mr-4">
|
||||
<div class="absolute bottom-0 left-[30%] top-[35%] text-center">
|
||||
<div class="charts absolute text-center">
|
||||
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
|
||||
<div class="text-[18px] font-medium">{{ props.requestTotal }}</div>
|
||||
</div>
|
||||
|
@ -53,4 +53,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.charts {
|
||||
top: 30%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -249,6 +249,7 @@
|
|||
color: '#00C261',
|
||||
class: 'bg-[rgb(var(--success-6))]',
|
||||
rateKey: 'requestPassRate',
|
||||
key: 'SUCCESS',
|
||||
},
|
||||
{
|
||||
label: 'report.detail.api.misstatement',
|
||||
|
@ -256,6 +257,7 @@
|
|||
color: '#FFC14E',
|
||||
class: 'bg-[rgb(var(--warning-6))]',
|
||||
rateKey: 'requestFakeErrorRate',
|
||||
key: 'FAKE_ERROR',
|
||||
},
|
||||
{
|
||||
label: 'report.detail.api.error',
|
||||
|
@ -263,6 +265,7 @@
|
|||
color: '#ED0303',
|
||||
class: 'bg-[rgb(var(--danger-6))]',
|
||||
rateKey: 'requestErrorRate',
|
||||
key: 'ERROR',
|
||||
},
|
||||
{
|
||||
label: 'report.detail.api.pending',
|
||||
|
@ -270,14 +273,16 @@
|
|||
color: '#D4D4D8',
|
||||
class: 'bg-[var(--color-text-input-border)]',
|
||||
rateKey: 'requestPendingRate',
|
||||
key: 'PENDING',
|
||||
},
|
||||
];
|
||||
let validArr;
|
||||
if (props?.detailInfo?.integrated) {
|
||||
validArr = cloneDeep(tempArr);
|
||||
} else {
|
||||
validArr = props?.detailInfo?.status === 'SUCCESS' ? [tempArr[0]] : [tempArr[2]];
|
||||
validArr = tempArr.filter((e) => e.key === props?.detailInfo?.status);
|
||||
}
|
||||
|
||||
charOptions.value.series.data = validArr.map((item: any) => {
|
||||
return {
|
||||
value: detail.value[item.value] || 0,
|
||||
|
@ -287,6 +292,7 @@
|
|||
},
|
||||
};
|
||||
});
|
||||
|
||||
legendData.value = validArr.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
|
|
|
@ -50,8 +50,10 @@
|
|||
</MsButton> -->
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ detail }">
|
||||
<CaseReportCom :detail-info="detail" />
|
||||
<template #default="{ loading }">
|
||||
<a-spin class="h-full w-full" :loading="loading">
|
||||
<CaseReportCom :detail-info="reportStepDetail" />
|
||||
</a-spin>
|
||||
</template>
|
||||
</MsDetailDrawer>
|
||||
</template>
|
||||
|
@ -103,7 +105,7 @@
|
|||
const innerReportId = ref(props.reportId);
|
||||
const detailDrawerRef = ref();
|
||||
|
||||
const reportStepDetail = ref<ReportDetail>({
|
||||
const initReportDetail = {
|
||||
id: '',
|
||||
name: '', // 报告名称
|
||||
testPlanId: '',
|
||||
|
@ -141,6 +143,10 @@
|
|||
children: [], // 步骤列表
|
||||
stepTotal: 0, // 步骤总数
|
||||
console: '',
|
||||
};
|
||||
|
||||
const reportStepDetail = ref<ReportDetail>({
|
||||
...initReportDetail,
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -185,8 +191,22 @@
|
|||
// 详情
|
||||
function loadedReport(detail: ReportDetail) {
|
||||
innerReportId.value = detail.id;
|
||||
reportStepDetail.value = { ...initReportDetail };
|
||||
reportStepDetail.value = cloneDeep(detail);
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
detailDrawerRef.value?.destroy();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => showDrawer.value,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
reportStepDetail.value = { ...initReportDetail };
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
:table-data="props.tableData"
|
||||
:page-change="props.pageChange"
|
||||
show-full-screen
|
||||
:unmount-on-close="true"
|
||||
unmount-on-close
|
||||
@loaded="loadedReport"
|
||||
>
|
||||
<template #titleRight="{ loading }">
|
||||
|
@ -51,8 +51,10 @@
|
|||
</MsButton> -->
|
||||
</div>
|
||||
</template>
|
||||
<template #default="{ detail }">
|
||||
<ScenarioCom :detail-info="detail" />
|
||||
<template #default="{ loading }">
|
||||
<a-spin class="h-full w-full" :loading="loading">
|
||||
<ScenarioCom :detail-info="reportStepDetail" />
|
||||
</a-spin>
|
||||
</template>
|
||||
</MsDetailDrawer>
|
||||
</template>
|
||||
|
@ -67,15 +69,13 @@
|
|||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||
import ScenarioCom from './scenarioCom.vue';
|
||||
|
||||
import { getShareInfo, getShareTime, reportScenarioDetail } from '@/api/modules/api-test/report';
|
||||
import { getShareInfo, reportScenarioDetail } from '@/api/modules/api-test/report';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import type { ReportDetail } from '@/models/apiTest/report';
|
||||
import { RouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import * as constants from 'constants';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -105,7 +105,7 @@
|
|||
|
||||
const innerReportId = ref(props.reportId);
|
||||
|
||||
const reportStepDetail = ref<ReportDetail>({
|
||||
const initReportStepDetail = {
|
||||
id: '',
|
||||
name: '', // 报告名称
|
||||
testPlanId: '',
|
||||
|
@ -143,6 +143,10 @@
|
|||
children: [], // 步骤列表
|
||||
stepTotal: 0, // 步骤总数
|
||||
console: '',
|
||||
};
|
||||
|
||||
const reportStepDetail = ref<ReportDetail>({
|
||||
...initReportStepDetail,
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -150,6 +154,7 @@
|
|||
*/
|
||||
function loadedReport(detail: ReportDetail) {
|
||||
innerReportId.value = detail.id;
|
||||
reportStepDetail.value = { ...initReportStepDetail };
|
||||
reportStepDetail.value = cloneDeep(detail);
|
||||
}
|
||||
|
||||
|
@ -195,6 +200,21 @@
|
|||
*/
|
||||
const exportLoading = ref<boolean>(false);
|
||||
function exportHandler() {}
|
||||
|
||||
const detailDrawerRef = ref();
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
detailDrawerRef.value?.destroy();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => showDrawer.value,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
reportStepDetail.value = { ...initReportStepDetail };
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -11,13 +11,19 @@
|
|||
></div>
|
||||
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div class="one-line-text max-w-[150px]"> {{ props.detail.environmentName || '-' }}</div>
|
||||
<div class="one-line-text max-w-[150px]">
|
||||
{{ props.detail.environmentName || t('report.detail.api.defaultEnv') }}</div
|
||||
>
|
||||
<a-divider direction="vertical" :margin="4" class="!mx-2"></a-divider>
|
||||
<template #content>
|
||||
<div class="max-w-[400px] items-center gap-[8px] text-[14px]">
|
||||
<div class="flex-shrink-0 text-[var(--color-text-4)]">{{ t('report.detail.api.executeEnv') }}</div>
|
||||
<div>
|
||||
{{ props.detail.environmentName || '-' }}
|
||||
{{
|
||||
props.detail.environmentName || props.showType === 'CASE'
|
||||
? t('report.detail.api.caseSaveEnv')
|
||||
: t('report.detail.api.scenarioSavedEnv')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -50,10 +56,10 @@
|
|||
</template>
|
||||
</a-popover>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<span v-if="showRunMode">
|
||||
<span v-if="props.detail.integrated">
|
||||
{{ props.detail.runMode === 'SERIAL' ? t('case.execute.serial') : t('case.execute.parallel') }}</span
|
||||
>
|
||||
<a-divider v-if="showRunMode" direction="vertical" :margin="4" class="!mx-2"></a-divider>
|
||||
<a-divider v-if="props.detail.integrated" direction="vertical" :margin="4" class="!mx-2"></a-divider>
|
||||
<template #content>
|
||||
<div class="items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('report.detail.api.runMode') }}</div>
|
||||
|
@ -99,10 +105,6 @@
|
|||
detail: ReportDetail;
|
||||
showType: 'API' | 'CASE';
|
||||
}>();
|
||||
|
||||
const showRunMode = computed(() => {
|
||||
return props.showType === 'API' ? props.detail.runMode : props.detail.runMode && props.detail.integrated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
@ -38,6 +38,21 @@
|
|||
{{ record.integrated ? t('report.collection') : t('report.independent') }}
|
||||
</MsTag>
|
||||
</template>
|
||||
<template #integratedFilter="{ columnConfig }">
|
||||
<TableFilter
|
||||
v-model:visible="reportTypeVisible"
|
||||
v-model:status-filters="integratedFiltersMap[showType]"
|
||||
:title="(columnConfig.title as string)"
|
||||
:list="reportTypeList"
|
||||
@search="initData()"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<MsTag theme="light" :type="item.value === 'INTEGRATED' ? 'primary' : undefined">
|
||||
{{ item.value === 'INTEGRATED' ? t('report.collection') : t('report.independent') }}
|
||||
</MsTag>
|
||||
</template>
|
||||
</TableFilter>
|
||||
</template>
|
||||
<!-- 报告触发方式筛选 -->
|
||||
<template #triggerModeFilter="{ columnConfig }">
|
||||
<a-trigger
|
||||
|
@ -45,7 +60,11 @@
|
|||
trigger="click"
|
||||
@popup-visible-change="handleFilterHidden"
|
||||
>
|
||||
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="triggerModeFilterVisible = true">
|
||||
<a-button
|
||||
type="text"
|
||||
class="arco-btn-text--secondary p-[8px_4px]"
|
||||
@click.stop="triggerModeFilterVisible = true"
|
||||
>
|
||||
<div class="font-medium">
|
||||
{{ t(columnConfig.title as string) }}
|
||||
</div>
|
||||
|
@ -79,7 +98,7 @@
|
|||
trigger="click"
|
||||
@popup-visible-change="handleFilterHidden"
|
||||
>
|
||||
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="statusFilterVisible = true">
|
||||
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click.stop="statusFilterVisible = true">
|
||||
<div class="font-medium">
|
||||
{{ t(columnConfig.title as string) }}
|
||||
</div>
|
||||
|
@ -170,6 +189,7 @@
|
|||
import CaseReportDrawer from './caseReportDrawer.vue';
|
||||
import ReportDetailDrawer from './reportDetailDrawer.vue';
|
||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
||||
import TableFilter from '@/views/case-management/caseManagementFeature/components/tableFilter.vue';
|
||||
|
||||
import {
|
||||
getShareTime,
|
||||
|
@ -230,6 +250,7 @@
|
|||
title: 'report.type',
|
||||
slotName: 'integrated',
|
||||
dataIndex: 'integrated',
|
||||
titleSlotName: 'integratedFilter',
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
|
@ -321,11 +342,48 @@
|
|||
const allListFilters = ref<string[]>([]);
|
||||
const independentListFilters = ref<string[]>([]);
|
||||
const integratedListFilters = ref<string[]>([]);
|
||||
|
||||
const statusListFiltersMap = ref<Record<string, string[]>>({
|
||||
all: allListFilters.value,
|
||||
All: allListFilters.value,
|
||||
INDEPENDENT: independentListFilters.value,
|
||||
INTEGRATED: integratedListFilters.value,
|
||||
});
|
||||
// 全部过滤条件
|
||||
const allIntegratedFilters = ref<string[]>([]);
|
||||
const independentIntegratedFilters = ref<string[]>([]);
|
||||
const integratedIntegratedFilters = ref<string[]>([]);
|
||||
|
||||
const reportTypeVisible = ref<boolean>(false);
|
||||
|
||||
const integratedFiltersMap = ref<Record<string, string[]>>({
|
||||
All: allIntegratedFilters.value,
|
||||
INDEPENDENT: independentIntegratedFilters.value,
|
||||
INTEGRATED: integratedIntegratedFilters.value,
|
||||
});
|
||||
|
||||
const reportTypeList = ref([
|
||||
{
|
||||
value: 'INDEPENDENT',
|
||||
label: t('report.independent'),
|
||||
},
|
||||
{
|
||||
value: 'INTEGRATED',
|
||||
label: t('report.collection'),
|
||||
},
|
||||
]);
|
||||
|
||||
const integratedFilters = computed(() => {
|
||||
if (showType.value === 'All') {
|
||||
if (integratedFiltersMap.value[showType.value].length === 1) {
|
||||
return integratedFiltersMap.value[showType.value].includes('INDEPENDENT') ? [false] : [true];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (showType.value === 'INTEGRATED') {
|
||||
return [true];
|
||||
}
|
||||
return [false];
|
||||
});
|
||||
|
||||
function initData() {
|
||||
setLoadListParams({
|
||||
|
@ -334,7 +392,7 @@
|
|||
moduleType: props.moduleType,
|
||||
filter: {
|
||||
status: statusListFiltersMap.value[showType.value],
|
||||
integrated: showType.value === 'All' ? undefined : Array.of((showType.value === 'INTEGRATED').toString()),
|
||||
integrated: integratedFilters.value,
|
||||
triggerMode: triggerModeListFilters.value,
|
||||
},
|
||||
});
|
||||
|
@ -366,7 +424,7 @@
|
|||
condition: {
|
||||
filter: {
|
||||
status: statusListFiltersMap.value[showType.value],
|
||||
integrated: showType.value === 'All' ? undefined : Array.of((showType.value === 'INTEGRATED').toString()),
|
||||
integrated: integratedFilters.value,
|
||||
triggerMode: triggerModeListFilters.value,
|
||||
},
|
||||
keyword: keyword.value,
|
||||
|
@ -433,7 +491,7 @@
|
|||
});
|
||||
|
||||
const statusFilters = computed(() => {
|
||||
return Object.keys(ReportStatus[props.moduleType]);
|
||||
return Object.keys(ReportStatus[props.moduleType]) || [];
|
||||
});
|
||||
|
||||
function handleFilterHidden(val: boolean) {
|
||||
|
@ -470,13 +528,13 @@
|
|||
const showCaseDetailDrawer = ref<boolean>(false);
|
||||
|
||||
function showReportDetail(id: string, rowIndex: number) {
|
||||
activeDetailId.value = id;
|
||||
activeReportIndex.value = rowIndex - 1;
|
||||
if (props.moduleType === ReportEnum.API_SCENARIO_REPORT) {
|
||||
showDetailDrawer.value = true;
|
||||
} else {
|
||||
showCaseDetailDrawer.value = true;
|
||||
}
|
||||
activeDetailId.value = id;
|
||||
activeReportIndex.value = rowIndex - 1;
|
||||
}
|
||||
|
||||
const shareTime = ref<string>('');
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<div class="block-title">{{ t('report.detail.api.requestAnalysis') }}</div>
|
||||
<div class="flex min-h-[110px] items-center">
|
||||
<div class="relative mr-4">
|
||||
<div class="absolute bottom-0 left-[30%] top-[35%] text-center">
|
||||
<div class="charts absolute text-center">
|
||||
<div class="text-[12px] text-[(var(--color-text-4))]">{{ t('report.detail.api.total') }}</div>
|
||||
<div class="text-[18px] font-medium">{{ getIndicators(detail.requestTotal) }}</div>
|
||||
</div>
|
||||
|
@ -388,4 +388,11 @@
|
|||
.block-title {
|
||||
@apply mb-4 font-medium;
|
||||
}
|
||||
.charts {
|
||||
top: 30%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
<template>
|
||||
<div class="flex h-full flex-col gap-[16px]">
|
||||
<a-spin class="w-full" :loading="loading">
|
||||
<!-- 不做虚拟滚动 :virtual-list-props="{
|
||||
height: `calc(100vh - 454px)`,
|
||||
threshold: 20,
|
||||
fixedSize: true,
|
||||
buffer: 15,
|
||||
}" -->
|
||||
<MsTree
|
||||
ref="treeRef"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
|
@ -16,6 +10,14 @@
|
|||
:field-names="{ title: 'name', key: 'stepId', children: 'children' }"
|
||||
title-class="step-tree-node-title"
|
||||
node-highlight-class="step-tree-node-focus"
|
||||
:virtual-list-props="{
|
||||
height: 'calc(100vh - 200px)',
|
||||
threshold: 200,
|
||||
fixedSize: true,
|
||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||
isStaticItemHeight: true,
|
||||
estimatedSize: 48,
|
||||
}"
|
||||
action-on-node-click="expand"
|
||||
disabled-title-tooltip
|
||||
block-node
|
||||
|
@ -24,142 +26,138 @@
|
|||
@more-actions-close="() => setFocusNodeKey('')"
|
||||
>
|
||||
<template #title="step">
|
||||
<div class="flex w-full items-center gap-[8px]">
|
||||
<div
|
||||
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white"
|
||||
>
|
||||
{{ step.sort }}
|
||||
</div>
|
||||
<div class="step-node-content flex justify-between">
|
||||
<div class="flex flex-1 items-center">
|
||||
<!-- 步骤展开折叠按钮 -->
|
||||
<a-tooltip
|
||||
v-if="step.children?.length > 0"
|
||||
:content="
|
||||
t(step.expanded ? 'apiScenario.collapseStepTip' : 'apiScenario.expandStepTip', {
|
||||
count: step.children.length,
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-4)]">
|
||||
<MsIcon
|
||||
:type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'"
|
||||
:size="14"
|
||||
/>
|
||||
<span class="mx-1"> {{ step.children?.length || 0 }}</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 展开折叠控制器 -->
|
||||
<div v-if="getShowExpand(step)" class="mx-1" @click.stop="expandHandler(step)">
|
||||
<span v-if="step.fold" class="collapsebtn flex items-center justify-center">
|
||||
<icon-right class="text-[var(--color-text-4)]" :style="{ 'font-size': '12px' }" />
|
||||
</span>
|
||||
<span v-else class="expand flex items-center justify-center">
|
||||
<icon-down class="text-[rgb(var(--primary-6))]" :style="{ 'font-size': '12px' }" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="props.showType === 'API' && showCondition.includes(step.stepType)" class="flex-shrink-0">
|
||||
<ConditionStatus class="mx-1" :status="step.stepType || ''" />
|
||||
</div>
|
||||
|
||||
<a-tooltip :content="step.name" position="tl">
|
||||
<div class="step-name-container w-full flex-grow" @click.stop="showDetail(step)">
|
||||
<div class="one-line-text mx-[4px] max-w-[150px] text-[var(--color-text-1)]">
|
||||
{{ step.name }}
|
||||
<div class="flex flex-col">
|
||||
<div class="flex w-full items-center gap-[8px]">
|
||||
<div
|
||||
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] px-[2px] !text-white"
|
||||
>
|
||||
{{ step.sort }}
|
||||
</div>
|
||||
<div class="step-node-content flex justify-between">
|
||||
<div class="flex flex-1 items-center">
|
||||
<!-- 步骤展开折叠按钮 -->
|
||||
<a-tooltip
|
||||
v-if="step.children?.length > 0"
|
||||
:content="
|
||||
t(step.expanded ? 'apiScenario.collapseStepTip' : 'apiScenario.expandStepTip', {
|
||||
count: step.children.length,
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="flex cursor-pointer items-center gap-[2px] text-[var(--color-text-4)]">
|
||||
<MsIcon
|
||||
:type="step.expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'"
|
||||
:size="14"
|
||||
/>
|
||||
<span class="mx-1"> {{ step.children?.length || 0 }}</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!-- 展开折叠控制器 -->
|
||||
<div v-show="getShowExpand(step)" class="mx-1" @click.stop="expandHandler(step)">
|
||||
<span v-if="step.fold" class="collapsebtn flex items-center justify-center">
|
||||
<icon-right class="text-[var(--color-text-4)]" :style="{ 'font-size': '12px' }" />
|
||||
</span>
|
||||
<span v-else class="expand flex items-center justify-center">
|
||||
<icon-down class="text-[rgb(var(--primary-6))]" :style="{ 'font-size': '12px' }" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="props.showType === 'API' && showCondition.includes(step.stepType)" class="flex-shrink-0">
|
||||
<ConditionStatus class="mx-1" :status="step.stepType || ''" />
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<stepStatus :status="step.status || 'PENDING'" />
|
||||
<!-- 脚本报错 -->
|
||||
<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 class="max-w-[400px]">{{ step.scriptIdentifier }}</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<div v-show="showStatus(step)" class="flex">
|
||||
<span class="statusCode mx-2">
|
||||
<div v-if="step.code" class="mr-2"> {{ t('report.detail.api.statusCode') }}</div>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div
|
||||
v-if="step.code"
|
||||
class="one-line-text max-w-[200px]"
|
||||
:style="{ color: statusCodeColor(step.code) }"
|
||||
>
|
||||
{{ step.code || '-' }}
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||
<div :style="{ color: statusCodeColor(step.code) }">
|
||||
{{ step.code || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</span>
|
||||
|
||||
<span v-if="step.requestTime !== null" class="resTime">
|
||||
{{ t('report.detail.api.responseTime') }}
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<span class="resTimeCount ml-2"
|
||||
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-'
|
||||
}}{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms' }}</span
|
||||
>
|
||||
<template #content>
|
||||
<span v-if="step.requestTime !== null" class="resTime">
|
||||
{{ t('report.detail.api.responseTime') }}
|
||||
<span class="resTimeCount ml-2"
|
||||
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-'
|
||||
}}{{
|
||||
step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms'
|
||||
}}</span
|
||||
></span
|
||||
>
|
||||
<a-tooltip :content="step.name" position="tl">
|
||||
<div class="step-name-container w-full flex-grow" @click.stop="showDetail(step)">
|
||||
<div class="one-line-text mx-[4px] max-w-[150px] text-[var(--color-text-1)]">
|
||||
{{ step.name }}
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<stepStatus :status="step.status || 'PENDING'" />
|
||||
<!-- 脚本报错 -->
|
||||
<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>
|
||||
</a-popover></span
|
||||
>
|
||||
<span v-if="step.responseSize !== null" class="resSize">
|
||||
{{ t('report.detail.api.responseSize') }}
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span>
|
||||
<template #content>
|
||||
<span class="resSize">
|
||||
{{ t('report.detail.api.responseSize') }}
|
||||
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</span></span
|
||||
</MsTag>
|
||||
<template #content>
|
||||
<div class="max-w-[400px] break-words">{{ step.scriptIdentifier }}</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
<div v-show="showStatus(step)" class="flex">
|
||||
<span class="statusCode mx-2">
|
||||
<div v-show="step.code" class="mr-2"> {{ t('report.detail.api.statusCode') }}</div>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<div
|
||||
v-show="step.code"
|
||||
class="one-line-text max-w-[200px]"
|
||||
:style="{ color: statusCodeColor(step.code) }"
|
||||
>
|
||||
</template>
|
||||
</a-popover></span
|
||||
>
|
||||
{{ step.code || '-' }}
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="flex items-center gap-[8px] text-[14px]">
|
||||
<div class="text-[var(--color-text-4)]">{{ t('apiTestDebug.statusCode') }}</div>
|
||||
<div :style="{ color: statusCodeColor(step.code) }">
|
||||
{{ step.code || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</span>
|
||||
|
||||
<span v-show="step.requestTime !== null" class="resTime">
|
||||
{{ t('report.detail.api.responseTime') }}
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<span class="resTimeCount ml-2"
|
||||
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-'
|
||||
}}{{
|
||||
step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms'
|
||||
}}</span
|
||||
>
|
||||
<template #content>
|
||||
<span v-show="step.requestTime !== null" class="resTime">
|
||||
{{ t('report.detail.api.responseTime') }}
|
||||
<span class="resTimeCount ml-2"
|
||||
>{{ step.requestTime !== null ? formatDuration(step.requestTime).split('-')[0] : '-'
|
||||
}}{{
|
||||
step.requestTime !== null ? formatDuration(step.requestTime).split('-')[1] : 'ms'
|
||||
}}</span
|
||||
></span
|
||||
>
|
||||
</template>
|
||||
</a-popover></span
|
||||
>
|
||||
<span v-show="step.responseSize !== null" class="resSize">
|
||||
{{ t('report.detail.api.responseSize') }}
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
<span class="resTimeCount ml-2">{{ step.responseSize || 0 }} bytes</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></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!step.fold" class="line"></div>
|
||||
</div>
|
||||
<div v-if="!step.fold" class="line"></div>
|
||||
</div>
|
||||
<!-- 折叠展开内容 v-if="showResContent(step)" -->
|
||||
<div v-if="showResContent(step)" class="foldContent mt-4 pl-2">
|
||||
<a-scrollbar
|
||||
:style="{
|
||||
overflow: 'auto',
|
||||
height: 'calc(100vh - 540px)',
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
<!-- 折叠展开内容 -->
|
||||
<div v-show="showResContent(step)" class="foldContent mt-4 pl-2">
|
||||
<StepDetailContent
|
||||
:mode="props.activeType"
|
||||
:step-item="step"
|
||||
|
@ -171,7 +169,14 @@
|
|||
:report-id="props?.reportId"
|
||||
:steps="steps"
|
||||
/>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="steps.length === 0" #empty>
|
||||
<div
|
||||
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-center text-[12px] leading-[16px] text-[var(--color-text-4)]"
|
||||
>
|
||||
{{ t('common.noData') }}
|
||||
</div>
|
||||
</template>
|
||||
</MsTree>
|
||||
|
@ -182,12 +187,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
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';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { findNodeByKey, formatDuration, mapTree } from '@/utils';
|
||||
|
@ -195,6 +195,14 @@
|
|||
import type { ScenarioItemType } from '@/models/apiTest/report';
|
||||
import { ScenarioStepType } from '@/enums/apiEnum';
|
||||
|
||||
const StepDetailContent = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/result/index.vue')
|
||||
);
|
||||
const stepStatus = defineAsyncComponent(() => import('./stepStatus.vue'));
|
||||
const ConditionStatus = defineAsyncComponent(() => import('@/views/api-test/report/component/conditionStatus.vue'));
|
||||
const MsTag = defineAsyncComponent(() => import('@/components/pure/ms-tag/ms-tag.vue'));
|
||||
const MsTree = defineAsyncComponent(() => import('@/components/business/ms-tree/index.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
stepKeyword?: string;
|
||||
|
@ -430,7 +438,6 @@
|
|||
background: var(--color-text-n8) !important;
|
||||
}
|
||||
.resContentWrapper {
|
||||
border-top: 1px solid red;
|
||||
border-radius: 0 0 6px 6px;
|
||||
@apply mb-4 bg-white p-4;
|
||||
.resContent {
|
||||
|
@ -451,4 +458,8 @@
|
|||
height: 1px;
|
||||
background: var(--color-text-n8);
|
||||
}
|
||||
.foldContent {
|
||||
height: 100%;
|
||||
height: calc(100vh);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
v-model:params="assertionConfig.assertions"
|
||||
:is-definition="false"
|
||||
:assertion-config="assertionConfig"
|
||||
:show-extraction="true"
|
||||
@change="emit('change')"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -246,6 +246,7 @@
|
|||
is-definition
|
||||
:disabled="!isEditableApi"
|
||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||
:show-extraction="true"
|
||||
/>
|
||||
<auth
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.AUTH"
|
||||
|
|
|
@ -192,6 +192,7 @@
|
|||
is-definition
|
||||
:disabled="!isEditableApi"
|
||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||
:show-extraction="true"
|
||||
/>
|
||||
<auth
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.AUTH"
|
||||
|
|
|
@ -199,7 +199,7 @@ export function initFormCreate(customFields: CustomAttributes[], permission: str
|
|||
currentDefaultValue = item.type === 'MEMBER' ? item.defaultValue : JSON.parse(item.defaultValue);
|
||||
}
|
||||
} else if (multipleInputType.includes(item.type)) {
|
||||
currentDefaultValue = JSON.parse(item.defaultValue);
|
||||
currentDefaultValue = Array.isArray(item.defaultValue) ? item.defaultValue : JSON.parse(item.defaultValue);
|
||||
} else if (singleType.includes(item.type)) {
|
||||
const optionsIds = optionsValue.map((e: any) => e.value);
|
||||
currentDefaultValue = (optionsIds || []).find((e: any) => item.defaultValue === e) || '';
|
||||
|
|
|
@ -200,6 +200,7 @@
|
|||
showInTable: true,
|
||||
width: 200,
|
||||
showDrag: true,
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'project.commonScript.createTime',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MsAssertion v-model:params="params" />
|
||||
<MsAssertion v-model:params="params" :show-extraction="false" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
v-model:model-value="form.requestTimeout"
|
||||
:min="0"
|
||||
:step="100"
|
||||
:precision="0"
|
||||
class="w-[180px]"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
|
@ -29,6 +30,7 @@
|
|||
v-model:model-value="form.responseTimeout"
|
||||
:min="0"
|
||||
:step="100"
|
||||
:precision="0"
|
||||
class="w-[180px]"
|
||||
:disabled="isDisabled"
|
||||
>
|
||||
|
|
Loading…
Reference in New Issue