feat(接口测试): 接口测试前端页面
This commit is contained in:
parent
a404376e51
commit
f05e536cb3
|
@ -647,7 +647,9 @@ public class ApiScenarioDataTransferService {
|
|||
}
|
||||
}
|
||||
});
|
||||
List<ApiTestCaseWithBlob> apiTestCaseWithBlobs = extApiTestCaseMapper.selectAllDetailByApiIds(apiCaseIdList);
|
||||
List<ApiTestCaseWithBlob> apiTestCaseWithBlobs = null;
|
||||
if (CollectionUtils.isNotEmpty(apiCaseIdList)) {
|
||||
apiTestCaseWithBlobs = extApiTestCaseMapper.selectAllDetailByApiIds(apiCaseIdList);
|
||||
if (CollectionUtils.isNotEmpty(apiCaseIdList)) {
|
||||
apiTestCaseWithBlobs.forEach(item -> {
|
||||
if (!apiDefinitionIdList.contains(item.getApiDefinitionId())) {
|
||||
|
@ -655,6 +657,7 @@ public class ApiScenarioDataTransferService {
|
|||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Map<String, String>> projectApiModuleIdMap = new HashMap<>();
|
||||
if (CollectionUtils.isNotEmpty(apiDefinitionIdList)) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
|
||||
import MSR from '@/api/http/index';
|
||||
import { ExportDefinitionUrl, StopApiExportUrl } from '@/api/requrls/api-test/management';
|
||||
import {
|
||||
AddModuleUrl,
|
||||
AddScenarioUrl,
|
||||
|
@ -19,6 +20,7 @@ import {
|
|||
ExecuteScenarioUrl,
|
||||
ExportScenarioUrl,
|
||||
FollowScenarioUrl,
|
||||
GetExportScenarioFileUrl,
|
||||
GetModuleCountUrl,
|
||||
GetModuleTreeUrl,
|
||||
GetScenarioBatchExportParamsUrl,
|
||||
|
@ -44,6 +46,7 @@ import {
|
|||
ScenarioTransferModuleOptionsUrl,
|
||||
ScenarioTrashPageUrl,
|
||||
ScenarioUploadTempFileUrl,
|
||||
StopExportScenarioUrl,
|
||||
UpdateModuleUrl,
|
||||
UpdateScenarioPriorityUrl,
|
||||
UpdateScenarioStatusUrl,
|
||||
|
@ -51,6 +54,7 @@ import {
|
|||
} from '@/api/requrls/api-test/scenario';
|
||||
|
||||
import { ExecuteConditionProcessor } from '@/models/apiTest/common';
|
||||
import type { ApiDefinitionBatchExportParams } from '@/models/apiTest/management';
|
||||
import {
|
||||
ApiScenarioBatchDeleteParams,
|
||||
ApiScenarioBatchEditParams,
|
||||
|
@ -341,6 +345,22 @@ export function importScenario(params: ImportScenarioParams) {
|
|||
}
|
||||
|
||||
// 导出场景
|
||||
export function exportScenario(data: ExportScenarioParams) {
|
||||
return MSR.post({ url: ExportScenarioUrl, data });
|
||||
export function exportScenario(data: ExportScenarioParams, type: string) {
|
||||
return MSR.post({ url: `${ExportScenarioUrl}/${type}`, data });
|
||||
}
|
||||
|
||||
// 停止导出场景
|
||||
export function stopScenarioExport(taskId: string) {
|
||||
return MSR.get({ url: `${StopExportScenarioUrl}/${taskId}` });
|
||||
}
|
||||
|
||||
// 获取导出的文件
|
||||
export function getScenarioDownloadFile(projectId: string, fileId: string) {
|
||||
return MSR.get(
|
||||
{
|
||||
url: `${GetExportScenarioFileUrl}/${projectId}/${fileId}`,
|
||||
responseType: 'blob',
|
||||
},
|
||||
{ isTransformResponse: false }
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,9 +28,11 @@ export const BatchEditScenarioUrl = '/api/scenario/batch-operation/edit'; // 批
|
|||
export const BatchRunScenarioUrl = '/api/scenario/batch-operation/run'; // 批量执行接口场景
|
||||
export const UpdateScenarioPriorityUrl = '/api/scenario/update-priority'; // 场景更新等级
|
||||
export const UpdateScenarioStatusUrl = '/api/scenario/update-status'; // 场景更新状态
|
||||
// 场景导入导出相关
|
||||
export const ImportScenarioUrl = '/api/scenario/import'; // 导入场景
|
||||
export const ExportScenarioUrl = '/api/scenario/export'; // 导入场景
|
||||
|
||||
export const ExportScenarioUrl = '/api/scenario/export'; // 导出场景
|
||||
export const StopExportScenarioUrl = '/api/scenario/stop';
|
||||
export const GetExportScenarioFileUrl = '/api/scenario/download/file';
|
||||
// 场景拖拽排序
|
||||
export const dragSortUrl = '/api/scenario/edit/pos';
|
||||
// 回收站相关
|
||||
|
|
|
@ -256,6 +256,12 @@ export enum ScenarioExecuteStatus {
|
|||
UN_EXECUTE = 'UN_EXECUTE',
|
||||
FAKE_ERROR = 'FAKE_ERROR',
|
||||
}
|
||||
|
||||
// 场景导出配置
|
||||
export enum ScenarioExportType {
|
||||
SIMPLE = 'METERSPHERE_SIMPLE',
|
||||
ALL = 'METERSPHERE_ALL_DATA',
|
||||
}
|
||||
// 场景步骤类型
|
||||
export enum ScenarioStepType {
|
||||
API_CASE = 'API_CASE', // 接口用例
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
async function downloadFile(id: string) {
|
||||
try {
|
||||
const response = await getApiDownload.value(appStore.currentProjectId, id);
|
||||
downloadByteFile(response, 'metersphere-export.json');
|
||||
downloadByteFile(response, 'metersphere-definition.zip');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
:title="t('common.export')"
|
||||
title-align="start"
|
||||
class="ms-modal-upload ms-modal-medium"
|
||||
:width="400"
|
||||
>
|
||||
<a-radio-group v-model:model-value="exportTypeRadio">
|
||||
<a-radio :value="ScenarioExportType.SIMPLE"
|
||||
>{{ t('apiScenario.export.type.simple') }}
|
||||
<a-tooltip :content="t('apiScenario.export.simple.tooltip')" position="tl">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-radio>
|
||||
<a-radio :value="ScenarioExportType.ALL">{{ t('apiScenario.export.type.all') }}</a-radio>
|
||||
</a-radio-group>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<a-button type="secondary" :disabled="exportLoading" @click="cancelExport">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button class="ml-3" type="primary" :loading="exportLoading" @click="exportApi">
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||
|
||||
import { exportScenario, getScenarioDownloadFile, stopScenarioExport } from '@/api/modules/api-test/scenario';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useWebsocket from '@/hooks/useWebsocket';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { downloadByteFile, getGenerateId } from '@/utils';
|
||||
|
||||
import { ScenarioExportType } from '@/enums/apiEnum';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
batchParams: BatchActionQueryParams;
|
||||
conditionParams: Record<string, any> | (() => Record<string, any>);
|
||||
sorter?: Record<string, any>;
|
||||
isShare?: boolean; // 分享文档id
|
||||
}>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', { required: true });
|
||||
|
||||
const exportLoading = ref(false);
|
||||
const exportTypeRadio = ref(ScenarioExportType.SIMPLE);
|
||||
|
||||
function cancelExport() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
const websocket = ref<WebSocket>();
|
||||
const reportId = ref('');
|
||||
const isShowExportingMessage = ref(false); // 正在导出提示显示中
|
||||
const exportingMessage = ref();
|
||||
|
||||
// 下载文件
|
||||
async function downloadFile(id: string) {
|
||||
try {
|
||||
const response = await getScenarioDownloadFile(appStore.currentProjectId, id);
|
||||
downloadByteFile(response, 'metersphere-scenaro.zip');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
// 提示:导出成功
|
||||
function showExportSuccessfulMessage(id: string, count: number) {
|
||||
Message.success({
|
||||
content: () =>
|
||||
h('div', { class: 'flex flex-col gap-[8px] items-start' }, [
|
||||
h('div', { class: 'font-medium' }, t('common.exportSuccessful')),
|
||||
h('div', { class: 'flex items-center gap-[12px]' }, [
|
||||
h('div', t('caseManagement.featureCase.exportApiCount', { number: count })),
|
||||
h(
|
||||
MsButton,
|
||||
{
|
||||
type: 'text',
|
||||
onClick() {
|
||||
downloadFile(id);
|
||||
},
|
||||
},
|
||||
{ default: () => t('common.downloadFile') }
|
||||
),
|
||||
]),
|
||||
]),
|
||||
duration: 10000, // 10s 自动关闭
|
||||
closable: true,
|
||||
});
|
||||
}
|
||||
// 开启websocket监听,接收结果
|
||||
async function startWebsocketGetExportResult() {
|
||||
const { createSocket, websocket: _websocket } = useWebsocket({
|
||||
reportId: reportId.value,
|
||||
socketUrl: '/ws/export',
|
||||
onMessage: (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.msgType === 'EXEC_RESULT') {
|
||||
exportingMessage.value.close();
|
||||
reportId.value = data.fileId;
|
||||
// taskId.value = data.taskId;
|
||||
if (data.isSuccessful) {
|
||||
showExportSuccessfulMessage(reportId.value, data.count);
|
||||
} else {
|
||||
Message.error({
|
||||
content: t('common.exportFailed'),
|
||||
duration: 999999999, // 一直展示,除非手动关闭
|
||||
closable: true,
|
||||
});
|
||||
}
|
||||
websocket.value?.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
await createSocket();
|
||||
websocket.value = _websocket.value;
|
||||
}
|
||||
|
||||
// 取消导出
|
||||
async function stopExport(taskId: string) {
|
||||
try {
|
||||
await stopScenarioExport(taskId);
|
||||
exportingMessage.value.close();
|
||||
websocket.value?.close();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
// 提示:正在导出
|
||||
function showExportingMessage(taskId: string) {
|
||||
if (isShowExportingMessage.value) return;
|
||||
isShowExportingMessage.value = true;
|
||||
exportingMessage.value = Message.loading({
|
||||
content: () =>
|
||||
h('div', { class: 'flex items-center gap-[12px]' }, [
|
||||
h('div', t('common.exporting')),
|
||||
h(
|
||||
MsButton,
|
||||
{
|
||||
type: 'text',
|
||||
onClick() {
|
||||
stopExport(taskId);
|
||||
},
|
||||
},
|
||||
{ default: () => t('common.cancel') }
|
||||
),
|
||||
]),
|
||||
duration: 999999999, // 一直展示,除非手动关闭
|
||||
closable: true,
|
||||
onClose() {
|
||||
isShowExportingMessage.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出接口
|
||||
*/
|
||||
async function exportApi() {
|
||||
try {
|
||||
exportLoading.value = true;
|
||||
reportId.value = getGenerateId();
|
||||
await startWebsocketGetExportResult();
|
||||
const batchConditionParams =
|
||||
typeof props.conditionParams === 'function' ? await props.conditionParams() : props.conditionParams;
|
||||
const { selectedIds, selectAll, excludeIds } = props.batchParams;
|
||||
const res = await exportScenario(
|
||||
{
|
||||
selectIds: selectedIds || [],
|
||||
selectAll: !!selectAll,
|
||||
excludeIds: excludeIds || [],
|
||||
...batchConditionParams,
|
||||
sort: props.sorter || {},
|
||||
fileId: reportId.value,
|
||||
},
|
||||
exportTypeRadio.value
|
||||
);
|
||||
showExportingMessage(res);
|
||||
visible.value = false;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
exportLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.import-item {
|
||||
@apply flex cursor-pointer items-center bg-white;
|
||||
|
||||
padding: 8px;
|
||||
width: 150px;
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: var(--border-radius-small);
|
||||
gap: 6px;
|
||||
}
|
||||
.import-item--active {
|
||||
border: 1px solid rgb(var(--primary-5));
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
</style>
|
|
@ -465,6 +465,13 @@
|
|||
is-scenario
|
||||
:report-id="tableRecord?.lastReportId || ''"
|
||||
/>
|
||||
<!-- 场景导出-->
|
||||
<ScenarioExportModal
|
||||
v-model:visible="showExportModal"
|
||||
:batch-params="batchParams"
|
||||
:condition-params="getBatchConditionParams"
|
||||
:sorter="propsRes.sorter || {}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -488,8 +495,10 @@
|
|||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
import caseAndScenarioReportDrawer from '@/views/api-test/components/caseAndScenarioReportDrawer.vue';
|
||||
import ApiExportModal from '@/views/api-test/management/components/management/api/apiExportModal.vue';
|
||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
||||
import BatchRunModal from '@/views/api-test/scenario/components/batchRunModal.vue';
|
||||
import ScenarioExportModal from '@/views/api-test/scenario/components/common/exportScenario/scenarioExportModal.vue';
|
||||
import operationScenarioModuleTree from '@/views/api-test/scenario/components/operationScenarioModuleTree.vue';
|
||||
|
||||
import { getEnvList } from '@/api/modules/api-test/common';
|
||||
|
@ -547,7 +556,7 @@
|
|||
|
||||
const appStore = useAppStore();
|
||||
const cacheStore = useCacheStore();
|
||||
|
||||
const showExportModal = ref(false);
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
const tableRecord = ref<ApiScenarioTableItem>();
|
||||
|
@ -824,6 +833,11 @@
|
|||
);
|
||||
const batchActions = {
|
||||
baseAction: [
|
||||
{
|
||||
label: 'common.export',
|
||||
eventTag: 'export',
|
||||
permission: ['PROJECT_API_SCENARIO:READ+EXPORT'],
|
||||
},
|
||||
{
|
||||
label: 'common.edit',
|
||||
eventTag: 'edit',
|
||||
|
@ -1067,7 +1081,7 @@
|
|||
|
||||
const tableSelected = ref<(string | number)[]>([]);
|
||||
|
||||
const batchParams = ref<BatchActionQueryParams>();
|
||||
const batchParams = ref<BatchActionQueryParams>({ selectAll: false });
|
||||
const batchOptionParams = ref<any>();
|
||||
async function getBatchConditionParams() {
|
||||
const selectModules = await getModuleIds();
|
||||
|
@ -1445,6 +1459,9 @@
|
|||
|
||||
batchParams.value = { ...params };
|
||||
switch (event.eventTag) {
|
||||
case 'export':
|
||||
showExportModal.value = true;
|
||||
break;
|
||||
case 'delete':
|
||||
deleteScenario(undefined, true, batchParams.value);
|
||||
break;
|
||||
|
|
|
@ -286,4 +286,7 @@ export default {
|
|||
'apiScenario.csvQuote': 'CSV quote',
|
||||
'apiScenario.csvNameNotNull': 'CSV name cannot be empty',
|
||||
'apiScenario.csvFileNotNull': 'CSV file cannot be empty',
|
||||
'apiScenario.export.type.simple': 'Simple',
|
||||
'apiScenario.export.type.all': 'All data',
|
||||
'apiScenario.export.simple.tooltip': 'Process referenced or copied request steps as custom requests',
|
||||
};
|
||||
|
|
|
@ -280,4 +280,7 @@ export default {
|
|||
'apiScenario.csvQuote': 'CSV 引用',
|
||||
'apiScenario.csvNameNotNull': 'CSV 名称不能为空',
|
||||
'apiScenario.csvFileNotNull': 'CSV 文件不能为空',
|
||||
'apiScenario.export.type.simple': '普通导出',
|
||||
'apiScenario.export.type.all': '保留引用关系',
|
||||
'apiScenario.export.simple.tooltip': '将引用或复制的请求步骤处理为自定义请求',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue