feat(接口测试): 接口测试diff对比同步以及状态接口联调部分

This commit is contained in:
xinxin.wu 2024-08-07 11:06:23 +08:00 committed by 刘瑞斌
parent 5bdbb92b14
commit a7a50d1516
20 changed files with 354 additions and 89 deletions

View File

@ -20,7 +20,9 @@ import {
BatchRecoverCaseUrl, BatchRecoverCaseUrl,
BatchUpdateDefinitionUrl, BatchUpdateDefinitionUrl,
CasePageUrl, CasePageUrl,
caseTableBatchSyncUrl,
CheckDefinitionScheduleUrl, CheckDefinitionScheduleUrl,
clearThisChangeUrl,
ConvertJsonSchemaToJsonUrl, ConvertJsonSchemaToJsonUrl,
CopyMockUrl, CopyMockUrl,
DebugCaseUrl, DebugCaseUrl,
@ -35,6 +37,7 @@ import {
DeleteModuleUrl, DeleteModuleUrl,
DeleteRecycleApiUrl, DeleteRecycleApiUrl,
DeleteRecycleCaseUrl, DeleteRecycleCaseUrl,
diffDataUrl,
ExecuteCaseUrl, ExecuteCaseUrl,
ExportDefinitionUrl, ExportDefinitionUrl,
GetCaseDetailUrl, GetCaseDetailUrl,
@ -54,6 +57,7 @@ import {
GetPoolOptionUrl, GetPoolOptionUrl,
GetTrashModuleCountUrl, GetTrashModuleCountUrl,
GetTrashModuleTreeUrl, GetTrashModuleTreeUrl,
ignoreEveryTimeApiChangeUrl,
ImportDefinitionUrl, ImportDefinitionUrl,
JsonSchemaAutoGenerateUrl, JsonSchemaAutoGenerateUrl,
MockDetailUrl, MockDetailUrl,
@ -137,6 +141,7 @@ import {
MoveModules, MoveModules,
TransferFileParams, TransferFileParams,
} from '@/models/common'; } from '@/models/common';
import { TableQueryParams } from '@/models/common';
import { ResourcePoolItem } from '@/models/setting/resourcePool'; import { ResourcePoolItem } from '@/models/setting/resourcePool';
// 更新模块 // 更新模块
@ -313,6 +318,22 @@ export function convertJsonSchemaToJson(data: JsonSchema) {
export function jsonSchemaAutoGenerate(data: JsonSchema) { export function jsonSchemaAutoGenerate(data: JsonSchema) {
return MSR.post({ url: JsonSchemaAutoGenerateUrl, data }); return MSR.post({ url: JsonSchemaAutoGenerateUrl, data });
} }
// 接口定义-用例接口对比-清除本次变更
export function clearThisChange(id: string) {
return MSR.get({ url: `${clearThisChangeUrl}/${id}` });
}
// 接口定义-用例接口对比-忽略每次变更
export function ignoreEveryTimeChange(id: string, ignore: boolean) {
return MSR.get({ url: `${ignoreEveryTimeApiChangeUrl}/${id}`, params: { ignore } });
}
// 接口测试-接口管理-接口用例-批量同步编辑
export function caseTableBatchSync(data: TableQueryParams) {
return MSR.post({ url: caseTableBatchSyncUrl, data });
}
// // 接口测试-接口用例-定义对比用例
export function diffDataRequest(id: string) {
return MSR.get({ url: `${diffDataUrl}/${id}` });
}
/** /**
* Mock * Mock

View File

@ -38,6 +38,10 @@ export const RecoverOperationHistoryUrl = '/api/definition/operation-history/rec
export const DefinitionReferenceUrl = '/api/definition/get-reference'; // 获取接口引用关系 export const DefinitionReferenceUrl = '/api/definition/get-reference'; // 获取接口引用关系
export const ConvertJsonSchemaToJsonUrl = '/api/definition/json-schema/preview'; // 将json-schema转换为 json 数据 export const ConvertJsonSchemaToJsonUrl = '/api/definition/json-schema/preview'; // 将json-schema转换为 json 数据
export const JsonSchemaAutoGenerateUrl = '/api/definition/json-schema/auto-generate'; // 将json-schema转换为 json 数据 export const JsonSchemaAutoGenerateUrl = '/api/definition/json-schema/auto-generate'; // 将json-schema转换为 json 数据
export const clearThisChangeUrl = '/api/case/api-change/clear'; // 接口定义-变更对比-清除本次变更
export const caseTableBatchSyncUrl = '/api/case/batch/api-change/sync'; // 接口测试-接口管理-接口用例-批量同步
export const ignoreEveryTimeApiChangeUrl = '/api/case/api-change/ignore'; // 接口测试-接口用例-忽略每次接口变更
export const diffDataUrl = '/api/case/api/compare'; // 接口测试-接口用例-定义对比用例
/** /**
* Mock * Mock

View File

@ -549,7 +549,7 @@
return totalWidth + tablePadding; return totalWidth + tablePadding;
}; };
// // TODO
const getMaxRowTagWidth = (rows: TableData[], dataIndex: string) => { const getMaxRowTagWidth = (rows: TableData[], dataIndex: string) => {
const allTags = ((rows as TableData) || []).map((row: TableData) => row[dataIndex] || []); const allTags = ((rows as TableData) || []).map((row: TableData) => row[dataIndex] || []);

View File

@ -328,6 +328,8 @@ export interface ApiCaseDetail extends ExecuteRequestParams {
deleteTime: number; deleteTime: number;
deleteUser: string; deleteUser: string;
deleteName: string; deleteName: string;
apiChange: boolean; // 接口定义参数变更标识
inconsistentWithApi: boolean; // 与接口定义不一致
} }
// 批量操作参数 // 批量操作参数
export interface ApiCaseBatchParams extends BatchApiParams { export interface ApiCaseBatchParams extends BatchApiParams {
@ -401,3 +403,19 @@ export interface ApiCaseExecuteHistoryItem {
triggerMode: string; triggerMode: string;
deleted: boolean; deleted: boolean;
} }
export interface syncItem {
header: boolean;
body: boolean;
query: boolean;
rest: boolean;
}
// 批量同步
export interface batchSyncForm {
notificationConfig: {
apiCreator: boolean;
scenarioCreator: boolean;
};
// 同步项目
syncItems: syncItem;
deleteRedundantParam: boolean;
}

View File

@ -1002,7 +1002,6 @@
() => requestMethodsOptions.value, () => requestMethodsOptions.value,
() => { () => {
initFilterColumn(); initFilterColumn();
apiTableRef.value.initColumn(columns);
} }
); );
</script> </script>

View File

@ -12,19 +12,12 @@
<icon-right :size="10" class="block" /> <icon-right :size="10" class="block" />
</div> </div>
</div> </div>
<MsTag <ApiChangeTag
v-if="props.detail.inconsistentWithApi" :ignore-api-change="props.detail.ignoreApiChange"
class="cursor-pointer" :ignore-api-diff="props.detail.ignoreApiDiff"
type="warning" :inconsistent-with-api="props.detail.inconsistentWithApi"
theme="light" @show-diff="showDiffDrawer"
:tooltip-disabled="true" />
@click.stop="showDiffDrawer"
>
<template #icon>
<MsIcon type="icon-icon_warning_colorful" size="16" />
</template>
<span class="ml-[8px]"> {{ statusText }}</span>
</MsTag>
</div> </div>
</template> </template>
<div class="detail-collapse-item"> <div class="detail-collapse-item">
@ -435,10 +428,10 @@
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue'; import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue';
import { parseSchemaToJsonSchemaTableData } from '@/components/pure/ms-json-schema/utils'; import { parseSchemaToJsonSchemaTableData } from '@/components/pure/ms-json-schema/utils';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue'; import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
import responseCodeTimeSize from '@/views/api-test/components/requestComposition/response/responseCodeTimeSize.vue'; import responseCodeTimeSize from '@/views/api-test/components/requestComposition/response/responseCodeTimeSize.vue';
import Result from '@/views/api-test/components/requestComposition/response/result.vue'; import Result from '@/views/api-test/components/requestComposition/response/result.vue';
import ApiChangeTag from '@/views/api-test/management/components/management/case/apiChangeTag.vue';
import { getPluginScript } from '@/api/modules/api-test/common'; import { getPluginScript } from '@/api/modules/api-test/common';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -898,13 +891,6 @@
} }
} }
const statusText = computed(() => {
if (props.detail.inconsistentWithApi) {
return t('case.definitionInconsistent');
}
// TODO
});
// diff // diff
function showDiffDrawer() { function showDiffDrawer() {
emit('showDiff'); emit('showDiff');

View File

@ -87,6 +87,8 @@
:active-api-case-id="activeApiCaseId" :active-api-case-id="activeApiCaseId"
:active-defined-id="activeDefinedId" :active-defined-id="activeDefinedId"
@close="closeDifferent" @close="closeDifferent"
@clear-this-change="clearThisChangeHandler"
@sync="syncHandler"
/> />
</div> </div>
<tab-case-dependency v-else-if="activeKey === 'reference'" :source-id="caseDetail.id" /> <tab-case-dependency v-else-if="activeKey === 'reference'" :source-id="caseDetail.id" />
@ -396,11 +398,20 @@
} }
const isPriorityLocalExec = computed(() => executeRef.value?.isPriorityLocalExec ?? false); const isPriorityLocalExec = computed(() => executeRef.value?.isPriorityLocalExec ?? false);
const caseId = ref<string>(route.query.id as string);
//
async function clearThisChangeHandler() {
getCaseDetailInfo(caseId.value);
}
function syncHandler(id: string) {
// TODO
createAndEditCaseDrawerRef.value?.open(id, caseDetail.value, false);
}
onBeforeMount(() => { onBeforeMount(() => {
initProtocolList(); initProtocolList();
const caseId = route.query.id; getCaseDetailInfo(caseId.value);
getCaseDetailInfo(caseId as string);
}); });
</script> </script>

View File

@ -0,0 +1,58 @@
<template>
<MsTag
v-if="props.inconsistentWithApi"
class="cursor-pointer font-normal"
:type="props.ignoreApiDiff ? 'default' : 'warning'"
theme="light"
:tooltip-disabled="true"
max-width="160px"
@click.stop="showDiffDrawer"
>
<template #icon>
<div class="!text-[var(--color-text-4)]">
<MsIcon v-if="props.ignoreApiDiff" type="icon-icon_warning_filled" size="16" />
<MsIcon v-else class="!text-[var(--color-text-4)]" type="icon-icon_warning_colorful" size="16" />
</div>
</template>
<span class="ml-[4px]"> {{ statusText }}</span>
</MsTag>
</template>
<script setup lang="ts">
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const props = defineProps<{
inconsistentWithApi?: boolean;
ignoreApiDiff?: boolean;
ignoreApiChange?: boolean;
}>();
const emit = defineEmits<{
(e: 'showDiff'): void;
}>();
function showDiffDrawer() {
emit('showDiff');
}
const statusText = computed(() => {
//
if (props.ignoreApiChange) {
return t('case.eachHasBeenIgnored');
}
//
if (props.ignoreApiDiff) {
return t('case.haveIgnoredTheChange');
}
//
if (props.inconsistentWithApi) {
return t('case.definitionInconsistent');
}
});
</script>
<style scoped></style>

View File

@ -73,6 +73,8 @@
:active-api-case-id="activeApiCaseId" :active-api-case-id="activeApiCaseId"
:active-defined-id="activeDefinedId" :active-defined-id="activeDefinedId"
@close="closeDifferent" @close="closeDifferent"
@clear-this-change="clearThisChangeHandler"
@sync="syncHandler"
/> />
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]"> <a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]">
@ -132,9 +134,11 @@
isDrawer?: boolean; // isDrawer?: boolean; //
detail: RequestParam; detail: RequestParam;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'updateFollow'): void; (e: 'updateFollow'): void;
(e: 'deleteCase', id: string): void; (e: 'deleteCase', id: string): void;
(e: 'loadCase', id: string): void;
}>(); }>();
const appStore = useAppStore(); const appStore = useAppStore();
@ -342,6 +346,16 @@
activeDefinedId.value = ''; activeDefinedId.value = '';
} }
//
async function clearThisChangeHandler() {
emit('loadCase', props.detail.id as string);
}
function syncHandler(id: string) {
// TODO
createAndEditCaseDrawerRef.value?.open(id, caseDetail.value, false);
}
watch( watch(
() => props.detail, () => props.detail,
() => { () => {

View File

@ -275,6 +275,7 @@
</a-modal> </a-modal>
<createAndEditCaseDrawer <createAndEditCaseDrawer
ref="createAndEditCaseDrawerRef" ref="createAndEditCaseDrawerRef"
v-model:visible="showCaseVisible"
:api-detail="apiDetail" :api-detail="apiDetail"
@load-case="loadCaseListAndResetSelector()" @load-case="loadCaseListAndResetSelector()"
@show-diff="showDifferences" @show-diff="showDifferences"
@ -300,13 +301,21 @@
<!-- 执行结果抽屉 --> <!-- 执行结果抽屉 -->
<caseAndScenarioReportDrawer v-model:visible="showExecuteResult" :report-id="activeReportId" /> <caseAndScenarioReportDrawer v-model:visible="showExecuteResult" :report-id="activeReportId" />
<!-- 同步抽屉 --> <!-- 同步抽屉 -->
<SyncModal v-model:visible="showSyncModal" :batch-params="batchParams" /> <SyncModal
ref="syncModalRef"
v-model:visible="showSyncModal"
:loading="syncLoading"
:batch-params="batchParams"
@batch-sync="handleBatchSync"
/>
<!-- diff对比抽屉 --> <!-- diff对比抽屉 -->
<DifferentDrawer <DifferentDrawer
v-model:visible="showDifferentDrawer" v-model:visible="showDifferentDrawer"
:active-api-case-id="activeApiCaseId" :active-api-case-id="activeApiCaseId"
:active-defined-id="activeDefinedId" :active-defined-id="activeDefinedId"
@close="closeDifferent" @close="closeDifferent"
@clear-this-change="handleClearThisChange"
@sync="syncHandler"
/> />
</template> </template>
@ -339,6 +348,7 @@
batchDeleteCase, batchDeleteCase,
batchEditCase, batchEditCase,
batchExecuteCase, batchExecuteCase,
caseTableBatchSync,
deleteCase, deleteCase,
dragSort, dragSort,
getCaseDetail, getCaseDetail,
@ -353,6 +363,7 @@
import { characterLimit, operationWidth } from '@/utils'; import { characterLimit, operationWidth } from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import type { batchSyncForm } from '@/models/apiTest/management';
import { ApiCaseDetail } from '@/models/apiTest/management'; import { ApiCaseDetail } from '@/models/apiTest/management';
import { DragSortParams } from '@/models/common'; import { DragSortParams } from '@/models/common';
import { RequestCaseStatus } from '@/enums/apiEnum'; import { RequestCaseStatus } from '@/enums/apiEnum';
@ -860,7 +871,6 @@
const batchConditionParams = ref<any>(); const batchConditionParams = ref<any>();
const showSyncModal = ref<boolean>(false); const showSyncModal = ref<boolean>(false);
// TODO
function syncParams() { function syncParams() {
showSyncModal.value = true; showSyncModal.value = true;
} }
@ -975,7 +985,6 @@
const activeDefinedId = ref<string>(''); const activeDefinedId = ref<string>('');
const showDifferentDrawer = ref<boolean>(false); const showDifferentDrawer = ref<boolean>(false);
// TODO
async function showDifferences(record: ApiCaseDetail) { async function showDifferences(record: ApiCaseDetail) {
activeApiCaseId.value = record.id; activeApiCaseId.value = record.id;
activeDefinedId.value = record.apiDefinitionId; activeDefinedId.value = record.apiDefinitionId;
@ -987,6 +996,48 @@
activeDefinedId.value = ''; activeDefinedId.value = '';
} }
const syncLoading = ref<boolean>(false);
const syncModalRef = ref<InstanceType<typeof SyncModal>>();
//
async function handleBatchSync(syncForm: batchSyncForm) {
try {
syncLoading.value = true;
const selectModules = await getModuleIds();
const params = await genBatchConditionParams();
await caseTableBatchSync({
selectIds: batchParams.value?.selectedIds || [],
selectAll: !!batchParams.value?.selectAll,
excludeIds: batchParams.value?.excludeIds || [],
...params,
...syncForm,
moduleIds: selectModules,
});
Message.success(t('bugManagement.syncSuccess'));
syncModalRef.value?.resetForm();
resetSelector();
loadCaseListAndResetSelector();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
syncLoading.value = false;
}
}
const showCaseVisible = ref(false);
//
async function handleClearThisChange() {
await loadCaseList();
await getCaseDetailInfo(activeApiCaseId.value);
if (showCaseVisible.value) {
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value as RequestParam, false);
}
}
//
function syncHandler(definedId: string) {
// TODO
createAndEditCaseDrawerRef.value?.open(definedId, caseDetail.value as RequestParam, false);
}
defineExpose({ defineExpose({
loadCaseList, loadCaseList,
}); });

View File

@ -71,20 +71,12 @@
</a-form> </a-form>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="px-[16px] font-medium">{{ t('apiTestManagement.requestParams') }}</div> <div class="px-[16px] font-medium">{{ t('apiTestManagement.requestParams') }}</div>
<!-- 与定义不一致 TODO 等待联调 --> <ApiChangeTag
<MsTag :ignore-api-change="detailForm.ignoreApiChange"
v-if="detailForm.inconsistentWithApi" :ignore-api-diff="detailForm.ignoreApiDiff"
class="cursor-pointer" :inconsistent-with-api="detailForm.inconsistentWithApi"
type="warning" @show-diff="showDiffDrawer"
theme="light" />
:tooltip-disabled="true"
@click="showDiffDrawer"
>
<template #icon>
<MsIcon type="icon-icon_warning_colorful" size="16" />
</template>
<span class="ml-[8px]"> {{ t('case.definitionInconsistent') }}</span>
</MsTag>
</div> </div>
<requestComposition <requestComposition
@ -111,7 +103,6 @@
import MsDetailCard, { type Description } from '@/components/pure/ms-detail-card/index.vue'; import MsDetailCard, { type Description } from '@/components/pure/ms-detail-card/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue'; import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue'; import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import type { CaseLevel } from '@/components/business/ms-case-associate/types'; import type { CaseLevel } from '@/components/business/ms-case-associate/types';
@ -120,6 +111,7 @@
import apiStatus from '@/views/api-test/components/apiStatus.vue'; import apiStatus from '@/views/api-test/components/apiStatus.vue';
import executeButton from '@/views/api-test/components/executeButton.vue'; import executeButton from '@/views/api-test/components/executeButton.vue';
import requestComposition, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue'; import requestComposition, { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
import ApiChangeTag from '@/views/api-test/management/components/management/case/apiChangeTag.vue';
import { localExecuteApiDebug } from '@/api/modules/api-test/common'; import { localExecuteApiDebug } from '@/api/modules/api-test/common';
import { import {
@ -153,7 +145,10 @@
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
const innerVisible = ref(false); const innerVisible = defineModel<boolean>('visible', {
default: false,
});
const drawerLoading = ref(false); const drawerLoading = ref(false);
const apiDefinitionId = ref(''); const apiDefinitionId = ref('');

View File

@ -14,7 +14,7 @@
<div>{{ t('case.apiAndCaseDiff') }}</div> <div>{{ t('case.apiAndCaseDiff') }}</div>
<div class="flex items-center text-[14px]"> <div class="flex items-center text-[14px]">
<div class="-mt-[2px] mr-[8px]"> {{ t('case.syncItem') }}</div> <div class="-mt-[2px] mr-[8px]"> {{ t('case.syncItem') }}</div>
<a-checkbox-group v-model="form.checkType"> <a-checkbox-group v-model="checkType">
<a-checkbox v-for="item of checkList" :key="item.value" :value="item.value"> <a-checkbox v-for="item of checkList" :key="item.value" :value="item.value">
<div class="flex items-center" <div class="flex items-center"
>{{ item.label }} >{{ item.label }}
@ -31,17 +31,21 @@
</a-checkbox> </a-checkbox>
</a-checkbox-group> </a-checkbox-group>
<a-divider direction="vertical" :margin="0" class="!mr-[8px]"></a-divider> <a-divider direction="vertical" :margin="0" class="!mr-[8px]"></a-divider>
<a-switch v-model:model-value="form.ignoreUpdate" size="small" /> <a-switch
v-model:model-value="form.ignoreApiChange"
:before-change="(val) => changeIgnore(val)"
size="small"
/>
<div class="ml-[8px]">{{ t('case.ignoreAllChange') }}</div> <div class="ml-[8px]">{{ t('case.ignoreAllChange') }}</div>
<a-divider direction="vertical" :margin="8"></a-divider> <a-divider direction="vertical" :margin="8"></a-divider>
<a-switch v-model:model-value="form.deleteParams" size="small" /> <a-switch v-model:model-value="form.deleteRedundantParam" size="small" />
<div class="ml-[8px] font-normal text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div> <div class="ml-[8px] font-normal text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div>
<a-divider direction="vertical" :margin="0" class="!ml-[8px]"></a-divider> <a-divider direction="vertical" :margin="0" class="!ml-[8px]"></a-divider>
<a-button class="mx-[12px]" type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button> <a-button class="mx-[12px]" type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button>
<a-button class="mr-[12px]" type="outline"> <a-button class="mr-[12px]" type="outline" @click="clearThisChangeHandler">
{{ t('case.ignoreAllChange') }} {{ t('case.ignoreThisChange') }}
</a-button> </a-button>
<a-button type="primary" :loading="syncLoading" :disabled="!form.checkType.length" @click="confirmBatchSync"> <a-button type="primary" :loading="syncLoading" :disabled="!checkType.length" @click="confirmSync">
{{ t('case.apiSyncChange') }} {{ t('case.apiSyncChange') }}
</a-button> </a-button>
</div> </div>
@ -87,6 +91,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
@ -94,10 +99,17 @@
import DiffItem from './diffItem.vue'; import DiffItem from './diffItem.vue';
import DiffRequestBody from './diffRequestBody.vue'; import DiffRequestBody from './diffRequestBody.vue';
import { getCaseDetail, getDefinitionDetail } from '@/api/modules/api-test/management'; import {
clearThisChange,
diffDataRequest,
getCaseDetail,
getDefinitionDetail,
ignoreEveryTimeChange,
} from '@/api/modules/api-test/management';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { EnableKeyValueParam, ExecuteRequestCommonParam } from '@/models/apiTest/common'; import { EnableKeyValueParam, ExecuteRequestCommonParam } from '@/models/apiTest/common';
import type { syncItem } from '@/models/apiTest/management';
import { ApiDefinitionDetail } from '@/models/apiTest/management'; import { ApiDefinitionDetail } from '@/models/apiTest/management';
import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum'; import { RequestBodyFormat, RequestComposition } from '@/enums/apiEnum';
@ -113,6 +125,8 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'close'): void; (e: 'close'): void;
(e: 'clearThisChange'): void;
(e: 'sync', activeDefinedId: string): void;
}>(); }>();
const showDiffVisible = defineModel<boolean>('visible', { const showDiffVisible = defineModel<boolean>('visible', {
@ -140,18 +154,21 @@
]); ]);
const initForm = { const initForm = {
deleteParams: false, deleteRedundantParam: false,
checkType: [], syncItems: {
header: false,
body: false,
query: false,
rest: false,
},
noticeApiCaseCreator: true, noticeApiCaseCreator: true,
noticeApiScenarioCreator: true, noticeApiScenarioCreator: true,
ignoreUpdate: false, ignoreApiChange: false,
ignoreUpdateType: ['THIS_TIME'],
}; };
const form = ref({ ...initForm }); const checkType = ref([]);
// const form = ref({ ...initForm });
function changeIgnoreType() {}
function cancel() { function cancel() {
showDiffVisible.value = false; showDiffVisible.value = false;
@ -159,7 +176,16 @@
} }
const syncLoading = ref<boolean>(false); const syncLoading = ref<boolean>(false);
// //
function confirmBatchSync() {} function confirmSync() {
//
checkType.value.forEach((e: any) => {
const key = e.toLowerCase() as keyof syncItem;
form.value.syncItems[key] = true;
});
emit('sync', props.activeDefinedId);
showDiffVisible.value = false;
}
const defaultCaseParams = inject<RequestParam>('defaultCaseParams'); const defaultCaseParams = inject<RequestParam>('defaultCaseParams');
const caseDetail = ref<Record<string, any>>({}); const caseDetail = ref<Record<string, any>>({});
@ -297,12 +323,12 @@
caseDetail.value = { caseDetail.value = {
...cloneDeep(defaultCaseParams as RequestParam), ...cloneDeep(defaultCaseParams as RequestParam),
...({ ...({
...res.request,
...res, ...res,
url: res.path, url: res.path,
...parseRequestBodyResult, ...parseRequestBodyResult,
} as Partial<TabItem>), } as Partial<TabItem>),
}; };
form.value.ignoreApiChange = caseDetail.value.ignoreApiChange;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -313,24 +339,68 @@
try { try {
const detail = await getDefinitionDetail(apiDefinitionId); const detail = await getDefinitionDetail(apiDefinitionId);
apiDetailInfo.value = detail as ApiDefinitionDetail; apiDetailInfo.value = detail as ApiDefinitionDetail;
apiDefinedRequest.value = detail.request as unknown as RequestParam;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
} }
} }
async function getDiffDataRequest(activeApiCaseId: string) {
try {
const result = await diffDataRequest(activeApiCaseId);
const { caseRequest, apiRequest } = result;
caseDetail.value = {
...caseDetail.value,
...caseRequest,
num: caseDetail.value.num,
};
apiDefinedRequest.value = apiRequest;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
async function getRequestDetail(definedId: string, apiCaseId: string) { async function getRequestDetail(definedId: string, apiCaseId: string) {
loading.value = true; loading.value = true;
try { try {
await Promise.all([getApiDetail(definedId), getCaseDetailInfo(apiCaseId)]); await Promise.all([getApiDetail(definedId), getCaseDetailInfo(apiCaseId)]);
await getDiffDataRequest(props.activeApiCaseId);
processData(); processData();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.error(error); console.error(error);
} finally { } finally {
loading.value = false; loading.value = false;
} }
} }
//
async function clearThisChangeHandler() {
if (props.activeApiCaseId) {
try {
await clearThisChange(props.activeApiCaseId);
getRequestDetail(props.activeDefinedId, props.activeApiCaseId);
emit('clearThisChange');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
}
//
async function changeIgnore(newValue: string | number | boolean) {
try {
await ignoreEveryTimeChange(props.activeApiCaseId, newValue as boolean);
Message.success(newValue ? t('case.eachHasBeenIgnored') : t('case.eachHasBeenIgnoredClosed'));
getRequestDetail(props.activeDefinedId, props.activeApiCaseId);
emit('clearThisChange');
return false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
watchEffect(() => { watchEffect(() => {
if (props.activeDefinedId && props.activeApiCaseId) { if (props.activeDefinedId && props.activeApiCaseId) {

View File

@ -4,7 +4,7 @@
title-align="start" title-align="start"
class="ms-modal-upload ms-modal-medium" class="ms-modal-upload ms-modal-medium"
:width="600" :width="600"
@close="cancel" @close="resetForm"
> >
<template #title> <template #title>
{{ t('case.apiSyncChange') }} {{ t('case.apiSyncChange') }}
@ -21,7 +21,7 @@
<div class="mb-[8px]"> <div class="mb-[8px]">
{{ t('case.syncItem') }} {{ t('case.syncItem') }}
</div> </div>
<a-checkbox-group v-model="form.checkType"> <a-checkbox-group v-model="checkType">
<a-checkbox v-for="item of checkList" :key="item.value" :value="item.value"> <a-checkbox v-for="item of checkList" :key="item.value" :value="item.value">
<div class="flex items-center"> <div class="flex items-center">
{{ item.label }} {{ item.label }}
@ -37,7 +37,7 @@
</a-checkbox> </a-checkbox>
</a-checkbox-group> </a-checkbox-group>
<div class="my-[16px] flex items-center"> <div class="my-[16px] flex items-center">
<a-switch v-model:model-value="form.deleteParams" size="small" /> <a-switch v-model:model-value="form.deleteRedundantParam" size="small" />
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div> <div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.deleteNotCorrespondValue') }}</div>
</div> </div>
@ -53,17 +53,17 @@
</a-tooltip> </a-tooltip>
</div> </div>
<div class="my-[16px] flex items-center"> <div class="my-[16px] flex items-center">
<a-switch v-model:model-value="form.noticeApiCaseCreator" size="small" /> <a-switch v-model:model-value="form.notificationConfig.apiCreator" size="small" />
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.NoticeApiCaseCreator') }}</div> <div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.NoticeApiCaseCreator') }}</div>
</div> </div>
<div class="my-[16px] flex items-center"> <div class="my-[16px] flex items-center">
<a-switch v-model:model-value="form.noticeApiScenarioCreator" size="small" /> <a-switch v-model:model-value="form.notificationConfig.scenarioCreator" size="small" />
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.NoticeApiScenarioCreator') }}</div> <div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.NoticeApiScenarioCreator') }}</div>
</div> </div>
<template #footer> <template #footer>
<a-button type="secondary" @click="cancel">{{ t('common.cancel') }}</a-button> <a-button type="secondary" @click="resetForm">{{ t('common.cancel') }}</a-button>
<a-button type="primary" :loading="syncLoading" :disabled="!form.checkType.length" @click="confirmBatchSync"> <a-button type="primary" :loading="props.loading" :disabled="!checkType.length" @click="confirmBatchSync">
{{ t('case.apiSyncChange') }} {{ t('case.apiSyncChange') }}
</a-button> </a-button>
</template> </template>
@ -72,31 +72,47 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type'; import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import type { batchSyncForm, syncItem } from '@/models/apiTest/management';
import { RequestComposition } from '@/enums/apiEnum'; import { RequestComposition } from '@/enums/apiEnum';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
batchParams: BatchActionQueryParams; batchParams: BatchActionQueryParams;
loading: boolean;
}>();
const emit = defineEmits<{
(e: 'batchSync', form: batchSyncForm): void;
}>(); }>();
const showBatchSyncModal = defineModel<boolean>('visible', { const showBatchSyncModal = defineModel<boolean>('visible', {
required: true, required: true,
}); });
const initForm = { const initForm: batchSyncForm = {
deleteParams: false, notificationConfig: {
checkType: [], apiCreator: false,
noticeApiCaseCreator: true, scenarioCreator: false,
noticeApiScenarioCreator: true, },
//
syncItems: {
header: false,
body: false,
query: false,
rest: false,
},
deleteRedundantParam: false,
}; };
const form = ref({ ...initForm }); const form = ref<batchSyncForm>(cloneDeep(initForm));
const checkType = ref([]);
const checkList = ref([ const checkList = ref([
{ {
@ -118,13 +134,24 @@
}, },
]); ]);
const syncLoading = ref<boolean>(false); function resetForm() {
form.value = cloneDeep(initForm);
function cancel() { checkType.value = [];
showBatchSyncModal.value = false; showBatchSyncModal.value = false;
} }
// TODO
function confirmBatchSync() {} function confirmBatchSync() {
checkType.value.forEach((e: string) => {
const key = e.toLowerCase() as keyof syncItem;
form.value.syncItems[key] = true;
});
emit('batchSync', form.value);
}
defineExpose({
resetForm,
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -228,6 +228,7 @@ export default {
'case.definitionInconsistent': 'Inconsistent with the definition', 'case.definitionInconsistent': 'Inconsistent with the definition',
'case.haveIgnoredTheChange': 'Have ignored the change', 'case.haveIgnoredTheChange': 'Have ignored the change',
'case.eachHasBeenIgnored': 'Each change difference has been ignored', 'case.eachHasBeenIgnored': 'Each change difference has been ignored',
'case.eachHasBeenIgnoredClosed': 'Closed Ignore each change difference',
'case.apiCaseList': 'List', 'case.apiCaseList': 'List',
'case.apiCaseDetail': 'Use case details', 'case.apiCaseDetail': 'Use case details',
'case.belongingApi': 'Belonging interface', 'case.belongingApi': 'Belonging interface',

View File

@ -215,6 +215,7 @@ export default {
'case.definitionInconsistent': '与定义不一致', 'case.definitionInconsistent': '与定义不一致',
'case.haveIgnoredTheChange': '已忽略本次变更差异', 'case.haveIgnoredTheChange': '已忽略本次变更差异',
'case.eachHasBeenIgnored': '已忽略每次变更差异', 'case.eachHasBeenIgnored': '已忽略每次变更差异',
'case.eachHasBeenIgnoredClosed': '已关闭忽略每次变更差异',
'case.apiCaseList': '列表', 'case.apiCaseList': '列表',
'case.apiCaseDetail': '用例详情', 'case.apiCaseDetail': '用例详情',
'case.belongingApi': '所属接口', 'case.belongingApi': '所属接口',

View File

@ -52,7 +52,7 @@
</span> </span>
</template> </template>
<template #name="{ record }"> <template #name="{ record }">
<div class="one-line-text">{{ characterLimit(record.name) }}</div> <div class="one-line-text">{{ record.name }}</div>
</template> </template>
<template #caseLevel="{ record }"> <template #caseLevel="{ record }">
<a-select <a-select

View File

@ -29,8 +29,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue'; import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue'; import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
@ -61,7 +59,7 @@
(e: 'handleSummary', content: string): void; (e: 'handleSummary', content: string): void;
}>(); }>();
const innerSummary = useVModel(props, 'richText', emit); const innerSummary = ref(props.richText);
function handleCancel() { function handleCancel() {
emit('cancel'); emit('cancel');
@ -135,6 +133,18 @@
emit('handleClick'); emit('handleClick');
} }
} }
watch(
() => props.richText,
(val) => {
if (val) {
innerSummary.value = { ...val };
}
},
{
deep: true,
}
);
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -173,7 +173,7 @@
:is-preview="props.isPreview" :is-preview="props.isPreview"
/> />
<Summary <Summary
v-else-if="item.value === ReportCardTypeEnum.SUMMARY" v-if="item.value === ReportCardTypeEnum.SUMMARY"
:rich-text="getContent(item)" :rich-text="getContent(item)"
:share-id="shareId" :share-id="shareId"
:is-preview="props.isPreview" :is-preview="props.isPreview"

View File

@ -89,9 +89,9 @@ export default {
'testPlan.planForm.repeatCaseTip1': 'Enable: Repeatedly associate the same case', 'testPlan.planForm.repeatCaseTip1': 'Enable: Repeatedly associate the same case',
'testPlan.planForm.repeatCaseTip2': 'Close: Cannot be associated with the same case repeatedly', 'testPlan.planForm.repeatCaseTip2': 'Close: Cannot be associated with the same case repeatedly',
'testPlan.planForm.enableAutomaticStatusTip': 'testPlan.planForm.enableAutomaticStatusTip':
'Enable: function cases associated interface/UI/case execution successful performance, function of the use case status automatically updated to success', 'Enable: function case execution successful cases associated interface, function of the use case status automatically updated to success',
'testPlan.planForm.closeAutomaticStatusTip': 'testPlan.planForm.closeAutomaticStatusTip':
'Close: the function of use case execution result is not affected by the interface/UI/performance', 'Close: The result of execution of a functional use case is not affected by the interface',
'testPlan.planForm.passThresholdTip': 'If the pass rate reaches the specified pass threshold, the result is passed', 'testPlan.planForm.passThresholdTip': 'If the pass rate reaches the specified pass threshold, the result is passed',
'testPlan.planForm.pickCases': 'Select cases', 'testPlan.planForm.pickCases': 'Select cases',
'testPlan.testPlanDetail.executed': 'Executed', 'testPlan.testPlanDetail.executed': 'Executed',

View File

@ -85,9 +85,8 @@ export default {
'testPlan.planForm.selectPlanGroup': '选择计划组', 'testPlan.planForm.selectPlanGroup': '选择计划组',
'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例', 'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例',
'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例', 'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例',
'testPlan.planForm.enableAutomaticStatusTip': 'testPlan.planForm.enableAutomaticStatusTip': '开启:功能用例关联的接口用例执行成功,功能用例的状态自动更新为成功',
'开启:功能用例关联的接口/UI/性能用例执行成功,功能用例的状态自动更新为成功', 'testPlan.planForm.closeAutomaticStatusTip': '关闭:功能用例的执行结果不受接口的影响',
'testPlan.planForm.closeAutomaticStatusTip': '关闭:功能用例的执行结果不受接口/UI/性能的影响',
'testPlan.planForm.passThresholdTip': '通过率达到设置的通过阈值时,报告结果为通过', 'testPlan.planForm.passThresholdTip': '通过率达到设置的通过阈值时,报告结果为通过',
'testPlan.planForm.pickCases': '选择用例', 'testPlan.planForm.pickCases': '选择用例',
'testPlan.testPlanDetail.executed': '已执行', 'testPlan.testPlanDetail.executed': '已执行',