feat(环境管理): 断言的请求体文档类型表格

This commit is contained in:
RubyLiu 2024-02-22 20:03:45 +08:00 committed by 刘瑞斌
parent 2ddfe47a39
commit f777cb034e
8 changed files with 339 additions and 25 deletions

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-col">
<div class="flex w-full flex-col">
<div>
<a-radio-group v-model:model-value="activeTab" type="button" size="small">
<a-radio v-for="item of responseRadios" :key="item.value" :value="item.value">
@ -14,7 +14,7 @@
:columns="jsonPathColumns"
:scroll="{ minWidth: '700px' }"
:default-param-item="jsonPathDefaultParamItem"
@change="handleJsonPathChange"
@change="handleChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
>
<template #expression="{ record }">
@ -86,7 +86,7 @@
<div class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseContentType') }}</div>
<a-radio-group
v-model:model-value="innerParams.xPath.responseFormat"
class="mt-[16px]"
class="mb-[16px] mt-[16px]"
type="button"
size="small"
>
@ -95,12 +95,11 @@
</a-radio-group>
<paramsTable
v-model:params="innerParams.xPath.data"
class="mt-[16px]"
:selectable="false"
:columns="xPathColumns"
:scroll="{ minWidth: '700px' }"
:default-param-item="xPathDefaultParamItem"
@change="handleXPathChange"
@change="handleChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
>
<template #expression="{ record }">
@ -168,15 +167,130 @@
</template>
</paramsTable>
</div>
<div v-if="activeTab === 'document'">
<paramsTable
v-model:params="innerParams.document.data"
:selectable="false"
:columns="documentColumns"
:scroll="{
minWidth: '700px',
}"
:default-param-item="documentDefaultParamItem"
@change="handleChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
>
<template #operationPre="{ record }">
<a-tooltip v-if="['object', 'array'].includes(record.paramType)" :content="t('ms.assertion.addChild')">
<div
class="flex h-[24px] w-[24px] cursor-pointer items-center justify-center rounded text-[rgb(var(--primary-5))] hover:bg-[rgb(var(--primary-1))]"
@click="addChild"
>
<icon-plus size="14" />
</div>
</a-tooltip>
<a-tooltip v-else :content="t('ms.assertion.validateChild')">
<div
class="flex h-[24px] w-[24px] cursor-pointer items-center justify-center rounded text-[rgb(var(--primary-5))] hover:bg-[rgb(var(--primary-1))]"
@click="addValidateChild"
>
<icon-bookmark size="14" />
</div>
</a-tooltip>
</template>
</paramsTable>
</div>
<div v-if="activeTab === 'regular'" class="mt-[16px]">
<paramsTable
v-model:params="innerParams.regular"
:selectable="false"
:columns="xPathColumns"
:scroll="{ minWidth: '700px' }"
:default-param-item="xPathDefaultParamItem"
@change="handleChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
>
<template #expression="{ record }">
<a-popover
position="tl"
:disabled="!record.expression || record.expression.trim() === ''"
class="ms-params-input-popover"
>
<template #content>
<div class="param-popover-title">
{{ t('apiTestDebug.expression') }}
</div>
<div class="param-popover-value">
{{ record.expression }}
</div>
</template>
<a-input
v-model:model-value="record.expression"
class="ms-params-input"
:max-length="255"
@input="handleExpressionChange"
@change="handleExpressionChange"
>
<template #suffix>
<a-tooltip :disabled="!disabledExpressionSuffix">
<template #content>
<div>{{ t('apiTestDebug.expressionTip1') }}</div>
<div>{{ t('apiTestDebug.expressionTip2') }}</div>
<div>{{ t('apiTestDebug.expressionTip3') }}</div>
</template>
<MsIcon
type="icon-icon_flashlamp"
:size="15"
:class="
disabledExpressionSuffix ? 'ms-params-input-suffix-icon--disabled' : 'ms-params-input-suffix-icon'
"
@click.stop="() => showFastExtraction(record, RequestExtractExpressionEnum.JSON_PATH)"
/>
</a-tooltip>
</template>
</a-input>
</a-popover>
</template>
<template #operationPre="{ record }">
<a-popover
v-model:popupVisible="record.moreSettingPopoverVisible"
position="tl"
trigger="click"
:title="t('common.setting')"
:content-style="{ width: '480px' }"
>
<template #content>
<moreSetting v-model:config="activeRecord" is-popover class="mt-[12px]" />
<div class="flex items-center justify-end gap-[8px]">
<a-button type="secondary" size="mini" @click="record.moreSettingPopoverVisible = false">
{{ t('common.cancel') }}
</a-button>
<a-button type="primary" size="mini" @click="() => applyMoreSetting(record)">
{{ t('common.confirm') }}
</a-button>
</div>
</template>
<span class="invisible relative"></span>
</a-popover>
</template>
</paramsTable>
</div>
<div v-if="activeTab === 'script'" class="mt-[16px]">
<conditionContent
:data="innerParams.script"
class="mt-[16px]"
@copy="copyListItem"
@delete="deleteListItem"
@change="emit('change')"
/>
</div>
</div>
<fastExtraction v-model:visible="fastExtractionVisible" :config="activeRecord" @apply="handleFastExtractionApply" />
</template>
<script setup lang="ts">
import { defineModel } from 'vue';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import conditionContent from '@/views/api-test/components/condition/content.vue';
import fastExtraction from '@/views/api-test/components/fastExtraction/index.vue';
import moreSetting from '@/views/api-test/components/fastExtraction/moreSetting.vue';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
@ -191,6 +305,8 @@
XPathExtract,
} from '@/models/apiTest/debug';
import {
RequestConditionProcessor,
RequestConditionScriptLanguage,
RequestExtractEnvType,
RequestExtractExpressionEnum,
RequestExtractExpressionRuleType,
@ -209,9 +325,39 @@
(e: 'delete', id: number): void;
(e: 'change'): void;
}>();
const { t } = useI18n();
const innerParams = defineModel<Param>('modelValue', {
default: { jsonPath: [], xPath: { responseFormat: 'XML', data: [] } },
// const innerParams = defineModel<Param>('modelValue', {
// default: {
// jsonPath: [],
// xPath: { responseFormat: 'XML', data: [] },
// script: {
// id: new Date().getTime(),
// processorType: RequestConditionProcessor.SCRIPT,
// scriptName: '',
// enableCommonScript: false,
// params: [],
// },
// },
// });
const innerParams = ref<Param>({
jsonPath: [],
xPath: { responseFormat: 'XML', data: [] },
document: {
data: [],
responseFormat: 'JSON',
followApi: false,
},
script: {
id: new Date().getTime(),
processorType: RequestConditionProcessor.SCRIPT,
scriptName: '断言脚本名称',
enableCommonScript: false,
params: [],
scriptId: '',
scriptLanguage: RequestConditionScriptLanguage.JAVASCRIPT,
script: new Date().getTime().toString(),
},
});
const activeTab = ref('jsonPath');
const extractParamsTableRef = ref<InstanceType<typeof paramsTable>>();
@ -285,8 +431,8 @@
matchValue: '',
enable: true,
};
const handleJsonPathChange = () => {
console.log('jsonPath change');
const handleChange = () => {
emit('change');
};
function handleExpressionChange(val: string) {
extractParamsTableRef.value?.addTableLine(val, 'expression');
@ -319,12 +465,74 @@
const xPathDefaultParamItem = {
expression: '',
matchCondition: '',
matchValue: '',
enable: true,
};
const handleXPathChange = () => {
console.log('jsonPath change');
const documentColumns: ParamTableColumn[] = [
{
title: 'ms.assertion.paramsName',
dataIndex: 'paramsName',
slotName: 'key',
},
{
title: 'ms.assertion.mustInclude',
dataIndex: 'mustInclude',
slotName: 'mustContain',
titleSlotName: 'documentMustIncludeTitle',
align: 'left',
},
{
title: 'ms.assertion.typeChecking',
dataIndex: 'typeChecking',
slotName: 'typeChecking',
titleSlotName: 'documentTypeCheckingTitle',
align: 'left',
},
{
title: 'project.environmental.paramType',
dataIndex: 'paramType',
slotName: 'paramType',
showInTable: true,
showDrag: true,
columnSelectorDisabled: true,
typeOptions: [
{ label: 'object', value: 'object' },
{ label: 'array', value: 'array' },
{ label: 'string', value: 'string' },
{ label: 'integer', value: 'integer' },
{ label: 'number', value: 'number' },
{ label: 'boolean', value: 'boolean' },
],
titleSlotName: 'typeTitle',
typeTitleTooltip: t('project.environmental.paramTypeTooltip'),
},
{
title: 'ms.assertion.matchCondition',
dataIndex: 'matchCondition',
slotName: 'matchCondition',
options: statusCodeOptions,
},
{
title: 'ms.assertion.matchValue',
dataIndex: 'matchValue',
slotName: 'matchValue',
hasRequired: true,
},
{
title: '',
slotName: 'operation',
fixed: 'right',
width: 130,
},
];
const documentDefaultParamItem = {
id: new Date().getTime(),
paramsName: '',
mustInclude: false,
typeChecking: false,
paramType: '',
matchCondition: '',
matchValue: '',
};
/**
@ -376,10 +584,39 @@
}
}
/**
* 复制列表项
*/
function copyListItem() {}
/**
* 删除列表项
*/
function deleteListItem(id: string | number) {}
function showFastExtraction(record: ExpressionConfig, type: ExpressionType) {
activeRecord.value = { ...record, extractType: type };
fastExtractionVisible.value = true;
}
//
const addChild = (record: Record<string, any>) => {
const children = record.children || [];
const newRecord = {
id: new Date().getTime(),
parentId: record.id,
rowIndex: children.length,
};
record.children = [...children, newRecord];
};
const { t } = useI18n();
//
const addValidateChild = (record: Record<string, any>) => {
const children = record.children || [];
const newRecord = {
id: new Date().getTime(),
parentId: record.id,
rowIndex: children.length,
};
record.children = [...children, newRecord];
};
</script>

View File

@ -16,4 +16,9 @@ export default {
'ms.assertion.script': '脚本',
'ms.assertion.expression': '表达式',
'ms.assertion.responseContentType': '响应内容类型',
'ms.assertion.paramsName': '参数名',
'ms.assertion.mustInclude': '必含',
'ms.assertion.typeChecking': '类型校验',
'ms.assertion.addChild': '添加子字段',
'ms.assertion.validateChild': '添加子校验',
};

View File

@ -63,6 +63,11 @@ export interface CreateOrUpdateComment {
content: string;
event: string; // 任务事件(仅评论: COMMENT; 评论并@: AT; 回复评论/回复并@: REPLAY;)
}
export interface CustomFieldItemOptionItem {
fieldId: string;
value: string;
text: string;
}
export interface CustomFieldItem {
fieldId: string;
fieldName: string;
@ -71,7 +76,7 @@ export interface CustomFieldItem {
apiFieldId: string;
defaultValue: string;
type: string;
options: string;
options: CustomFieldItemOptionItem[];
platformOptionJson: string;
supportSearch: boolean;
optionMethod: string;

View File

@ -36,7 +36,7 @@ export interface EnvConfig {
commmonVariables?: EnvConfigItem[];
httpConfig?: EnvConfigItem[];
dataSource?: DataSourceItem[];
hostConfig?: EnvConfigItem;
hostConfig?: EnvConfigItem[];
authConfig?: EnvConfigItem;
preScript?: EnvConfigItem[];
postScript?: EnvConfigItem[];

View File

@ -2,9 +2,10 @@ import JSEncrypt from 'jsencrypt';
import { BatchActionQueryParams, MsTableColumnData } from '@/components/pure/ms-table/type';
import { CustomFieldItem } from '@/models/bug-management';
import { BugEditCustomField, CustomFieldItem } from '@/models/bug-management';
import { isObject } from './is';
import { json } from 'stream/consumers';
type TargetContext = '_self' | '_parent' | '_blank' | '_top';
@ -574,10 +575,31 @@ export function parseQueryParams(url: string): QueryParam[] {
/**
*
*/
export function customFieldDataToTableData(customFieldData: Record<string, any>[]) {
export function customFieldDataToTableData(customFieldData: Record<string, any>[], customFields: BugEditCustomField[]) {
if (!customFieldData || !customFields) return {};
const tableData: Record<string, any> = {};
customFieldData.forEach((field) => {
tableData[field.id] = field.value;
const customField = customFields.find((item) => item.fieldId === field.id);
if (!customField) return;
if (customField.platformOptionJson) {
// 对jira模板字段做特殊处理
field.options = JSON.parse(customField.platformOptionJson);
} else if (customField.options && customField.options.length > 0) {
field.options = customField.options;
}
// 后端返回来的数据这个字段没值
field.type = customField.type;
if (['SELECT', 'RADIO'].includes(field.type) && Array.isArray(field.options)) {
tableData[field.id] = field.options.find((option) => option.value === field.value)?.text;
} else if (['MUTIPLE_SELECT', 'CHECKBOX'].includes(field.type) && Array.isArray(field.options)) {
// 多值的类型后端返回的是json字符串
field.value = JSON.parse(field.value);
tableData[field.id] = field.value
.map((val: string) => field.options.find((option: { value: string }) => option.value === val)?.text)
.join(',');
} else {
tableData[field.id] = field.value;
}
});
return tableData;
}

View File

@ -27,6 +27,26 @@
</a-tooltip>
</div>
</template>
<template #documentMustIncludeTitle>
<div class="flex flex-row items-center gap-[4px]">
<a-checkbox
:model-value="mustIncludeAllChecked"
:indeterminate="mustIncludeIndeterminate"
@change="(v) => handleMustIncludeChange(v as boolean)"
/>
<div class="ml-[4px] text-[var(--color-text-3)]">{{ t('ms.assertion.mustInclude') }}</div>
</div>
</template>
<template #documentTypeCheckingTitle>
<div class="flex flex-row items-center gap-[4px]">
<a-checkbox
:model-value="typeCheckingAllChecked"
:indeterminate="typeCheckingIndeterminate"
@change="(v) => handleTypeCheckingChange(v as boolean)"
/>
<div class="ml-[4px] text-[var(--color-text-3)]">{{ t('ms.assertion.typeChecking') }}</div>
</div>
</template>
<!-- 表格列 slot -->
<template #key="{ record, columnConfig }">
<a-popover
@ -409,6 +429,7 @@
const { t } = useI18n();
const tableStore = useTableStore();
async function initColumns() {
if (props.showSetting && props.tableKey) {
await tableStore.initColumn(props.tableKey, props.columns);
@ -673,6 +694,30 @@
addTableLine(val as string, 'projectId');
}
/** 断言-文档-Begin */
// ---
const mustIncludeList = ref([]);
const mustIncludeAllChecked = ref(false);
const mustIncludeIndeterminate = ref(false);
const handleMustIncludeChange = (val: boolean) => {
mustIncludeAllChecked.value = val;
mustIncludeIndeterminate.value = false;
const data = propsRes.value;
mustIncludeList.value = val ? data.map((e: any) => e.id) : [];
};
// ---id
const typeCheckingList = ref([]);
const typeCheckingAllChecked = ref(false);
const typeCheckingIndeterminate = ref(false);
const handleTypeCheckingChange = (val: boolean) => {
typeCheckingAllChecked.value = val;
typeCheckingIndeterminate.value = false;
const data = propsRes.value;
typeCheckingList.value = val ? data.map((e: any) => e.id) : [];
};
/** 断言-文档-end */
defineExpose({
addTableLine,
});

View File

@ -4,8 +4,9 @@
:condition-types="[RequestConditionProcessor.SCRIPT, RequestConditionProcessor.TIME_WAITING]"
add-text="apiTestDebug.precondition"
@change="emit('change')"
>
<!-- <template #titleRight>
/>
<!-- <template #titleRight>
<a-switch v-model:model-value="innerConfig.enableGlobal" size="small" type="line"></a-switch>
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPrecondition') }}</div>
<a-tooltip :content="t('apiTestDebug.openGlobalPreconditionTip')" position="left">
@ -15,7 +16,6 @@
/>
</a-tooltip>
</template> -->
</condition>
</template>
<script setup lang="ts">

View File

@ -359,7 +359,7 @@ const { t } = useI18n();
(record: TableData) => ({
...record,
handleUser: record.handleUserName,
...customFieldDataToTableData(record.customFields),
...customFieldDataToTableData(record.customFields, customFields.value),
})
);