feat(用例管理): 新增用例执行功能

This commit is contained in:
guoyuqi 2024-03-15 17:52:24 +08:00 committed by Craftsman
parent 529ba4368c
commit 78d8e0d4d6
13 changed files with 721 additions and 29 deletions

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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

View File

@ -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 });
}

View File

@ -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'; // 获取用例的依赖关系
/**
*

View File

@ -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;

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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',
};

View File

@ -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': '执行成功',
};

View File

@ -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);