feat(接口测试): 断言&提取优化&部分 bug 修复
This commit is contained in:
parent
58a069dc82
commit
3ed55b0e07
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a-spin class="!block" :loading="props.loading" :size="28">
|
||||
<a-spin class="z-[100] !block" :loading="props.loading" :size="28">
|
||||
<div
|
||||
ref="fullRef"
|
||||
:class="[
|
||||
|
|
|
@ -310,6 +310,7 @@ export enum WhileConditionType {
|
|||
CONDITION = 'CONDITION',
|
||||
SCRIPT = 'SCRIPT',
|
||||
}
|
||||
// 场景步骤多态名
|
||||
export enum ScenarioStepPolymorphicName {
|
||||
COMMON_SCRIPT = 'MsCommentScriptElement',
|
||||
IF_CONTROLLER = 'MsIfController',
|
||||
|
@ -317,7 +318,20 @@ export enum ScenarioStepPolymorphicName {
|
|||
ONLY_ONCE = 'MsOnceOnlyController',
|
||||
TIME_CONTROLLER = 'MsConstantTimerController',
|
||||
}
|
||||
// 场景设置-失败执行策略
|
||||
export enum ScenarioFailureStrategy {
|
||||
CONTINUE = 'CONTINUE',
|
||||
STOP = 'STOP',
|
||||
}
|
||||
// 接口响应-断言项类型
|
||||
export enum FullResponseAssertionType {
|
||||
DOCUMENT = 'DOCUMENT',
|
||||
RESPONSE_CODE = 'RESPONSE_CODE',
|
||||
RESPONSE_HEADER = 'RESPONSE_HEADER',
|
||||
RESPONSE_TIME = 'RESPONSE_TIME',
|
||||
SCRIPT = 'SCRIPT',
|
||||
VARIABLE = 'VARIABLE',
|
||||
JSON_PATH = 'JSON_PATH',
|
||||
XPATH = 'XPATH',
|
||||
REGEX = 'REGEX',
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Language } from '@/components/pure/ms-code-editor/types';
|
||||
|
||||
import {
|
||||
type FullResponseAssertionType,
|
||||
RequestAssertionCondition,
|
||||
RequestAuthType,
|
||||
RequestBodyFormat,
|
||||
|
@ -385,6 +386,24 @@ export interface ExecuteRequestParams {
|
|||
frontendDebug?: boolean; // 是否本地调试,该模式下接口会返回执行参数,用来调用本地执行服务
|
||||
apiDefinitionId?: string | number; // 接口用例执行和调试时需要传
|
||||
}
|
||||
// 断言-断言列表表格子项
|
||||
export interface ResponseAssertionTableItem {
|
||||
actualValue: string;
|
||||
assertionType: FullResponseAssertionType;
|
||||
condition: RequestAssertionConditionType;
|
||||
content: string;
|
||||
expectedValue: string;
|
||||
message: string;
|
||||
name: string;
|
||||
pass: boolean;
|
||||
}
|
||||
// 请求提取结果项
|
||||
export interface ResponseExtractItem {
|
||||
name: string;
|
||||
value: string;
|
||||
type: RequestExtractEnvType;
|
||||
expression: string;
|
||||
}
|
||||
// 响应结果
|
||||
export interface ResponseResult {
|
||||
body: string;
|
||||
|
@ -402,7 +421,8 @@ export interface ResponseResult {
|
|||
tcpHandshakeTime: number;
|
||||
transferStartTime: number;
|
||||
vars: string;
|
||||
assertions: any;
|
||||
extractResults: ResponseExtractItem[];
|
||||
assertions: ResponseAssertionTableItem[];
|
||||
imageUrl?: string; // 返回为图片时的图片地址
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ export interface OrgIdNameMap {
|
|||
|
||||
// 资源池配置信息对象
|
||||
export interface TestResourceDTO {
|
||||
loadTestImage: string; // 镜像
|
||||
loadTestHeap: string; // Jmeter heap
|
||||
nodesList: NodesListItem[]; // node资源
|
||||
ip: string; // k8s ip
|
||||
token: string; // k8s token
|
||||
|
@ -36,7 +34,6 @@ export interface ResourcePoolInfo {
|
|||
type: string; // 资源池类型
|
||||
enable: boolean; // 是否启用
|
||||
apiTest: boolean; // 是否支持api测试
|
||||
loadTest: boolean; // 是否支持性能测试
|
||||
uiTest: boolean; // 是否支持ui测试
|
||||
serverUrl: string; // 资源池地址
|
||||
allOrg: boolean; // 是否应用范围选择全部组织
|
||||
|
|
|
@ -517,7 +517,7 @@
|
|||
ResponseBodyXPathAssertionFormat,
|
||||
} from '@/enums/apiEnum';
|
||||
|
||||
import { defaultKeyValueParamItem } from '@/views/api-test/components/config';
|
||||
import { defaultKeyValueParamItem, extractTypeOptions } from '@/views/api-test/components/config';
|
||||
|
||||
const { openModal } = useModal();
|
||||
|
||||
|
@ -782,21 +782,12 @@ if (!result){
|
|||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'variableType',
|
||||
slotName: 'variableType',
|
||||
typeOptions: [
|
||||
// 全局参数,暂时不上
|
||||
// {
|
||||
// label: t('apiTestDebug.globalParameter'),
|
||||
// value: RequestExtractEnvType.GLOBAL,
|
||||
// },
|
||||
{
|
||||
label: t('apiTestDebug.envParameter'),
|
||||
value: RequestExtractEnvType.ENVIRONMENT,
|
||||
},
|
||||
{
|
||||
label: t('apiTestDebug.tempParameter'),
|
||||
value: RequestExtractEnvType.TEMPORARY,
|
||||
},
|
||||
],
|
||||
typeOptions: extractTypeOptions.map((item) => {
|
||||
return {
|
||||
label: t(item.label),
|
||||
value: item.value,
|
||||
};
|
||||
}),
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from '@/models/apiTest/common';
|
||||
import type { MockParams } from '@/models/apiTest/mock';
|
||||
import {
|
||||
FullResponseAssertionType,
|
||||
RequestAssertionCondition,
|
||||
RequestBodyFormat,
|
||||
RequestCaseStatus,
|
||||
|
@ -144,6 +145,7 @@ export const defaultResponse: RequestTaskResult = {
|
|||
transferStartTime: 0,
|
||||
sslHandshakeTime: 0,
|
||||
vars: '',
|
||||
extractResults: [],
|
||||
assertions: [],
|
||||
},
|
||||
},
|
||||
|
@ -239,6 +241,34 @@ export const regexDefaultParamItem = {
|
|||
responseFormat: ResponseBodyXPathAssertionFormat.XML,
|
||||
moreSettingPopoverVisible: false,
|
||||
};
|
||||
// 响应断言类型映射
|
||||
export const responseAssertionTypeMap: Record<FullResponseAssertionType, string> = {
|
||||
[FullResponseAssertionType.DOCUMENT]: 'apiTestManagement.document',
|
||||
[FullResponseAssertionType.RESPONSE_CODE]: 'apiTestManagement.responseCode',
|
||||
[FullResponseAssertionType.RESPONSE_HEADER]: 'apiTestManagement.responseHeader',
|
||||
[FullResponseAssertionType.RESPONSE_TIME]: 'apiTestManagement.responseTime',
|
||||
[FullResponseAssertionType.SCRIPT]: 'apiTestManagement.script',
|
||||
[FullResponseAssertionType.VARIABLE]: 'apiTestManagement.variable',
|
||||
[FullResponseAssertionType.JSON_PATH]: 'jsonPath',
|
||||
[FullResponseAssertionType.XPATH]: 'xPath',
|
||||
[FullResponseAssertionType.REGEX]: 'apiTestManagement.regex',
|
||||
};
|
||||
// 提取类型选项
|
||||
export const extractTypeOptions = [
|
||||
// 全局参数,暂时不上
|
||||
// {
|
||||
// label: t('apiTestDebug.globalParameter'),
|
||||
// value: RequestExtractEnvType.GLOBAL,
|
||||
// },
|
||||
{
|
||||
label: 'apiTestDebug.envParameter',
|
||||
value: RequestExtractEnvType.ENVIRONMENT,
|
||||
},
|
||||
{
|
||||
label: 'apiTestDebug.tempParameter',
|
||||
value: RequestExtractEnvType.TEMPORARY,
|
||||
},
|
||||
];
|
||||
// mock 匹配规则默认项
|
||||
export const defaultMatchRuleItem = {
|
||||
key: '',
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
</a-empty>
|
||||
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
|
||||
<div v-if="!props.isCase" class="px-[18px] pt-[8px]">
|
||||
<div class="flex flex-wrap items-center justify-between gap-[12px]">
|
||||
<div class="flex flex-1 items-center gap-[16px]">
|
||||
<div class="flex flex-wrap items-baseline justify-between gap-[12px]">
|
||||
<div class="flex flex-1 flex-wrap items-center gap-[16px]">
|
||||
<a-select
|
||||
v-if="requestVModel.isNew"
|
||||
v-model:model-value="requestVModel.protocol"
|
||||
|
@ -53,6 +53,9 @@
|
|||
@change="handleUrlChange"
|
||||
/>
|
||||
</a-input-group>
|
||||
<div v-if="isUrlError" class="url-input-tip">
|
||||
<span>{{ t('apiTestDebug.apiUrlRequired') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a-radio-group
|
||||
|
@ -184,9 +187,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isUrlError" class="url-input-tip">
|
||||
<span>{{ t('apiTestDebug.apiUrlRequired') }}</span>
|
||||
</div>
|
||||
<div :class="`${!props.isCase ? 'request-tab-and-response' : ''} mt-[8px] flex-1`">
|
||||
<MsTab
|
||||
v-model:active-key="requestVModel.activeTab"
|
||||
|
@ -1622,11 +1622,13 @@
|
|||
@apply leading-none;
|
||||
}
|
||||
.url-input-tip {
|
||||
margin-top: 2px 0 250px;
|
||||
@apply w-full;
|
||||
|
||||
margin-top: -14px;
|
||||
padding-left: 226px;
|
||||
font-size: 12px;
|
||||
color: rgb(var(--danger-6));
|
||||
line-height: 16px;
|
||||
@apply flex flex-col flex-nowrap items-center justify-start;
|
||||
}
|
||||
.request-tab-and-response {
|
||||
overflow-x: hidden;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
v-if="props.requestResult?.responseResult?.responseCode"
|
||||
class="flex items-center justify-between gap-[24px] text-[14px]"
|
||||
>
|
||||
<a-tooltip :content="props.requestResult.fakeErrorCode">
|
||||
<a-tooltip :content="props.requestResult.fakeErrorCode" :disabled="!props.requestResult.fakeErrorCode">
|
||||
<executeStatus :status="finalStatus" size="small" class="ml-[4px]" />
|
||||
</a-tooltip>
|
||||
<a-popover position="left" content-class="response-popover-content">
|
||||
|
|
|
@ -12,16 +12,17 @@
|
|||
/>
|
||||
<ResConsole v-else-if="activeTab === ResponseComposition.CONSOLE" :console="props.console?.trim()" />
|
||||
<ResValueScript
|
||||
v-else-if="
|
||||
activeTab === ResponseComposition.HEADER ||
|
||||
activeTab === ResponseComposition.REAL_REQUEST ||
|
||||
activeTab === ResponseComposition.EXTRACT
|
||||
"
|
||||
v-else-if="activeTab === ResponseComposition.HEADER || activeTab === ResponseComposition.REAL_REQUEST"
|
||||
:active-tab="activeTab"
|
||||
:request-result="props.requestResult"
|
||||
/>
|
||||
<ExtractTable
|
||||
v-else-if="activeTab === ResponseComposition.EXTRACT"
|
||||
:request-result="props.requestResult"
|
||||
:scroll="{ x: '100%' }"
|
||||
/>
|
||||
<ResAssertion
|
||||
v-else-if="activeTab === 'ASSERTION'"
|
||||
v-else-if="activeTab === ResponseComposition.ASSERTION"
|
||||
:request-result="props.requestResult"
|
||||
:scroll="{ x: '100%' }"
|
||||
/>
|
||||
|
@ -58,6 +59,7 @@
|
|||
import ResAssertion from './result/assertionTable.vue';
|
||||
import ResBody from './result/body.vue';
|
||||
import ResConsole from './result/console.vue';
|
||||
import ExtractTable from './result/extractTable.vue';
|
||||
import ResValueScript from './result/resValueScript.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
|
|
@ -6,6 +6,15 @@
|
|||
:selectable="false"
|
||||
:scroll="props.scroll"
|
||||
>
|
||||
<template #assertionItem="{ record }">
|
||||
<div class="flex items-center gap-[4px]">
|
||||
【{{ t(responseAssertionTypeMap[(record as ResponseAssertionTableItem).assertionType]) }}】
|
||||
{{ record.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #condition="{ record }">
|
||||
{{ t(statusCodeOptions.find((item) => item.value === record.condition)?.label || '') }}
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<MsTag :type="record.pass === true ? 'success' : 'danger'" theme="light">
|
||||
{{ record.pass === true ? t('common.success') : t('common.fail') }}
|
||||
|
@ -16,13 +25,16 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { statusCodeOptions } from '@/components/pure/ms-advance-filter/index';
|
||||
import MsFormTable from '@/components/pure/ms-form-table/index.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestResult } from '@/models/apiTest/common';
|
||||
import { RequestResult, ResponseAssertionTableItem } from '@/models/apiTest/common';
|
||||
|
||||
import { responseAssertionTypeMap } from '@/views/api-test/components/config';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
|
@ -37,10 +49,29 @@
|
|||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'apiTestDebug.content',
|
||||
dataIndex: 'content',
|
||||
title: 'apiTestDebug.assertionItem',
|
||||
dataIndex: 'assertionItem',
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
slotName: 'assertionItem',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.actualValue',
|
||||
dataIndex: 'actualValue',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.condition',
|
||||
dataIndex: 'condition',
|
||||
slotName: 'condition',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.expectedValue',
|
||||
dataIndex: 'expectedValue',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.status',
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<a-scrollbar class="h-full overflow-y-auto">
|
||||
<MsFormTable
|
||||
:data="props.requestResult?.responseResult.extractResults"
|
||||
:columns="columns"
|
||||
:selectable="false"
|
||||
:scroll="props.scroll"
|
||||
>
|
||||
<template #type="{ record }">
|
||||
{{ t(extractTypeOptions.find((item) => item.value === record.type)?.label || '') }}
|
||||
</template>
|
||||
</MsFormTable>
|
||||
</a-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsFormTable from '@/components/pure/ms-form-table/index.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestResult } from '@/models/apiTest/common';
|
||||
|
||||
import { extractTypeOptions } from '@/views/api-test/components/config';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
requestResult?: RequestResult;
|
||||
scroll?: {
|
||||
x?: number | string;
|
||||
y?: number | string;
|
||||
maxHeight?: number | string;
|
||||
minWidth?: number | string;
|
||||
};
|
||||
}>();
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.extractValue',
|
||||
dataIndex: 'value',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.expression',
|
||||
dataIndex: 'expression',
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.arco-scrollbar {
|
||||
@apply h-full;
|
||||
}
|
||||
</style>
|
|
@ -76,13 +76,13 @@
|
|||
</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>
|
||||
<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">{{
|
||||
props.environmentName
|
||||
}}</div>
|
||||
<div v-if="props.showType && props.showType !== 'CASE'" class="one-line-text">
|
||||
{{ props.environmentName }}
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</div>
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
props.requestResult.headers
|
||||
}\nBody:\n${props.requestResult.body.trim()}`
|
||||
: '';
|
||||
case ResponseComposition.EXTRACT:
|
||||
return props.requestResult?.responseResult?.vars?.trim();
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -206,4 +206,8 @@ export default {
|
|||
'Enter the column name and corresponding value in column storage. If you want to extract the first value of the name column, enter name_1',
|
||||
'apiTestDebug.responseRepeatMessage': 'The name is duplicated, please re-enter it.',
|
||||
'apiTestDebug.saveAsApi': 'Save as Api',
|
||||
'apiTestDebug.assertionItem': 'Assertion item',
|
||||
'apiTestDebug.actualValue': 'Actual value',
|
||||
'apiTestDebug.condition': 'condition',
|
||||
'apiTestDebug.expectedValue': 'Expected value',
|
||||
};
|
||||
|
|
|
@ -192,4 +192,9 @@ export default {
|
|||
'apiTestDebug.extractValueTitleTip': '输入按列存储中的列名和对应的数值,如提取name列的第一个值则输入name_1',
|
||||
'apiTestDebug.responseRepeatMessage': '名称重复,请重新输入',
|
||||
'apiTestDebug.saveAsApi': '另存为接口',
|
||||
'apiTestDebug.assertionItem': '断言项',
|
||||
'apiTestDebug.actualValue': '返回值',
|
||||
'apiTestDebug.condition': '匹配条件',
|
||||
'apiTestDebug.expectedValue': '匹配值',
|
||||
'apiTestDebug.extractValue': '提取值',
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@close="handleCancel"
|
||||
>
|
||||
<template #tbutton>
|
||||
<div v-if="mockDetail.id" class="right-operation-button-icon flex items-center gap-[4px]">
|
||||
<div v-if="isReadOnly" class="right-operation-button-icon flex items-center gap-[4px]">
|
||||
<MsButton
|
||||
v-permission="['PROJECT_API_DEFINITION_MOCK:READ+UPDATE']"
|
||||
type="icon"
|
||||
|
|
|
@ -154,6 +154,12 @@ export default {
|
|||
'apiTestManagement.quoteSearchPlaceholder': 'Enter ID or name to search',
|
||||
'apiTestManagement.tableNoDataAndPlease': 'No data yet, please',
|
||||
'apiTestManagement.or': 'or',
|
||||
'apiTestManagement.document': 'Document',
|
||||
'apiTestManagement.responseHeader': 'Response header',
|
||||
'apiTestManagement.responseTime': 'Response time',
|
||||
'apiTestManagement.script': 'Script',
|
||||
'apiTestManagement.variable': 'Variable',
|
||||
'apiTestManagement.regex': 'Regular',
|
||||
'case.execute.selectEnv': 'Environmental choice',
|
||||
'case.execute.defaultEnv': 'Default environment',
|
||||
'case.execute.newEnv': 'New environment',
|
||||
|
|
|
@ -150,6 +150,12 @@ export default {
|
|||
'apiTestManagement.getResponse': '获取响应内容',
|
||||
'apiTestManagement.tableNoDataAndPlease': '暂无数据,请',
|
||||
'apiTestManagement.or': '或',
|
||||
'apiTestManagement.document': '文档断言',
|
||||
'apiTestManagement.responseHeader': '响应头',
|
||||
'apiTestManagement.responseTime': '响应时间',
|
||||
'apiTestManagement.script': '脚本',
|
||||
'apiTestManagement.variable': '变量',
|
||||
'apiTestManagement.regex': '正则表达式',
|
||||
'case.execute.selectEnv': '环境选择',
|
||||
'case.execute.defaultEnv': '默认环境',
|
||||
'case.execute.newEnv': '新环境',
|
||||
|
|
|
@ -274,6 +274,7 @@
|
|||
// 如果有父节点
|
||||
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||
node.draggable = !node.parent.isQuoteScenarioStep; // 引用场景下的任何步骤不可拖拽
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
|
|
|
@ -352,6 +352,7 @@
|
|||
// 如果有父节点
|
||||
child.isQuoteScenarioStep = child.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||
child.isRefScenarioStep = child.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||
child.draggable = !node.parent.isQuoteScenarioStep; // 引用场景下的任何步骤不可拖拽
|
||||
}
|
||||
if (selectedKeys.value.includes(node.uniqueId) && !selectedKeys.value.includes(child.uniqueId)) {
|
||||
// 如果有新增的子步骤,且当前步骤被选中,则这个新增的子步骤也要选中
|
||||
|
|
|
@ -561,7 +561,8 @@
|
|||
const loading = ref(false);
|
||||
const treeRef = ref<InstanceType<typeof MsTree>>();
|
||||
const focusStepKey = ref<string | number>(''); // 聚焦的key
|
||||
const activeStep = ref<ScenarioStepItem>(); // 用于抽屉操作创建步骤、弹窗配置时记录当前操作的步骤节点
|
||||
const activeStep = ref<ScenarioStepItem>(); // 用于弹窗配置时记录当前操作的步骤节点
|
||||
const activeStepByCreate = ref<ScenarioStepItem | undefined>(); // 用于抽屉操作创建步骤时记录当前操作的步骤节点
|
||||
|
||||
function setFocusNodeKey(id: string | number) {
|
||||
focusStepKey.value = id || '';
|
||||
|
@ -1151,11 +1152,13 @@
|
|||
if (activeStep.value) {
|
||||
return stepDetails.value[activeStep.value.id];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const currentStepFileParams = computed<ScenarioStepFileParams | undefined>(() => {
|
||||
if (activeStep.value) {
|
||||
return scenario.value.stepFileParam[activeStep.value.id];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
function handleAddStepDone(newStep: ScenarioStepItem) {
|
||||
|
@ -1447,7 +1450,7 @@
|
|||
step?: ScenarioStepItem,
|
||||
_activeCreateAction?: CreateStepAction
|
||||
) {
|
||||
activeStep.value = step;
|
||||
activeStepByCreate.value = step;
|
||||
activeCreateAction.value = _activeCreateAction;
|
||||
switch (type) {
|
||||
case ScenarioAddStepActionType.IMPORT_SYSTEM_API:
|
||||
|
@ -1473,16 +1476,16 @@
|
|||
*/
|
||||
function handleImportApiApply(type: 'copy' | 'quote', data: ImportData) {
|
||||
let sort = steps.value.length + 1;
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
if (activeStepByCreate.value && activeCreateAction.value) {
|
||||
switch (activeCreateAction.value) {
|
||||
case 'inside':
|
||||
sort = activeStep.value.children ? activeStep.value.children.length : 0;
|
||||
sort = activeStepByCreate.value.children ? activeStepByCreate.value.children.length : 0;
|
||||
break;
|
||||
case 'before':
|
||||
sort = activeStep.value.sort;
|
||||
sort = activeStepByCreate.value.sort;
|
||||
break;
|
||||
case 'after':
|
||||
sort = activeStep.value.sort + 1;
|
||||
sort = activeStepByCreate.value.sort + 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -1511,8 +1514,14 @@
|
|||
appStore.currentProjectId
|
||||
);
|
||||
const insertSteps = insertApiSteps.concat(insertCaseSteps).concat(insertScenarioSteps);
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
handleCreateSteps(activeStep.value, insertSteps, steps.value, activeCreateAction.value, selectedKeys.value);
|
||||
if (activeStepByCreate.value && activeCreateAction.value) {
|
||||
handleCreateSteps(
|
||||
activeStepByCreate.value,
|
||||
insertSteps,
|
||||
steps.value,
|
||||
activeCreateAction.value,
|
||||
selectedKeys.value
|
||||
);
|
||||
} else {
|
||||
steps.value = steps.value.concat(insertSteps);
|
||||
}
|
||||
|
@ -1533,7 +1542,7 @@
|
|||
unLinkFileIds: request.unLinkFileIds,
|
||||
};
|
||||
emit('updateResource', request.uploadFileIds, request.linkFileIds);
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
if (activeStepByCreate.value && activeCreateAction.value) {
|
||||
handleCreateStep(
|
||||
{
|
||||
stepType: ScenarioStepType.CUSTOM_REQUEST,
|
||||
|
@ -1546,7 +1555,7 @@
|
|||
uniqueId: request.stepId,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
activeStep.value,
|
||||
activeStepByCreate.value,
|
||||
steps.value,
|
||||
activeCreateAction.value,
|
||||
selectedKeys.value
|
||||
|
@ -1641,7 +1650,7 @@
|
|||
function addScriptStep(name: string, scriptProcessor: ExecuteConditionProcessor) {
|
||||
const id = getGenerateId();
|
||||
stepDetails.value[id] = cloneDeep(scriptProcessor);
|
||||
if (activeStep.value && activeCreateAction.value) {
|
||||
if (activeStepByCreate.value && activeCreateAction.value) {
|
||||
handleCreateStep(
|
||||
{
|
||||
id,
|
||||
|
@ -1651,7 +1660,7 @@
|
|||
name,
|
||||
projectId: appStore.currentProjectId,
|
||||
},
|
||||
activeStep.value,
|
||||
activeStepByCreate.value,
|
||||
steps.value,
|
||||
activeCreateAction.value,
|
||||
selectedKeys.value
|
||||
|
|
|
@ -386,6 +386,7 @@
|
|||
// 如果有父节点
|
||||
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||
node.draggable = !node.parent.isQuoteScenarioStep; // 引用场景下的任何步骤不可拖拽
|
||||
}
|
||||
if (!node.isQuoteScenarioStep && !node.isRefScenarioStep) {
|
||||
// 非引用场景步骤
|
||||
|
@ -410,6 +411,7 @@
|
|||
// 如果有父节点
|
||||
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||
node.draggable = !node.parent.isQuoteScenarioStep; // 引用场景下的任何步骤不可拖拽
|
||||
}
|
||||
node.uniqueId = getGenerateId();
|
||||
return node;
|
||||
|
@ -533,6 +535,7 @@
|
|||
// 如果有父节点
|
||||
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||
node.draggable = !node.parent.isQuoteScenarioStep; // 引用场景下的任何步骤不可拖拽
|
||||
}
|
||||
node.uniqueId = getGenerateId();
|
||||
return node;
|
||||
|
|
|
@ -78,6 +78,22 @@
|
|||
<a-option v-for="org of orgOptions" :key="org.id" :value="org.id">{{ org.name }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item
|
||||
:label="t('system.resourcePool.use')"
|
||||
field="use"
|
||||
class="form-item"
|
||||
:rules="[{ required: true, message: t('system.resourcePool.useRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<a-checkbox-group v-model:model-value="form.use" @change="() => setIsSave(false)">
|
||||
<a-checkbox v-for="use of useList" :key="use.value" :value="use.value">{{ t(use.label) }}</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<MsFormItemSub
|
||||
v-if="form.use.length === 3"
|
||||
:text="t('system.resourcePool.allUseTip')"
|
||||
:show-fill-icon="false"
|
||||
/>
|
||||
</a-form-item> -->
|
||||
<!--TODO:暂无性能测试-->
|
||||
<!-- <template v-if="isCheckedPerformance">
|
||||
<a-form-item :label="t('system.resourcePool.mirror')" field="testResourceDTO.loadTestImage" class="form-item">
|
||||
|
@ -342,7 +358,7 @@
|
|||
</template>
|
||||
</a-form>
|
||||
<template #footerLeft>
|
||||
<MsButton v-if="isCheckedPerformance && isShowK8SResources" @click="showJobDrawer = true">
|
||||
<MsButton v-if="isShowK8SResources" @click="showJobDrawer = true">
|
||||
{{ t('system.resourcePool.customJobTemplate') }}
|
||||
<a-tooltip :content="t('system.resourcePool.jobTemplateTip')" position="tl" mini>
|
||||
<icon-question-circle class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-6))]" />
|
||||
|
@ -405,8 +421,6 @@
|
|||
type: 'Node',
|
||||
addType: 'single',
|
||||
testResourceDTO: {
|
||||
loadTestImage: '',
|
||||
loadTestHeap: '',
|
||||
uiGrid: '',
|
||||
girdConcurrentNumber: 1,
|
||||
podThreads: 1,
|
||||
|
@ -466,7 +480,7 @@
|
|||
...res,
|
||||
addType: 'single',
|
||||
orgType: res.allOrg ? 'allOrg' : 'set',
|
||||
use: [res.loadTest ? 'performance' : '', res.apiTest ? 'API' : '', res.uiTest ? 'UI' : ''].filter((e) => e),
|
||||
use: [res.apiTest ? 'API' : '', res.uiTest ? 'UI' : ''].filter((e) => e),
|
||||
testResourceDTO: {
|
||||
...testResourceReturnDTO,
|
||||
girdConcurrentNumber: girdConcurrentNumber || 1,
|
||||
|
@ -522,14 +536,12 @@
|
|||
*/
|
||||
// 是否选择应用组织为指定组织
|
||||
const isSpecifiedOrg = computed(() => form.value.orgType === 'set');
|
||||
// 是否勾选了性能测试
|
||||
const isCheckedPerformance = computed(() => form.value.use.includes('performance'));
|
||||
// 是否勾选了UI测试
|
||||
const isCheckedUI = computed(() => form.value.use.includes('UI'));
|
||||
// 是否勾选了接口测试
|
||||
const isCheckedAPI = computed(() => form.value.use.includes('API'));
|
||||
// 是否显示类型切换表单项
|
||||
const isShowTypeItem = computed(() => ['API', 'performance'].some((s) => form.value.use.includes(s)));
|
||||
const isShowTypeItem = computed(() => ['API'].some((s) => form.value.use.includes(s)));
|
||||
// 是否显示Node资源配置信息
|
||||
const isShowNodeResources = computed(() => form.value.type === 'Node' && isShowTypeItem.value);
|
||||
// 是否显示K8S资源配置信息
|
||||
|
@ -567,6 +579,13 @@
|
|||
rules: [{ required: true, message: t('system.resourcePool.portRequired') }],
|
||||
placeholder: 'system.resourcePool.portPlaceholder',
|
||||
},
|
||||
{
|
||||
filed: 'monitor',
|
||||
type: 'input',
|
||||
label: 'system.resourcePool.monitor',
|
||||
rules: [{ required: true, message: t('system.resourcePool.monitorRequired') }],
|
||||
placeholder: 'system.resourcePool.monitorPlaceholder',
|
||||
},
|
||||
{
|
||||
filed: 'concurrentNumber',
|
||||
type: 'inputNumber',
|
||||
|
@ -632,11 +651,12 @@
|
|||
if (e.trim() !== '') {
|
||||
// 排除空串
|
||||
const line = e.split(',');
|
||||
if (line.every((s) => s.trim() !== '') && !Number.isNaN(Number(line[2]))) {
|
||||
if (line.every((s) => s.trim() !== '') && !Number.isNaN(Number(line[3]))) {
|
||||
const item = {
|
||||
ip: line[0],
|
||||
port: line[1],
|
||||
concurrentNumber: Number(line[2]),
|
||||
monitor: line[2],
|
||||
concurrentNumber: Number(line[3]),
|
||||
};
|
||||
if (i === 0) {
|
||||
// 第四个是concurrentNumber,需要是数字
|
||||
|
@ -727,8 +747,6 @@
|
|||
jobDefinition, // k8s job自定义模板
|
||||
deployName, // k8s api测试部署名称
|
||||
nodesList,
|
||||
loadTestImage,
|
||||
loadTestHeap,
|
||||
uiGrid,
|
||||
girdConcurrentNumber,
|
||||
} = testResourceDTO;
|
||||
|
@ -748,15 +766,6 @@
|
|||
deployName: isCheckedAPI.value ? deployName : null, // 勾选了接口测试才需要传
|
||||
}
|
||||
: {};
|
||||
// 性能测试资源
|
||||
const performanceDTO = isCheckedPerformance.value
|
||||
? {
|
||||
loadTestImage,
|
||||
loadTestHeap,
|
||||
...nodeResourceDTO,
|
||||
...k8sResourceDTO,
|
||||
}
|
||||
: {};
|
||||
// 接口测试资源
|
||||
const apiDTO = isCheckedAPI.value
|
||||
? {
|
||||
|
@ -772,15 +781,14 @@
|
|||
}
|
||||
: {};
|
||||
|
||||
const jobDTO = isCheckedPerformance.value && isShowK8SResources ? { jobDefinition } : {};
|
||||
const jobDTO = isShowK8SResources ? { jobDefinition } : {};
|
||||
return {
|
||||
...form.value,
|
||||
type: isShowTypeItem.value ? form.value.type : 'Node', // 默认给 Node,后台需要
|
||||
allOrg: form.value.orgType === 'allOrg',
|
||||
apiTest: form.value.use.includes('API'), // 是否支持api测试
|
||||
loadTest: form.value.use.includes('performance'), // 是否支持性能测试
|
||||
uiTest: form.value.use.includes('UI'), // 是否支持ui测试
|
||||
testResourceDTO: { ...performanceDTO, ...apiDTO, ...uiDTO, ...jobDTO, orgIds: form.value.testResourceDTO.orgIds },
|
||||
testResourceDTO: { ...apiDTO, ...uiDTO, ...jobDTO, orgIds: form.value.testResourceDTO.orgIds },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,6 @@
|
|||
/**
|
||||
* @description 系统设置-资源池
|
||||
*/
|
||||
import { onMounted, Ref, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
|
@ -192,6 +191,7 @@
|
|||
Message.success(t('system.resourcePool.enablePoolSuccess'));
|
||||
loadList();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
@ -298,7 +298,7 @@
|
|||
activePool.value.apiTest ? t('system.resourcePool.useAPI') : '',
|
||||
activePool.value.uiTest ? t('system.resourcePool.useUI') : '',
|
||||
];
|
||||
const { type, testResourceReturnDTO, loadTest, apiTest, uiTest } = activePool.value;
|
||||
const { type, testResourceReturnDTO, apiTest, uiTest } = activePool.value;
|
||||
const {
|
||||
ip,
|
||||
token, // k8s token
|
||||
|
@ -308,8 +308,6 @@
|
|||
deployName, // k8s api测试部署名称
|
||||
girdConcurrentNumber,
|
||||
nodesList,
|
||||
loadTestImage,
|
||||
loadTestHeap,
|
||||
uiGrid,
|
||||
} = testResourceReturnDTO;
|
||||
// Node
|
||||
|
@ -358,7 +356,7 @@
|
|||
]
|
||||
: [];
|
||||
const jobTemplate =
|
||||
loadTest && type === 'Kubernetes'
|
||||
type === 'Kubernetes'
|
||||
? [
|
||||
{
|
||||
label: t('system.resourcePool.jobTemplate'),
|
||||
|
@ -370,21 +368,8 @@
|
|||
},
|
||||
]
|
||||
: [];
|
||||
// 性能测试
|
||||
const performanceDesc = loadTest
|
||||
? [
|
||||
{
|
||||
label: t('system.resourcePool.mirror'),
|
||||
value: loadTestImage,
|
||||
},
|
||||
{
|
||||
label: t('system.resourcePool.testHeap'),
|
||||
value: loadTestHeap,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
// 接口测试/性能测试
|
||||
const resourceDesc = apiTest || loadTest ? [...nodeResourceDesc, ...k8sResourceDesc] : [];
|
||||
// 接口测试
|
||||
const resourceDesc = apiTest ? [...nodeResourceDesc, ...k8sResourceDesc] : [];
|
||||
// ui 测试资源
|
||||
const uiDesc = uiTest
|
||||
? [
|
||||
|
@ -399,15 +384,14 @@
|
|||
]
|
||||
: [];
|
||||
|
||||
const detailType =
|
||||
apiTest || loadTest
|
||||
? [
|
||||
{
|
||||
label: t('system.resourcePool.detailType'),
|
||||
value: activePool.value.type,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
const detailType = apiTest
|
||||
? [
|
||||
{
|
||||
label: t('system.resourcePool.detailType'),
|
||||
value: activePool.value.type,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
activePoolDesc.value = [
|
||||
{
|
||||
label: t('system.resourcePool.detailDesc'),
|
||||
|
@ -433,7 +417,6 @@
|
|||
tagType: 'default',
|
||||
isTag: true,
|
||||
},
|
||||
...performanceDesc,
|
||||
...uiDesc,
|
||||
...detailType,
|
||||
...resourceDesc,
|
||||
|
|
Loading…
Reference in New Issue