feat(用例管理): 新增用例执行功能
This commit is contained in:
parent
529ba4368c
commit
78d8e0d4d6
|
@ -9,4 +9,16 @@ import lombok.Setter;
|
|||
public class QueryResourcePoolRequest extends BasePageRequest {
|
||||
@Schema(description = "是否禁用")
|
||||
private Boolean enable;
|
||||
|
||||
@Schema(description = "是否删除")
|
||||
private Boolean deleted = false;
|
||||
|
||||
@Schema(description = "是否用于接口测试")
|
||||
private Boolean apiTest = false;
|
||||
|
||||
@Schema(description = "是否用于性能测试")
|
||||
private Boolean loadTest = false;
|
||||
|
||||
@Schema(description = "是否用于ui测试")
|
||||
private Boolean uiTest = false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.system.mapper;
|
||||
|
||||
import io.metersphere.system.domain.TestResourcePool;
|
||||
import io.metersphere.system.dto.sdk.QueryResourcePoolRequest;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ExtResourcePoolMapper {
|
||||
|
||||
List<TestResourcePool> getResourcePoolList(@Param("request") QueryResourcePoolRequest request);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="io.metersphere.system.mapper.ExtResourcePoolMapper">
|
||||
|
||||
<select id="getResourcePoolList" resultType="io.metersphere.system.domain.TestResourcePool">
|
||||
SELECT *
|
||||
FROM test_resource_pool t
|
||||
<include refid="queryWhereCondition"/>
|
||||
order by t.update_time desc
|
||||
</select>
|
||||
|
||||
<sql id="queryWhereCondition">
|
||||
<where>
|
||||
<if test="request.keyword != null">
|
||||
and t.name like concat('%', #{request.keyword},'%')
|
||||
</if>
|
||||
<if test="request.enable != null">
|
||||
and t.enable = #{request.enable}
|
||||
</if>
|
||||
<if test="request.deleted != null">
|
||||
and t.deleted = #{request.deleted}
|
||||
</if>
|
||||
<if test="request.apiTest != null and request.apiTest == true">
|
||||
and t.api_test = true
|
||||
</if>
|
||||
<if test="request.loadTest != null and request.loadTest == true">
|
||||
and t.load_test = true
|
||||
</if>
|
||||
<if test="request.uiTest != null and request.uiTest == true">
|
||||
and t.ui_test = true
|
||||
</if>
|
||||
<include refid="filter"/>
|
||||
<include refid="combine">
|
||||
<property name="condition" value="request.combine"/>
|
||||
</include>
|
||||
</where>
|
||||
</sql>
|
||||
|
||||
<sql id="filter">
|
||||
<if test="request.filter != null and request.filter.size() > 0">
|
||||
<foreach collection="request.filter.entrySet()" index="key" item="values">
|
||||
<if test="values != null and values.size() > 0">
|
||||
<choose>
|
||||
<when test="key=='createUser'">
|
||||
and t.create_user in
|
||||
<include refid="io.metersphere.system.mapper.BaseMapper.filterInWrapper"/>
|
||||
</when>
|
||||
</choose>
|
||||
</if>
|
||||
</foreach>
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<sql id="combine">
|
||||
<if test="request.combine != null">
|
||||
<if test='${condition}.createUser != null'>
|
||||
and t.create_user
|
||||
<include refid="io.metersphere.system.mapper.BaseMapper.condition">
|
||||
<property name="object" value="${condition}.createUser"/>
|
||||
</include>
|
||||
</if>
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
|
||||
</mapper>
|
|
@ -9,16 +9,16 @@ import io.metersphere.sdk.util.CommonBeanFactory;
|
|||
import io.metersphere.sdk.util.JSON;
|
||||
import io.metersphere.sdk.util.Translator;
|
||||
import io.metersphere.system.domain.*;
|
||||
import io.metersphere.system.dto.pool.*;
|
||||
import io.metersphere.system.dto.pool.TestResourceDTO;
|
||||
import io.metersphere.system.dto.pool.TestResourcePoolDTO;
|
||||
import io.metersphere.system.dto.pool.TestResourcePoolReturnDTO;
|
||||
import io.metersphere.system.dto.pool.TestResourceReturnDTO;
|
||||
import io.metersphere.system.dto.sdk.OptionDTO;
|
||||
import io.metersphere.system.dto.sdk.QueryResourcePoolRequest;
|
||||
import io.metersphere.system.log.constants.OperationLogModule;
|
||||
import io.metersphere.system.log.constants.OperationLogType;
|
||||
import io.metersphere.system.log.dto.LogDTO;
|
||||
import io.metersphere.system.mapper.OrganizationMapper;
|
||||
import io.metersphere.system.mapper.TestResourcePoolBlobMapper;
|
||||
import io.metersphere.system.mapper.TestResourcePoolMapper;
|
||||
import io.metersphere.system.mapper.TestResourcePoolOrganizationMapper;
|
||||
import io.metersphere.system.mapper.*;
|
||||
import io.metersphere.system.uid.IDGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
@ -48,6 +48,8 @@ public class TestResourcePoolService {
|
|||
private SqlSessionFactory sqlSessionFactory;
|
||||
@Resource
|
||||
private OrganizationMapper organizationMapper;
|
||||
@Resource
|
||||
private ExtResourcePoolMapper extResourcePoolMapper;
|
||||
|
||||
|
||||
public void checkAndSaveOrgRelation(TestResourcePool testResourcePool, String id, TestResourceDTO testResourceDTO) {
|
||||
|
@ -136,7 +138,7 @@ public class TestResourcePoolService {
|
|||
testResourceDTO.setNodesList(new ArrayList<>());
|
||||
}
|
||||
TestResourcePoolValidateService testResourcePoolValidateService = CommonBeanFactory.getBean(TestResourcePoolValidateService.class);
|
||||
if (testResourcePoolValidateService!=null) {
|
||||
if (testResourcePoolValidateService != null) {
|
||||
testResourcePoolValidateService.validateNodeList(testResourceDTO.getNodesList());
|
||||
}
|
||||
String configuration = JSON.toJSONString(testResourceDTO);
|
||||
|
@ -149,17 +151,7 @@ public class TestResourcePoolService {
|
|||
}
|
||||
|
||||
public List<TestResourcePoolDTO> listResourcePools(QueryResourcePoolRequest request) {
|
||||
TestResourcePoolExample example = new TestResourcePoolExample();
|
||||
TestResourcePoolExample.Criteria criteria = example.createCriteria();
|
||||
if (StringUtils.isNotBlank(request.getKeyword())) {
|
||||
criteria.andNameLike(StringUtils.wrapIfMissing(request.getKeyword(), "%"));
|
||||
}
|
||||
if (request.getEnable() != null) {
|
||||
criteria.andEnableEqualTo(request.getEnable());
|
||||
}
|
||||
criteria.andDeletedEqualTo(false);
|
||||
example.setOrderByClause("update_time desc");
|
||||
List<TestResourcePool> testResourcePools = testResourcePoolMapper.selectByExample(example);
|
||||
List<TestResourcePool> testResourcePools = extResourcePoolMapper.getResourcePoolList(request);
|
||||
List<TestResourcePoolDTO> testResourcePoolDTOS = new ArrayList<>();
|
||||
testResourcePools.forEach(pool -> {
|
||||
TestResourcePoolBlob testResourcePoolBlob = testResourcePoolBlobMapper.selectByPrimaryKey(pool.getId());
|
||||
|
@ -266,6 +258,7 @@ public class TestResourcePoolService {
|
|||
|
||||
/**
|
||||
* 校验该组织是否有权限使用该资源池
|
||||
*
|
||||
* @param resourcePool
|
||||
* @param orgId
|
||||
* @return
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
BatchDeleteDefinitionUrl,
|
||||
BatchDeleteRecycleCaseUrl,
|
||||
BatchEditCaseUrl,
|
||||
BatchExecuteCaseUrl,
|
||||
BatchMoveDefinitionUrl,
|
||||
BatchRecoverApiUrl,
|
||||
BatchRecoverCaseUrl,
|
||||
|
@ -26,9 +27,14 @@ import {
|
|||
DeleteModuleUrl,
|
||||
DeleteRecycleApiUrl,
|
||||
DeleteRecycleCaseUrl,
|
||||
ExecuteCaseUrl,
|
||||
GetChangeHistoryUrl,
|
||||
GetDefinitionDetailUrl,
|
||||
GetDefinitionScheduleUrl,
|
||||
GetDependencyUrl,
|
||||
GetEnvListUrl,
|
||||
GetEnvModuleUrl,
|
||||
GetExecuteHistoryUrl,
|
||||
GetModuleCountUrl,
|
||||
GetModuleOnlyTreeUrl,
|
||||
GetModuleTreeUrl,
|
||||
|
@ -61,8 +67,9 @@ import { ExecuteRequestParams } from '@/models/apiTest/common';
|
|||
import {
|
||||
AddApiCaseParams,
|
||||
ApiCaseBatchEditParams,
|
||||
ApiCaseBatchParams,
|
||||
ApiCaseDetail,
|
||||
ApiCaseBatchExecuteParams,
|
||||
ApiCaseBatchParams, ApiCaseChangeHistoryParams, ApiCaseDependencyParams,
|
||||
ApiCaseDetail, ApiCaseExecuteHistoryParams,
|
||||
ApiCasePageParams,
|
||||
ApiDefinitionBatchDeleteParams,
|
||||
ApiDefinitionBatchMoveParams,
|
||||
|
@ -83,6 +90,7 @@ import {
|
|||
DefinitionHistoryItem,
|
||||
DefinitionHistoryPageParams,
|
||||
DefinitionReferencePageParams,
|
||||
Environment,
|
||||
EnvModule,
|
||||
ImportApiDefinitionParams,
|
||||
mockParams,
|
||||
|
@ -94,7 +102,7 @@ import {
|
|||
CommonList,
|
||||
DragSortParams,
|
||||
ModuleTreeNode,
|
||||
MoveModules,
|
||||
MoveModules, TableQueryParams,
|
||||
TransferFileParams,
|
||||
} from '@/models/common';
|
||||
|
||||
|
@ -382,3 +390,33 @@ export function batchDeleteRecycleCase(data: ApiCaseBatchParams) {
|
|||
export function addCase(data: AddApiCaseParams) {
|
||||
return MSR.post({ url: AddCaseUrl, data });
|
||||
}
|
||||
|
||||
// 执行接口用例
|
||||
export function executeCase(id: string) {
|
||||
return MSR.get({ url: ExecuteCaseUrl, params: id });
|
||||
}
|
||||
|
||||
// 批量执行接口用例
|
||||
export function batchExecuteCase(data: ApiCaseBatchExecuteParams) {
|
||||
return MSR.post({ url: BatchExecuteCaseUrl, data });
|
||||
}
|
||||
|
||||
// 获取接口测试-环境列表
|
||||
export function getEnvList(projectId: string) {
|
||||
return MSR.get<Environment[]>({ url: GetEnvListUrl, params: projectId });
|
||||
}
|
||||
|
||||
// 获取接口用例-执行历史
|
||||
export function getApiCaseExecuteHistory(data: ApiCaseExecuteHistoryParams) {
|
||||
return MSR.post({ url: GetExecuteHistoryUrl, data });
|
||||
}
|
||||
|
||||
// 获取接口用例-变更历史
|
||||
export function getApiCaseChangeHistory(data: ApiCaseChangeHistoryParams) {
|
||||
return MSR.post({ url: GetChangeHistoryUrl, data });
|
||||
}
|
||||
|
||||
// 获取接口用例-依赖关系
|
||||
export function getApiCaseDependency(data: ApiCaseDependencyParams) {
|
||||
return MSR.post({ url: GetDependencyUrl, data });
|
||||
}
|
|
@ -61,6 +61,14 @@ export const DeleteCaseUrl = '/api/case/delete-to-gc'; // 删除接口用例
|
|||
export const BatchDeleteCaseUrl = '/api/case/batch/delete-to-gc'; // 批量删除接口用例
|
||||
export const BatchEditCaseUrl = '/api/case/batch/edit'; // 批量编辑接口用例
|
||||
export const SortCaseUrl = '/api/case/edit/pos'; // 接口用例拖拽
|
||||
export const GetEnvListUrl = '/api/test/env-list'; // 接口测试-环境列表
|
||||
export const BatchExecuteCaseUrl = '/api/case/batch/run'; // 批量执行接口用例
|
||||
export const ExecuteCaseUrl = '/api/case/run/'; // 单独执行接口用例
|
||||
export const GetExecuteHistoryUrl = 'api/case/execute/page'; // 获取用的执行历史
|
||||
export const GetDependencyUrl = '/api/case/get-reference'; // 获取用例的依赖关系
|
||||
export const GetChangeHistoryUrl = '/api/case/operation-history/page'; // 获取用例的依赖关系
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 接口用例回收站
|
||||
|
|
|
@ -443,7 +443,16 @@
|
|||
height: 14px;
|
||||
}
|
||||
}
|
||||
margin-right: 24px;
|
||||
}
|
||||
.arco-form-item{
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.arco-icon-hover.arco-radio-icon-hover::before {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.arco-radio-checked:not(.arco-radio-disabled) {
|
||||
.arco-radio-icon {
|
||||
@apply !bg-white;
|
||||
|
|
|
@ -121,6 +121,13 @@ export interface EnvModule {
|
|||
selectedModules: SelectedModule[];
|
||||
}
|
||||
|
||||
// 环境列表
|
||||
export interface Environment {
|
||||
id: string;
|
||||
name: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
// 定义列表查询参数
|
||||
export interface ApiDefinitionPageParams extends TableQueryParams {
|
||||
id: string;
|
||||
|
@ -339,3 +346,38 @@ export interface AddApiCaseParams extends ExecuteRequestParams {
|
|||
apiDefinitionId: string | number;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface ApiRunModeRequest {
|
||||
runMode: string;
|
||||
integratedReport: boolean;
|
||||
integratedReportName: string;
|
||||
stopOnFailure: boolean;
|
||||
poolId: string;
|
||||
grouped: boolean;
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
// 接口用例批量执行参数
|
||||
export interface ApiCaseBatchExecuteParams extends BatchApiParams {
|
||||
apiDefinitionId?: string | number;
|
||||
protocol: string;
|
||||
versionId?: string;
|
||||
refId?: string;
|
||||
runModeConfig: ApiRunModeRequest;
|
||||
}
|
||||
|
||||
export interface ApiCaseExecuteHistoryParams extends TableQueryParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ApiCaseChangeHistoryParams extends TableQueryParams {
|
||||
resourceId: string;
|
||||
projectId: string;
|
||||
createUser?: string;
|
||||
types: string[];
|
||||
modules: string[];
|
||||
}
|
||||
|
||||
export interface ApiCaseDependencyParams extends TableQueryParams {
|
||||
resourceId: string;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<div class="arco-table-filters-content">
|
||||
<div class="flex items-center justify-center px-[6px] py-[2px]">
|
||||
<a-checkbox-group v-model:model-value="caseFilters" direction="vertical" size="small">
|
||||
<a-checkbox v-for="item of caseLevelList" :key="item.value" :value="item.value">
|
||||
<a-checkbox v-for="item of caseLevelList" :key="item.text" :value="item.text">
|
||||
<caseLevel :case-level="item.text" />
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
|
@ -140,7 +140,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<MsButton type="text" class="!mr-0">
|
||||
<MsButton type="text" class="!mr-0" @click="onExecute(record.id)">
|
||||
{{ t('apiTestManagement.execute') }}
|
||||
</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
|
@ -223,6 +223,97 @@
|
|||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
<a-modal v-model:visible="showBatchExecute" title-align="start" class="ms-modal-upload ms-modal-medium" :width="480">
|
||||
<template #title>
|
||||
{{ t('report.trigger.batch.execution') }}
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{
|
||||
t('case.batchModalSubTitle', {
|
||||
count: batchParams.currentSelectCount || tableSelected.length,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-form ref="batchExecuteFormRef" class="rounded-[4px]" :model="batchExecuteForm" layout="vertical">
|
||||
<a-form-item field="defaultEnv" :label="t('case.execute.selectEnv')">
|
||||
<a-radio-group v-model="batchExecuteForm.defaultEnv">
|
||||
<a-radio value="true"
|
||||
>{{ t('case.execute.defaultEnv') }}
|
||||
<a-tooltip :content="t('case.execute.defaultEnvTip')" position="top">
|
||||
<icon-question-circle
|
||||
class="text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/> </a-tooltip
|
||||
></a-radio>
|
||||
<a-radio value="false">{{ t('case.execute.newEnv') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="batchExecuteForm.defaultEnv == 'false'"
|
||||
field="environmentId"
|
||||
:label="t('case.execute.newEnv')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
asterisk-position="end"
|
||||
required
|
||||
>
|
||||
<a-select v-model="batchExecuteForm.environmentId" :placeholder="t('common.pleaseSelect')">
|
||||
<a-option v-for="item of environmentList" :key="item.id" :value="item.id">
|
||||
{{ t(item.name) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item field="runMode" :label="t('case.execute.model')">
|
||||
<a-radio-group v-model="batchExecuteForm.runMode">
|
||||
<a-radio value="SERIAL">{{ t('case.execute.serial') }}</a-radio>
|
||||
<a-radio value="PARALLEL">{{ t('case.execute.parallel') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<div v-if="batchExecuteForm.runMode == 'SERIAL'" class="ms-switch">
|
||||
<a-switch
|
||||
v-model="batchExecuteForm.stopOnFailure"
|
||||
type="line"
|
||||
class="ms-form-table-input-switch execute-form-table-input-switch"
|
||||
size="small"
|
||||
/>
|
||||
<span class="ml-3 font-normal text-[var(--color-text-1)]">{{ t('case.execute.StopOnFailure') }}</span>
|
||||
</div>
|
||||
<a-form-item field="integratedReport" :label="t('case.execute.reportSetting')">
|
||||
<a-radio-group v-model="batchExecuteForm.integratedReport" type="button">
|
||||
<a-radio value="false">{{ t('case.execute.independentReporting') }}</a-radio>
|
||||
<a-radio value="true">{{ t('case.execute.CollectionReport') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="batchExecuteForm.integratedReport == 'true'"
|
||||
field="integratedReport"
|
||||
:label="t('case.execute.reportName')"
|
||||
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
|
||||
:validate-trigger="['blur', 'input']"
|
||||
>
|
||||
<a-input
|
||||
v-model="batchExecuteForm.integratedReportName"
|
||||
:max-length="255"
|
||||
:placeholder="t('formCreate.PleaseEnter')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item field="poolId" :label="t('case.execute.pool')">
|
||||
<a-select v-model="batchExecuteForm.poolId" :placeholder="t('common.pleaseSelect')">
|
||||
<a-option v-for="item of resourcePoolList" :key="item.id" :value="item.id">
|
||||
{{ t(item.name) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button type="secondary" :disabled="batchExecuteLoading" @click="cancelBatchExecute">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="batchExecuteLoading" @click="handleBatchExecuteCase">
|
||||
{{ t('system.log.operateType.execute') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -241,20 +332,25 @@
|
|||
import {
|
||||
batchDeleteCase,
|
||||
batchEditCase,
|
||||
batchExecuteCase,
|
||||
deleteCase,
|
||||
dragSort,
|
||||
executeCase,
|
||||
getCasePage,
|
||||
getEnvList,
|
||||
updateCasePriority,
|
||||
updateCaseStatus,
|
||||
updateCaseStatus, updateDefinition,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { getCaseDefaultFields } from '@/api/modules/case-management/featureCase';
|
||||
import { getPoolList } from '@/api/modules/setting/resourcePool';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useTableStore from '@/hooks/useTableStore';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { ApiCaseDetail } from '@/models/apiTest/management';
|
||||
import { DragSortParams } from '@/models/common';
|
||||
import { ApiCaseDetail, Environment } from '@/models/apiTest/management';
|
||||
import { DragSortParams, TableQueryParams } from '@/models/common';
|
||||
import { ResourcePoolItem } from '@/models/setting/resourcePool';
|
||||
import { RequestDefinitionStatus } from '@/enums/apiEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
|
@ -412,6 +508,11 @@
|
|||
eventTag: 'edit',
|
||||
permission: ['PROJECT_API_DEFINITION_CASE:READ+UPDATE'],
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.execute',
|
||||
eventTag: 'execute',
|
||||
permission: ['PROJECT_API_DEFINITION_CASE:READ+EXECUTE'],
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
eventTag: 'delete',
|
||||
|
@ -441,6 +542,10 @@
|
|||
const lastReportStatusList = ['error', 'FakeError', 'success'];
|
||||
const lastReportStatusFilters = ref<string[]>([...lastReportStatusList]);
|
||||
|
||||
const environmentList = ref<Environment[]>();
|
||||
|
||||
const resourcePoolList = ref<ResourcePoolItem[]>();
|
||||
|
||||
const moduleIds = computed(() => {
|
||||
return props.activeModule === 'all' ? [] : [props.activeModule];
|
||||
});
|
||||
|
@ -468,11 +573,31 @@
|
|||
async function getCaseLevelFields() {
|
||||
const result = await getCaseDefaultFields(appStore.currentProjectId);
|
||||
caseLevelFields.value = result.customFields.find((item: any) => item.internal && item.fieldName === '用例等级');
|
||||
caseFilters.value = caseLevelFields.value?.options.map((item: any) => item.value);
|
||||
caseFilters.value = caseLevelFields.value?.options.map((item: any) => item.text);
|
||||
}
|
||||
|
||||
// 初始化环境列表
|
||||
async function initEnvList() {
|
||||
environmentList.value = await getEnvList(appStore.currentProjectId);
|
||||
}
|
||||
|
||||
// 初始化资源池列表
|
||||
async function initPoolList() {
|
||||
const searchPoolParams = ref<TableQueryParams>({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
keyword: '',
|
||||
deleted: false,
|
||||
apiTest: true,
|
||||
enable: false,
|
||||
});
|
||||
const result = await getPoolList(searchPoolParams.value);
|
||||
resourcePoolList.value = result.list;
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadCaseList();
|
||||
initPoolList();
|
||||
getCaseLevelFields();
|
||||
});
|
||||
|
||||
|
@ -611,13 +736,27 @@
|
|||
|
||||
// 用例编辑
|
||||
const showBatchEditModal = ref(false);
|
||||
// 用例执行
|
||||
const showBatchExecute = ref(false);
|
||||
const batchEditLoading = ref(false);
|
||||
const batchExecuteLoading = ref(false);
|
||||
const batchFormRef = ref<FormInstance>();
|
||||
const batchForm = ref({
|
||||
attr: '',
|
||||
value: '',
|
||||
values: [],
|
||||
});
|
||||
const batchExecuteFormRef = ref<FormInstance>();
|
||||
const batchExecuteForm = ref({
|
||||
defaultEnv: 'true',
|
||||
runMode: 'SERIAL',
|
||||
integratedReport: 'false',
|
||||
integratedReportName: '',
|
||||
stopOnFailure: false,
|
||||
poolId: '',
|
||||
grouped: false,
|
||||
environmentId: '',
|
||||
});
|
||||
const attrOptions = [
|
||||
{
|
||||
name: 'case.caseLevel',
|
||||
|
@ -660,6 +799,17 @@
|
|||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
async function onExecute(id: string) {
|
||||
try {
|
||||
await executeCase(id);
|
||||
Message.success(t('case.detail.execute.success'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelBatchEdit() {
|
||||
showBatchEditModal.value = false;
|
||||
batchFormRef.value?.resetFields();
|
||||
|
@ -695,6 +845,51 @@
|
|||
});
|
||||
}
|
||||
|
||||
function cancelBatchExecute() {
|
||||
showBatchExecute.value = false;
|
||||
batchFormRef.value?.resetFields();
|
||||
batchForm.value = {
|
||||
attr: '',
|
||||
value: '',
|
||||
values: [],
|
||||
};
|
||||
}
|
||||
function handleBatchExecuteCase() {
|
||||
batchExecuteFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
batchExecuteLoading.value = true;
|
||||
await batchExecuteCase({
|
||||
selectIds: batchParams.value?.selectedIds || [],
|
||||
selectAll: !!batchParams.value?.selectAll,
|
||||
excludeIds: batchParams.value?.excludeIds || [],
|
||||
...batchConditionParams.value,
|
||||
runModeConfig: {
|
||||
runMode: batchExecuteForm.value.runMode,
|
||||
integratedReport: batchExecuteForm.value.integratedReport === 'true',
|
||||
integratedReportName: batchExecuteForm.value.integratedReportName,
|
||||
stopOnFailure: batchExecuteForm.value.stopOnFailure,
|
||||
poolId: batchExecuteForm.value.poolId,
|
||||
grouped: batchExecuteForm.value.grouped,
|
||||
environmentId: batchExecuteForm.value.environmentId,
|
||||
},
|
||||
apiDefinitionId: '',
|
||||
versionId: '',
|
||||
refId: '',
|
||||
});
|
||||
Message.success(t('common.updateSuccess'));
|
||||
cancelBatchExecute();
|
||||
loadCaseListAndResetSelector();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
batchExecuteLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 处理表格选中后批量操作
|
||||
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||
tableSelected.value = params?.selectedIds || [];
|
||||
|
@ -706,6 +901,10 @@
|
|||
case 'edit':
|
||||
showBatchEditModal.value = true;
|
||||
break;
|
||||
case 'execute':
|
||||
showBatchExecute.value = true;
|
||||
initEnvList();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -727,4 +926,14 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
:deep(.arco-radio-group) {
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.ms-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-alert v-if="isShowTip" class="mb-6" type="warning">
|
||||
<div class="flex items-start justify-between">
|
||||
<span class="w-[80%]">{{ t('case.detail.changeHistoryTip') }}</span>
|
||||
<span class="cursor-pointer text-[var(--color-text-2)]" @click="noRemindHandler">{{
|
||||
t('case.detail.noReminders')
|
||||
}}</span>
|
||||
</div>
|
||||
</a-alert>
|
||||
<ms-base-table v-bind="propsRes" v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<a-button type="text" class="px-0">{{ record.name }}</a-button>
|
||||
</template>
|
||||
<template #type="{ record }">
|
||||
{{ t(typeOptions.find((e) => e.value === record.type)?.label || '') }}
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<!-- TODO 这一版本不上 -->
|
||||
<!-- <MsRemoveButton
|
||||
position="br"
|
||||
:title="
|
||||
t('caseManagement.featureCase.confirmRecoverChangeHistoryTitle', { name: characterLimit(record.name) })
|
||||
"
|
||||
:sub-title-tip="
|
||||
t('caseManagement.featureCase.recoverChangeHistoryTip', { name: characterLimit(record.name) })
|
||||
"
|
||||
:loading="recoverLoading"
|
||||
@ok="recoverHandler(record)"
|
||||
/> -->
|
||||
<MsButton @click="saveAsHandler(record)">{{ t('caseManagement.featureCase.saveAsVersion') }}</MsButton>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
<a-modal
|
||||
v-model:visible="showModal"
|
||||
title-align="start"
|
||||
class="ms-modal-form ms-modal-medium"
|
||||
:ok-text="t('organization.member.Confirm')"
|
||||
:cancel-text="t('organization.member.Cancel')"
|
||||
unmount-on-close
|
||||
@close="handleCancel"
|
||||
>
|
||||
<template #title> {{ t('caseManagement.featureCase.saveAsVersion') }} </template>
|
||||
<div class="form">
|
||||
<a-form ref="versionFormRef" :model="form" size="large" layout="vertical">
|
||||
<a-form-item
|
||||
field="versionId"
|
||||
:label="t('caseManagement.featureCase.tableColumnVersion')"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('caseManagement.featureCase.saveAsVersionPlaceholder') }]"
|
||||
>
|
||||
<a-select
|
||||
v-model="form.versionId"
|
||||
multiple
|
||||
allow-clear
|
||||
:placeholder="t('organization.member.selectUserScope')"
|
||||
>
|
||||
<a-option v-for="item of versionOptions" :key="item.id" :value="item.id">{{ item.name }}</a-option>
|
||||
</a-select>
|
||||
<MsFormItemSub :text="t('caseManagement.featureCase.saveAsVersionTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button class="ml-[12px]" type="primary" :loading="confirmLoading" @click="handleOK">
|
||||
{{ t('common.save') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
|
||||
|
||||
import { getApiCaseChangeHistory } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useVisit from '@/hooks/useVisit';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const visitedKey = 'notRemindChangeHistoryTip';
|
||||
const { addVisited } = useVisit(visitedKey);
|
||||
const { getIsVisited } = useVisit(visitedKey);
|
||||
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
}>();
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'case.detail.changeNumber',
|
||||
dataIndex: 'id',
|
||||
showTooltip: true,
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: 'case.detail.changeType',
|
||||
slotName: 'type',
|
||||
dataIndex: 'type',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'case.detail.operator',
|
||||
dataIndex: 'createUserName',
|
||||
slotName: 'createUserName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'case.detail.tableColumnUpdateTime',
|
||||
slotName: 'createTime',
|
||||
dataIndex: 'createTime',
|
||||
width: 200,
|
||||
},
|
||||
// {
|
||||
// title: 'caseManagement.featureCase.tableColumnActions',
|
||||
// slotName: 'operation',
|
||||
// dataIndex: 'operation',
|
||||
// fixed: 'right',
|
||||
// width: 140,
|
||||
// showInTable: true,
|
||||
// showDrag: false,
|
||||
// },
|
||||
];
|
||||
|
||||
const typeOptions = [
|
||||
{
|
||||
label: 'system.log.operateType.add',
|
||||
value: 'ADD',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.update',
|
||||
value: 'UPDATE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.import',
|
||||
value: 'IMPORT',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.delete',
|
||||
value: 'DELETE',
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getApiCaseChangeHistory, {
|
||||
columns,
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_CHANGE_HISTORY,
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
heightUsed: 340,
|
||||
enableDrag: false,
|
||||
});
|
||||
|
||||
const form = ref({
|
||||
versionId: '',
|
||||
});
|
||||
|
||||
const versionOptions = ref([
|
||||
{
|
||||
id: '1001',
|
||||
name: 'v1.0',
|
||||
},
|
||||
{
|
||||
id: '1002',
|
||||
name: 'v1.1',
|
||||
},
|
||||
]);
|
||||
|
||||
const recoverLoading = ref<boolean>(false);
|
||||
// 恢复
|
||||
async function recoverHandler(record: any) {
|
||||
recoverLoading.value = true;
|
||||
try {
|
||||
Message.success(t('caseManagement.featureCase.recoveredSuccessfully'));
|
||||
resetSelector();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
recoverLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const showModal = ref<boolean>(false);
|
||||
|
||||
// 另存为版本
|
||||
function saveAsHandler(record: any) {
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
showModal.value = false;
|
||||
};
|
||||
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
const versionFormRef = ref<FormInstance | null>(null);
|
||||
|
||||
const handleOK = () => {
|
||||
versionFormRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
confirmLoading.value = true;
|
||||
Message.success(t('common.saveSuccess'));
|
||||
handleCancel();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isShowTip = ref<boolean>(true);
|
||||
|
||||
const noRemindHandler = () => {
|
||||
isShowTip.value = false;
|
||||
addVisited();
|
||||
};
|
||||
|
||||
const doCheckIsTip = () => {
|
||||
isShowTip.value = !getIsVisited();
|
||||
};
|
||||
|
||||
async function initData() {
|
||||
setLoadListParams({
|
||||
projectId: appStore.currentProjectId,
|
||||
sourceId: props.caseId,
|
||||
types: ['IMPORT', 'ADD', 'UPDATE', 'DELETE'],
|
||||
modules: ['API_TEST_MANAGEMENT_CASE'],
|
||||
});
|
||||
await loadList();
|
||||
}
|
||||
|
||||
// watch(
|
||||
// () => activeTab.value,
|
||||
// (val) => {
|
||||
// if (val === 'changeHistory') {
|
||||
// doCheckIsTip();
|
||||
// initData();
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
onMounted(() => {
|
||||
doCheckIsTip();
|
||||
initData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -153,6 +153,19 @@ export default {
|
|||
'apiTestManagement.belongOrg': 'Organization',
|
||||
'apiTestManagement.belongProject': 'Project',
|
||||
'apiTestManagement.quoteSearchPlaceholder': 'Enter ID or name to search',
|
||||
'case.execute.selectEnv': 'Environmental choice',
|
||||
'case.execute.defaultEnv': 'Default environment',
|
||||
'case.execute.newEnv': 'New environment',
|
||||
'case.execute.defaultEnvTip': 'The environment where the use case is saved',
|
||||
'case.execute.model': 'Model',
|
||||
'case.execute.serial': 'Serial',
|
||||
'case.execute.parallel': 'Parallel',
|
||||
'case.execute.StopOnFailure': 'Stop on failure',
|
||||
'case.execute.reportSetting': 'Report configuration',
|
||||
'case.execute.independentReporting': 'Independent reporting',
|
||||
'case.execute.CollectionReport': 'Collection report',
|
||||
'case.execute.reportName': 'Report name',
|
||||
'case.execute.pool': 'Resource pool operation',
|
||||
'case.allCase': 'All Case',
|
||||
'case.caseName': 'Case Name',
|
||||
'case.caseLevel': 'Case Level',
|
||||
|
@ -177,4 +190,12 @@ export default {
|
|||
'case.batchRecoverCaseTip': 'Are you sure you want to recover {count} selected cases?',
|
||||
'case.recycle.recoverCaseTip': 'When restoring the case, the deleted API will be restored simultaneously.',
|
||||
'case.recycle.confirmRecovery': 'Confirm recovery',
|
||||
'case.detail.changeHistoryTip': `View and compare historical changes. According to the administrator's setting rules, historical changes will be automatically deleted`,
|
||||
'case.detail.noReminders': 'No longer remind',
|
||||
'case.detail.changeNumber': 'Change sequence',
|
||||
'case.detail.changeType': 'type',
|
||||
'case.detail.operator': 'operator',
|
||||
'case.detail.tableColumnUpdateTime': 'UpdateTime',
|
||||
'case.detail.execute.success': 'Execute success',
|
||||
|
||||
};
|
||||
|
|
|
@ -147,6 +147,19 @@ export default {
|
|||
'apiTestManagement.quoteSearchPlaceholder': '输入 ID 或名称搜索',
|
||||
'apiTestManagement.click': '点击',
|
||||
'apiTestManagement.getResponse': '获取响应内容',
|
||||
'case.execute.selectEnv': '环境选择',
|
||||
'case.execute.defaultEnv': '默认环境',
|
||||
'case.execute.newEnv': '新环境',
|
||||
'case.execute.defaultEnvTip': '用例保存的环境',
|
||||
'case.execute.model': '模式',
|
||||
'case.execute.serial': '串行',
|
||||
'case.execute.parallel': '并行',
|
||||
'case.execute.StopOnFailure': '失败停止',
|
||||
'case.execute.reportSetting': '报告配置',
|
||||
'case.execute.independentReporting': '独立报告',
|
||||
'case.execute.CollectionReport': '集合报告',
|
||||
'case.execute.reportName': '报告名称',
|
||||
'case.execute.pool': '资源池运行',
|
||||
'case.allCase': '全部CASE',
|
||||
'case.caseName': '用例名称',
|
||||
'case.caseNameRequired': '用例名称不能为空',
|
||||
|
@ -169,4 +182,12 @@ export default {
|
|||
'case.batchRecoverCaseTip': '确认恢复已选中的 {count} 个用例吗?',
|
||||
'case.recycle.recoverCaseTip': '恢复case时会同步恢复被删除的api',
|
||||
'case.recycle.confirmRecovery': '确认恢复',
|
||||
'case.detail.changeHistoryTip': '查看、对比历史修改,根据管理员设置规则,变更历史数据将自动删除',
|
||||
'case.detail.noReminders': '不再提醒',
|
||||
'case.detail.changeNumber': '变更序号',
|
||||
'case.detail.changeType': '变更类型',
|
||||
'case.detail.operator': '操作人',
|
||||
'case.detail.tableColumnUpdateTime': '更新时间',
|
||||
'case.detail.execute.success': '执行成功',
|
||||
|
||||
};
|
||||
|
|
|
@ -80,14 +80,12 @@
|
|||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
|
||||
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
|
||||
|
||||
import { getChangeHistoryList } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useVisit from '@/hooks/useVisit';
|
||||
import { useAppStore } from '@/store';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
|
@ -95,7 +93,6 @@
|
|||
|
||||
const appStore = useAppStore();
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
// const activeTab = computed(() => featureCaseStore.activeTab);
|
||||
const visitedKey = 'notRemindChangeHistoryTip';
|
||||
const { addVisited } = useVisit(visitedKey);
|
||||
const { getIsVisited } = useVisit(visitedKey);
|
||||
|
|
Loading…
Reference in New Issue