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> <template>
<div class="flex flex-col"> <div class="flex w-full flex-col">
<div> <div>
<a-radio-group v-model:model-value="activeTab" type="button" size="small"> <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"> <a-radio v-for="item of responseRadios" :key="item.value" :value="item.value">
@ -14,7 +14,7 @@
:columns="jsonPathColumns" :columns="jsonPathColumns"
:scroll="{ minWidth: '700px' }" :scroll="{ minWidth: '700px' }"
:default-param-item="jsonPathDefaultParamItem" :default-param-item="jsonPathDefaultParamItem"
@change="handleJsonPathChange" @change="handleChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)" @more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
> >
<template #expression="{ record }"> <template #expression="{ record }">
@ -86,7 +86,7 @@
<div class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseContentType') }}</div> <div class="text-[var(--color-text-1)]">{{ t('ms.assertion.responseContentType') }}</div>
<a-radio-group <a-radio-group
v-model:model-value="innerParams.xPath.responseFormat" v-model:model-value="innerParams.xPath.responseFormat"
class="mt-[16px]" class="mb-[16px] mt-[16px]"
type="button" type="button"
size="small" size="small"
> >
@ -95,12 +95,11 @@
</a-radio-group> </a-radio-group>
<paramsTable <paramsTable
v-model:params="innerParams.xPath.data" v-model:params="innerParams.xPath.data"
class="mt-[16px]"
:selectable="false" :selectable="false"
:columns="xPathColumns" :columns="xPathColumns"
:scroll="{ minWidth: '700px' }" :scroll="{ minWidth: '700px' }"
:default-param-item="xPathDefaultParamItem" :default-param-item="xPathDefaultParamItem"
@change="handleXPathChange" @change="handleChange"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)" @more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
> >
<template #expression="{ record }"> <template #expression="{ record }">
@ -168,15 +167,130 @@
</template> </template>
</paramsTable> </paramsTable>
</div> </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> </div>
<fastExtraction v-model:visible="fastExtractionVisible" :config="activeRecord" @apply="handleFastExtractionApply" /> <fastExtraction v-model:visible="fastExtractionVisible" :config="activeRecord" @apply="handleFastExtractionApply" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineModel } from 'vue';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter'; import { statusCodeOptions } from '@/components/pure/ms-advance-filter';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; 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 fastExtraction from '@/views/api-test/components/fastExtraction/index.vue';
import moreSetting from '@/views/api-test/components/fastExtraction/moreSetting.vue'; import moreSetting from '@/views/api-test/components/fastExtraction/moreSetting.vue';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
@ -191,6 +305,8 @@
XPathExtract, XPathExtract,
} from '@/models/apiTest/debug'; } from '@/models/apiTest/debug';
import { import {
RequestConditionProcessor,
RequestConditionScriptLanguage,
RequestExtractEnvType, RequestExtractEnvType,
RequestExtractExpressionEnum, RequestExtractExpressionEnum,
RequestExtractExpressionRuleType, RequestExtractExpressionRuleType,
@ -209,9 +325,39 @@
(e: 'delete', id: number): void; (e: 'delete', id: number): void;
(e: 'change'): void; (e: 'change'): void;
}>(); }>();
const { t } = useI18n();
const innerParams = defineModel<Param>('modelValue', { // const innerParams = defineModel<Param>('modelValue', {
default: { jsonPath: [], xPath: { responseFormat: 'XML', data: [] } }, // 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 activeTab = ref('jsonPath');
const extractParamsTableRef = ref<InstanceType<typeof paramsTable>>(); const extractParamsTableRef = ref<InstanceType<typeof paramsTable>>();
@ -285,8 +431,8 @@
matchValue: '', matchValue: '',
enable: true, enable: true,
}; };
const handleJsonPathChange = () => { const handleChange = () => {
console.log('jsonPath change'); emit('change');
}; };
function handleExpressionChange(val: string) { function handleExpressionChange(val: string) {
extractParamsTableRef.value?.addTableLine(val, 'expression'); extractParamsTableRef.value?.addTableLine(val, 'expression');
@ -319,12 +465,74 @@
const xPathDefaultParamItem = { const xPathDefaultParamItem = {
expression: '', expression: '',
matchCondition: '',
matchValue: '',
enable: true, 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) { function showFastExtraction(record: ExpressionConfig, type: ExpressionType) {
activeRecord.value = { ...record, extractType: type }; activeRecord.value = { ...record, extractType: type };
fastExtractionVisible.value = true; 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> </script>

View File

@ -16,4 +16,9 @@ export default {
'ms.assertion.script': '脚本', 'ms.assertion.script': '脚本',
'ms.assertion.expression': '表达式', 'ms.assertion.expression': '表达式',
'ms.assertion.responseContentType': '响应内容类型', '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; content: string;
event: string; // 任务事件(仅评论: COMMENT; 评论并@: AT; 回复评论/回复并@: REPLAY;) event: string; // 任务事件(仅评论: COMMENT; 评论并@: AT; 回复评论/回复并@: REPLAY;)
} }
export interface CustomFieldItemOptionItem {
fieldId: string;
value: string;
text: string;
}
export interface CustomFieldItem { export interface CustomFieldItem {
fieldId: string; fieldId: string;
fieldName: string; fieldName: string;
@ -71,7 +76,7 @@ export interface CustomFieldItem {
apiFieldId: string; apiFieldId: string;
defaultValue: string; defaultValue: string;
type: string; type: string;
options: string; options: CustomFieldItemOptionItem[];
platformOptionJson: string; platformOptionJson: string;
supportSearch: boolean; supportSearch: boolean;
optionMethod: string; optionMethod: string;

View File

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

View File

@ -2,9 +2,10 @@ import JSEncrypt from 'jsencrypt';
import { BatchActionQueryParams, MsTableColumnData } from '@/components/pure/ms-table/type'; 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 { isObject } from './is';
import { json } from 'stream/consumers';
type TargetContext = '_self' | '_parent' | '_blank' | '_top'; 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> = {}; const tableData: Record<string, any> = {};
customFieldData.forEach((field) => { 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; return tableData;
} }

View File

@ -27,6 +27,26 @@
</a-tooltip> </a-tooltip>
</div> </div>
</template> </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 --> <!-- 表格列 slot -->
<template #key="{ record, columnConfig }"> <template #key="{ record, columnConfig }">
<a-popover <a-popover
@ -409,6 +429,7 @@
const { t } = useI18n(); const { t } = useI18n();
const tableStore = useTableStore(); const tableStore = useTableStore();
async function initColumns() { async function initColumns() {
if (props.showSetting && props.tableKey) { if (props.showSetting && props.tableKey) {
await tableStore.initColumn(props.tableKey, props.columns); await tableStore.initColumn(props.tableKey, props.columns);
@ -673,6 +694,30 @@
addTableLine(val as string, 'projectId'); 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({ defineExpose({
addTableLine, addTableLine,
}); });

View File

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

View File

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