feat(接口场景): 执行响应优化&循环响应展示
This commit is contained in:
parent
2441a882cc
commit
e8f1ce98cc
|
@ -3,7 +3,7 @@ import { SelectProps } from '@arco-design/web-vue';
|
|||
|
||||
import { Size } from './types';
|
||||
|
||||
export const PAGE_ITEM_TYPES = ['page', 'more', 'previous', 'next'] as const;
|
||||
export const PAGE_ITEM_TYPES = ['page', 'more', 'previous', 'next', 'jumper'] as const;
|
||||
|
||||
export type PageItemType = (typeof PAGE_ITEM_TYPES)[number];
|
||||
|
||||
|
|
|
@ -15,10 +15,12 @@
|
|||
@change="handleChange"
|
||||
@enter="handleChange"
|
||||
/>
|
||||
<span v-if="$slots['jumper-append']" :class="`${prefixCls}-append`"><slot name="jumper-append" /></span>
|
||||
<span :class="`${prefixCls}-total-page`" :style="{ 'min-width': totalPageWidth }">{{
|
||||
t('msPagination.page', { page: pages })
|
||||
}}</span>
|
||||
<span v-if="$slots['jumper-append']" :class="`${prefixCls}-append`">
|
||||
<slot name="jumper-append" />
|
||||
</span>
|
||||
<span :class="`${prefixCls}-total-page`" :style="{ 'min-width': totalPageWidth }">
|
||||
{{ t('msPagination.page', { page: pages }) }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -389,7 +389,7 @@ export interface Scenario {
|
|||
executeSuccessCount: number; // 执行成功数量
|
||||
executeFailCount: number; // 执行失败数量
|
||||
reportId?: string | number; // 场景报告 id
|
||||
stepResponses: Record<string | number, RequestResult>; // 步骤响应集合,key 为步骤 id,value 为步骤响应内容
|
||||
stepResponses: Record<string | number, Array<RequestResult>>; // 步骤响应集合,key 为步骤 id,value 为步骤响应内容
|
||||
isExecute?: boolean; // 是否从列表执行进去场景详情
|
||||
isDebug?: boolean; // 是否调试,区分执行场景和批量调试步骤
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { cloneDeep } from 'lodash-es';
|
||||
import { cloneDeep, each } from 'lodash-es';
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
|
||||
import { BatchActionQueryParams, MsTableColumnData } from '@/components/pure/ms-table/type';
|
||||
|
@ -194,6 +194,31 @@ export interface TreeNode<T> {
|
|||
* @param tree 树形数组或树
|
||||
* @param customNodeFn 自定义节点函数
|
||||
* @param customChildrenKey 自定义子节点的key
|
||||
*/
|
||||
export function traverseTree<T>(
|
||||
tree: TreeNode<T> | TreeNode<T>[] | T | T[],
|
||||
customNodeFn: (node: TreeNode<T>) => TreeNode<T> | null = (node) => node,
|
||||
customChildrenKey = 'children'
|
||||
) {
|
||||
if (!Array.isArray(tree)) {
|
||||
tree = [tree];
|
||||
}
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const node = tree[i];
|
||||
if (typeof customNodeFn === 'function') {
|
||||
customNodeFn(node);
|
||||
}
|
||||
if (node[customChildrenKey] && Array.isArray(node[customChildrenKey]) && node[customChildrenKey].length > 0) {
|
||||
traverseTree(node[customChildrenKey], customNodeFn, customChildrenKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归遍历树形数组或树,返回新的树
|
||||
* @param tree 树形数组或树
|
||||
* @param customNodeFn 自定义节点函数
|
||||
* @param customChildrenKey 自定义子节点的key
|
||||
* @param parent 父节点
|
||||
* @param parentPath 父节点路径
|
||||
* @param level 节点层级
|
||||
|
|
|
@ -61,7 +61,9 @@
|
|||
value: item.id,
|
||||
}));
|
||||
currentEnv.value = currentEnv.value.length ? currentEnv.value : res[0]?.id;
|
||||
if (currentEnv.value) {
|
||||
await initEnvironment();
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<MsEditableTab
|
||||
v-model:active-tab="activeDebug"
|
||||
v-model:tabs="debugTabs"
|
||||
:limit="10"
|
||||
:readonly="!hasAnyPermission(['PROJECT_API_DEBUG:READ+ADD'])"
|
||||
at-least-one
|
||||
@add="addDebugTab"
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
>
|
||||
<template #title>
|
||||
<div class="flex max-w-[60%] items-center gap-[8px]">
|
||||
<stepTypeVue v-if="props.step" :step="props.step" />
|
||||
<stepTypeVue
|
||||
v-if="props.step && [ScenarioStepType.API, ScenarioStepType.CUSTOM_REQUEST].includes(props.step?.stepType)"
|
||||
:step="props.step"
|
||||
/>
|
||||
<a-tooltip :content="title" position="bottom">
|
||||
<div class="one-line-text">
|
||||
{{ title }}
|
||||
|
@ -228,7 +231,7 @@
|
|||
<postcondition
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.POST_CONDITION"
|
||||
v-model:config="requestVModel.children[0].postProcessorConfig"
|
||||
:response="props.stepResponses?.[requestVModel.stepId]?.responseResult.body"
|
||||
:response="responseResultBody"
|
||||
:layout="activeLayout"
|
||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
||||
:second-box-height="secondBoxHeight"
|
||||
|
@ -238,7 +241,7 @@
|
|||
<assertion
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.ASSERTION"
|
||||
v-model:params="requestVModel.children[0].assertionConfig.assertions"
|
||||
:response="props.stepResponses?.[requestVModel.stepId]?.responseResult.body"
|
||||
:response="responseResultBody"
|
||||
is-definition
|
||||
:disabled="!isEditableApi || isQuoteScenarioStep"
|
||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||
|
@ -269,8 +272,8 @@
|
|||
:is-priority-local-exec="isPriorityLocalExec"
|
||||
:request-url="requestVModel.url"
|
||||
:is-expanded="isVerticalExpanded"
|
||||
:request-result="props.stepResponses?.[requestVModel.stepId]"
|
||||
:console="props.stepResponses?.[requestVModel.stepId]?.console"
|
||||
:request-result="requestResult"
|
||||
:console="requestResult?.console"
|
||||
:is-edit="false"
|
||||
is-definition
|
||||
:loading="requestVModel.executeLoading || loading"
|
||||
|
@ -387,7 +390,7 @@
|
|||
create: string;
|
||||
update: string;
|
||||
};
|
||||
stepResponses?: Record<string | number, RequestResult>;
|
||||
stepResponses?: Record<string | number, RequestResult[]>;
|
||||
fileParams?: ScenarioStepFileParams;
|
||||
}>();
|
||||
|
||||
|
@ -485,13 +488,27 @@
|
|||
if (_stepType.value.isCopyApi || _stepType.value.isQuoteApi) {
|
||||
return props.step?.name;
|
||||
}
|
||||
return props.step?.name || t('apiScenario.customApi');
|
||||
return t('apiScenario.customApi');
|
||||
});
|
||||
const showEnvPrefix = computed(
|
||||
() =>
|
||||
requestVModel.value.customizeRequestEnvEnable &&
|
||||
currentEnvConfig?.value.httpConfig.find((e) => e.type === 'NONE')?.url
|
||||
);
|
||||
const responseResultBody = computed(() => {
|
||||
const length = props.stepResponses?.[requestVModel.value.stepId]
|
||||
? props.stepResponses?.[requestVModel.value.stepId]?.length
|
||||
: 0;
|
||||
// 取最后一次执行的结果
|
||||
return props.stepResponses?.[requestVModel.value.stepId]?.[length].responseResult.body;
|
||||
});
|
||||
const requestResult = computed(() => {
|
||||
const length = props.stepResponses?.[requestVModel.value.stepId]
|
||||
? props.stepResponses?.[requestVModel.value.stepId]?.length
|
||||
: 0;
|
||||
// 取最后一次执行的结果
|
||||
return props.stepResponses?.[requestVModel.value.stepId]?.[length];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.stepResponses,
|
||||
|
@ -1086,6 +1103,7 @@
|
|||
requestVModel.value = cloneDeep({
|
||||
...defaultApiParams,
|
||||
...props.request,
|
||||
url: props.request.path, // 后台字段是 path
|
||||
activeTab: contentTabList.value[0].value,
|
||||
responseActiveTab: ResponseComposition.BODY,
|
||||
isNew: false,
|
||||
|
|
|
@ -178,7 +178,7 @@
|
|||
<postcondition
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.POST_CONDITION"
|
||||
v-model:config="requestVModel.children[0].postProcessorConfig"
|
||||
:response="props.stepResponses?.[requestVModel.stepId]?.responseResult.body"
|
||||
:response="responseResultBody"
|
||||
:layout="activeLayout"
|
||||
:disabled="!isEditableApi"
|
||||
:second-box-height="secondBoxHeight"
|
||||
|
@ -188,7 +188,7 @@
|
|||
<assertion
|
||||
v-else-if="requestVModel.activeTab === RequestComposition.ASSERTION"
|
||||
v-model:params="requestVModel.children[0].assertionConfig.assertions"
|
||||
:response="props.stepResponses?.[requestVModel.stepId]?.responseResult.body"
|
||||
:response="responseResultBody"
|
||||
is-definition
|
||||
:disabled="!isEditableApi"
|
||||
:assertion-config="requestVModel.children[0].assertionConfig"
|
||||
|
@ -219,8 +219,8 @@
|
|||
:is-priority-local-exec="isPriorityLocalExec"
|
||||
:request-url="requestVModel.url"
|
||||
:is-expanded="isVerticalExpanded"
|
||||
:request-result="props.stepResponses?.[requestVModel.stepId]"
|
||||
:console="props.stepResponses?.[requestVModel.stepId]?.console"
|
||||
:request-result="requestResult"
|
||||
:console="requestResult?.console"
|
||||
:is-edit="false"
|
||||
is-definition
|
||||
:loading="requestVModel.executeLoading || loading"
|
||||
|
@ -297,7 +297,7 @@
|
|||
|
||||
const props = defineProps<{
|
||||
request?: RequestParam; // 请求参数集合
|
||||
stepResponses?: Record<string | number, RequestResult>;
|
||||
stepResponses?: Record<string | number, RequestResult[]>;
|
||||
fileParams?: ScenarioStepFileParams;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
|
@ -396,6 +396,20 @@
|
|||
() =>
|
||||
activeStep.value?.stepType === ScenarioStepType.API_CASE && activeStep.value?.refType === ScenarioStepRefType.REF
|
||||
);
|
||||
const responseResultBody = computed(() => {
|
||||
const length = props.stepResponses?.[requestVModel.value.stepId]
|
||||
? props.stepResponses?.[requestVModel.value.stepId]?.length
|
||||
: 0;
|
||||
// 取最后一次执行的结果
|
||||
return props.stepResponses?.[requestVModel.value.stepId]?.[length].responseResult.body;
|
||||
});
|
||||
const requestResult = computed(() => {
|
||||
const length = props.stepResponses?.[requestVModel.value.stepId]
|
||||
? props.stepResponses?.[requestVModel.value.stepId]?.length
|
||||
: 0;
|
||||
// 取最后一次执行的结果
|
||||
return props.stepResponses?.[requestVModel.value.stepId]?.[length];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.stepResponses,
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<MsPagination
|
||||
v-if="props.loopTotal > 1"
|
||||
v-model:current="currentLoop"
|
||||
:page-size="1"
|
||||
:total="props.loopTotal"
|
||||
:show-jumper="props.loopTotal > 5"
|
||||
show-total
|
||||
size="mini"
|
||||
class="loop-pagination"
|
||||
>
|
||||
<template #total="{ total }">
|
||||
<div
|
||||
class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n8)] p-[2px_6px] leading-[20px] text-[var(--color-text-2)]"
|
||||
>
|
||||
{{ t('apiScenario.sumLoop', { count: total }) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #jumper-append>
|
||||
{{ t('apiScenario.times') }}
|
||||
</template>
|
||||
</MsPagination>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import MsPagination from '@/components/pure/ms-pagination';
|
||||
|
||||
const props = defineProps<{
|
||||
loopTotal: number;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const currentLoop = defineModel<number>('currentLoop', {
|
||||
required: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.arco-popover {
|
||||
.loop-pagination {
|
||||
@apply justify-start;
|
||||
|
||||
gap: 2px;
|
||||
margin-bottom: 8px;
|
||||
.ms-pagination-list {
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
border-radius: var(--border-radius-small);
|
||||
background-color: var(--color-text-n8);
|
||||
.ms-pagination-item {
|
||||
padding: 0 6px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-mini);
|
||||
color: var(--color-text-1);
|
||||
background-color: white;
|
||||
line-height: 20px;
|
||||
}
|
||||
.ms-pagination-item-previous {
|
||||
margin-left: 0;
|
||||
}
|
||||
.ms-pagination-item-disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.ms-pagination-item-active {
|
||||
border: 1px solid rgb(var(--primary-5)) !important;
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
}
|
||||
.ms-pagination-jumper {
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--border-radius-mini);
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-text-n8);
|
||||
.ms-pagination-jumper-input {
|
||||
width: 48px;
|
||||
background-color: white;
|
||||
input {
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
.ms-pagination-jumper-total-page {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<a-popover
|
||||
position="br"
|
||||
content-class="scenario-step-response-popover"
|
||||
@popup-visible-change="emit('visibleChange', $event, props.step)"
|
||||
>
|
||||
<executeStatus :status="lastExecuteStatus" size="small" class="ml-[4px]" />
|
||||
<template #content>
|
||||
<div class="flex h-full flex-col">
|
||||
<loopPagination v-model:current-loop="currentLoop" :loop-total="loopTotal" />
|
||||
<div class="flex-1">
|
||||
<responseResult
|
||||
:active-tab="ResponseComposition.BODY"
|
||||
:request-result="currentResponse"
|
||||
:console="currentResponse?.console"
|
||||
:show-empty="false"
|
||||
:is-edit="false"
|
||||
is-definition
|
||||
>
|
||||
<template #titleLeft>
|
||||
<div class="flex items-center text-[14px]">
|
||||
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiScenario.response') }}</div>
|
||||
<a-tooltip :content="props.step.name">
|
||||
<div class="one-line-text">({{ props.step.name }})</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</responseResult>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import executeStatus from './executeStatus.vue';
|
||||
import loopPagination from './loopPagination.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestResult } from '@/models/apiTest/common';
|
||||
import { ScenarioStepItem } from '@/models/apiTest/scenario';
|
||||
import { ResponseComposition, ScenarioExecuteStatus } from '@/enums/apiEnum';
|
||||
|
||||
const responseResult = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
||||
);
|
||||
|
||||
const props = defineProps<{
|
||||
step: ScenarioStepItem;
|
||||
stepResponses: Record<string | number, Array<RequestResult>>;
|
||||
}>();
|
||||
const emit = defineEmits(['visibleChange']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const currentLoop = ref(1);
|
||||
const currentResponse = computed(() => props.stepResponses?.[props.step.id]?.[currentLoop.value - 1]);
|
||||
const loopTotal = computed(() => props.stepResponses?.[props.step.id]?.length || 0);
|
||||
const lastExecuteStatus = computed(() => {
|
||||
if (props.stepResponses[props.step.id] && props.stepResponses[props.step.id].length > 0) {
|
||||
// 有一次失败就是失败
|
||||
return props.stepResponses[props.step.id].some((report) => !report.isSuccessful)
|
||||
? ScenarioExecuteStatus.FAILED
|
||||
: ScenarioExecuteStatus.SUCCESS;
|
||||
}
|
||||
return props.step.executeStatus;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.scenario-step-response-popover {
|
||||
width: 540px;
|
||||
height: 500px;
|
||||
.arco-popover-content {
|
||||
@apply h-full;
|
||||
.response {
|
||||
.response-head {
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: var(--border-radius-small);
|
||||
.arco-spin {
|
||||
padding: 0;
|
||||
.response-container {
|
||||
padding: 0 16px 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -82,8 +82,16 @@
|
|||
() => visible.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
scriptName.value = props.name || '';
|
||||
activeItem.value = cloneDeep(props.detail || defaultScript);
|
||||
scriptName.value = props.detail ? props.name || '' : '';
|
||||
activeItem.value = cloneDeep(
|
||||
props.detail
|
||||
? {
|
||||
...props.detail,
|
||||
processorType: RequestConditionProcessor.SCRIPT,
|
||||
polymorphicName: 'MsScriptElement',
|
||||
}
|
||||
: defaultScript
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -23,7 +23,7 @@ export default function useCreateActions() {
|
|||
* @param steps 需要判断的步骤
|
||||
* @param parent 需要判断的父节点
|
||||
*/
|
||||
function checkedIfNeed(
|
||||
function selectedIfNeed(
|
||||
selectedKeys: (string | number)[],
|
||||
steps: (ScenarioStepItem | TreeNode<ScenarioStepItem>)[],
|
||||
parent?: TreeNode<ScenarioStepItem>
|
||||
|
@ -59,7 +59,7 @@ export default function useCreateActions() {
|
|||
step.id,
|
||||
newStep,
|
||||
createStepAction,
|
||||
(newNode, parent) => checkedIfNeed(selectedKeys, [newNode], parent),
|
||||
(newNode, parent) => selectedIfNeed(selectedKeys, [newNode], parent),
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
@ -174,13 +174,13 @@ export default function useCreateActions() {
|
|||
undefined,
|
||||
'id'
|
||||
);
|
||||
checkedIfNeed(selectedKeys, readyInsertSteps, step);
|
||||
selectedIfNeed(selectedKeys, readyInsertSteps, step);
|
||||
}
|
||||
|
||||
return {
|
||||
handleCreateStep,
|
||||
buildInsertStepInfos,
|
||||
handleCreateSteps,
|
||||
checkedIfNeed,
|
||||
selectedIfNeed,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<template>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<a-popover
|
||||
<div
|
||||
v-if="
|
||||
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.API_SCENARIO].includes(props.data.stepType)
|
||||
"
|
||||
position="bl"
|
||||
content-class="detail-popover"
|
||||
arrow-class="hidden"
|
||||
class="flex items-center gap-[4px]"
|
||||
>
|
||||
<a-popover position="bl" content-class="detail-popover" arrow-class="hidden">
|
||||
<MsIcon type="icon-icon-draft" class="text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" />
|
||||
<template #content>
|
||||
<div class="flex flex-col gap-[16px]">
|
||||
|
|
|
@ -178,7 +178,7 @@
|
|||
/>
|
||||
</template>
|
||||
<template #extraEnd="step">
|
||||
<a-popover
|
||||
<responsePopover
|
||||
v-if="
|
||||
![
|
||||
ScenarioStepType.LOOP_CONTROLLER,
|
||||
|
@ -189,32 +189,16 @@
|
|||
(getExecuteStatus(step) === ScenarioExecuteStatus.SUCCESS ||
|
||||
getExecuteStatus(step) === ScenarioExecuteStatus.FAILED)
|
||||
"
|
||||
position="br"
|
||||
content-class="scenario-step-response-popover"
|
||||
@popup-visible-change="handleResponsePopoverVisibleChange($event, step)"
|
||||
>
|
||||
<executeStatus :status="getExecuteStatus(step)" size="small" />
|
||||
<template #content>
|
||||
<responseResult
|
||||
:active-tab="ResponseComposition.BODY"
|
||||
:request-result="scenario.stepResponses?.[step.id]"
|
||||
:console="scenario.stepResponses?.[step.id]?.console"
|
||||
:show-empty="false"
|
||||
:is-edit="false"
|
||||
is-definition
|
||||
>
|
||||
<template #titleLeft>
|
||||
<div class="flex items-center text-[14px]">
|
||||
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiScenario.response') }}</div>
|
||||
<a-tooltip :content="step.name">
|
||||
<div class="one-line-text">({{ step.name }})</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</responseResult>
|
||||
</template>
|
||||
</a-popover>
|
||||
<executeStatus v-else-if="step.executeStatus" :status="getExecuteStatus(step)" size="small" />
|
||||
:step="step"
|
||||
:step-responses="scenario.stepResponses"
|
||||
@visible-change="handleResponsePopoverVisibleChange"
|
||||
/>
|
||||
<executeStatus
|
||||
v-else-if="step.executeStatus"
|
||||
:status="getExecuteStatus(step)"
|
||||
size="small"
|
||||
class="ml-[4px]"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="steps.length === 0 && stepKeyword.trim() !== ''" #empty>
|
||||
<div
|
||||
|
@ -422,6 +406,7 @@
|
|||
handleTreeDragDrop,
|
||||
insertNodes,
|
||||
mapTree,
|
||||
traverseTree,
|
||||
TreeNode,
|
||||
} from '@/utils';
|
||||
|
||||
|
@ -437,7 +422,6 @@
|
|||
} from '@/models/apiTest/scenario';
|
||||
import { EnvConfig } from '@/models/projectManagement/environmental';
|
||||
import {
|
||||
ResponseComposition,
|
||||
ScenarioAddStepActionType,
|
||||
ScenarioExecuteStatus,
|
||||
ScenarioStepRefType,
|
||||
|
@ -445,6 +429,7 @@
|
|||
} from '@/enums/apiEnum';
|
||||
|
||||
import type { RequestParam } from '../common/customApiDrawer.vue';
|
||||
import updateStepStatus from '../utils';
|
||||
import useCreateActions from './createAction/useCreateActions';
|
||||
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||
import getStepType from '@/views/api-test/scenario/components/common/stepType/utils';
|
||||
|
@ -456,9 +441,7 @@
|
|||
const customCaseDrawer = defineAsyncComponent(() => import('../common/customCaseDrawer.vue'));
|
||||
const importApiDrawer = defineAsyncComponent(() => import('../common/importApiDrawer/index.vue'));
|
||||
const scriptOperationDrawer = defineAsyncComponent(() => import('../common/scriptOperationDrawer.vue'));
|
||||
const responseResult = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/response/index.vue')
|
||||
);
|
||||
const responsePopover = defineAsyncComponent(() => import('../common/responsePopover.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
stepKeyword: string;
|
||||
|
@ -500,9 +483,10 @@
|
|||
|
||||
function getExecuteStatus(step: ScenarioStepItem) {
|
||||
if (scenario.value.stepResponses && scenario.value.stepResponses[step.id]) {
|
||||
return scenario.value.stepResponses[step.id].isSuccessful
|
||||
? ScenarioExecuteStatus.SUCCESS
|
||||
: ScenarioExecuteStatus.FAILED;
|
||||
// 有一次失败就是失败
|
||||
return scenario.value.stepResponses[step.id].some((report) => !report.isSuccessful)
|
||||
? ScenarioExecuteStatus.FAILED
|
||||
: ScenarioExecuteStatus.SUCCESS;
|
||||
}
|
||||
return step.executeStatus;
|
||||
}
|
||||
|
@ -552,7 +536,7 @@
|
|||
/**
|
||||
* 增加步骤时判断父节点是否选中,如果选中则需要把新节点也选中
|
||||
*/
|
||||
function checkedIfNeed(step: TreeNode<ScenarioStepItem>, parent?: TreeNode<ScenarioStepItem>) {
|
||||
function selectedIfNeed(step: TreeNode<ScenarioStepItem>, parent?: TreeNode<ScenarioStepItem>) {
|
||||
if (parent && selectedKeys.value.includes(parent.id)) {
|
||||
// 添加子节点时,当前节点已选中,则需要把新节点也需要选中(因为父级选中子级也会展示选中状态)
|
||||
selectedKeys.value.push(step.id);
|
||||
|
@ -803,7 +787,7 @@
|
|||
id,
|
||||
},
|
||||
'after',
|
||||
checkedIfNeed,
|
||||
selectedIfNeed,
|
||||
'id'
|
||||
);
|
||||
scenario.value.unSaved = true;
|
||||
|
@ -971,6 +955,15 @@
|
|||
customApiDrawerVisible.value = true;
|
||||
} else if (step.stepType === ScenarioStepType.API_CASE) {
|
||||
activeStep.value = step;
|
||||
if (
|
||||
_stepType.isCopyCase &&
|
||||
((stepDetails.value[step.id] === undefined && step.copyFromStepId) ||
|
||||
(stepDetails.value[step.id] === undefined && !step.isNew))
|
||||
) {
|
||||
// 只有复制的 case 需要查看步骤详情,引用的无法更改所以不需要在此初始化详情
|
||||
// 查看场景详情时,详情映射中没有对应数据,初始化步骤详情(复制的步骤没有加载详情前就被复制,打开复制后的步骤就初始化被复制步骤的详情)
|
||||
await getStepDetail(step);
|
||||
}
|
||||
customCaseDrawerVisible.value = true;
|
||||
} else if (step.stepType === ScenarioStepType.SCRIPT) {
|
||||
activeStep.value = step;
|
||||
|
@ -998,6 +991,7 @@
|
|||
scenario.value.stepResponses[report.stepId] = temporaryStepReportMap[key];
|
||||
});
|
||||
temporaryStepReportMap = {};
|
||||
updateStepStatus(steps.value, scenario.value.stepResponses);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -1017,22 +1011,25 @@
|
|||
if (step.reportId === data.reportId) {
|
||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||
data.taskResult.requestResults.forEach((result) => {
|
||||
scenario.value.stepResponses[result.stepId] = {
|
||||
if (scenario.value.stepResponses[result.stepId] === undefined) {
|
||||
scenario.value.stepResponses[result.stepId] = [];
|
||||
}
|
||||
scenario.value.stepResponses[result.stepId].push({
|
||||
...result,
|
||||
console: data.taskResult.console,
|
||||
};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// 不是则需要把报告缓存起来,等切换到对应的tab再赋值
|
||||
data.taskResult.requestResults.forEach((result) => {
|
||||
if (step.reportId) {
|
||||
if (temporaryStepReportMap[step.reportId] === undefined) {
|
||||
temporaryStepReportMap[step.reportId] = {};
|
||||
temporaryStepReportMap[step.reportId] = [];
|
||||
}
|
||||
temporaryStepReportMap[step.reportId] = {
|
||||
temporaryStepReportMap[step.reportId].push({
|
||||
...result,
|
||||
console: data.taskResult.console,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1041,6 +1038,7 @@
|
|||
websocketMap[reportId].close();
|
||||
if (step.reportId === data.reportId) {
|
||||
step.isExecuting = false;
|
||||
updateStepStatus([step], scenario.value.stepResponses);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1056,6 +1054,7 @@
|
|||
const [currentStep] = executeParams.steps;
|
||||
try {
|
||||
currentStep.isExecuting = true;
|
||||
currentStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
debugSocket(currentStep, executeParams.reportId, executeType); // 开启websocket
|
||||
const res = await debugScenario({
|
||||
id: scenario.value.id || '',
|
||||
|
@ -1093,21 +1092,18 @@
|
|||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, node.id, 'id');
|
||||
if (realStep) {
|
||||
realStep.reportId = getGenerateId();
|
||||
if (
|
||||
[ScenarioStepType.API, ScenarioStepType.API_CASE, ScenarioStepType.CUSTOM_REQUEST].includes(realStep.stepType)
|
||||
) {
|
||||
// 请求和场景类型才直接显示执行中,其他控制器需要等待执行完毕才结算执行结果
|
||||
realStep.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
}
|
||||
const stepDetail = stepDetails.value[realStep.id];
|
||||
delete scenario.value.stepResponses[realStep.id]; // 先移除上一次的执行结果
|
||||
const _stepDetails = {};
|
||||
const stepFileParam = scenario.value.stepFileParam[realStep.id];
|
||||
traverseTree(realStep, (step) => {
|
||||
_stepDetails[step.id] = stepDetails.value[step.id];
|
||||
step.executeStatus = ScenarioExecuteStatus.EXECUTING;
|
||||
delete scenario.value.stepResponses[step.id]; // 先移除上一次的执行结果
|
||||
return step;
|
||||
});
|
||||
realExecute(
|
||||
{
|
||||
steps: [realStep as ScenarioStepItem],
|
||||
stepDetails: {
|
||||
[realStep.id]: stepDetail,
|
||||
},
|
||||
stepDetails: _stepDetails,
|
||||
reportId: realStep.reportId,
|
||||
uploadFileIds: stepFileParam?.uploadFileIds || [],
|
||||
linkFileIds: stepFileParam?.linkFileIds || [],
|
||||
|
@ -1286,7 +1282,10 @@
|
|||
{
|
||||
stepType: ScenarioStepType.CUSTOM_REQUEST,
|
||||
name: t('apiScenario.customApi'),
|
||||
config: {
|
||||
protocol: request.protocol,
|
||||
method: request.method,
|
||||
},
|
||||
id: request.stepId,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
|
@ -1355,6 +1354,8 @@
|
|||
if (activeStep.value && activeCreateAction.value) {
|
||||
handleCreateStep(
|
||||
{
|
||||
id,
|
||||
refType: ScenarioStepRefType.DIRECT,
|
||||
stepType: ScenarioStepType.SCRIPT,
|
||||
name,
|
||||
projectId: appStore.currentProjectId,
|
||||
|
@ -1532,27 +1533,6 @@
|
|||
color: rgb(var(--primary-5));
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
.scenario-step-response-popover {
|
||||
width: 500px;
|
||||
height: 450px;
|
||||
.arco-popover-content {
|
||||
@apply h-full;
|
||||
.response {
|
||||
.response-head {
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: var(--border-radius-small);
|
||||
.arco-spin {
|
||||
padding: 0;
|
||||
.response-container {
|
||||
padding: 0 16px 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -8,10 +8,11 @@ import { ScenarioExecuteStatus, ScenarioStepType } from '@/enums/apiEnum';
|
|||
*/
|
||||
export default function updateStepStatus(
|
||||
steps: ScenarioStepItem[],
|
||||
stepResponses: Record<string | number, RequestResult>
|
||||
stepResponses: Record<string | number, RequestResult[]>
|
||||
) {
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const node = steps[i];
|
||||
console.log('node', node.stepType, node.executeStatus);
|
||||
if (
|
||||
[
|
||||
ScenarioStepType.LOOP_CONTROLLER,
|
||||
|
@ -54,10 +55,11 @@ export default function updateStepStatus(
|
|||
node.executeStatus = ScenarioExecuteStatus.SUCCESS;
|
||||
} else if (node.executeStatus === ScenarioExecuteStatus.EXECUTING) {
|
||||
// 非逻辑控制器直接更改本身状态
|
||||
if (stepResponses[node.id]) {
|
||||
node.executeStatus = stepResponses[node.id].isSuccessful
|
||||
? ScenarioExecuteStatus.SUCCESS
|
||||
: ScenarioExecuteStatus.FAILED;
|
||||
if (stepResponses[node.id] && stepResponses[node.id].length > 0) {
|
||||
console.log('stepResponses[node.id]', stepResponses[node.id]);
|
||||
node.executeStatus = stepResponses[node.id].some((report) => !report.isSuccessful)
|
||||
? ScenarioExecuteStatus.FAILED
|
||||
: ScenarioExecuteStatus.SUCCESS;
|
||||
} else {
|
||||
node.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
|
||||
}
|
||||
|
|
|
@ -161,13 +161,6 @@
|
|||
|
||||
function setStepExecuteStatus() {
|
||||
updateStepStatus(activeScenarioTab.value.steps, activeScenarioTab.value.stepResponses);
|
||||
// activeScenarioTab.value.steps = mapTree<ScenarioStepItem>(activeScenarioTab.value.steps, (step) => {
|
||||
// if (step.executeStatus === ScenarioExecuteStatus.EXECUTING) {
|
||||
// // 如果结束执行时还有步骤是执行中状态,则设置为未执行
|
||||
// step.executeStatus = ScenarioExecuteStatus.UN_EXECUTE;
|
||||
// }
|
||||
// return step;
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,10 +178,13 @@
|
|||
if (activeScenarioTab.value.reportId === data.reportId) {
|
||||
// 判断当前查看的tab是否是当前返回的报告的tab,是的话直接赋值
|
||||
data.taskResult.requestResults.forEach((result) => {
|
||||
activeScenarioTab.value.stepResponses[result.stepId] = {
|
||||
if (activeScenarioTab.value.stepResponses[result.stepId] === undefined) {
|
||||
activeScenarioTab.value.stepResponses[result.stepId] = [];
|
||||
}
|
||||
activeScenarioTab.value.stepResponses[result.stepId].push({
|
||||
...result,
|
||||
console: data.taskResult.console,
|
||||
};
|
||||
});
|
||||
if (result.isSuccessful) {
|
||||
activeScenarioTab.value.executeSuccessCount += 1;
|
||||
} else {
|
||||
|
@ -202,10 +198,13 @@
|
|||
if (temporaryScenarioReportMap[activeScenarioTab.value.reportId] === undefined) {
|
||||
temporaryScenarioReportMap[activeScenarioTab.value.reportId] = {};
|
||||
}
|
||||
temporaryScenarioReportMap[activeScenarioTab.value.reportId][result.stepId] = {
|
||||
if (temporaryScenarioReportMap[activeScenarioTab.value.reportId][result.stepId]) {
|
||||
temporaryScenarioReportMap[activeScenarioTab.value.reportId][result.stepId] = [];
|
||||
}
|
||||
temporaryScenarioReportMap[activeScenarioTab.value.reportId][result.stepId].push({
|
||||
...result,
|
||||
console: data.taskResult.console,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -60,6 +60,10 @@ export default {
|
|||
'api_scenario.table.tableNoDataAndPlease': '暂无数据,请',
|
||||
'api_scenario.table.or': '或',
|
||||
'apiScenario.execute': '执行',
|
||||
'apiScenario.prev': '上一次',
|
||||
'apiScenario.next': '下一次',
|
||||
'apiScenario.sumLoop': '共{count}次循环',
|
||||
'apiScenario.times': '次',
|
||||
// 批量操作文案
|
||||
'api_scenario.batch_operation.success': '成功{opt}至 {name}',
|
||||
'api_scenario.table.batchMoveConfirm': '{opt}{count}个场景至已选模块',
|
||||
|
|
Loading…
Reference in New Issue