feat(接口测试): 场景导入
This commit is contained in:
parent
7450a45384
commit
b27da7e476
|
@ -17,6 +17,7 @@ import {
|
|||
dragSortUrl,
|
||||
ExecuteHistoryUrl,
|
||||
ExecuteScenarioUrl,
|
||||
ExportScenarioUrl,
|
||||
FollowScenarioUrl,
|
||||
GetModuleCountUrl,
|
||||
GetModuleTreeUrl,
|
||||
|
@ -27,6 +28,7 @@ import {
|
|||
GetSystemRequestUrl,
|
||||
GetTrashModuleCountUrl,
|
||||
GetTrashModuleTreeUrl,
|
||||
ImportScenarioUrl,
|
||||
MoveModuleUrl,
|
||||
RecoverScenarioUrl,
|
||||
RecycleScenarioUrl,
|
||||
|
@ -63,7 +65,9 @@ import {
|
|||
ApiScenarioUpdateDTO,
|
||||
ExecuteHistoryItem,
|
||||
ExecutePageParams,
|
||||
type ExportScenarioParams,
|
||||
GetSystemRequestParams,
|
||||
type ImportScenarioParams,
|
||||
ImportSystemData,
|
||||
Scenario,
|
||||
ScenarioDetail,
|
||||
|
@ -325,7 +329,18 @@ export function logScenarioReportBatchExport(data: BatchApiParams) {
|
|||
export function getScenarioBatchExportParams(data: BatchApiParams) {
|
||||
return MSR.post({ url: `${GetScenarioBatchExportParamsUrl}`, data });
|
||||
}
|
||||
|
||||
// 场景导出报告id集合
|
||||
export function scenarioAssociateExport(data: ImportSystemData) {
|
||||
return MSR.post({ url: `${ScenarioAssociateExportUrl}`, data });
|
||||
}
|
||||
|
||||
// 导入场景
|
||||
export function importScenario(params: ImportScenarioParams) {
|
||||
return MSR.uploadFile({ url: ImportScenarioUrl }, { fileList: [params.file], request: params.request }, 'file');
|
||||
}
|
||||
|
||||
// 导出场景
|
||||
export function exportScenario(data: ExportScenarioParams) {
|
||||
return MSR.post({ url: ExportScenarioUrl, data });
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ 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 dragSortUrl = '/api/scenario/edit/pos';
|
||||
// 回收站相关
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||
import type { saveParams } from '@/components/business/ms-associate-case/types';
|
||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
|
||||
|
@ -7,6 +8,7 @@ import {
|
|||
RequestAssertionCondition,
|
||||
RequestComposition,
|
||||
RequestDefinitionStatus,
|
||||
type RequestImportFormat,
|
||||
RequestMethods,
|
||||
ScenarioExecuteStatus,
|
||||
ScenarioFailureStrategy,
|
||||
|
@ -521,9 +523,30 @@ export interface ScenarioAssociateCaseParams {
|
|||
protocols: string[];
|
||||
associateType?: string;
|
||||
}
|
||||
|
||||
// 多模块关联
|
||||
export interface ImportSystemData {
|
||||
API: ScenarioAssociateCaseParams; // 接口
|
||||
CASE: ScenarioAssociateCaseParams; // 用例
|
||||
SCENARIO: ScenarioAssociateCaseParams; // 场景
|
||||
}
|
||||
|
||||
// 导入场景请求参数
|
||||
export interface ImportScenarioRequest {
|
||||
moduleId: string;
|
||||
projectId: string;
|
||||
type: RequestImportFormat.MeterSphere | RequestImportFormat.Jmeter;
|
||||
coverData: boolean;
|
||||
}
|
||||
|
||||
// 导入场景参数
|
||||
export interface ImportScenarioParams {
|
||||
file: File | null;
|
||||
request: ImportScenarioRequest;
|
||||
}
|
||||
|
||||
// 导出场景参数
|
||||
export interface ExportScenarioParams extends BatchActionQueryParams {
|
||||
apiScenarioId: string;
|
||||
fileId: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
<template>
|
||||
<div>
|
||||
<MsDrawer
|
||||
v-model:visible="visible"
|
||||
:width="960"
|
||||
:title="t('apiScenario.importScenario')"
|
||||
:closable="false"
|
||||
:ok-disabled="!fileList.length"
|
||||
:ok-text="t('common.import')"
|
||||
:ok-loading="importLoading"
|
||||
disabled-width-drag
|
||||
desc
|
||||
@confirm="confirmImport"
|
||||
@cancel="cancelImport"
|
||||
>
|
||||
<div class="mb-[16px] flex items-center gap-[16px]">
|
||||
<div
|
||||
v-for="item of platformList"
|
||||
:key="item.value"
|
||||
:class="`import-item ${importForm.type === item.value ? 'import-item--active' : ''}`"
|
||||
@click="() => setActiveImportFormat(item.value)"
|
||||
>
|
||||
<div class="text-[var(--color-text-1)]">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-form ref="importFormRef" :model="importForm" layout="vertical">
|
||||
<a-form-item :label="t('apiTestManagement.belongModule')">
|
||||
<a-tree-select
|
||||
v-model:modelValue="importForm.moduleId"
|
||||
:data="innerModuleTree"
|
||||
class="w-[436px]"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
:draggable="false"
|
||||
allow-search
|
||||
allow-clear
|
||||
:filter-tree-node="filterTreeNode"
|
||||
:fallback-option="(key: string | number) => ({
|
||||
name: t('apiTestManagement.moduleNotExist'),
|
||||
key,
|
||||
disabled: true,
|
||||
})"
|
||||
>
|
||||
<template #tree-slot-title="node">
|
||||
<a-tooltip :content="`${node.name}`" position="tl">
|
||||
<div class="one-line-text w-[300px]">{{ node.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('apiTestManagement.importMode')">
|
||||
<a-radio-group v-model:model-value="importForm.coverData">
|
||||
<a-radio :value="true">
|
||||
<div class="flex items-center gap-[2px]">
|
||||
{{ t('apiTestManagement.cover') }}
|
||||
<a-tooltip position="right" :content="t('apiScenario.importScenarioCoverTip')">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio :value="false">
|
||||
<div class="flex items-center gap-[2px]">
|
||||
{{ t('apiTestManagement.uncover') }}
|
||||
<a-tooltip position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<template #content>
|
||||
<div>{{ t('apiScenario.importScenarioUncoverTip1') }}</div>
|
||||
<div>{{ t('apiScenario.importScenarioUncoverTip2') }}</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<MsUpload
|
||||
v-model:file-list="fileList"
|
||||
:accept="fileAccept"
|
||||
:auto-upload="false"
|
||||
draggable
|
||||
size-unit="MB"
|
||||
class="w-full"
|
||||
>
|
||||
<template #subText>
|
||||
<div class="flex">
|
||||
{{ t('apiScenario.importScenarioUploadTip', { type: fileAccept, size: appStore.getFileMaxSize }) }}
|
||||
</div>
|
||||
</template>
|
||||
</MsUpload>
|
||||
</a-form>
|
||||
</MsDrawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
||||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
|
||||
import { importScenario } from '@/api/modules/api-test/scenario';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { filterTree, filterTreeNode, TreeNode } from '@/utils';
|
||||
|
||||
import { ImportScenarioRequest } from '@/models/apiTest/scenario';
|
||||
import type { ModuleTreeNode } from '@/models/common';
|
||||
import { RequestImportFormat } from '@/enums/apiEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
moduleTree: ModuleTreeNode[];
|
||||
activeModule: string;
|
||||
}>();
|
||||
const emit = defineEmits(['update:visible', 'done']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const visible = useVModel(props, 'visible', emit);
|
||||
const innerModuleTree = ref<TreeNode<ModuleTreeNode>[]>([]);
|
||||
const platformList: { name: string; value: RequestImportFormat.MeterSphere | RequestImportFormat.Jmeter }[] = [
|
||||
{
|
||||
name: 'MeterSphere',
|
||||
value: RequestImportFormat.MeterSphere,
|
||||
},
|
||||
{
|
||||
name: 'Jmeter',
|
||||
value: RequestImportFormat.Jmeter,
|
||||
},
|
||||
];
|
||||
const fileList = ref<MsFileItem[]>([]);
|
||||
const defaultForm: ImportScenarioRequest = {
|
||||
moduleId: '',
|
||||
coverData: false,
|
||||
projectId: appStore.currentProjectId,
|
||||
type: RequestImportFormat.MeterSphere,
|
||||
};
|
||||
const importForm = ref({ ...defaultForm });
|
||||
const importFormRef = ref<FormInstance>();
|
||||
const fileAccept = computed(() => {
|
||||
return importForm.value.type === RequestImportFormat.MeterSphere ? 'json' : 'jmx';
|
||||
});
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
importForm.value.moduleId = props.activeModule !== 'all' ? props.activeModule : '';
|
||||
innerModuleTree.value = filterTree(props.moduleTree, (node) => node.type === 'MODULE');
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const importLoading = ref(false);
|
||||
|
||||
function setActiveImportFormat(format: RequestImportFormat.MeterSphere | RequestImportFormat.Jmeter) {
|
||||
importForm.value.type = format;
|
||||
}
|
||||
|
||||
function cancelImport() {
|
||||
visible.value = false;
|
||||
importForm.value = { ...defaultForm };
|
||||
importFormRef.value?.resetFields();
|
||||
fileList.value = [];
|
||||
}
|
||||
|
||||
async function doImportScenario() {
|
||||
try {
|
||||
importLoading.value = true;
|
||||
await importScenario({
|
||||
file: fileList.value[0].file || null,
|
||||
request: importForm.value,
|
||||
});
|
||||
Message.success(t('common.importSuccess'));
|
||||
emit('done');
|
||||
cancelImport();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
importLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function confirmImport() {
|
||||
importFormRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
doImportScenario();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.import-item {
|
||||
@apply flex cursor-pointer items-center bg-white;
|
||||
|
||||
padding: 8px;
|
||||
width: 200px;
|
||||
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));
|
||||
}
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
:deep(.arco-select-view-value::after) {
|
||||
@apply hidden;
|
||||
}
|
||||
</style>
|
|
@ -1,24 +1,37 @@
|
|||
<template>
|
||||
<div class="h-full">
|
||||
<div class="mb-[8px] flex items-center gap-[8px]">
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('apiScenario.tree.selectorPlaceholder')"
|
||||
allow-clear
|
||||
/>
|
||||
<a-button
|
||||
v-permission="['PROJECT_API_SCENARIO:READ+ADD']"
|
||||
type="primary"
|
||||
value="newScenario"
|
||||
long
|
||||
@click="
|
||||
() => {
|
||||
emit('newScenario');
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ t('common.newCreate') }}</a-button
|
||||
{{ t('apiScenario.createScenario') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
v-permission="['PROJECT_API_SCENARIO:READ+ADD']"
|
||||
type="outline"
|
||||
long
|
||||
@click="
|
||||
() => {
|
||||
emit('import');
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ t('apiScenario.importScenario') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('apiScenario.tree.selectorPlaceholder')"
|
||||
class="mb-[8px]"
|
||||
allow-clear
|
||||
/>
|
||||
<div class="folder" @click="setActiveFolder('all')">
|
||||
<div :class="allFolderClass">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
@init="handleModuleInit"
|
||||
@new-scenario="() => newTab()"
|
||||
@change="handleModuleChange"
|
||||
@import="importDrawerVisible = true"
|
||||
></scenarioModuleTree>
|
||||
</div>
|
||||
<a-divider margin="0" />
|
||||
|
@ -104,6 +105,13 @@
|
|||
</template>
|
||||
</MsSplitBox>
|
||||
</MsCard>
|
||||
<importScenario
|
||||
v-model:visible="importDrawerVisible"
|
||||
:module-tree="moduleTree"
|
||||
:active-module="activeModule"
|
||||
popup-container="#managementContainer"
|
||||
@done="handleImportDone"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -123,6 +131,7 @@
|
|||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import MsEnvironmentSelect from '@/components/business/ms-environment-select/index.vue';
|
||||
import importScenario from './components/import.vue';
|
||||
import scenarioModuleTree from './components/scenarioModuleTree.vue';
|
||||
import executeButton from '@/views/api-test/components/executeButton.vue';
|
||||
import ScenarioTable from '@/views/api-test/scenario/components/scenarioTable.vue';
|
||||
|
@ -717,6 +726,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
const importDrawerVisible = ref(false);
|
||||
|
||||
async function handleImportDone() {
|
||||
await scenarioModuleTreeRef.value?.refresh();
|
||||
apiTableRef.value?.loadScenarioList();
|
||||
}
|
||||
|
||||
const { registerCatchSaveShortcut, removeCatchSaveShortcut } = useShortcutSave(saveScenario);
|
||||
onBeforeMount(async () => {
|
||||
selectRecycleCount();
|
||||
|
|
|
@ -3,6 +3,11 @@ export default {
|
|||
'apiScenario.allScenario': 'All Scenarios',
|
||||
'apiScenario.createScenario': 'Create Scenario',
|
||||
'apiScenario.importScenario': 'Import Scenario',
|
||||
'apiScenario.importScenarioCoverTip': 'If the same scene already exists in the system, it will be overwritten.',
|
||||
'apiScenario.importScenarioUncoverTip1':
|
||||
'1. If the same scene already exists in the system (with a unique name under the module), no changes will be made',
|
||||
'apiScenario.importScenarioUncoverTip2': '2. If the scenario does not exist in the system, add',
|
||||
'apiScenario.importScenarioUploadTip': '仅支持 json格式文件,单个大小不超过 {size} MB',
|
||||
'apiScenario.tree.selectorPlaceholder': 'Please enter module name',
|
||||
'apiScenario.tree.folder.allScenario': 'All Scenarios',
|
||||
'apiScenario.tree.noMatchModule': 'No matching module found',
|
||||
|
|
|
@ -3,6 +3,10 @@ export default {
|
|||
'apiScenario.allScenario': '全部场景',
|
||||
'apiScenario.createScenario': '新建场景',
|
||||
'apiScenario.importScenario': '导入场景',
|
||||
'apiScenario.importScenarioCoverTip': '系统已存在同一场景则覆盖',
|
||||
'apiScenario.importScenarioUncoverTip1': '1.系统已存在的同一场景(模块下名称唯一),则不做变更',
|
||||
'apiScenario.importScenarioUncoverTip2': '2.系统不存在的场景,则新增',
|
||||
'apiScenario.importScenarioUploadTip': '仅支持 {type} 格式文件,单个大小不超过 {size} MB',
|
||||
'apiScenario.tree.selectorPlaceholder': '请输入模块名称进行搜索',
|
||||
'apiScenario.tree.folder.allScenario': '全部场景',
|
||||
'apiScenario.tree.noMatchModule': '暂无匹配的模块',
|
||||
|
|
Loading…
Reference in New Issue