feat(接口测试): 断言&提取优化&部分 bug 修复

This commit is contained in:
baiqi 2024-05-09 18:23:18 +08:00 committed by 刘瑞斌
parent 58a069dc82
commit 3ed55b0e07
24 changed files with 293 additions and 113 deletions

View File

@ -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="[

View File

@ -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',
}

View File

@ -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; // 返回为图片时的图片地址
}

View File

@ -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; // 是否应用范围选择全部组织

View File

@ -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,
},
{

View File

@ -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: '',

View File

@ -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;

View File

@ -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">

View File

@ -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';

View File

@ -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',

View File

@ -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>

View File

@ -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>

View File

@ -30,8 +30,6 @@
props.requestResult.headers
}\nBody:\n${props.requestResult.body.trim()}`
: '';
case ResponseComposition.EXTRACT:
return props.requestResult?.responseResult?.vars?.trim();
default:
return '';
}

View File

@ -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',
};

View File

@ -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': '提取值',
};

View File

@ -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"

View File

@ -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',

View File

@ -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': '新环境',

View File

@ -274,6 +274,7 @@
//
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; //
node.isRefScenarioStep = node.parent.isRefScenarioStep; //
node.draggable = !node.parent.isQuoteScenarioStep; //
}
return {
...node,

View File

@ -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)) {
//

View File

@ -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

View File

@ -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;

View File

@ -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 },
};
}

View File

@ -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,8 +384,7 @@
]
: [];
const detailType =
apiTest || loadTest
const detailType = apiTest
? [
{
label: t('system.resourcePool.detailType'),
@ -433,7 +417,6 @@
tagType: 'default',
isTag: true,
},
...performanceDesc,
...uiDesc,
...detailType,
...resourceDesc,