feat(测试计划): 测试计划不再强制关联用例时选择环境

--story=1012486 --user=宋天阳
【货车之家】接口场景包含跨项目步骤时取消跨项目环境必选&测试计划关联接口、UI测试时取消运行环境必选
https://www.tapd.cn/55049933/s/1401625
This commit is contained in:
song-tianyang 2023-08-02 15:00:52 +08:00 committed by 建国
parent eed243d40c
commit dd92feeba2
28 changed files with 12863 additions and 12428 deletions

View File

@ -287,8 +287,16 @@ public class ApiExecuteService {
TestPlanApiCaseExample example = new TestPlanApiCaseExample(); TestPlanApiCaseExample example = new TestPlanApiCaseExample();
example.createCriteria().andTestPlanIdEqualTo(request.getTestPlanId()).andApiCaseIdEqualTo(request.getCaseId()); example.createCriteria().andTestPlanIdEqualTo(request.getTestPlanId()).andApiCaseIdEqualTo(request.getCaseId());
List<TestPlanApiCase> list = testPlanApiCaseMapper.selectByExample(example); List<TestPlanApiCase> list = testPlanApiCaseMapper.selectByExample(example);
request.setEnvironmentId(list.get(0).getEnvironmentId()); if (CollectionUtils.isNotEmpty(list)) {
element.setName(list.get(0).getId()); request.setEnvironmentId(list.get(0).getEnvironmentId());
element.setName(list.get(0).getId());
} else {
TestPlanApiCase apiCase = testPlanApiCaseMapper.selectByPrimaryKey(request.getCaseId());
if (apiCase != null) {
request.setEnvironmentId(apiCase.getEnvironmentId());
element.setName(request.getCaseId());
}
}
} else { } else {
element.setName(request.getCaseId()); element.setName(request.getCaseId());
} }

View File

@ -11,10 +11,7 @@ import io.metersphere.base.mapper.ext.BaseApiExecutionQueueMapper;
import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper; import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper; import io.metersphere.base.mapper.ext.ExtApiScenarioReportMapper;
import io.metersphere.base.mapper.plan.ext.ExtTestPlanApiCaseMapper; import io.metersphere.base.mapper.plan.ext.ExtTestPlanApiCaseMapper;
import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.constants.*;
import io.metersphere.commons.constants.CommonConstants;
import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.commons.constants.TestPlanReportStatus;
import io.metersphere.commons.enums.ApiReportStatus; import io.metersphere.commons.enums.ApiReportStatus;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.JSON; import io.metersphere.commons.utils.JSON;
@ -89,9 +86,11 @@ public class ApiExecutionQueueService {
Map<String, String> detailMap = new HashMap<>(); Map<String, String> detailMap = new HashMap<>();
List<ApiExecutionQueueDetail> queueDetails = new LinkedList<>(); List<ApiExecutionQueueDetail> queueDetails = new LinkedList<>();
// 初始化API/用例队列 // 初始化API/用例队列
String redisLockType = TestPlanExecuteCaseType.SCENARIO.name();
if (StringUtils.equalsAnyIgnoreCase(type, ApiRunMode.DEFINITION.name(), ApiRunMode.API_PLAN.name())) { if (StringUtils.equalsAnyIgnoreCase(type, ApiRunMode.DEFINITION.name(), ApiRunMode.API_PLAN.name())) {
Map<String, ApiDefinitionExecResult> runMap = (Map<String, ApiDefinitionExecResult>) runObj; Map<String, ApiDefinitionExecResult> runMap = (Map<String, ApiDefinitionExecResult>) runObj;
initApi(runMap, resQueue, config, detailMap, queueDetails); initApi(runMap, resQueue, config, detailMap, queueDetails);
redisLockType = TestPlanExecuteCaseType.API_CASE.name();
} }
// 初始化场景 // 初始化场景
else { else {
@ -101,11 +100,16 @@ public class ApiExecutionQueueService {
if (CollectionUtils.isNotEmpty(queueDetails)) { if (CollectionUtils.isNotEmpty(queueDetails)) {
extApiExecutionQueueMapper.sqlInsert(queueDetails); extApiExecutionQueueMapper.sqlInsert(queueDetails);
} }
//redis移除key 执行测试计划时会添加key)
redisTemplateService.unlock(reportId, redisLockType, reportId);
resQueue.setDetailMap(detailMap); resQueue.setDetailMap(detailMap);
LoggerUtil.info("报告【" + type + "】生成执行链结束", reportId); LoggerUtil.info("报告【" + type + "】生成执行链结束", reportId);
return resQueue; return resQueue;
} }
@Resource
private RedisTemplateService redisTemplateService;
private void initScenario(Map<String, RunModeDataDTO> runMap, DBTestQueue resQueue, RunModeConfigDTO config, Map<String, String> detailMap, List<ApiExecutionQueueDetail> queueDetails) { private void initScenario(Map<String, RunModeDataDTO> runMap, DBTestQueue resQueue, RunModeConfigDTO config, Map<String, String> detailMap, List<ApiExecutionQueueDetail> queueDetails) {
final int[] sort = {0}; final int[] sort = {0};
runMap.forEach((k, v) -> { runMap.forEach((k, v) -> {

View File

@ -846,9 +846,8 @@ public class MockConfigService {
if (project != null) { if (project != null) {
RequestMockParams requestMockParams = MockApiUtils.genRequestMockParamsFromHttpRequest(request, true); RequestMockParams requestMockParams = MockApiUtils.genRequestMockParamsFromHttpRequest(request, true);
String urlSuffix = this.getUrlSuffix(project.getSystemId(), request); String urlSuffix = this.getUrlSuffix(project.getSystemId(), request);
LogUtil.info("Mock urlSuffix:{}", urlSuffix); LogUtil.info("Mock [" + url + "] Header:{}", requestHeaderMap);
LogUtil.info("Mock requestHeaderMap:{}", requestHeaderMap); LogUtil.info("Mock [" + url + "] request:{}", JSON.toJSONString(requestMockParams));
LogUtil.info("Mock requestMockParams:{}", JSON.toJSONString(requestMockParams));
List<ApiDefinitionWithBLOBs> qualifiedApiList = apiDefinitionService.preparedUrl(project.getId(), method, urlSuffix, requestHeaderMap.get(MockApiHeaders.MOCK_API_RESOURCE_ID)); List<ApiDefinitionWithBLOBs> qualifiedApiList = apiDefinitionService.preparedUrl(project.getId(), method, urlSuffix, requestHeaderMap.get(MockApiHeaders.MOCK_API_RESOURCE_ID));
for (ApiDefinitionWithBLOBs api : qualifiedApiList) { for (ApiDefinitionWithBLOBs api : qualifiedApiList) {
if (StringUtils.isEmpty(returnStr)) { if (StringUtils.isEmpty(returnStr)) {
@ -872,6 +871,7 @@ public class MockConfigService {
response.setStatus(404); response.setStatus(404);
returnStr = Translator.get("mock_warning"); returnStr = Translator.get("mock_warning");
} }
LogUtil.info("Mock [" + url + "] response:{}", returnStr);
return returnStr; return returnStr;
} }
@ -883,9 +883,8 @@ public class MockConfigService {
RequestMockParams requestMockParams = MockApiUtils.genRequestMockParamsFromHttpRequest(request, false); RequestMockParams requestMockParams = MockApiUtils.genRequestMockParamsFromHttpRequest(request, false);
String urlSuffix = this.getUrlSuffix(project.getSystemId(), request); String urlSuffix = this.getUrlSuffix(project.getSystemId(), request);
LogUtil.info("Mock urlSuffix:{}", urlSuffix); LogUtil.info("Mock [" + url + "] Header:{}", requestHeaderMap);
LogUtil.info("Mock requestHeaderMap:{}", requestHeaderMap); LogUtil.info("Mock [" + url + "] request:{}", JSON.toJSONString(requestMockParams));
LogUtil.info("Mock requestMockParams:{}", JSON.toJSONString(requestMockParams));
List<ApiDefinitionWithBLOBs> qualifiedApiList = apiDefinitionService.preparedUrl(project.getId(), method, urlSuffix, requestHeaderMap.get(MockApiHeaders.MOCK_API_RESOURCE_ID)); List<ApiDefinitionWithBLOBs> qualifiedApiList = apiDefinitionService.preparedUrl(project.getId(), method, urlSuffix, requestHeaderMap.get(MockApiHeaders.MOCK_API_RESOURCE_ID));
/* /*
GET/DELETE 这种通过url穿参数的接口在接口路径相同的情况下可能会出现这样的情况 GET/DELETE 这种通过url穿参数的接口在接口路径相同的情况下可能会出现这样的情况
@ -919,6 +918,7 @@ public class MockConfigService {
response.setStatus(404); response.setStatus(404);
returnStr = Translator.get("mock_warning"); returnStr = Translator.get("mock_warning");
} }
LogUtil.info("Mock [" + url + "] response:{}", returnStr);
return returnStr; return returnStr;
} }

View File

@ -99,4 +99,10 @@ public class RedisTemplateService {
} }
return false; return false;
} }
public void unlock(String testPlanReportId, String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
this.redisTemplate.execute(redisScript, Collections.singletonList(StringUtils.join(testPlanReportId, key)), new Object[]{value});
}
} }

View File

@ -1,9 +1,9 @@
<template> <template>
<test-case-relevance-base <test-case-relevance-base
:is-across-space="isAcrossSpace" :is-across-space="isAcrossSpace"
@setProject="setProject" @setProject="setProject"
:dialog-title="$t('api_test.definition.api_import')" :dialog-title="$t('api_test.definition.api_import')"
ref="baseRelevance"> ref="baseRelevance">
<template v-slot:aside> <template v-slot:aside>
<ms-api-module <ms-api-module
class="node-tree" class="node-tree"
@ -18,60 +18,60 @@
</template> </template>
<scenario-relevance-api-list <scenario-relevance-api-list
v-if="isApiListEnable" v-if="isApiListEnable"
:project-id="projectId" :project-id="projectId"
:version-filters="versionFilters" :version-filters="versionFilters"
:current-version="currentVersion" :current-version="currentVersion"
:current-protocol="currentProtocol" :current-protocol="currentProtocol"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable" :is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange" @isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts" @selectCountChange="setSelectCounts"
ref="apiList"> ref="apiList">
<template v-slot:version> <template v-slot:version>
<mx-version-select <mx-version-select
v-xpack v-xpack
:project-id="projectId" :project-id="projectId"
:default-version="currentVersion" :default-version="currentVersion"
@changeVersion="currentVersionChange" /> @changeVersion="currentVersionChange"/>
</template> </template>
</scenario-relevance-api-list> </scenario-relevance-api-list>
<scenario-relevance-case-list <scenario-relevance-case-list
v-if="!isApiListEnable" v-if="!isApiListEnable"
:project-id="projectId" :project-id="projectId"
:version-filters="versionFilters" :version-filters="versionFilters"
:current-version="currentVersion" :current-version="currentVersion"
:current-protocol="currentProtocol" :current-protocol="currentProtocol"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable" :is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange" @isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts" @selectCountChange="setSelectCounts"
ref="apiCaseList"> ref="apiCaseList">
<template v-slot:version> <template v-slot:version>
<mx-version-select <mx-version-select
v-xpack v-xpack
:project-id="projectId" :project-id="projectId"
:default-version="currentVersion" :default-version="currentVersion"
@changeVersion="currentVersionChange" /> @changeVersion="currentVersionChange"/>
</template> </template>
</scenario-relevance-case-list> </scenario-relevance-case-list>
<template v-slot:headerBtn> <template v-slot:headerBtn>
<!-- 显示数量 --> <!-- 显示数量 -->
<table-select-count-bar :count="selectCounts" style="float: left; margin: 5px" /> <table-select-count-bar :count="selectCounts" style="float: left; margin: 5px"/>
<el-button size="mini" icon="el-icon-refresh" @click="refreshData" /> <el-button size="mini" icon="el-icon-refresh" @click="refreshData"/>
<el-button type="primary" @click="copy" :loading="buttonIsWorking" @keydown.enter.native.prevent size="mini"> <el-button type="primary" @click="copy" :loading="buttonIsWorking" @keydown.enter.native.prevent size="mini">
{{ $t('commons.copy') }} {{ $t('commons.copy') }}
</el-button> </el-button>
<el-button <el-button
v-if="!isApiListEnable" v-if="!isApiListEnable"
type="primary" type="primary"
:loading="buttonIsWorking" :loading="buttonIsWorking"
@click="reference" @click="reference"
size="mini" size="mini"
@keydown.enter.native.prevent> @keydown.enter.native.prevent>
{{ $t('api_test.scenario.reference') }} {{ $t('api_test.scenario.reference') }}
</el-button> </el-button>
</template> </template>
@ -79,9 +79,9 @@
</template> </template>
<script> <script>
import { getApiCaseWithBLOBs } from '@/api/api-test-case'; import {getApiCaseWithBLOBs} from '@/api/api-test-case';
import { apiListBatch } from '@/api/definition'; import {apiListBatch} from '@/api/definition';
import { getProjectVersions } from '@/api/xpack'; import {getProjectVersions} from '@/api/xpack';
import ScenarioRelevanceCaseList from './RelevanceCaseList'; import ScenarioRelevanceCaseList from './RelevanceCaseList';
import MsApiModule from '../../../definition/components/module/ApiModule'; import MsApiModule from '../../../definition/components/module/ApiModule';
import MsContainer from 'metersphere-frontend/src/components/MsContainer'; import MsContainer from 'metersphere-frontend/src/components/MsContainer';
@ -90,9 +90,9 @@ import MsMainContainer from 'metersphere-frontend/src/components/MsMainContainer
import ScenarioRelevanceApiList from './RelevanceApiList'; import ScenarioRelevanceApiList from './RelevanceApiList';
import RelevanceDialog from '@/business/commons/RelevanceDialog'; import RelevanceDialog from '@/business/commons/RelevanceDialog';
import TestCaseRelevanceBase from '@/business/commons/TestCaseRelevanceBase'; import TestCaseRelevanceBase from '@/business/commons/TestCaseRelevanceBase';
import { hasLicense } from 'metersphere-frontend/src/utils/permission'; import {hasLicense} from 'metersphere-frontend/src/utils/permission';
import TableSelectCountBar from '@/business/automation/scenario/api/TableSelectCountBar'; import TableSelectCountBar from '@/business/automation/scenario/api/TableSelectCountBar';
import { operationConfirm } from 'metersphere-frontend/src/utils'; import {operationConfirm} from 'metersphere-frontend/src/utils';
export default { export default {
name: 'ApiRelevance', name: 'ApiRelevance',
@ -166,15 +166,15 @@ export default {
} else { } else {
if (params.condition.selectAll) { if (params.condition.selectAll) {
operationConfirm( operationConfirm(
this, this,
this.$t('automation.scenario_step_ref_message') + '', this.$t('automation.scenario_step_ref_message') + '',
() => { () => {
this.$emit('save', apis, 'API', reference); this.$emit('save', apis, 'API', reference);
this.$refs.baseRelevance.close(); this.$refs.baseRelevance.close();
}, },
() => { () => {
this.buttonIsWorking = false; this.buttonIsWorking = false;
} }
); );
} else { } else {
this.$emit('save', apis, 'API', reference); this.$emit('save', apis, 'API', reference);
@ -192,15 +192,15 @@ export default {
} else { } else {
if (this.$refs.apiCaseList.condition.selectAll) { if (this.$refs.apiCaseList.condition.selectAll) {
operationConfirm( operationConfirm(
this, this,
this.$t('automation.scenario_step_ref_message') + '', this.$t('automation.scenario_step_ref_message') + '',
() => { () => {
this.$emit('save', apiCases, 'CASE', reference); this.$emit('save', apiCases, 'CASE', reference);
this.$refs.baseRelevance.close(); this.$refs.baseRelevance.close();
}, },
() => { () => {
this.buttonIsWorking = false; this.buttonIsWorking = false;
} }
); );
} else { } else {
this.$emit('save', apiCases, 'CASE', reference); this.$emit('save', apiCases, 'CASE', reference);
@ -258,13 +258,13 @@ export default {
getProjectVersions(this.projectId).then((response) => { getProjectVersions(this.projectId).then((response) => {
if (currentVersion) { if (currentVersion) {
this.versionFilters = response.data this.versionFilters = response.data
.filter((u) => u.id === currentVersion) .filter((u) => u.id === currentVersion)
.map((u) => { .map((u) => {
return { text: u.name, value: u.id }; return {text: u.name, value: u.id};
}); });
} else { } else {
this.versionFilters = response.data.map((u) => { this.versionFilters = response.data.map((u) => {
return { text: u.name, value: u.id }; return {text: u.name, value: u.id};
}); });
} }
}); });

View File

@ -1,58 +1,58 @@
<template> <template>
<test-case-relevance-base <test-case-relevance-base
@setProject="setProject" @setProject="setProject"
@save="save" @save="save"
:plan-id="planId" :plan-id="planId"
:dialog-title="dialogTitle" :dialog-title="dialogTitle"
ref="baseRelevance"> ref="baseRelevance">
<template v-slot:aside> <template v-slot:aside>
<ms-api-module <ms-api-module
:options="options" :options="options"
:relevance-project-id="projectId" :relevance-project-id="projectId"
@nodeSelectEvent="nodeChange" @nodeSelectEvent="nodeChange"
@protocolChange="handleProtocolChange" @protocolChange="handleProtocolChange"
@refreshTable="refresh" @refreshTable="refresh"
@setModuleOptions="setModuleOptions" @setModuleOptions="setModuleOptions"
:is-read-only="true" :is-read-only="true"
:is-relevance="true" :is-relevance="true"
ref="nodeTree" /> ref="nodeTree"/>
</template> </template>
<relevance-api-list <relevance-api-list
v-if="isApiListEnable" v-if="isApiListEnable"
:current-protocol="currentProtocol" :current-protocol="currentProtocol"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable" :is-api-list-enable="isApiListEnable"
:project-id="projectId" :project-id="projectId"
:is-test-plan="isTestPlan" :is-test-plan="isTestPlan"
:is-script="isScript" :is-script="isScript"
:plan-id="planId" :plan-id="planId"
@isApiListEnableChange="isApiListEnableChange" @isApiListEnableChange="isApiListEnableChange"
ref="apiList" /> ref="apiList"/>
<relevance-case-list <relevance-case-list
v-if="!isApiListEnable" v-if="!isApiListEnable"
:current-protocol="currentProtocol" :current-protocol="currentProtocol"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable" :is-api-list-enable="isApiListEnable"
:project-id="projectId" :project-id="projectId"
:is-test-plan="isTestPlan" :is-test-plan="isTestPlan"
:is-script="isScript" :is-script="isScript"
:plan-id="planId" :plan-id="planId"
@isApiListEnableChange="isApiListEnableChange" @isApiListEnableChange="isApiListEnableChange"
ref="apiCaseList" /> ref="apiCaseList"/>
</test-case-relevance-base> </test-case-relevance-base>
</template> </template>
<script> <script>
import { getApiCaseWithBLOBs } from '@/api/api-test-case'; import {getApiCaseWithBLOBs} from '@/api/api-test-case';
import { apiListBatch } from '@/api/definition'; import {apiListBatch} from '@/api/definition';
import RelevanceCaseList from '@/business/automation/scenario/api/RelevanceCaseList'; import RelevanceCaseList from '@/business/automation/scenario/api/RelevanceCaseList';
import RelevanceApiList from '@/business/automation/scenario/api/RelevanceApiList'; import RelevanceApiList from '@/business/automation/scenario/api/RelevanceApiList';
import MsApiModule from '@/business/definition/components/module/ApiModule'; import MsApiModule from '@/business/definition/components/module/ApiModule';
import { getEnvironmentById } from 'metersphere-frontend/src/api/environment'; import {getEnvironmentById} from 'metersphere-frontend/src/api/environment';
import TestCaseRelevanceBase from '@/business/commons/TestCaseRelevanceBase'; import TestCaseRelevanceBase from '@/business/commons/TestCaseRelevanceBase';
import { parseEnvironment } from '@/business/environment/model/EnvironmentModel'; import {parseEnvironment} from '@/business/environment/model/EnvironmentModel';
export default { export default {
name: 'ApiFuncRelevance', name: 'ApiFuncRelevance',
@ -75,7 +75,7 @@ export default {
condition: {}, condition: {},
currentRow: {}, currentRow: {},
projectId: '', projectId: '',
options: [{ value: 'HTTP', name: 'HTTP' }], options: [{value: 'HTTP', name: 'HTTP'}],
}; };
}, },
props: { props: {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -51,153 +51,169 @@ export const GROUP_WORKSPACE = 'WORKSPACE';
export const GROUP_PROJECT = 'PROJECT'; export const GROUP_PROJECT = 'PROJECT';
export const GROUP_TYPE = { export const GROUP_TYPE = {
SYSTEM: 'SYSTEM', SYSTEM: 'SYSTEM',
WORKSPACE: 'WORKSPACE', WORKSPACE: 'WORKSPACE',
PROJECT: 'PROJECT' PROJECT: 'PROJECT'
} }
export const SCHEDULE_TYPE = { export const SCHEDULE_TYPE = {
API_TEST: 'API_TEST', API_TEST: 'API_TEST',
PERFORMANCE_TEST: 'PERFORMANCE_TEST' PERFORMANCE_TEST: 'PERFORMANCE_TEST'
} }
export const REQUEST_HEADERS = [ export const REQUEST_HEADERS = [
{value: 'Accept'}, {value: 'Accept'},
{value: 'Accept-Charset'}, {value: 'Accept-Charset'},
{value: 'Accept-Language'}, {value: 'Accept-Language'},
{value: 'Accept-Datetime'}, {value: 'Accept-Datetime'},
{value: 'Authorization'}, {value: 'Authorization'},
{value: 'Cache-Control'}, {value: 'Cache-Control'},
{value: 'Connection'}, {value: 'Connection'},
{value: 'Cookie'}, {value: 'Cookie'},
{value: 'Content-Length'}, {value: 'Content-Length'},
{value: 'Content-MD5'}, {value: 'Content-MD5'},
{value: 'Content-Type'}, {value: 'Content-Type'},
{value: 'Date'}, {value: 'Date'},
{value: 'Expect'}, {value: 'Expect'},
{value: 'From'}, {value: 'From'},
{value: 'Host'}, {value: 'Host'},
{value: 'If-Match'}, {value: 'If-Match'},
{value: 'If-Modified-Since'}, {value: 'If-Modified-Since'},
{value: 'If-None-Match'}, {value: 'If-None-Match'},
{value: 'If-Range'}, {value: 'If-Range'},
{value: 'If-Unmodified-Since'}, {value: 'If-Unmodified-Since'},
{value: 'Max-Forwards'}, {value: 'Max-Forwards'},
{value: 'Origin'}, {value: 'Origin'},
{value: 'Pragma'}, {value: 'Pragma'},
{value: 'Proxy-Authorization'}, {value: 'Proxy-Authorization'},
{value: 'Range'}, {value: 'Range'},
{value: 'Referer'}, {value: 'Referer'},
{value: 'TE'}, {value: 'TE'},
{value: 'User-Agent'}, {value: 'User-Agent'},
{value: 'Upgrade'}, {value: 'Upgrade'},
{value: 'Via'}, {value: 'Via'},
{value: 'Warning'} {value: 'Warning'}
] ]
export const MOCKJS_FUNC = [ export const MOCKJS_FUNC = [
{name: '@boolean', des: i18n.t('api_test.request.boolean'), ex: true}, {name: '@boolean', des: i18n.t('api_test.request.boolean'), ex: true},
{name: '@natural', des: i18n.t('api_test.request.natural'), ex: 72834}, {name: '@natural', des: i18n.t('api_test.request.natural'), ex: 72834},
{name: '@integer', des: i18n.t('api_test.request.integer'), ex: 79750}, {name: '@integer', des: i18n.t('api_test.request.integer'), ex: 79750},
{name: '@float', des: i18n.t('api_test.request.float'), ex: 24.2}, {name: '@float', des: i18n.t('api_test.request.float'), ex: 24.2},
{name: '@character', des: i18n.t('api_test.request.character'), ex: "k"}, {name: '@character', des: i18n.t('api_test.request.character'), ex: "k"},
{name: '@string', des: i18n.t('api_test.request.string'), ex: "hello"}, {name: '@string', des: i18n.t('api_test.request.string'), ex: "hello"},
{name: '@range', des: i18n.t('api_test.request.range'), ex: "org.mozilla.javascript.NavicatArray@1739f809"}, {name: '@range', des: i18n.t('api_test.request.range'), ex: "org.mozilla.javascript.NavicatArray@1739f809"},
{name: '@date', des: i18n.t('api_test.request.date'), ex: "1973-01-08"}, {name: '@date', des: i18n.t('api_test.request.date'), ex: "1973-01-08"},
{name: '@time', des: i18n.t('api_test.request.time'), ex: "06:15:27"}, {name: '@time', des: i18n.t('api_test.request.time'), ex: "06:15:27"},
{name: '@datetime', des: i18n.t('api_test.request.datetime'), ex: "1975-10-12 02:32:04"}, {name: '@datetime', des: i18n.t('api_test.request.datetime'), ex: "1975-10-12 02:32:04"},
{name: '@now', des: i18n.t('api_test.request.now'), ex: (new Date()).toLocaleTimeString().toLocaleString()}, {name: '@now', des: i18n.t('api_test.request.now'), ex: (new Date()).toLocaleTimeString().toLocaleString()},
{name: '@img', des: i18n.t('api_test.request.img'), ex: "http://dummyimage.com/120x60"}, {name: '@img', des: i18n.t('api_test.request.img'), ex: "http://dummyimage.com/120x60"},
{name: '@color', des: i18n.t('api_test.request.color'), ex: "#b479f2"}, {name: '@color', des: i18n.t('api_test.request.color'), ex: "#b479f2"},
{name: '@hex', des: i18n.t('api_test.request.hex'), ex: "#f27984"}, {name: '@hex', des: i18n.t('api_test.request.hex'), ex: "#f27984"},
{name: '@rgb', des: i18n.t('api_test.request.rgb'), ex: "rgb(203, 242, 121)"}, {name: '@rgb', des: i18n.t('api_test.request.rgb'), ex: "rgb(203, 242, 121)"},
{name: '@rgba', des: i18n.t('api_test.request.rgba'), ex: "rgba(242, 121, 238, 0.66)"}, {name: '@rgba', des: i18n.t('api_test.request.rgba'), ex: "rgba(242, 121, 238, 0.66)"},
{name: '@hsl', des: i18n.t('api_test.request.hsl'), ex: "hsl(164, 82, 71)"}, {name: '@hsl', des: i18n.t('api_test.request.hsl'), ex: "hsl(164, 82, 71)"},
{name: '@paragraph', des: i18n.t('api_test.request.paragraph'), ex: "Iwvh qxuvn uigzjw xijvntv dfidxtof"}, {name: '@paragraph', des: i18n.t('api_test.request.paragraph'), ex: "Iwvh qxuvn uigzjw xijvntv dfidxtof"},
{name: '@sentence', des: i18n.t('api_test.request.sentence'), ex: "Hfi fpqnqerrs sghxldx oqpghvnmy"}, {name: '@sentence', des: i18n.t('api_test.request.sentence'), ex: "Hfi fpqnqerrs sghxldx oqpghvnmy"},
{name: '@word', des: i18n.t('api_test.request.word'), ex: "shnjlyazvi"}, {name: '@word', des: i18n.t('api_test.request.word'), ex: "shnjlyazvi"},
{name: '@title', des: i18n.t('api_test.request.title'), ex: "Tefsdc Vhs Ujx"}, {name: '@title', des: i18n.t('api_test.request.title'), ex: "Tefsdc Vhs Ujx"},
{name: '@cparagraph', des: i18n.t('api_test.request.cparagraph'), ex: "色青元处才不米拉律消叫别金如上。"}, {name: '@cparagraph', des: i18n.t('api_test.request.cparagraph'), ex: "色青元处才不米拉律消叫别金如上。"},
{name: '@csentence', des: i18n.t('api_test.request.csentence'), ex: "与形府部速她运改织图集料进完。"}, {name: '@csentence', des: i18n.t('api_test.request.csentence'), ex: "与形府部速她运改织图集料进完。"},
{name: '@cword', des: i18n.t('api_test.request.cword'), ex: "满"}, {name: '@cword', des: i18n.t('api_test.request.cword'), ex: "满"},
{name: '@ctitle', des: i18n.t('api_test.request.ctitle'), ex: "运满前省快"}, {name: '@ctitle', des: i18n.t('api_test.request.ctitle'), ex: "运满前省快"},
{name: '@first', des: i18n.t('api_test.request.first'), ex: "Mary"}, {name: '@first', des: i18n.t('api_test.request.first'), ex: "Mary"},
{name: '@last', des: i18n.t('api_test.request.last'), ex: "Miller"}, {name: '@last', des: i18n.t('api_test.request.last'), ex: "Miller"},
{name: '@name', des: i18n.t('api_test.request.name'), ex: "Robert Lee"}, {name: '@name', des: i18n.t('api_test.request.name'), ex: "Robert Lee"},
{name: '@cfirst', des: i18n.t('api_test.request.cfirst'), ex: "龚"}, {name: '@cfirst', des: i18n.t('api_test.request.cfirst'), ex: "龚"},
{name: '@clast', des: i18n.t('api_test.request.clast'), ex: "刚"}, {name: '@clast', des: i18n.t('api_test.request.clast'), ex: "刚"},
{name: '@cname', des: i18n.t('api_test.request.cname'), ex: "江娟"}, {name: '@cname', des: i18n.t('api_test.request.cname'), ex: "江娟"},
{name: '@url', des: i18n.t('api_test.request.url'), ex: "wais://jopnwwj.bh/lqnhn"}, {name: '@url', des: i18n.t('api_test.request.url'), ex: "wais://jopnwwj.bh/lqnhn"},
{name: '@domain', des: i18n.t('api_test.request.domain'), ex: "rsh.bt"}, {name: '@domain', des: i18n.t('api_test.request.domain'), ex: "rsh.bt"},
{name: '@protocol', des: i18n.t('api_test.request.protocol'), ex: "rlogin"}, {name: '@protocol', des: i18n.t('api_test.request.protocol'), ex: "rlogin"},
{name: '@tld', des: i18n.t('api_test.request.tld'), ex: "sa"}, {name: '@tld', des: i18n.t('api_test.request.tld'), ex: "sa"},
{name: '@email', des: i18n.t('api_test.request.email'), ex: "d.somdg@edntlm.cd"}, {name: '@email', des: i18n.t('api_test.request.email'), ex: "d.somdg@edntlm.cd"},
{name: '@ip', des: i18n.t('api_test.request.ip'), ex: "22.151.93.255"}, {name: '@ip', des: i18n.t('api_test.request.ip'), ex: "22.151.93.255"},
{name: '@region', des: i18n.t('api_test.request.region'), ex: "东北"}, {name: '@region', des: i18n.t('api_test.request.region'), ex: "东北"},
{name: '@province', des: i18n.t('api_test.request.province'), ex: "陕西省"}, {name: '@province', des: i18n.t('api_test.request.province'), ex: "陕西省"},
{name: '@city', des: i18n.t('api_test.request.city'), ex: "珠海市"}, {name: '@city', des: i18n.t('api_test.request.city'), ex: "珠海市"},
{name: '@county', des: i18n.t('api_test.request.county'), ex: "正宁县"}, {name: '@county', des: i18n.t('api_test.request.county'), ex: "正宁县"},
{name: '@zip', des: i18n.t('api_test.request.zip'), ex: 873247}, {name: '@zip', des: i18n.t('api_test.request.zip'), ex: 873247},
{name: '@capitalize', des: i18n.t('api_test.request.capitalize'), ex: "Undefined"}, {name: '@capitalize', des: i18n.t('api_test.request.capitalize'), ex: "Undefined"},
{name: '@upper', des: i18n.t('api_test.request.upper'), ex: "UNDEFINED"}, {name: '@upper', des: i18n.t('api_test.request.upper'), ex: "UNDEFINED"},
{name: '@lower', des: i18n.t('api_test.request.lower'), ex: "undefined"}, {name: '@lower', des: i18n.t('api_test.request.lower'), ex: "undefined"},
{name: '@pick', des: i18n.t('api_test.request.pick'), ex: "None example"}, {name: '@pick', des: i18n.t('api_test.request.pick'), ex: "None example"},
{name: '@shuffle', des: i18n.t('api_test.request.shuffle'), ex: "org.mozilla.javascript.NavicatArray@2264545d"}, {name: '@shuffle', des: i18n.t('api_test.request.shuffle'), ex: "org.mozilla.javascript.NavicatArray@2264545d"},
{name: '@guid', des: i18n.t('api_test.request.guid'), ex: "4f9CeC2c-8d59-40f6-ec4F-2Abbc5C94Ddf"}, {name: '@guid', des: i18n.t('api_test.request.guid'), ex: "4f9CeC2c-8d59-40f6-ec4F-2Abbc5C94Ddf"},
{name: '@id', des: i18n.t('api_test.request.id'), ex: "450000197511051762"}, {name: '@id', des: i18n.t('api_test.request.id'), ex: "450000197511051762"},
{name: '@increment', des: i18n.t('api_test.request.increment'), ex: 1} {name: '@increment', des: i18n.t('api_test.request.increment'), ex: 1}
] ]
export const JMETER_FUNC = [ export const JMETER_FUNC = [
{type: "Information", name: "${__threadNum}", description: "get thread number"}, {type: "Information", name: "${__threadNum}", description: "get thread number"},
{type: "Information", name: "${__threadGroupName}", description: "get thread group name"}, {type: "Information", name: "${__threadGroupName}", description: "get thread group name"},
{type: "Information", name: "${__samplerName}", description: "get the sampler name (label)"}, {type: "Information", name: "${__samplerName}", description: "get the sampler name (label)"},
{type: "Information", name: "${__machineIP}", description: "get the local machine IP address"}, {type: "Information", name: "${__machineIP}", description: "get the local machine IP address"},
{type: "Information", name: "${__machineName}", description: "get the local machine name"}, {type: "Information", name: "${__machineName}", description: "get the local machine name"},
{type: "Information", name: "${__time}", description: "return current time in various formats"}, {type: "Information", name: "${__time}", description: "return current time in various formats"},
{type: "Information", name: "${__timeShift}", description: "return a date in various formats with the specified amount of seconds/minutes/hours/days added"}, {
{type: "Information", name: "${__log}", description: "log (or display) a message (and return the value)"}, type: "Information",
{type: "Information", name: "${__logn}", description: "log (or display) a message (empty return value)"}, name: "${__timeShift}",
{type: "Input", name: "${__StringFromFile}", description: "read a line from a file"}, description: "return a date in various formats with the specified amount of seconds/minutes/hours/days added"
{type: "Input", name: "${__FileToString}", description: "read an entire file"}, },
{type: "Input", name: "${__CSVRead}", description: "read from CSV delimited file"}, {type: "Information", name: "${__log}", description: "log (or display) a message (and return the value)"},
{type: "Input", name: "${__XPath}", description: "Use an XPath expression to read from a file"}, {type: "Information", name: "${__logn}", description: "log (or display) a message (empty return value)"},
{type: "Input", name: "${__StringToFile}", description: "write a string to a file"}, {type: "Input", name: "${__StringFromFile}", description: "read a line from a file"},
{type: "Calculation", name: "${__counter}", description: "generate an incrementing number"}, {type: "Input", name: "${__FileToString}", description: "read an entire file"},
{type: "Formatting", name: "${__dateTimeConvert}", description: "Convert a date or time from source to target format"}, {type: "Input", name: "${__CSVRead}", description: "read from CSV delimited file"},
{type: "Calculation", name: "${__digest}", description: "Generate a digest (SHA-1, SHA-256, MD5...)"}, {type: "Input", name: "${__XPath}", description: "Use an XPath expression to read from a file"},
{type: "Calculation", name: "${__intSum}", description: "add int numbers"}, {type: "Input", name: "${__StringToFile}", description: "write a string to a file"},
{type: "Calculation", name: "${__longSum}", description: "add long numbers"}, {type: "Calculation", name: "${__counter}", description: "generate an incrementing number"},
{type: "Calculation", name: "${__Random}", description: "generate a random number"}, {
{type: "Calculation", name: "${__RandomDate}", description: "generate random date within a specific date range"}, type: "Formatting",
{type: "Calculation", name: "${__RandomFromMultipleVars}", description: "extracts an element from the values of a set of variables separated by |"}, name: "${__dateTimeConvert}",
{type: "Calculation", name: "${__RandomString}", description: "generate a random string"}, description: "Convert a date or time from source to target format"
{type: "Calculation", name: "${__UUID}", description: "generate a random type 4 UUID"}, },
{type: "Scripting", name: "${__groovy}", description: "run an Apache Groovy script"}, {type: "Calculation", name: "${__digest}", description: "Generate a digest (SHA-1, SHA-256, MD5...)"},
{type: "Scripting", name: "${__BeanShell}", description: "run a BeanShell script"}, {type: "Calculation", name: "${__intSum}", description: "add int numbers"},
{type: "Scripting", name: "${__javaScript}", description: "process JavaScript (Nashorn)"}, {type: "Calculation", name: "${__longSum}", description: "add long numbers"},
{type: "Scripting", name: "${__jexl2}", description: "evaluate a Commons Jexl2 expression"}, {type: "Calculation", name: "${__Random}", description: "generate a random number"},
{type: "Scripting", name: "${__jexl3}", description: "evaluate a Commons Jexl3 expression"}, {type: "Calculation", name: "${__RandomDate}", description: "generate random date within a specific date range"},
{type: "Properties", name: "${__isPropDefined}", description: "Test if a property exists"}, {
{type: "Properties", name: "${__property}", description: "read a property"}, type: "Calculation",
{type: "Properties", name: "${__P}", description: "read a property (shorthand method)"}, name: "${__RandomFromMultipleVars}",
{type: "Properties", name: "${__setProperty}", description: "set a JMeter property"}, description: "extracts an element from the values of a set of variables separated by |"
{type: "Variables", name: "${__split}", description: "Split a string into variables"}, },
{type: "Variables", name: "${__eval}", description: "evaluate a variable expression"}, {type: "Calculation", name: "${__RandomString}", description: "generate a random string"},
{type: "Variables", name: "${__evalVar}", description: "evaluate an expression stored in a variable"}, {type: "Calculation", name: "${__UUID}", description: "generate a random type 4 UUID"},
{type: "Properties", name: "${__isVarDefined}", description: "Test if a variable exists"}, {type: "Scripting", name: "${__groovy}", description: "run an Apache Groovy script"},
{type: "Variables", name: "${__V}", description: "evaluate a variable name"}, {type: "Scripting", name: "${__BeanShell}", description: "run a BeanShell script"},
{type: "String", name: "${__char}", description: "generate Unicode char values from a list of numbers"}, {type: "Scripting", name: "${__javaScript}", description: "process JavaScript (Nashorn)"},
{type: "String", name: "${__changeCase}", description: "Change case following different modes"}, {type: "Scripting", name: "${__jexl2}", description: "evaluate a Commons Jexl2 expression"},
{type: "String", name: "${__escapeHtml}", description: "Encode strings using HTML encoding"}, {type: "Scripting", name: "${__jexl3}", description: "evaluate a Commons Jexl3 expression"},
{type: "String", name: "${__escapeOroRegexpChars}", description: "quote meta chars used by ORO regular expression"}, {type: "Properties", name: "${__isPropDefined}", description: "Test if a property exists"},
{type: "String", name: "${__escapeXml}", description: "Encode strings using XMl encoding"}, {type: "Properties", name: "${__property}", description: "read a property"},
{type: "String", name: "${__regexFunction}", description: "parse previous response using a regular expression"}, {type: "Properties", name: "${__P}", description: "read a property (shorthand method)"},
{type: "String", name: "${__unescape}", description: "Process strings containing Java escapes (e.g. \n & \t)"}, {type: "Properties", name: "${__setProperty}", description: "set a JMeter property"},
{type: "String", name: "${__unescapeHtml}", description: "Decode HTML-encoded strings"}, {type: "Variables", name: "${__split}", description: "Split a string into variables"},
{type: "String", name: "${__urldecode}", description: "Decode a application/x-www-form-urlencoded string"}, {type: "Variables", name: "${__eval}", description: "evaluate a variable expression"},
{type: "String", name: "${__urlencode}", description: "Encode a string to a application/x-www-form-urlencoded string"}, {type: "Variables", name: "${__evalVar}", description: "evaluate an expression stored in a variable"},
{type: "String", name: "${__TestPlanName}", description: "Return name of current test plan"}, {type: "Properties", name: "${__isVarDefined}", description: "Test if a variable exists"},
{type: "Variables", name: "${__V}", description: "evaluate a variable name"},
{type: "String", name: "${__char}", description: "generate Unicode char values from a list of numbers"},
{type: "String", name: "${__changeCase}", description: "Change case following different modes"},
{type: "String", name: "${__escapeHtml}", description: "Encode strings using HTML encoding"},
{type: "String", name: "${__escapeOroRegexpChars}", description: "quote meta chars used by ORO regular expression"},
{type: "String", name: "${__escapeXml}", description: "Encode strings using XMl encoding"},
{type: "String", name: "${__regexFunction}", description: "parse previous response using a regular expression"},
{type: "String", name: "${__unescape}", description: "Process strings containing Java escapes (e.g. \n & \t)"},
{type: "String", name: "${__unescapeHtml}", description: "Decode HTML-encoded strings"},
{type: "String", name: "${__urldecode}", description: "Decode a application/x-www-form-urlencoded string"},
{
type: "String",
name: "${__urlencode}",
description: "Encode a string to a application/x-www-form-urlencoded string"
},
{type: "String", name: "${__TestPlanName}", description: "Return name of current test plan"},
] ]
export const ORIGIN_COLOR = '#783887'; export const ORIGIN_COLOR = '#783887';
@ -207,164 +223,341 @@ export const COUNT_NUMBER_SHALLOW = '#CDB9D2';
export const PRIMARY_COLOR = '#783887'; export const PRIMARY_COLOR = '#783887';
export const CONFIG_TYPE = { export const CONFIG_TYPE = {
NOT: "NOT", NOT: "NOT",
NORMAL: "NORMAL", NORMAL: "NORMAL",
ABNORMAL: "ABNORMAL" ABNORMAL: "ABNORMAL"
} }
export const WORKSTATION={ export const WORKSTATION = {
UPCOMING:"upcoming", UPCOMING: "upcoming",
FOCUS:"focus", FOCUS: "focus",
NODE:"node" NODE: "node"
} }
export const ENV_TYPE = { export const ENV_TYPE = {
JSON: "JSON", DEFAULT: "DEFAULT",
GROUP: "GROUP" JSON: "JSON",
GROUP: "GROUP"
} }
export const DEFAULT_XSS_ATTR = ['style', 'class']; export const DEFAULT_XSS_ATTR = ['style', 'class'];
export const SECOND_LEVEL_ROUTE_PERMISSION_MAP = { export const SECOND_LEVEL_ROUTE_PERMISSION_MAP = {
API: [ API: [
{router: '/api/home', permission: ['PROJECT_API_HOME:READ']}, {router: '/api/home', permission: ['PROJECT_API_HOME:READ']},
{router: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ']}, {router: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ']},
{router: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ']}, {router: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ']},
{router: '/api/automation/report', permission: ['PROJECT_API_REPORT:READ']}, {router: '/api/automation/report', permission: ['PROJECT_API_REPORT:READ']},
], ],
TRACK: [ TRACK: [
{router: '/track/home', permission: ['PROJECT_TRACK_HOME:READ']}, {router: '/track/home', permission: ['PROJECT_TRACK_HOME:READ']},
{router: '/track/case/all', permission: ['PROJECT_TRACK_CASE:READ']}, {router: '/track/case/all', permission: ['PROJECT_TRACK_CASE:READ']},
{router: '/track/review/all', permission: ['PROJECT_TRACK_REVIEW:READ']}, {router: '/track/review/all', permission: ['PROJECT_TRACK_REVIEW:READ']},
{router: '/track/plan/all', permission: ['PROJECT_TRACK_PLAN:READ']}, {router: '/track/plan/all', permission: ['PROJECT_TRACK_PLAN:READ']},
{router: '/track/issue', permission: ['PROJECT_TRACK_ISSUE:READ']}, {router: '/track/issue', permission: ['PROJECT_TRACK_ISSUE:READ']},
{router: '/track/testPlan/reportList', permission: ['PROJECT_TRACK_REPORT:READ']}, {router: '/track/testPlan/reportList', permission: ['PROJECT_TRACK_REPORT:READ']},
], ],
LOAD: [ LOAD: [
{router: '/performance/home', permission: ['PROJECT_PERFORMANCE_HOME:READ']}, {router: '/performance/home', permission: ['PROJECT_PERFORMANCE_HOME:READ']},
{router: '/performance/test/all', permission: ['PROJECT_PERFORMANCE_TEST:READ']}, {router: '/performance/test/all', permission: ['PROJECT_PERFORMANCE_TEST:READ']},
{router: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ']}, {router: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ']},
], ],
UI: [ UI: [
{router: '/ui/automation', permission: ['PROJECT_UI_SCENARIO:READ']}, {router: '/ui/automation', permission: ['PROJECT_UI_SCENARIO:READ']},
{router: '/ui/element', permission: ['PROJECT_UI_ELEMENT:READ']}, {router: '/ui/element', permission: ['PROJECT_UI_ELEMENT:READ']},
{router: '/ui/report', permission: ['PROJECT_UI_REPORT:READ']}, {router: '/ui/report', permission: ['PROJECT_UI_REPORT:READ']},
], ],
REPORT: [ REPORT: [
{router: '/report/projectStatistics', permission: ['PROJECT_REPORT_ANALYSIS:READ']}, {router: '/report/projectStatistics', permission: ['PROJECT_REPORT_ANALYSIS:READ']},
{ {
router: '/report/projectReport', router: '/report/projectReport',
permission: [ permission: [
'PROJECT_ENTERPRISE_REPORT:READ+EXPORT', 'PROJECT_ENTERPRISE_REPORT:READ+CREATE', 'PROJECT_ENTERPRISE_REPORT:READ+EXPORT', 'PROJECT_ENTERPRISE_REPORT:READ+CREATE',
'PROJECT_ENTERPRISE_REPORT:READ+DELETE', 'PROJECT_ENTERPRISE_REPORT:READ+COPY', 'PROJECT_ENTERPRISE_REPORT:READ+DELETE', 'PROJECT_ENTERPRISE_REPORT:READ+COPY',
'PROJECT_ENTERPRISE_REPORT:READ+SCHEDULE', 'PROJECT_ENTERPRISE_REPORT:READ+EDIT' 'PROJECT_ENTERPRISE_REPORT:READ+SCHEDULE', 'PROJECT_ENTERPRISE_REPORT:READ+EDIT'
] ]
} }
] ]
} }
export const TASK_PATH = [ export const TASK_PATH = [
"/test/case/add", "/test/case/add",
"/test/case/review/save", "/test/case/review/save",
"/test/case/comment/save", "/test/case/comment/save",
"/test/plan/add", "/test/plan/add",
"/test/plan/relevance", "/test/plan/relevance",
"issues/add", "issues/add",
"test/case/issues/relate", "test/case/issues/relate",
"/api/definition/create", "/api/definition/create",
"/api/definition/run/debug", "/api/definition/run/debug",
"/api/testcase/create", "/api/testcase/create",
"/share/generate/api/document", "/share/generate/api/document",
"/api/definition/import", "/api/definition/import",
"/api/automation/create", "/api/automation/create",
"/api/automation/schedule/create", "/api/automation/schedule/create",
"/performance/save", "/performance/save",
"/share/generate/expired", "/share/generate/expired",
"/project/add", "/project/add",
"/project/member/add", "/project/member/add",
"/setting/user/project/member/add", "/setting/user/project/member/add",
"/environment/add", "/environment/add",
"/ui/element/add", "/ui/element/add",
"/ui/automation/create", "/ui/automation/create",
"/ui/automation/run/debug", "/ui/automation/run/debug",
]; ];
export const TASK_DATA = [ export const TASK_DATA = [
{ {
id: 1, id: 1,
name: "track", name: "track",
title: "side_task.test_tracking.title", title: "side_task.test_tracking.title",
percentage: 14, percentage: 14,
permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ','PROJECT_TRACK_CASE:READ+CREATE','PROJECT_TRACK_REVIEW:READ+CREATE','PROJECT_TRACK_REVIEW:READ+COMMENT','PROJECT_TRACK_PLAN:READ+CREATE','PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL','PROJECT_TRACK_ISSUE:READ+CREATE','PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'], permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ', 'PROJECT_TRACK_CASE:READ+CREATE', 'PROJECT_TRACK_REVIEW:READ+CREATE', 'PROJECT_TRACK_REVIEW:READ+COMMENT', 'PROJECT_TRACK_PLAN:READ+CREATE', 'PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL', 'PROJECT_TRACK_ISSUE:READ+CREATE', 'PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'],
taskData: [ taskData: [
{ id: 1, name: "side_task.test_tracking.task_1", status: 1, permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ'], api: [''], path: '/setting/project/:type', url: "" }, {
{ id: 2, name: "side_task.test_tracking.task_2", status: 0, permission: ['PROJECT_TRACK_CASE:READ+CREATE'], api: ["/test/case/add"], path: '/track/case/all', url: "/assets/guide/track/task-2.gif" }, id: 1,
{ id: 3, name: "side_task.test_tracking.task_3", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+CREATE'], api: ["/test/case/review/save"], path: '/track/review/all', url: "/assets/guide/track/task-3.gif" }, name: "side_task.test_tracking.task_1",
{ id: 4, name: "side_task.test_tracking.task_4", status: 0, permission: ['PROJECT_TRACK_REVIEW:READ+COMMENT'], api: ["/test/case/comment/save"], path: '/track/review/all', url: "/assets/guide/track/task-4.gif" }, status: 1,
{ id: 5, name: "side_task.test_tracking.task_5", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+CREATE'], api: ["/test/plan/add"], path: '/track/plan/all', url: "/assets/guide/track/task-5.gif" }, permission: ['PROJECT_MANAGER:READ', 'WORKSPACE_PROJECT_MANAGER:READ'],
{ id: 6, name: "side_task.test_tracking.task_6", status: 0, permission: ['PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL'], api: ["/test/plan/relevance"], path: '/track/plan/all', url: "/assets/guide/track/task-6.gif" }, api: [''],
{ id: 7, name: "side_task.test_tracking.task_7", status: 0, permission: ['PROJECT_TRACK_ISSUE:READ+CREATE','PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'], api: ["issues/add","test/case/issues/relate"], path: '/track/issue', url: "/assets/guide/track/task-7.gif" }, path: '/setting/project/:type',
], url: ""
rate: 1, },
status: 0 {
}, id: 2,
{ name: "side_task.test_tracking.task_2",
id: 2, status: 0,
name: "api", permission: ['PROJECT_TRACK_CASE:READ+CREATE'],
title: 'side_task.api_test.title', api: ["/test/case/add"],
percentage: 0, path: '/track/case/all',
permission: ['PROJECT_API_DEFINITION:READ+CREATE_API','PROJECT_API_DEFINITION:READ+IMPORT_API','PROJECT_API_DEFINITION:READ+DEBUG','PROJECT_API_DEFINITION:READ+CREATE_CASE','PROJECT_API_DEFINITION:READ','PROJECT_API_SCENARIO:READ+CREATE','PROJECT_API_SCENARIO:READ+SCHEDULE'], url: "/assets/guide/track/task-2.gif"
taskData: [ },
{id: 1, name: "side_task.api_test.task_1", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_API'], api: ["/api/definition/create"], url: "/assets/guide/api/task-1.gif" }, {
{id: 2, name: "side_task.api_test.task_2", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+IMPORT_API'], api: ["/api/definition/import"], url: "/assets/guide/api/task-2.gif" }, id: 3,
{id: 3, name: "side_task.api_test.task_3", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+DEBUG'], api: ["/api/definition/run/debug"], url: "/assets/guide/api/task-3.gif" }, name: "side_task.test_tracking.task_3",
{id: 4, name: "side_task.api_test.task_4", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ+CREATE_CASE'], api: ["/api/testcase/create"], url: "/assets/guide/api/task-4.gif" }, status: 0,
{id: 5, name: "side_task.api_test.task_5", status: 0, path: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ'], api: ["/share/generate/api/document"], url: "/assets/guide/api/task-5.gif" }, permission: ['PROJECT_TRACK_REVIEW:READ+CREATE'],
{id: 6, name: "side_task.api_test.task_6", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+CREATE'], api: ["/api/automation/create"], url: "/assets/guide/api/task-6.gif" }, api: ["/test/case/review/save"],
{id: 7, name: "side_task.api_test.task_7", status: 0, path: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ+SCHEDULE'], api: ["/api/automation/schedule/create"], url: "/assets/guide/api/task-7.gif" }, path: '/track/review/all',
], url: "/assets/guide/track/task-3.gif"
rate: 0, },
status: 0 {
}, id: 4,
{ name: "side_task.test_tracking.task_4",
id: 3, status: 0,
name: "performance", permission: ['PROJECT_TRACK_REVIEW:READ+COMMENT'],
title: 'side_task.performance_test.title', api: ["/test/case/comment/save"],
percentage: 0, path: '/track/review/all',
permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE',"PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH",'PROJECT_PERFORMANCE_REPORT:READ'], url: "/assets/guide/track/task-4.gif"
taskData: [ },
{id: 1, name: 'side_task.performance_test.task_1', status: 0, path: '/performance/test/all', permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE',"PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH"], api: ["/performance/save"], url: "/assets/guide/performance/task-1.gif" }, {
{id: 2, name: 'side_task.performance_test.task_2', status: 0, path: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ'], api: ["/share/generate/expired"], url: "/assets/guide/performance/task-2.gif" }, id: 5,
], name: "side_task.test_tracking.task_5",
rate: 0, status: 0,
status: 0 permission: ['PROJECT_TRACK_PLAN:READ+CREATE'],
}, api: ["/test/plan/add"],
{ path: '/track/plan/all',
id: 4, url: "/assets/guide/track/task-5.gif"
name: "project", },
title: 'side_task.project_setting.title', {
percentage: 0, id: 6,
permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE','PROJECT_USER:READ+CREATE','PROJECT_ENVIRONMENT:READ+CREATE'], name: "side_task.test_tracking.task_6",
taskData: [ status: 0,
{id: 1, name: 'side_task.project_setting.task_1', status: 0, permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE'], api: ["/project/add"], path: '/setting/project/:type', url: "/assets/guide/project/task-1.gif" }, permission: ['PROJECT_TRACK_PLAN:READ+RELEVANCE_OR_CANCEL'],
{id: 2, name: 'side_task.project_setting.task_2', status: 0, permission: ['PROJECT_USER:READ+CREATE'], api: ["/project/member/add","/setting/user/project/member/add"], path: '/project/member', url: "/assets/guide/project/task-2.gif" }, api: ["/test/plan/relevance"],
{id: 3, name: 'side_task.project_setting.task_3', status: 0, permission: ['PROJECT_ENVIRONMENT:READ+CREATE'], api: ["/environment/add"], path: '/project/env', url: "/assets/guide/project/task-3.gif" }, path: '/track/plan/all',
], url: "/assets/guide/track/task-6.gif"
rate: 0, },
status: 0 {
}, id: 7,
{ name: "side_task.test_tracking.task_7",
id: 5, status: 0,
name: "ui", permission: ['PROJECT_TRACK_ISSUE:READ+CREATE', 'PROJECT_TRACK_CASE:READ+BATCH_ADD_PUBLIC'],
title: 'side_task.ui_test.title', api: ["issues/add", "test/case/issues/relate"],
percentage: 0, path: '/track/issue',
permission: ['PROJECT_UI_ELEMENT:READ+CREATE','PROJECT_UI_SCENARIO:READ+CREATE','PROJECT_UI_SCENARIO:READ+RUN','PROJECT_UI_SCENARIO:READ+DEBUG'], url: "/assets/guide/track/task-7.gif"
taskData: [ },
{id: 1, name: 'side_task.ui_test.task_1', status: 0, permission: ['PROJECT_UI_ELEMENT:READ+CREATE'], api: ["/ui/element/add"], path: '/ui/element', url: "/assets/guide/ui/task-1.gif" }, ],
{id: 2, name: 'side_task.ui_test.task_2', status: 0, permission: ['PROJECT_UI_SCENARIO:READ+CREATE'], api: ["/ui/automation/create"], path: '/ui/automation', url: "/assets/guide/ui/task-2.gif" }, rate: 1,
{id: 2, name: 'side_task.ui_test.task_3', status: 0, permission: ['PROJECT_UI_SCENARIO:READ+RUN','PROJECT_UI_SCENARIO:READ+DEBUG'], api: ["/ui/automation/run/debug"], path: '/ui/automation', url: "/assets/guide/ui/task-3.gif" }, status: 0
], },
rate: 0, {
status: 0 id: 2,
}, name: "api",
title: 'side_task.api_test.title',
percentage: 0,
permission: ['PROJECT_API_DEFINITION:READ+CREATE_API', 'PROJECT_API_DEFINITION:READ+IMPORT_API', 'PROJECT_API_DEFINITION:READ+DEBUG', 'PROJECT_API_DEFINITION:READ+CREATE_CASE', 'PROJECT_API_DEFINITION:READ', 'PROJECT_API_SCENARIO:READ+CREATE', 'PROJECT_API_SCENARIO:READ+SCHEDULE'],
taskData: [
{
id: 1,
name: "side_task.api_test.task_1",
status: 0,
path: '/api/definition',
permission: ['PROJECT_API_DEFINITION:READ+CREATE_API'],
api: ["/api/definition/create"],
url: "/assets/guide/api/task-1.gif"
},
{
id: 2,
name: "side_task.api_test.task_2",
status: 0,
path: '/api/definition',
permission: ['PROJECT_API_DEFINITION:READ+IMPORT_API'],
api: ["/api/definition/import"],
url: "/assets/guide/api/task-2.gif"
},
{
id: 3,
name: "side_task.api_test.task_3",
status: 0,
path: '/api/definition',
permission: ['PROJECT_API_DEFINITION:READ+DEBUG'],
api: ["/api/definition/run/debug"],
url: "/assets/guide/api/task-3.gif"
},
{
id: 4,
name: "side_task.api_test.task_4",
status: 0,
path: '/api/definition',
permission: ['PROJECT_API_DEFINITION:READ+CREATE_CASE'],
api: ["/api/testcase/create"],
url: "/assets/guide/api/task-4.gif"
},
{
id: 5,
name: "side_task.api_test.task_5",
status: 0,
path: '/api/definition',
permission: ['PROJECT_API_DEFINITION:READ'],
api: ["/share/generate/api/document"],
url: "/assets/guide/api/task-5.gif"
},
{
id: 6,
name: "side_task.api_test.task_6",
status: 0,
path: '/api/automation',
permission: ['PROJECT_API_SCENARIO:READ+CREATE'],
api: ["/api/automation/create"],
url: "/assets/guide/api/task-6.gif"
},
{
id: 7,
name: "side_task.api_test.task_7",
status: 0,
path: '/api/automation',
permission: ['PROJECT_API_SCENARIO:READ+SCHEDULE'],
api: ["/api/automation/schedule/create"],
url: "/assets/guide/api/task-7.gif"
},
],
rate: 0,
status: 0
},
{
id: 3,
name: "performance",
title: 'side_task.performance_test.title',
percentage: 0,
permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE', "PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH", 'PROJECT_PERFORMANCE_REPORT:READ'],
taskData: [
{
id: 1,
name: 'side_task.performance_test.task_1',
status: 0,
path: '/performance/test/all',
permission: ['PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE', "PROJECT_API_SCENARIO:READ+CREATE_PERFORMANCE_BATCH"],
api: ["/performance/save"],
url: "/assets/guide/performance/task-1.gif"
},
{
id: 2,
name: 'side_task.performance_test.task_2',
status: 0,
path: '/performance/report/all',
permission: ['PROJECT_PERFORMANCE_REPORT:READ'],
api: ["/share/generate/expired"],
url: "/assets/guide/performance/task-2.gif"
},
],
rate: 0,
status: 0
},
{
id: 4,
name: "project",
title: 'side_task.project_setting.title',
percentage: 0,
permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE', 'PROJECT_USER:READ+CREATE', 'PROJECT_ENVIRONMENT:READ+CREATE'],
taskData: [
{
id: 1,
name: 'side_task.project_setting.task_1',
status: 0,
permission: ['WORKSPACE_PROJECT_MANAGER:READ+CREATE'],
api: ["/project/add"],
path: '/setting/project/:type',
url: "/assets/guide/project/task-1.gif"
},
{
id: 2,
name: 'side_task.project_setting.task_2',
status: 0,
permission: ['PROJECT_USER:READ+CREATE'],
api: ["/project/member/add", "/setting/user/project/member/add"],
path: '/project/member',
url: "/assets/guide/project/task-2.gif"
},
{
id: 3,
name: 'side_task.project_setting.task_3',
status: 0,
permission: ['PROJECT_ENVIRONMENT:READ+CREATE'],
api: ["/environment/add"],
path: '/project/env',
url: "/assets/guide/project/task-3.gif"
},
],
rate: 0,
status: 0
},
{
id: 5,
name: "ui",
title: 'side_task.ui_test.title',
percentage: 0,
permission: ['PROJECT_UI_ELEMENT:READ+CREATE', 'PROJECT_UI_SCENARIO:READ+CREATE', 'PROJECT_UI_SCENARIO:READ+RUN', 'PROJECT_UI_SCENARIO:READ+DEBUG'],
taskData: [
{
id: 1,
name: 'side_task.ui_test.task_1',
status: 0,
permission: ['PROJECT_UI_ELEMENT:READ+CREATE'],
api: ["/ui/element/add"],
path: '/ui/element',
url: "/assets/guide/ui/task-1.gif"
},
{
id: 2,
name: 'side_task.ui_test.task_2',
status: 0,
permission: ['PROJECT_UI_SCENARIO:READ+CREATE'],
api: ["/ui/automation/create"],
path: '/ui/automation',
url: "/assets/guide/ui/task-2.gif"
},
{
id: 2,
name: 'side_task.ui_test.task_3',
status: 0,
permission: ['PROJECT_UI_SCENARIO:READ+RUN', 'PROJECT_UI_SCENARIO:READ+DEBUG'],
api: ["/ui/automation/run/debug"],
path: '/ui/automation',
url: "/assets/guide/ui/task-3.gif"
},
],
rate: 0,
status: 0
},
] ]

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum TestPlanExecuteCaseType {
API_CASE, SCENARIO, UI_SCENARIO, LOAD_CASE
}

View File

@ -0,0 +1,93 @@
package io.metersphere.service;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Service
public class RedisTemplateService {
public static final long TIME_OUT = 480;
@Resource
private RedisTemplate<String, Object> redisTemplate;
public boolean setIfAbsent(String key, String value) {
try {
return redisTemplate.opsForValue().setIfAbsent(key, value);
} catch (Exception e) {
LoggerUtil.error(key, e);
return true;
}
}
public Object get(String key) {
try {
return redisTemplate.opsForValue().get(key);
} catch (Exception e) {
LoggerUtil.error(key, e);
}
return null;
}
public boolean delete(String key) {
try {
return redisTemplate.delete(key);
} catch (Exception e) {
LoggerUtil.error(key, e);
return false;
}
}
/**
* 加锁
*/
public boolean lock(String testPlanReportId, String key, String value) {
Boolean hasReport = redisTemplate.opsForValue().setIfAbsent(
StringUtils.join(testPlanReportId, key),
value,
TIME_OUT,
TimeUnit.MINUTES);
if (Boolean.FALSE.equals(hasReport)) {
redisTemplate.opsForValue().setIfPresent(
StringUtils.join(testPlanReportId, key),
value,
TIME_OUT,
TimeUnit.MINUTES);
return false;
} else {
return true;
}
}
public boolean has(String testPlanReportId, String key, String reportId) {
try {
Object value = redisTemplate.opsForValue().get(StringUtils.join(testPlanReportId, key));
return ObjectUtils.isNotEmpty(value) && StringUtils.equals(reportId, String.valueOf(value));
} catch (Exception e) {
LogUtil.error(e);
}
return false;
}
/**
* 解锁
*/
public boolean unlock(String testPlanReportId, String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(StringUtils.join(testPlanReportId, key)), value);
if (Objects.equals(1L, result)) {
return true;
}
return false;
}
}

View File

@ -6,6 +6,7 @@ import io.metersphere.base.mapper.ApiExecutionQueueMapper;
import io.metersphere.base.mapper.TestPlanLoadCaseMapper; import io.metersphere.base.mapper.TestPlanLoadCaseMapper;
import io.metersphere.base.mapper.ext.BaseApiExecutionQueueMapper; import io.metersphere.base.mapper.ext.BaseApiExecutionQueueMapper;
import io.metersphere.commons.constants.KafkaTopicConstants; import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
import io.metersphere.commons.constants.TestPlanLoadCaseStatus; import io.metersphere.commons.constants.TestPlanLoadCaseStatus;
import io.metersphere.commons.constants.TriggerMode; import io.metersphere.commons.constants.TriggerMode;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
@ -14,6 +15,7 @@ import io.metersphere.constants.RunModeConstants;
import io.metersphere.dto.RunModeConfigDTO; import io.metersphere.dto.RunModeConfigDTO;
import io.metersphere.plan.exec.queue.DBTestQueue; import io.metersphere.plan.exec.queue.DBTestQueue;
import io.metersphere.request.RunTestPlanRequest; import io.metersphere.request.RunTestPlanRequest;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
@ -175,13 +177,16 @@ public class PerfQueueService {
return queue; return queue;
} }
@Resource
private RedisTemplateService redisTemplateService;
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional(propagation = Propagation.REQUIRES_NEW)
public DBTestQueue add(Object runObj, String poolId, String reportId, String reportType, String runMode, RunModeConfigDTO config) { public DBTestQueue add(Object runObj, String poolId, String testPlanReportId, String reportType, String runMode, RunModeConfigDTO config) {
LoggerUtil.info("报告【" + reportId + "】开始生成执行链"); LoggerUtil.info("报告【" + testPlanReportId + "】开始生成执行链");
if (config.getEnvMap() == null) { if (config.getEnvMap() == null) {
config.setEnvMap(new LinkedHashMap<>()); config.setEnvMap(new LinkedHashMap<>());
} }
ApiExecutionQueue executionQueue = getApiExecutionQueue(poolId, reportId, reportType, runMode, config); ApiExecutionQueue executionQueue = getApiExecutionQueue(poolId, testPlanReportId, reportType, runMode, config);
queueMapper.insert(executionQueue); queueMapper.insert(executionQueue);
DBTestQueue resQueue = new DBTestQueue(); DBTestQueue resQueue = new DBTestQueue();
BeanUtils.copyBean(resQueue, executionQueue); BeanUtils.copyBean(resQueue, executionQueue);
@ -196,7 +201,9 @@ public class PerfQueueService {
extApiExecutionQueueMapper.sqlInsert(queueDetails); extApiExecutionQueueMapper.sqlInsert(queueDetails);
} }
resQueue.setDetailMap(detailMap); resQueue.setDetailMap(detailMap);
LoggerUtil.info("报告【" + reportId + "】生成执行链结束"); LoggerUtil.info("报告【" + testPlanReportId + "】生成执行链结束");
//移除Redis中的标志
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.LOAD_CASE.name(), testPlanReportId);
return resQueue; return resQueue;
} }
@ -207,7 +214,7 @@ public class PerfQueueService {
executionQueue.setPoolId(poolId); executionQueue.setPoolId(poolId);
executionQueue.setFailure(config.isOnSampleError()); executionQueue.setFailure(config.isOnSampleError());
executionQueue.setReportId(reportId); executionQueue.setReportId(reportId);
executionQueue.setReportType(StringUtils.isNotEmpty(reportType) ? reportType : RunModeConstants.INDEPENDENCE.toString()); executionQueue.setReportType(TestPlanExecuteCaseType.LOAD_CASE.name());
executionQueue.setRunMode(runMode); executionQueue.setRunMode(runMode);
return executionQueue; return executionQueue;
} }

View File

@ -5,6 +5,7 @@ import io.metersphere.base.mapper.ApiExecutionQueueMapper;
import io.metersphere.commons.constants.KafkaTopicConstants; import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.plan.service.AutomationCaseExecOverService; import io.metersphere.plan.service.AutomationCaseExecOverService;
import io.metersphere.plan.service.TestPlanReportService; import io.metersphere.plan.service.TestPlanReportService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
import io.metersphere.utils.NamedThreadFactory; import io.metersphere.utils.NamedThreadFactory;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -28,6 +29,8 @@ public class ExecReportListener {
@Resource @Resource
private TestPlanReportService testPlanReportService; private TestPlanReportService testPlanReportService;
@Resource @Resource
private RedisTemplateService redisTemplateService;
@Resource
private AutomationCaseExecOverService automationCaseExecOverService; private AutomationCaseExecOverService automationCaseExecOverService;
// 线程池维护线程的最少数量 // 线程池维护线程的最少数量
@ -57,6 +60,7 @@ public class ExecReportListener {
task.setApiExecutionQueueDetailMapper(executionQueueDetailMapper); task.setApiExecutionQueueDetailMapper(executionQueueDetailMapper);
task.setAutomationCaseExecOverService(automationCaseExecOverService); task.setAutomationCaseExecOverService(automationCaseExecOverService);
task.setTestPlanReportService(testPlanReportService); task.setTestPlanReportService(testPlanReportService);
task.setRedisTemplateService(redisTemplateService);
task.setRecord(item); task.setRecord(item);
threadPool.execute(task); threadPool.execute(task);
}); });

View File

@ -5,14 +5,16 @@ import io.metersphere.base.domain.ApiExecutionQueueDetailExample;
import io.metersphere.base.domain.ApiExecutionQueueExample; import io.metersphere.base.domain.ApiExecutionQueueExample;
import io.metersphere.base.mapper.ApiExecutionQueueDetailMapper; import io.metersphere.base.mapper.ApiExecutionQueueDetailMapper;
import io.metersphere.base.mapper.ApiExecutionQueueMapper; import io.metersphere.base.mapper.ApiExecutionQueueMapper;
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
import io.metersphere.commons.constants.TestPlanReportStatus; import io.metersphere.commons.constants.TestPlanReportStatus;
import io.metersphere.plan.service.AutomationCaseExecOverService; import io.metersphere.plan.service.AutomationCaseExecOverService;
import io.metersphere.plan.service.TestPlanReportService; import io.metersphere.plan.service.TestPlanReportService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.utils.LoggerUtil; import io.metersphere.utils.LoggerUtil;
import lombok.Data; import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -25,7 +27,7 @@ public class ExecReportListenerTask implements Runnable {
private ApiExecutionQueueDetailMapper apiExecutionQueueDetailMapper; private ApiExecutionQueueDetailMapper apiExecutionQueueDetailMapper;
private TestPlanReportService testPlanReportService; private TestPlanReportService testPlanReportService;
private AutomationCaseExecOverService automationCaseExecOverService; private AutomationCaseExecOverService automationCaseExecOverService;
private RedisTemplateService redisTemplateService;
@Override @Override
public void run() { public void run() {
@ -50,15 +52,15 @@ public class ExecReportListenerTask implements Runnable {
ApiExecutionQueueExample executionQueueExample = new ApiExecutionQueueExample(); ApiExecutionQueueExample executionQueueExample = new ApiExecutionQueueExample();
executionQueueExample.createCriteria().andReportIdEqualTo(testPlanReportId); executionQueueExample.createCriteria().andReportIdEqualTo(testPlanReportId);
List<ApiExecutionQueue> queues = apiExecutionQueueMapper.selectByExample(executionQueueExample); List<ApiExecutionQueue> queues = apiExecutionQueueMapper.selectByExample(executionQueueExample);
if (CollectionUtils.isEmpty(queues)) { if (CollectionUtils.isEmpty(queues) && this.isTestPlanIsEmptyInRedis(testPlanReportId)) {
LoggerUtil.info("Normal execution completes, update test plan report status" + testPlanReportId); LoggerUtil.info("Normal execution completes, update test plan report status" + testPlanReportId);
testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name()); testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name());
} else { } else if (CollectionUtils.isNotEmpty(queues)) {
List<String> ids = queues.stream().map(ApiExecutionQueue::getId).collect(Collectors.toList()); List<String> ids = queues.stream().map(ApiExecutionQueue::getId).collect(Collectors.toList());
ApiExecutionQueueDetailExample detailExample = new ApiExecutionQueueDetailExample(); ApiExecutionQueueDetailExample detailExample = new ApiExecutionQueueDetailExample();
detailExample.createCriteria().andQueueIdIn(ids); detailExample.createCriteria().andQueueIdIn(ids);
long count = apiExecutionQueueDetailMapper.countByExample(detailExample); long count = apiExecutionQueueDetailMapper.countByExample(detailExample);
if (count == 0) { if (count == 0 && this.isTestPlanIsEmptyInRedis(testPlanReportId)) {
LoggerUtil.info("Normal execution completes, update test plan report status" + testPlanReportId); LoggerUtil.info("Normal execution completes, update test plan report status" + testPlanReportId);
testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name()); testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name());
LoggerUtil.info("Clear Queue" + ids); LoggerUtil.info("Clear Queue" + ids);
@ -68,4 +70,15 @@ public class ExecReportListenerTask implements Runnable {
} }
} }
} }
/**
* 测试计划执行时会将运行标志放入redis中当测试计划执行队列入库后会将redis中的标志清除
*/
private boolean isTestPlanIsEmptyInRedis(String testPlanReportId) {
Object scenarioObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.SCENARIO);
Object apiCaseObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.API_CASE);
Object uiObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.UI_SCENARIO);
Object loadObj = redisTemplateService.get(testPlanReportId + TestPlanExecuteCaseType.LOAD_CASE);
return ObjectUtils.isEmpty(scenarioObj) && ObjectUtils.isEmpty(apiCaseObj) && ObjectUtils.isEmpty(uiObj) && ObjectUtils.isEmpty(loadObj);
}
} }

View File

@ -1,221 +0,0 @@
package io.metersphere.plan.service;
import io.metersphere.base.domain.TestPlanWithBLOBs;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.constants.RunModeConstants;
import io.metersphere.dto.*;
import io.metersphere.i18n.Translator;
import io.metersphere.plan.dto.ExecutionWay;
import io.metersphere.plan.request.api.TestPlanRunRequest;
import io.metersphere.plan.service.remote.api.PlanTestPlanApiCaseService;
import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService;
import io.metersphere.plan.service.remote.performance.PerfExecService;
import io.metersphere.plan.service.remote.ui.PlanTestPlanUiScenarioCaseService;
import io.metersphere.plan.utils.TestPlanRequestUtil;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
@Service
@Transactional
public class TestPlanExecuteService {
@Resource
@Lazy
private TestPlanService testPlanService;
@Resource
private TestPlanReportService testPlanReportService;
@Resource
private PlanTestPlanApiCaseService planTestPlanApiCaseService;
@Resource
private PlanTestPlanScenarioCaseService planTestPlanScenarioCaseService;
@Resource
private PerfExecService perfExecService;
@Resource
private PlanTestPlanUiScenarioCaseService planTestPlanUiScenarioCaseService;
@Resource
private TestPlanMapper testPlanMapper;
/**
* 执行测试计划流程是会调用其它服务的执行方法并通过kafka传递信息给test-track服务来判断测试计划是否执行结束
* 执行方法采用单独的事务控制执行完了就提交让测试报告以及包括执行内容的数据及时入库
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public String runTestPlan(String testPlanId, String projectId, String userId, String triggerMode, String planReportId, String executionWay, String apiRunConfig) {
// 校验测试计划是否在执行中
if (testPlanService.checkTestPlanIsRunning(testPlanId)) {
LogUtil.info("当前测试计划正在执行中,请稍后再试", testPlanId);
MSException.throwException(Translator.get("test_plan_run_message"));
}
RunModeConfigDTO runModeConfig = null;
try {
runModeConfig = JSON.parseObject(apiRunConfig, RunModeConfigDTO.class);
} catch (Exception e) {
LogUtil.error(e);
}
if (runModeConfig == null) {
runModeConfig = this.buildRunModeConfigDTO();
}
//环境参数为空时依据测试计划保存的环境执行
if (((StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && StringUtils.isBlank(runModeConfig.getEnvironmentGroupId()))
|| (!StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && MapUtils.isEmpty(runModeConfig.getEnvMap()) && MapUtils.isEmpty(runModeConfig.getTestPlanDefaultEnvMap())))
&& !StringUtils.equals(executionWay, ExecutionWay.RUN.name())) {
TestPlanWithBLOBs testPlanWithBLOBs = testPlanMapper.selectByPrimaryKey(testPlanId);
if (StringUtils.isNotEmpty(testPlanWithBLOBs.getRunModeConfig())) {
try {
Map json = JSON.parseMap(testPlanWithBLOBs.getRunModeConfig());
TestPlanRequestUtil.changeStringToBoolean(json);
TestPlanRunRequest testPlanRunRequest = JSON.parseObject(JSON.toJSONString(json), TestPlanRunRequest.class);
if (testPlanRunRequest != null) {
String envType = testPlanRunRequest.getEnvironmentType();
Map<String, String> envMap = testPlanRunRequest.getEnvMap();
String environmentGroupId = testPlanRunRequest.getEnvironmentGroupId();
runModeConfig = testPlanService.getRunModeConfigDTO(testPlanRunRequest, envType, envMap, environmentGroupId, testPlanId);
runModeConfig.setTestPlanDefaultEnvMap(testPlanRunRequest.getTestPlanDefaultEnvMap());
if (!testPlanRunRequest.isRunWithinResourcePool()) {
runModeConfig.setResourcePoolId(null);
}
}
} catch (Exception e) {
LogUtil.error("获取测试计划保存的环境信息出错!", e);
}
}
}
if (planReportId == null) {
planReportId = UUID.randomUUID().toString();
}
if (testPlanService.haveExecCase(testPlanId, true)) {
testPlanService.verifyPool(projectId, runModeConfig);
}
//创建测试报告然后返回的ID重新赋值为resourceID作为后续的参数
TestPlanScheduleReportInfoDTO reportInfoDTO = testPlanService.genTestPlanReport(planReportId, testPlanId, userId, triggerMode, runModeConfig);
LoggerUtil.info("预生成测试计划报告【" + reportInfoDTO.getTestPlanReport() != null ? reportInfoDTO.getTestPlanReport().getName() : StringUtils.EMPTY + "】计划报告ID[" + planReportId + "]");
List<TestPlanApiDTO> apiTestCases = null;
List<TestPlanScenarioDTO> scenarioCases = null;
List<TestPlanUiScenarioDTO> uiScenarios = null;
Map<String, String> loadCaseReportMap = null;
if (MapUtils.isNotEmpty(reportInfoDTO.getApiTestCaseDataMap())) {
try {
apiTestCases = planTestPlanApiCaseService.getFailureListByIds(reportInfoDTO.getApiTestCaseDataMap().keySet());
} catch (Exception e) {
LogUtil.error("测试计划执行查询接口用例失败!", e);
}
}
if (MapUtils.isNotEmpty(reportInfoDTO.getPlanScenarioIdMap())) {
try {
scenarioCases = planTestPlanScenarioCaseService.getFailureListByIds(reportInfoDTO.getPlanScenarioIdMap().keySet());
} catch (Exception e) {
LogUtil.error("测试计划执行查询场景用例失败!", e);
}
}
if (MapUtils.isNotEmpty(reportInfoDTO.getUiScenarioIdMap())) {
try {
uiScenarios = planTestPlanUiScenarioCaseService.getFailureListByIds(reportInfoDTO.getUiScenarioIdMap().keySet());
} catch (Exception e) {
LogUtil.error("测试计划执行查询UI用例失败!", e);
}
}
boolean haveApiCaseExec = false, haveScenarioCaseExec = false, haveLoadCaseExec = false, haveUICaseExec = false;
if (CollectionUtils.isNotEmpty(apiTestCases)) {
//执行接口案例任务
LoggerUtil.info("开始执行测试计划接口用例 " + planReportId);
try {
Map<String, String> apiCaseReportMap = testPlanService.executeApiTestCase(triggerMode, planReportId, userId, testPlanId, runModeConfig);
if (MapUtils.isNotEmpty(apiCaseReportMap)) {
haveApiCaseExec = true;
for (TestPlanApiDTO dto : apiTestCases) {
dto.setReportId(apiCaseReportMap.get(dto.getId()));
}
}
} catch (Exception e) {
apiTestCases = null;
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划接口用例失败! ", e);
}
}
if (CollectionUtils.isNotEmpty(scenarioCases)) {
//执行场景执行任务
LoggerUtil.info("开始执行测试计划场景用例 " + planReportId);
try {
Map<String, String> scenarioReportMap = testPlanService.executeScenarioCase(planReportId, testPlanId, projectId, runModeConfig, triggerMode, userId, reportInfoDTO.getPlanScenarioIdMap());
if (MapUtils.isNotEmpty(scenarioReportMap)) {
haveScenarioCaseExec = true;
List<TestPlanScenarioDTO> removeDTO = new ArrayList<>();
for (TestPlanScenarioDTO dto : scenarioCases) {
if (scenarioReportMap.containsKey(dto.getId())) {
dto.setReportId(scenarioReportMap.get(dto.getId()));
} else {
removeDTO.add(dto);
}
}
if (CollectionUtils.isNotEmpty(removeDTO)) {
scenarioCases.removeAll(removeDTO);
}
}
} catch (Exception e) {
scenarioCases = null;
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划场景用例失败! ", e);
}
}
if (MapUtils.isNotEmpty(reportInfoDTO.getPerformanceIdMap())) {
//执行性能测试任务
LoggerUtil.info("开始执行测试计划性能用例 " + planReportId);
try {
loadCaseReportMap = perfExecService.executeLoadCase(planReportId, runModeConfig, testPlanService.transformationPerfTriggerMode(triggerMode), reportInfoDTO.getPerformanceIdMap());
if (MapUtils.isNotEmpty(loadCaseReportMap)) {
haveLoadCaseExec = true;
}
} catch (Exception e) {
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划性能用例失败! ", e);
}
}
if (CollectionUtils.isNotEmpty(uiScenarios)) {
//执行UI场景执行任务
LoggerUtil.info("开始执行测试计划 UI 场景用例 " + planReportId);
try {
Map<String, String> uiScenarioReportMap = testPlanService.executeUiScenarioCase(planReportId, testPlanId, projectId, runModeConfig, triggerMode, userId, reportInfoDTO.getUiScenarioIdMap());
if (MapUtils.isNotEmpty(uiScenarioReportMap)) {
haveUICaseExec = true;
for (TestPlanUiScenarioDTO dto : uiScenarios) {
dto.setReportId(uiScenarioReportMap.get(dto.getId()));
}
}
} catch (Exception e) {
uiScenarios = null;
LoggerUtil.info("测试报告" + planReportId + "本次执行测试计划 UI 用例失败! ", e);
}
}
LoggerUtil.info("开始生成测试计划报告内容 " + planReportId);
testPlanReportService.createTestPlanReportContentReportIds(planReportId, apiTestCases, scenarioCases, uiScenarios, loadCaseReportMap);
if (!haveApiCaseExec && !haveScenarioCaseExec && !haveLoadCaseExec && !haveUICaseExec) {
//如果没有执行的自动化用例调用结束测试计划的方法 因为方法中包含着测试计划执行队列的处理逻辑
testPlanReportService.testPlanUnExecute(reportInfoDTO.getTestPlanReport());
}
return planReportId;
}
private RunModeConfigDTO buildRunModeConfigDTO() {
RunModeConfigDTO runModeConfig = new RunModeConfigDTO();
runModeConfig.setMode(RunModeConstants.SERIAL.name());
runModeConfig.setReportType("iddReport");
runModeConfig.setEnvMap(new HashMap<>());
runModeConfig.setOnSampleError(false);
return runModeConfig;
}
}

View File

@ -49,6 +49,7 @@ import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils; import org.mybatis.spring.SqlSessionUtils;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -622,6 +623,7 @@ public class TestPlanReportService {
} }
} }
@Async
public void testPlanUnExecute(TestPlanReport testPlanReport) { public void testPlanUnExecute(TestPlanReport testPlanReport) {
if (testPlanReport != null && !StringUtils.equalsIgnoreCase(testPlanReport.getStatus(), TestPlanReportStatus.COMPLETED.name())) { if (testPlanReport != null && !StringUtils.equalsIgnoreCase(testPlanReport.getStatus(), TestPlanReportStatus.COMPLETED.name())) {
testPlanReport.setIsApiCaseExecuting(false); testPlanReport.setIsApiCaseExecuting(false);

View File

@ -40,6 +40,7 @@ import io.metersphere.plan.request.performance.LoadPlanReportDTO;
import io.metersphere.plan.request.ui.RunUiScenarioRequest; import io.metersphere.plan.request.ui.RunUiScenarioRequest;
import io.metersphere.plan.request.ui.TestPlanUiExecuteReportDTO; import io.metersphere.plan.request.ui.TestPlanUiExecuteReportDTO;
import io.metersphere.plan.request.ui.UiPlanReportRequest; import io.metersphere.plan.request.ui.UiPlanReportRequest;
import io.metersphere.plan.service.execute.TestPlanExecuteService;
import io.metersphere.plan.service.remote.api.PlanApiAutomationService; import io.metersphere.plan.service.remote.api.PlanApiAutomationService;
import io.metersphere.plan.service.remote.api.PlanTestPlanApiCaseService; import io.metersphere.plan.service.remote.api.PlanTestPlanApiCaseService;
import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService; import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService;
@ -495,6 +496,22 @@ public class TestPlanService {
request.setProjectId(request.getProjectId()); request.setProjectId(request.getProjectId());
} }
List<TestPlanDTOWithMetric> testPlanList = extTestPlanMapper.list(request); List<TestPlanDTOWithMetric> testPlanList = extTestPlanMapper.list(request);
//统计测试计划的测试用例数
List<String> testPlanIdList = testPlanList.stream().map(TestPlanDTOWithMetric::getId).collect(Collectors.toList());
Map<String, ParamsDTO> planTestCaseCountMap = extTestPlanMapper.testPlanTestCaseCount(testPlanIdList);
Map<String, ParamsDTO> planApiCaseMap = extTestPlanMapper.testPlanApiCaseCount(testPlanIdList);
Map<String, ParamsDTO> planApiScenarioMap = extTestPlanMapper.testPlanApiScenarioCount(testPlanIdList);
Map<String, ParamsDTO> planUiScenarioMap = extTestPlanMapper.testPlanUiScenarioCount(testPlanIdList);
Map<String, ParamsDTO> planLoadCaseMap = extTestPlanMapper.testPlanLoadCaseCount(testPlanIdList);
for (TestPlanDTOWithMetric testPlanMetric : testPlanList) {
testPlanMetric.setTestPlanTestCaseCount(planTestCaseCountMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planTestCaseCountMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planTestCaseCountMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanApiCaseCount(planApiCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiCaseMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanApiScenarioCount(planApiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiScenarioMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanUiScenarioCount(planUiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planUiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planUiScenarioMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanLoadCaseCount(planLoadCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planLoadCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planLoadCaseMap.get(testPlanMetric.getId()).getValue()));
}
if (CollectionUtils.isNotEmpty(testPlanList)) { if (CollectionUtils.isNotEmpty(testPlanList)) {
List<String> changeToFinishedIds = new ArrayList<>(); List<String> changeToFinishedIds = new ArrayList<>();
//检查定时任务的设置 //检查定时任务的设置
@ -556,17 +573,7 @@ public class TestPlanService {
public List<TestPlanDTOWithMetric> selectTestPlanMetricById(List<String> idList) { public List<TestPlanDTOWithMetric> selectTestPlanMetricById(List<String> idList) {
List<TestPlanDTOWithMetric> testPlanMetricList = this.calcTestPlanRateByIdList(idList); List<TestPlanDTOWithMetric> testPlanMetricList = this.calcTestPlanRateByIdList(idList);
Map<String, ParamsDTO> planTestCaseCountMap = extTestPlanMapper.testPlanTestCaseCount(idList);
Map<String, ParamsDTO> planApiCaseMap = extTestPlanMapper.testPlanApiCaseCount(idList);
Map<String, ParamsDTO> planApiScenarioMap = extTestPlanMapper.testPlanApiScenarioCount(idList);
Map<String, ParamsDTO> planUiScenarioMap = extTestPlanMapper.testPlanUiScenarioCount(idList);
Map<String, ParamsDTO> planLoadCaseMap = extTestPlanMapper.testPlanLoadCaseCount(idList);
for (TestPlanDTOWithMetric testPlanMetric : testPlanMetricList) { for (TestPlanDTOWithMetric testPlanMetric : testPlanMetricList) {
testPlanMetric.setTestPlanTestCaseCount(planTestCaseCountMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planTestCaseCountMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planTestCaseCountMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanApiCaseCount(planApiCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiCaseMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanApiScenarioCount(planApiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planApiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planApiScenarioMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanUiScenarioCount(planUiScenarioMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planUiScenarioMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planUiScenarioMap.get(testPlanMetric.getId()).getValue()));
testPlanMetric.setTestPlanLoadCaseCount(planLoadCaseMap.get(testPlanMetric.getId()) == null ? 0 : Integer.parseInt(planLoadCaseMap.get(testPlanMetric.getId()).getValue() == null ? "0" : planLoadCaseMap.get(testPlanMetric.getId()).getValue()));
List<User> followUsers = this.getPlanFollow(testPlanMetric.getId()); List<User> followUsers = this.getPlanFollow(testPlanMetric.getId());
testPlanMetric.setFollowUsers(followUsers); testPlanMetric.setFollowUsers(followUsers);
} }

View File

@ -0,0 +1,352 @@
package io.metersphere.plan.service.execute;
import com.esotericsoftware.minlog.Log;
import io.metersphere.base.domain.TestPlanReport;
import io.metersphere.base.domain.TestPlanWithBLOBs;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.JSON;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.constants.RunModeConstants;
import io.metersphere.dto.*;
import io.metersphere.i18n.Translator;
import io.metersphere.plan.dto.ExecutionWay;
import io.metersphere.plan.request.api.TestPlanRunRequest;
import io.metersphere.plan.service.TestPlanReportService;
import io.metersphere.plan.service.TestPlanService;
import io.metersphere.plan.service.remote.api.PlanTestPlanApiCaseService;
import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService;
import io.metersphere.plan.service.remote.performance.PerfExecService;
import io.metersphere.plan.service.remote.ui.PlanTestPlanUiScenarioCaseService;
import io.metersphere.plan.utils.TestPlanRequestUtil;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.CountDownLatch;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanExecuteService {
@Resource
@Lazy
private TestPlanService testPlanService;
@Resource
private TestPlanReportService testPlanReportService;
@Resource
private PlanTestPlanApiCaseService planTestPlanApiCaseService;
@Resource
private PlanTestPlanScenarioCaseService planTestPlanScenarioCaseService;
@Resource
private PerfExecService perfExecService;
@Resource
private PlanTestPlanUiScenarioCaseService planTestPlanUiScenarioCaseService;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private RedisTemplateService redisTemplateService;
/**
* 执行测试计划流程是会调用其它服务的执行方法并通过kafka传递信息给test-track服务来判断测试计划是否执行结束
* 执行方法采用单独的事务控制执行完了就提交让测试报告以及包括执行内容的数据及时入库
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public String runTestPlan(String testPlanId, String projectId, String userId, String triggerMode, String planReportId, String executionWay, String apiRunConfig) {
//获取运行模式
RunModeConfigDTO runModeConfig = this.getRunModeConfig(apiRunConfig, executionWay, testPlanId);
if (StringUtils.isEmpty(planReportId)) {
planReportId = UUID.randomUUID().toString();
}
TestPlanReport testPlanReport = null;
try {
this.checkTestPlanCanRunning(testPlanId, projectId, runModeConfig);
//创建测试报告然后返回的ID重新赋值为resourceID作为后续的参数
TestPlanScheduleReportInfoDTO reportInfoDTO = testPlanService.genTestPlanReport(planReportId, testPlanId, userId, triggerMode, runModeConfig);
testPlanReport = reportInfoDTO.getTestPlanReport();
LoggerUtil.info("预生成测试计划报告【" + (reportInfoDTO.getTestPlanReport() != null ? reportInfoDTO.getTestPlanReport().getName() : StringUtils.EMPTY) + "】计划报告ID[" + planReportId + "]");
this.execute(reportInfoDTO, runModeConfig, triggerMode, projectId, userId);
} catch (Exception e) {
//如果执行失败要保证执行队列是否不被影响
if (testPlanReport == null) {
testPlanReport = new TestPlanReport();
testPlanReport.setId(planReportId);
}
testPlanReportService.testPlanUnExecute(testPlanReport);
Log.error("执行测试计划失败!", e);
}
return planReportId;
}
private void checkTestPlanCanRunning(String testPlanId, String projectId, RunModeConfigDTO runModeConfig) throws Exception {
// 校验测试计划是否在执行中
if (testPlanService.checkTestPlanIsRunning(testPlanId)) {
LogUtil.info("当前测试计划正在执行中,请稍后再试", testPlanId);
MSException.throwException(Translator.get("test_plan_run_message"));
}
//检查执行资源池
if (testPlanService.haveExecCase(testPlanId, true)) {
testPlanService.verifyPool(projectId, runModeConfig);
}
}
private void execute(TestPlanScheduleReportInfoDTO reportInfoDTO, RunModeConfigDTO runModeConfig, String triggerMode, String projectId, String executeUser) throws Exception {
CaseExecuteResult caseExecuteResult = new CaseExecuteResult();
CountDownLatch countDownLatch = this.countDownExecute(reportInfoDTO, caseExecuteResult, runModeConfig, triggerMode, projectId, executeUser);
countDownLatch.await();
LoggerUtil.info("开始生成测试计划报告内容 " + reportInfoDTO.getTestPlanReport().getId());
testPlanReportService.createTestPlanReportContentReportIds(reportInfoDTO.getTestPlanReport().getId(),
caseExecuteResult.getApiCaseDTO(), caseExecuteResult.getScenarioCases(), caseExecuteResult.getUiScenarios(), caseExecuteResult.getLoadCaseReportMap());
if (!caseExecuteResult.isExecuting()) {
MSException.throwException("测试计划执行失败不存在可执行的用例报告ID:[" + reportInfoDTO.getTestPlanReport().getTestPlanId() + "]");
}
}
private CountDownLatch countDownExecute(TestPlanScheduleReportInfoDTO reportInfoDTO, CaseExecuteResult caseExecuteResult, RunModeConfigDTO runModeConfig, String triggerMode, String projectId, String executeUser) {
CountDownLatch countDownLatch = new CountDownLatch(4);
try {
this.executeApiCase(caseExecuteResult, reportInfoDTO.getApiTestCaseDataMap(), triggerMode,
reportInfoDTO.getTestPlanReport().getId(), reportInfoDTO.getTestPlanReport().getTestPlanId(), executeUser, runModeConfig);
} catch (Exception e) {
LogUtil.error(e);
} finally {
countDownLatch.countDown();
}
try {
this.executeScenarioCase(caseExecuteResult, reportInfoDTO.getPlanScenarioIdMap(), triggerMode,
reportInfoDTO.getTestPlanReport().getId(), reportInfoDTO.getTestPlanReport().getTestPlanId(), projectId, executeUser, runModeConfig);
} catch (Exception e) {
LogUtil.error(e);
} finally {
countDownLatch.countDown();
}
try {
this.executeUiCase(caseExecuteResult, reportInfoDTO.getUiScenarioIdMap(), triggerMode,
reportInfoDTO.getTestPlanReport().getId(), reportInfoDTO.getTestPlanReport().getTestPlanId(), projectId, executeUser, runModeConfig);
} catch (Exception e) {
LogUtil.error(e);
} finally {
countDownLatch.countDown();
}
try {
this.executeLoadCase(caseExecuteResult, reportInfoDTO.getPerformanceIdMap(), triggerMode,
reportInfoDTO.getTestPlanReport().getId(), runModeConfig);
} catch (Exception e) {
LogUtil.error(e);
} finally {
countDownLatch.countDown();
}
return countDownLatch;
}
private void executeApiCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, String testPlanId, String executeUser, RunModeConfigDTO runModeConfig) {
boolean executing = false;
List<TestPlanApiDTO> apiTestCases = null;
if (MapUtils.isNotEmpty(executeCase)) {
try {
apiTestCases = planTestPlanApiCaseService.getFailureListByIds(executeCase.keySet());
} catch (Exception e) {
LogUtil.error("测试计划执行查询接口用例失败!", e);
}
}
if (CollectionUtils.isNotEmpty(apiTestCases)) {
//执行接口案例任务
LoggerUtil.info("开始执行测试计划接口用例 " + testPlanReportId);
try {
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.API_CASE.name(), testPlanReportId);
Map<String, String> apiCaseReportMap = testPlanService.executeApiTestCase(triggerMode, testPlanReportId, executeUser, testPlanId, runModeConfig);
if (MapUtils.isNotEmpty(apiCaseReportMap)) {
executing = true;
for (TestPlanApiDTO dto : apiTestCases) {
dto.setReportId(apiCaseReportMap.get(dto.getId()));
}
}
} catch (Exception e) {
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.API_CASE.name(), testPlanReportId);
apiTestCases = null;
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划接口用例失败! ", e);
}
}
executeResult.setApiCaseExecuting(executing);
executeResult.setApiCaseDTO(apiTestCases);
}
private void executeScenarioCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, String testPlanId, String projectId, String executeUser, RunModeConfigDTO runModeConfig) {
boolean executing = false;
List<TestPlanScenarioDTO> scenarioCases = null;
if (MapUtils.isNotEmpty(executeCase)) {
try {
scenarioCases = planTestPlanScenarioCaseService.getFailureListByIds(executeCase.keySet());
} catch (Exception e) {
LogUtil.error("测试计划执行查询场景用例失败!", e);
}
}
if (CollectionUtils.isNotEmpty(scenarioCases)) {
//执行场景执行任务
LoggerUtil.info("开始执行测试计划场景用例 " + testPlanReportId);
try {
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.SCENARIO.name(), testPlanReportId);
Map<String, String> scenarioReportMap = testPlanService.executeScenarioCase(testPlanReportId, testPlanId, projectId, runModeConfig, triggerMode, executeUser, executeCase);
if (MapUtils.isNotEmpty(scenarioReportMap)) {
executing = true;
List<TestPlanScenarioDTO> removeDTO = new ArrayList<>();
for (TestPlanScenarioDTO dto : scenarioCases) {
if (scenarioReportMap.containsKey(dto.getId())) {
dto.setReportId(scenarioReportMap.get(dto.getId()));
} else {
removeDTO.add(dto);
}
}
if (CollectionUtils.isNotEmpty(removeDTO)) {
scenarioCases.removeAll(removeDTO);
}
}
} catch (Exception e) {
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.SCENARIO.name(), testPlanReportId);
scenarioCases = null;
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划场景用例失败! ", e);
}
}
executeResult.setScenarioCases(scenarioCases);
executeResult.setScenarioExecuting(executing);
}
private void executeUiCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, String testPlanId, String projectId, String executeUser, RunModeConfigDTO runModeConfig) {
boolean executing = false;
List<TestPlanUiScenarioDTO> uiScenarios = null;
if (MapUtils.isNotEmpty(executeCase)) {
try {
uiScenarios = planTestPlanUiScenarioCaseService.getFailureListByIds(executeCase.keySet());
} catch (Exception e) {
LogUtil.error("测试计划执行查询UI用例失败!", e);
}
}
if (CollectionUtils.isNotEmpty(uiScenarios)) {
//执行UI场景执行任务
LoggerUtil.info("开始执行测试计划 UI 场景用例 " + testPlanReportId);
try {
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.UI_SCENARIO.name(), testPlanReportId);
Map<String, String> uiScenarioReportMap = testPlanService.executeUiScenarioCase(testPlanReportId, testPlanId, projectId, runModeConfig, triggerMode, executeUser, executeCase);
if (MapUtils.isNotEmpty(uiScenarioReportMap)) {
executing = true;
for (TestPlanUiScenarioDTO dto : uiScenarios) {
dto.setReportId(uiScenarioReportMap.get(dto.getId()));
}
}
} catch (Exception e) {
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.UI_SCENARIO.name(), testPlanReportId);
uiScenarios = null;
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划 UI 用例失败! ", e);
}
}
executeResult.setUiScenarios(uiScenarios);
executeResult.setUiScenarioExecuting(executing);
}
private void executeLoadCase(CaseExecuteResult executeResult, Map<String, String> executeCase, String triggerMode, String testPlanReportId, RunModeConfigDTO runModeConfig) {
boolean executing = false;
Map<String, String> loadCaseReportMap = null;
if (MapUtils.isNotEmpty(executeCase)) {
//执行性能测试任务
LoggerUtil.info("开始执行测试计划性能用例 " + testPlanReportId);
try {
redisTemplateService.lock(testPlanReportId, TestPlanExecuteCaseType.LOAD_CASE.name(), testPlanReportId);
loadCaseReportMap = perfExecService.executeLoadCase(testPlanReportId, runModeConfig, testPlanService.transformationPerfTriggerMode(triggerMode), executeCase);
if (MapUtils.isNotEmpty(loadCaseReportMap)) {
executing = true;
}
} catch (Exception e) {
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.LOAD_CASE.name(), testPlanReportId);
LoggerUtil.info("测试报告" + testPlanReportId + "本次执行测试计划性能用例失败! ", e);
}
}
executeResult.setLoadCaseReportMap(loadCaseReportMap);
executeResult.setLoadCaseExecuting(executing);
}
private RunModeConfigDTO getRunModeConfig(String apiRunConfig, String executionWay, String testPlanId) {
RunModeConfigDTO runModeConfig = null;
try {
runModeConfig = JSON.parseObject(apiRunConfig, RunModeConfigDTO.class);
} catch (Exception e) {
LogUtil.error(e);
}
if (runModeConfig == null) {
runModeConfig = this.buildRunModeConfigDTO();
}
//环境参数为空时依据测试计划保存的环境执行
if (((StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && StringUtils.isBlank(runModeConfig.getEnvironmentGroupId()))
|| (!StringUtils.equals("GROUP", runModeConfig.getEnvironmentType()) && MapUtils.isEmpty(runModeConfig.getEnvMap()) && MapUtils.isEmpty(runModeConfig.getTestPlanDefaultEnvMap())))
&& !StringUtils.equals(executionWay, ExecutionWay.RUN.name())) {
TestPlanWithBLOBs testPlanWithBLOBs = testPlanMapper.selectByPrimaryKey(testPlanId);
if (StringUtils.isNotEmpty(testPlanWithBLOBs.getRunModeConfig())) {
try {
Map json = JSON.parseMap(testPlanWithBLOBs.getRunModeConfig());
TestPlanRequestUtil.changeStringToBoolean(json);
TestPlanRunRequest testPlanRunRequest = JSON.parseObject(JSON.toJSONString(json), TestPlanRunRequest.class);
if (testPlanRunRequest != null) {
String envType = testPlanRunRequest.getEnvironmentType();
Map<String, String> envMap = testPlanRunRequest.getEnvMap();
String environmentGroupId = testPlanRunRequest.getEnvironmentGroupId();
runModeConfig = testPlanService.getRunModeConfigDTO(testPlanRunRequest, envType, envMap, environmentGroupId, testPlanId);
runModeConfig.setTestPlanDefaultEnvMap(testPlanRunRequest.getTestPlanDefaultEnvMap());
if (!testPlanRunRequest.isRunWithinResourcePool()) {
runModeConfig.setResourcePoolId(null);
}
}
} catch (Exception e) {
LogUtil.error("获取测试计划保存的环境信息出错!", e);
}
}
}
return runModeConfig;
}
private RunModeConfigDTO buildRunModeConfigDTO() {
RunModeConfigDTO runModeConfig = new RunModeConfigDTO();
runModeConfig.setMode(RunModeConstants.SERIAL.name());
runModeConfig.setReportType("iddReport");
runModeConfig.setEnvMap(new HashMap<>());
runModeConfig.setOnSampleError(false);
return runModeConfig;
}
}
@Data
class CaseExecuteResult {
private boolean apiCaseExecuting;
private boolean scenarioExecuting;
private boolean uiScenarioExecuting;
private boolean loadCaseExecuting;
private List<TestPlanApiDTO> apiCaseDTO;
private List<TestPlanScenarioDTO> scenarioCases;
private List<TestPlanUiScenarioDTO> uiScenarios;
private Map<String, String> loadCaseReportMap;
public boolean isExecuting() {
return apiCaseExecuting || scenarioExecuting || uiScenarioExecuting || loadCaseExecuting;
}
}

View File

@ -1,43 +1,45 @@
<template> <template>
<el-dialog <el-dialog
destroy-on-close destroy-on-close
:title="$t('load_test.runtime_config')" :title="$t('load_test.runtime_config')"
width="550px" width="550px"
style="margin-top: -8.65vh; max-height: 87.3vh" style="margin-top: -8.65vh; max-height: 87.3vh"
@close="close" @close="close"
:visible.sync="runModeVisible" :visible.sync="runModeVisible"
> >
<div class="env-container"> <div class="env-container">
<div> <div>
<div>{{ $t("commons.environment") }}</div> <div>{{ $t("commons.environment") }}</div>
<env-select-popover <env-select-popover
:project-ids="projectIds" :project-ids="projectIds"
:project-list="projectList" :project-list="projectList"
:project-env-map="projectEnvListMap" :project-env-map="projectEnvListMap"
:environment-type.sync="runConfig.environmentType" :environment-type.sync="runConfig.environmentType"
:has-option-group="true" :has-option-group="true"
:group-id="runConfig.environmentGroupId" :is-env-saved="isEnvSaved"
@setProjectEnvMap="setProjectEnvMap" :group-id="runConfig.environmentGroupId"
@setDefaultEnv="setDefaultEnv" @setProjectEnvMap="setProjectEnvMap"
@setEnvGroup="setEnvGroup" @setDefaultEnv="setDefaultEnv"
ref="envSelectPopover" @setEnvGroup="setEnvGroup"
class="mode-row" ref="envSelectPopover"
class="mode-row"
></env-select-popover> ></env-select-popover>
</div> </div>
<div v-if="haveUICase"> <div v-if="haveUICase">
<div>{{ $t("ui.browser") }}</div> <div>{{ $t("ui.browser") }}</div>
<div> <div>
<el-select <el-select
size="mini" size="mini"
v-model="runConfig.browser" v-model="runConfig.browser"
style="width: 100%" style="width: 100%"
class="mode-row" class="mode-row"
> >
<el-option <el-option
v-for="b in browsers" v-for="b in browsers"
:key="b.value" :key="b.value"
:value="b.value" :value="b.value"
:label="b.label" :label="b.label"
></el-option> ></el-option>
</el-select> </el-select>
</div> </div>
@ -46,10 +48,10 @@
<div class="mode-row">{{ $t("run_mode.title") }}</div> <div class="mode-row">{{ $t("run_mode.title") }}</div>
<div> <div>
<el-radio-group <el-radio-group
v-model="runConfig.mode" v-model="runConfig.mode"
@change="changeMode" @change="changeMode"
style="width: 100%" style="width: 100%"
class="radio-change mode-row" class="radio-change mode-row"
> >
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio> <el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
<el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio> <el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio>
@ -59,51 +61,25 @@
<div> <div>
<div class="mode-row">{{ $t("run_mode.other_config") }}</div> <div class="mode-row">{{ $t("run_mode.other_config") }}</div>
<div> <div>
<!-- 串行 --> <!-- 资源池 -->
<div <div
class="mode-row" class="mode-row"
v-if=" v-if="
runConfig.mode === 'serial' &&
testType === 'API' && testType === 'API' &&
haveOtherExecCase (haveOtherExecCase && !haveUICase)
" "
> >
<span>{{ $t("run_mode.run_with_resource_pool") }}: </span> <span>{{ $t("run_mode.run_with_resource_pool") }}: </span>
<el-select <el-select
v-model="runConfig.resourcePoolId" v-model="runConfig.resourcePoolId"
size="mini" size="mini"
style="width: 100%; margin-top: 8px" style="width: 100%; margin-top: 8px"
> >
<el-option <el-option
v-for="item in resourcePools" v-for="item in resourcePools"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
>
</el-option>
</el-select>
</div>
<!-- 并行 -->
<div
class="mode-row"
v-if="
runConfig.mode === 'parallel' &&
testType === 'API' &&
haveOtherExecCase
"
>
<span>{{ $t("run_mode.run_with_resource_pool") }}: </span>
<el-select
v-model="runConfig.resourcePoolId"
size="mini"
style="width: 100%; margin-top: 8px"
>
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:disabled="!item.api"
:value="item.id"
> >
</el-option> </el-option>
</el-select> </el-select>
@ -112,8 +88,8 @@
<!-- 失败重试 --> <!-- 失败重试 -->
<div class="mode-row"> <div class="mode-row">
<el-checkbox <el-checkbox
v-model="runConfig.retryEnable" v-model="runConfig.retryEnable"
class="radio-change ms-failure-div-right" class="radio-change ms-failure-div-right"
> >
{{ $t("run_mode.retry_on_failure") }} {{ $t("run_mode.retry_on_failure") }}
</el-checkbox> </el-checkbox>
@ -121,19 +97,19 @@
<el-tooltip placement="top" style="margin: 0 4px 0 2px"> <el-tooltip placement="top" style="margin: 0 4px 0 2px">
<div slot="content">{{ $t("run_mode.retry_message") }}</div> <div slot="content">{{ $t("run_mode.retry_message") }}</div>
<i <i
class="el-icon-question" class="el-icon-question"
style="cursor: pointer" style="cursor: pointer"
/> </el-tooltip /> </el-tooltip
><br /> ><br/>
<span> <span>
{{ $t("run_mode.retry") }} {{ $t("run_mode.retry") }}
<el-input-number <el-input-number
:value="runConfig.retryNum" :value="runConfig.retryNum"
v-model="runConfig.retryNum" v-model="runConfig.retryNum"
:min="1" :min="1"
:max="10000000" :max="10000000"
size="mini" size="mini"
style="width: 103px; margin-top: 8px" style="width: 103px; margin-top: 8px"
/> />
&nbsp; &nbsp;
{{ $t("run_mode.retry_frequency") }} {{ $t("run_mode.retry_frequency") }}
@ -143,14 +119,14 @@
<div class="mode-row" v-if="runConfig.mode === 'serial'"> <div class="mode-row" v-if="runConfig.mode === 'serial'">
<el-checkbox v-model="runConfig.onSampleError" class="radio-change" <el-checkbox v-model="runConfig.onSampleError" class="radio-change"
>{{ $t("api_test.fail_to_stop") }} >{{ $t("api_test.fail_to_stop") }}
</el-checkbox> </el-checkbox>
</div> </div>
<div class="mode-row" v-if="haveUICase"> <div class="mode-row" v-if="haveUICase">
<el-checkbox <el-checkbox
v-model="runConfig.headlessEnabled" v-model="runConfig.headlessEnabled"
class="radio-change" class="radio-change"
> >
{{ $t("ui.performance_mode") }} {{ $t("ui.performance_mode") }}
</el-checkbox> </el-checkbox>
@ -164,23 +140,24 @@
<el-button @click="close">{{ $t("commons.cancel") }}</el-button> <el-button @click="close">{{ $t("commons.cancel") }}</el-button>
<el-dropdown @command="handleCommand" style="margin-left: 5px"> <el-dropdown @command="handleCommand" style="margin-left: 5px">
<el-button type="primary"> <el-button type="primary">
{{ $t("api_test.run") {{
$t("api_test.run")
}}<i class="el-icon-arrow-down el-icon--right"></i> }}<i class="el-icon-arrow-down el-icon--right"></i>
</el-button> </el-button>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item command="run" <el-dropdown-item command="run"
>{{ $t("api_test.run") }} >{{ $t("api_test.run") }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item command="runAndSave" <el-dropdown-item command="runAndSave"
>{{ $t("load_test.save_and_run") }} >{{ $t("load_test.save_and_run") }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item command="save" <el-dropdown-item command="save"
>{{ $t("commons.save") }} >{{ $t("commons.save") }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</div> </div>
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch" /> <ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch"/>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
@ -220,7 +197,7 @@ export default {
btnStyle: { btnStyle: {
width: "260px", width: "260px",
}, },
result: { loading: false }, result: {loading: false},
runModeVisible: false, runModeVisible: false,
testType: null, testType: null,
resourcePools: [], resourcePools: [],
@ -233,7 +210,7 @@ export default {
resourcePoolId: null, resourcePoolId: null,
envMap: new Map(), envMap: new Map(),
environmentGroupId: "", environmentGroupId: "",
environmentType: ENV_TYPE.JSON, environmentType: ENV_TYPE.DEFAULT,
retryEnable: false, retryEnable: false,
retryNum: 1, retryNum: 1,
browser: "CHROME", browser: "CHROME",
@ -241,6 +218,8 @@ export default {
}, },
projectList: [], projectList: [],
projectIds: new Set(), projectIds: new Set(),
//
isEnvSaved: true,
options: [ options: [
{ {
value: "confirmAndRun", value: "confirmAndRun",
@ -279,6 +258,7 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
//
haveOtherExecCase: { haveOtherExecCase: {
type: Boolean, type: Boolean,
default: true, default: true,
@ -289,11 +269,22 @@ export default {
this.defaultEnvMap = {}; this.defaultEnvMap = {};
if (runModeConfig) { if (runModeConfig) {
this.runConfig = JSON.parse(runModeConfig); this.runConfig = JSON.parse(runModeConfig);
if (!this.runConfig.envMap || JSON.stringify(this.runConfig.envMap) === "{}") {
this.isEnvSaved = false;
this.runConfig.environmentType = ENV_TYPE.DEFAULT;
} else {
this.isEnvSaved = true;
this.runConfig.environmentType = ENV_TYPE.JSON;
}
this.runConfig.envMap = new Map(); this.runConfig.envMap = new Map();
this.runConfig.testPlanDefaultEnvMap = {}; this.runConfig.testPlanDefaultEnvMap = {};
this.runConfig.onSampleError = this.runConfig.onSampleError =
this.runConfig.onSampleError === "true" || this.runConfig.onSampleError === "true" ||
this.runConfig.onSampleError === true; this.runConfig.onSampleError === true;
} else {
this.isEnvSaved = false;
//default
this.runConfig.environmentType = ENV_TYPE.DEFAULT;
} }
this.runModeVisible = true; this.runModeVisible = true;
this.testType = testType; this.testType = testType;
@ -348,7 +339,7 @@ export default {
handleRunBatch() { handleRunBatch() {
if (this.runConfig.resourcePoolId == null && this.haveOtherExecCase) { if (this.runConfig.resourcePoolId == null && this.haveOtherExecCase) {
this.$warning( this.$warning(
this.$t("workspace.env_group.please_select_run_within_resource_pool")); this.$t("workspace.env_group.please_select_run_within_resource_pool"));
return; return;
} }
this.runConfig.testPlanDefaultEnvMap = this.defaultEnvMap; this.runConfig.testPlanDefaultEnvMap = this.defaultEnvMap;
@ -408,7 +399,7 @@ export default {
this.$refs.envSelectPopover.open(); this.$refs.envSelectPopover.open();
}); });
} else if (this.type === "plan") { } else if (this.type === "plan") {
param = { id: this.planId }; param = {id: this.planId};
getPlanCaseEnv(param).then((res) => { getPlanCaseEnv(param).then((res) => {
let data = res.data; let data = res.data;
if (data) { if (data) {
@ -418,7 +409,7 @@ export default {
} }
} }
if (this.projectIds.size === 0) { if (this.projectIds.size === 0) {
param = { id: this.planId }; param = {id: this.planId};
getPlanCaseProjectIds(param).then((res) => { getPlanCaseProjectIds(param).then((res) => {
let data = res.data; let data = res.data;
if (data) { if (data) {
@ -436,11 +427,11 @@ export default {
}, },
handleCommand(command) { handleCommand(command) {
if ( if (
this.runConfig.resourcePoolId == null && this.runConfig.resourcePoolId == null &&
this.haveOtherExecCase this.haveOtherExecCase
) { ) {
this.$warning( this.$warning(
this.$t("workspace.env_group.please_select_run_within_resource_pool") this.$t("workspace.env_group.please_select_run_within_resource_pool")
); );
return; return;
} }
@ -459,11 +450,12 @@ export default {
</script> </script>
<style scoped> <style scoped>
.env-container{ .env-container {
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
padding-bottom: 1px; padding-bottom: 1px;
} }
.env-container .title { .env-container .title {
width: 100px; width: 100px;
min-width: 100px; min-width: 100px;
@ -486,6 +478,7 @@ export default {
.radio-change:deep(.el-radio__input.is-checked + .el-radio__label) { .radio-change:deep(.el-radio__input.is-checked + .el-radio__label) {
color: #606266 !important; color: #606266 !important;
} }
.radio-change:deep(.el-checkbox__input.is-checked + .el-checkbox__label) { .radio-change:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
color: #606266 !important; color: #606266 !important;
} }

View File

@ -1,12 +1,12 @@
<template> <template>
<el-dialog <el-dialog
v-loading="result.loading" v-loading="result.loading"
:close-on-click-modal="false" :close-on-click-modal="false"
width="60%" width="60%"
class="schedule-edit" class="schedule-edit"
:visible.sync="dialogVisible" :visible.sync="dialogVisible"
:append-to-body="true" :append-to-body="true"
@close="close" @close="close"
> >
<template> <template>
<div> <div>
@ -17,32 +17,32 @@
</div> </div>
<span>{{ $t("schedule.edit_timer_task") }}</span> <span>{{ $t("schedule.edit_timer_task") }}</span>
<el-form <el-form
:model="form" :model="form"
:rules="rules" :rules="rules"
ref="from" ref="from"
style="padding-top: 10px; margin-left: 20px" style="padding-top: 10px; margin-left: 20px"
class="ms-el-form-item__error" class="ms-el-form-item__error"
> >
<el-form-item <el-form-item
:label="$t('commons.schedule_cron_title')" :label="$t('commons.schedule_cron_title')"
prop="cronValue" prop="cronValue"
style="height: 50px" style="height: 50px"
> >
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="16"> <el-col :span="16">
<el-input <el-input
:disabled="isReadOnly" :disabled="isReadOnly"
v-model="form.cronValue" v-model="form.cronValue"
class="inp" class="inp"
:placeholder="$t('schedule.please_input_cron_expression')" :placeholder="$t('schedule.please_input_cron_expression')"
size="mini" size="mini"
> >
<a <a
:disabled="isReadOnly" :disabled="isReadOnly"
type="primary" type="primary"
@click="showCronDialog" @click="showCronDialog"
slot="suffix" slot="suffix"
class="head" class="head"
> >
{{ $t("schedule.generate_expression") }} {{ $t("schedule.generate_expression") }}
</a> </a>
@ -50,197 +50,198 @@
<span>{{ this.$t("commons.schedule_switch") }}</span> <span>{{ this.$t("commons.schedule_switch") }}</span>
<el-tooltip <el-tooltip
effect="dark" effect="dark"
placement="bottom" placement="bottom"
:content=" :content="
schedule.enable schedule.enable
? $t('commons.close_schedule') ? $t('commons.close_schedule')
: $t('commons.open_schedule') : $t('commons.open_schedule')
" "
> >
<el-switch <el-switch
v-model="schedule.enable" v-model="schedule.enable"
style="margin-left: 20px" style="margin-left: 20px"
></el-switch> ></el-switch>
</el-tooltip> </el-tooltip>
</el-col> </el-col>
<el-col :span="2"> <el-col :span="2">
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly"
type="primary" type="primary"
@click="saveCron" @click="saveCron"
size="mini" size="mini"
>{{ $t("commons.save") }} >{{ $t("commons.save") }}
</el-button> </el-button>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<crontab-result :ex="form.cronValue" ref="crontabResult" /> <crontab-result :ex="form.cronValue" ref="crontabResult"/>
</el-form> </el-form>
<div class="el-step__icon is-text" style="margin-right: 10px"> <div v-if="haveUICase || haveOtherExecCase">
<div class="el-step__icon-inner">2</div> <div class="el-step__icon is-text" style="margin-right: 10px">
</div> <div class="el-step__icon-inner">2</div>
<span>{{ $t("load_test.runtime_config") }}</span> </div>
<div class="ms-mode-div"> <span>{{ $t("load_test.runtime_config") }}</span>
<span class="ms-mode-span">{{ $t("run_mode.title") }}</span> <div class="ms-mode-div">
<el-radio-group v-model="runConfig.mode" @change="changeMode"> <span class="ms-mode-span">{{ $t("run_mode.title") }}</span>
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio> <el-radio-group v-if="haveUICase || haveOtherExecCase" v-model="runConfig.mode" @change="changeMode">
<el-radio label="parallel" <el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
<el-radio label="parallel"
>{{ $t("run_mode.parallel") }} >{{ $t("run_mode.parallel") }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</div> </div>
<div style="margin-top: 10px"> <div v-if="haveUICase" style="margin-top: 10px">
<span class="ms-mode-span">{{ $t("浏览器") }}</span> <span class="ms-mode-span">{{ $t("浏览器") }}</span>
<el-select <el-select
size="mini" size="mini"
v-model="runConfig.browser" v-model="runConfig.browser"
style="margin-right: 30px; width: 100px" style="margin-right: 30px; width: 100px"
> >
<el-option <el-option
v-for="b in browsers" v-for="b in browsers"
:key="b.value" :key="b.value"
:value="b.value" :value="b.value"
:label="b.label" :label="b.label"
></el-option> ></el-option>
</el-select> </el-select>
</div> </div>
<div class="ms-mode-div" v-if="runConfig.mode === 'serial'"> <div class="ms-mode-div" v-if="(haveUICase || haveOtherExecCase) && runConfig.mode === 'serial'">
<el-row> <el-row>
<el-col :span="3"> <el-col :span="3">
<span class="ms-mode-span" <span class="ms-mode-span"
>{{ $t("run_mode.other_config") }}</span >{{ $t("run_mode.other_config") }}</span
> >
</el-col> </el-col>
<el-col :span="18"> <el-col :span="18">
<div v-if="testType === 'API'"> <div v-if="haveOtherExecCase && testType === 'API'">
<sapn>{{ $t("run_mode.run_with_resource_pool") }}: </sapn> <sapn>{{ $t("run_mode.run_with_resource_pool") }}:</sapn>
<el-select <el-select
v-model="runConfig.resourcePoolId" v-model="runConfig.resourcePoolId"
size="mini" size="mini"
>
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:value="item.id"
> >
</el-option> <el-option
</el-select> v-for="item in resourcePools"
</div> :key="item.id"
</el-col> :label="item.name"
</el-row> :value="item.id"
</div> >
<div class="ms-mode-div" v-if="runConfig.mode === 'parallel'"> </el-option>
<el-row> </el-select>
<el-col :span="3"> </div>
</el-col>
</el-row>
</div>
<div class="ms-mode-div" v-if="(haveUICase || haveOtherExecCase) && runConfig.mode === 'parallel'">
<el-row>
<el-col :span="3">
<span class="ms-mode-span" <span class="ms-mode-span"
>{{ $t("run_mode.other_config") }}</span >{{ $t("run_mode.other_config") }}</span
> >
</el-col> </el-col>
<el-col :span="18"> <el-col :span="18">
<div v-if="testType === 'API'"> <div v-if="haveOtherExecCase && testType === 'API'">
<span> <span>
{{ $t("run_mode.run_with_resource_pool") }} : {{ $t("run_mode.run_with_resource_pool") }} :
</span> </span>
<el-select <el-select
v-model="runConfig.resourcePoolId" v-model="runConfig.resourcePoolId"
size="mini" size="mini"
>
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:disabled="!item.api"
:value="item.id"
> >
</el-option> <el-option
</el-select> v-for="item in resourcePools"
</div> :key="item.id"
</el-col> :label="item.name"
</el-row> :disabled="!item.api"
</div> :value="item.id"
>
</el-option>
</el-select>
</div>
</el-col>
</el-row>
</div>
<!-- 失败重试 --> <!-- 失败重试 -->
<div class="ms-mode-div"> <div class="ms-mode-div">
<el-row> <el-row>
<el-col :span="3"> <el-col :span="3">
<span class="ms-mode-span">&nbsp;</span> <span class="ms-mode-span">&nbsp;</span>
</el-col> </el-col>
<el-col :span="18"> <el-col :span="18">
<el-checkbox <el-checkbox
v-model="runConfig.retryEnable" v-model="runConfig.retryEnable"
class="ms-failure-div-right" class="ms-failure-div-right"
> >
{{ $t("run_mode.retry_on_failure") }} {{ $t("run_mode.retry_on_failure") }}
</el-checkbox> </el-checkbox>
<span v-if="runConfig.retryEnable"> <span v-if="runConfig.retryEnable">
<el-tooltip placement="top"> <el-tooltip placement="top">
<div slot="content"> <div slot="content">
{{ $t("run_mode.retry_message") }} {{ $t("run_mode.retry_message") }}
</div> </div>
<i class="el-icon-question" style="cursor: pointer" /> <i class="el-icon-question" style="cursor: pointer"/>
</el-tooltip> </el-tooltip>
<span> <span>
{{ $t("run_mode.retry") }} {{ $t("run_mode.retry") }}
<el-input-number <el-input-number
:value="runConfig.retryNum" :value="runConfig.retryNum"
v-model="runConfig.retryNum" v-model="runConfig.retryNum"
:min="1" :min="1"
:max="10000000" :max="10000000"
size="mini" size="mini"
/> />
&nbsp; &nbsp;
{{ $t("run_mode.retry_frequency") }} {{ $t("run_mode.retry_frequency") }}
</span> </span>
</span> </span>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<div class="ms-failure-div" v-if="runConfig.mode === 'serial'"> <div class="ms-failure-div" v-if="runConfig.mode === 'serial'">
<el-row> <el-row>
<el-col :span="18" :offset="3"> <el-col :span="18" :offset="3">
<div> <div>
<el-checkbox v-model="runConfig.onSampleError" <el-checkbox v-model="runConfig.onSampleError"
>{{ $t("api_test.fail_to_stop") }} >{{ $t("api_test.fail_to_stop") }}
</el-checkbox> </el-checkbox>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</div>
<div v-if="haveUICase">
<el-row>
<el-col :span="3"> &nbsp;</el-col>
<el-col :span="18">
<div style="margin-top: 10px">
<el-checkbox v-model="runConfig.headlessEnabled">
{{ $t("性能模式") }}
</el-checkbox>
</div>
</el-col>
</el-row>
</div>
</div> </div>
<div>
<el-row>
<el-col :span="3"> &nbsp;</el-col>
<el-col :span="18">
<div style="margin-top: 10px">
<el-checkbox v-model="runConfig.headlessEnabled">
{{ $t("性能模式") }}
</el-checkbox>
</div>
</el-col>
</el-row>
</div>
<el-dialog <el-dialog
width="60%" width="60%"
:title="$t('schedule.generate_expression')" :title="$t('schedule.generate_expression')"
:visible.sync="showCron" :visible.sync="showCron"
:modal="false" :modal="false"
> >
<crontab <crontab
@hide="showCron = false" @hide="showCron = false"
@fill="crontabFill" @fill="crontabFill"
:expression="schedule.value" :expression="schedule.value"
ref="crontab" ref="crontab"
/> />
</el-dialog> </el-dialog>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('schedule.task_notification')" name="second" <el-tab-pane :label="$t('schedule.task_notification')" name="second"
v-permission="['PROJECT_MESSAGE:READ']"> v-permission="['PROJECT_MESSAGE:READ']">
<ms-schedule-notification <ms-schedule-notification
:test-id="testId" :test-id="testId"
:schedule-receiver-options="scheduleReceiverOptions" :schedule-receiver-options="scheduleReceiverOptions"
/> />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -250,21 +251,14 @@
</template> </template>
<script> <script>
import { import {getCurrentProjectID, getCurrentUser, getCurrentWorkspaceId,} from "metersphere-frontend/src/utils/token";
getCurrentProjectID, import {listenGoBack, removeGoBackListener,} from "metersphere-frontend/src/utils";
getCurrentUser,
getCurrentWorkspaceId,
} from "metersphere-frontend/src/utils/token";
import {
listenGoBack,
removeGoBackListener,
} from "metersphere-frontend/src/utils";
import Crontab from "metersphere-frontend/src/components/cron/Crontab"; import Crontab from "metersphere-frontend/src/components/cron/Crontab";
import CrontabResult from "metersphere-frontend/src/components/cron/CrontabResult"; import CrontabResult from "metersphere-frontend/src/components/cron/CrontabResult";
import { cronValidate } from "metersphere-frontend/src/utils/cron"; import {cronValidate} from "metersphere-frontend/src/utils/cron";
import MsScheduleNotification from "./ScheduleNotification"; import MsScheduleNotification from "./ScheduleNotification";
import ScheduleSwitch from "./ScheduleSwitch"; import ScheduleSwitch from "./ScheduleSwitch";
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants"; import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import MxNotification from "metersphere-frontend/src/components/MxNoticeTemplate"; import MxNotification from "metersphere-frontend/src/components/MxNoticeTemplate";
import { import {
createSchedule, createSchedule,
@ -272,14 +266,13 @@ import {
updateSchedule, updateSchedule,
updateScheduleEnableByPrimyKey, updateScheduleEnableByPrimyKey,
} from "@/api/remote/plan/test-plan"; } from "@/api/remote/plan/test-plan";
import { saveNotice } from "@/api/notice"; import {saveNotice} from "@/api/notice";
import { getProjectMember } from "@/api/user"; import {getProjectMember} from "@/api/user";
import { getQuotaValidResourcePools } from "@/api/remote/resource-pool"; import {getQuotaValidResourcePools} from "@/api/remote/resource-pool";
import { getProjectConfig } from "@/api/project"; import {getProjectConfig} from "@/api/project";
import { getSystemBaseSetting } from "metersphere-frontend/src/api/system";
function defaultCustomValidate() { function defaultCustomValidate() {
return { pass: true }; return {pass: true};
} }
export default { export default {
@ -308,6 +301,11 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
//
haveOtherExecCase: {
type: Boolean,
default: true,
},
}, },
watch: { watch: {
@ -351,7 +349,7 @@ export default {
activeName: "first", activeName: "first",
rules: { rules: {
cronValue: [ cronValue: [
{ required: true, validator: validateCron, trigger: "blur" }, {required: true, validator: validateCron, trigger: "blur"},
], ],
}, },
resourcePools: [], resourcePools: [],
@ -382,7 +380,7 @@ export default {
}; };
}, },
methods: { methods: {
async checkPool(){ async checkPool() {
let hasPool = false; let hasPool = false;
this.resourcePools.forEach(item => { this.resourcePools.forEach(item => {
if (item.id === this.runConfig.resourcePoolId) { if (item.id === this.runConfig.resourcePoolId) {
@ -454,22 +452,22 @@ export default {
async findSchedule() { async findSchedule() {
let scheduleResourceID = this.testId; let scheduleResourceID = this.testId;
this.result = getPlanSchedule(scheduleResourceID, "TEST_PLAN_TEST").then( this.result = getPlanSchedule(scheduleResourceID, "TEST_PLAN_TEST").then(
(response) => { (response) => {
if (response.data != null) { if (response.data != null) {
this.schedule = response.data; this.schedule = response.data;
if (response.data.config) { if (response.data.config) {
this.runConfig = JSON.parse(response.data.config); this.runConfig = JSON.parse(response.data.config);
if (this.runConfig.environmentType) { if (this.runConfig.environmentType) {
delete this.runConfig.environmentType; delete this.runConfig.environmentType;
}
} }
} else {
this.schedule = {
value: "",
enable: false,
};
} }
} else {
this.schedule = {
value: "",
enable: false,
};
} }
}
); );
}, },
crontabFill(value, resultList) { crontabFill(value, resultList) {
@ -492,10 +490,10 @@ export default {
}, },
saveCron() { saveCron() {
if ( if (
this.runConfig.resourcePoolId == null this.runConfig.resourcePoolId == null
) { ) {
this.$warning( this.$warning(
this.$t("workspace.env_group.please_select_run_within_resource_pool") this.$t("workspace.env_group.please_select_run_within_resource_pool")
); );
return; return;
} }
@ -525,10 +523,10 @@ export default {
param.workspaceId = getCurrentWorkspaceId(); param.workspaceId = getCurrentWorkspaceId();
} }
if ( if (
this.runConfig.resourcePoolId == null this.runConfig.resourcePoolId == null
) { ) {
this.$warning( this.$warning(
this.$t("workspace.env_group.please_select_run_within_resource_pool") this.$t("workspace.env_group.please_select_run_within_resource_pool")
); );
return; return;
} }
@ -636,7 +634,7 @@ export default {
border-bottom: 1px solid var(--primary_color); border-bottom: 1px solid var(--primary_color);
color: var(--primary_color); color: var(--primary_color);
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
Arial, sans-serif; Arial, sans-serif;
font-size: 13px; font-size: 13px;
cursor: pointer; cursor: pointer;
} }

View File

@ -1,79 +1,89 @@
<template> <template>
<div> <div>
<!-- {{ JSON.stringify(eventData) }}-->
<el-radio-group <el-radio-group
v-model="radio" v-model="radio"
style="width: 100%" style="width: 100%"
@change="radioChange" @change="radioChange"
class="radio-change" class="radio-change"
> >
<el-radio v-show="!isEnvSaved" :label="ENV_TYPE.DEFAULT">{{
$t("workspace.env_group.case_env")
}}
</el-radio>
<el-radio :label="ENV_TYPE.JSON">{{ <el-radio :label="ENV_TYPE.JSON">{{
$t("workspace.env_group.env_list") $t("workspace.env_group.env_list")
}}</el-radio> }}
</el-radio>
<el-radio :label="ENV_TYPE.GROUP" v-if="showEnvGroup" <el-radio :label="ENV_TYPE.GROUP" v-if="showEnvGroup"
>{{ $t("workspace.env_group.name") >{{
$t("workspace.env_group.name")
}}<i class="el-icon-tickets mode-span" @click="viewGroup"></i }}<i class="el-icon-tickets mode-span" @click="viewGroup"></i
></el-radio> ></el-radio>
</el-radio-group> </el-radio-group>
<div <div
v-for="(pe, pIndex) in eventData" v-for="(pe, pIndex) in eventData"
:key="pe.id" :key="pe.id"
v-show="radio === ENV_TYPE.JSON" v-show="radio === ENV_TYPE.JSON"
> >
<el-card <el-card
shadow="never" shadow="never"
style="margin-top: 8px; background: #f5f6f7; border-radius: 4px" style="margin-top: 8px; background: #f5f6f7; border-radius: 4px"
> >
<i <i
@click="expandCard(pIndex)" @click="expandCard(pIndex)"
v-if="pe.expendStatus === 'close'" v-if="pe.expendStatus === 'close'"
class="el-icon-caret-right" class="el-icon-caret-right"
style="color: var(--primary_color)" style="color: var(--primary_color)"
/> />
<i <i
@click="expandCard(pIndex)" @click="expandCard(pIndex)"
v-else v-else
class="el-icon-caret-bottom" class="el-icon-caret-bottom"
style="color: var(--primary_color)" style="color: var(--primary_color)"
/> />
<span class="project-name" :title="getProjectName(pe.id)"> <span class="project-name" :title="getProjectName(pe.id)">
{{ getProjectName(pe.id) }} </span {{ getProjectName(pe.id) }} </span
><br /> ><br/>
<div v-if="pe.expendStatus === 'open'"> <div v-if="pe.expendStatus === 'open'">
<el-radio-group <el-radio-group
v-model="pe.envRadio" v-model="pe.envRadio"
style="width: 100%" style="width: 100%"
@change="envRadioChange(pe.envRadio, pIndex)" @change="envRadioChange(pe.envRadio, pIndex)"
class="radio-change" class="radio-change"
> >
<el-radio label="DEFAULT_ENV" style="margin-top: 7px">{{ <el-radio label="DEFAULT_ENV" style="margin-top: 7px">{{
$t("api_test.environment.default_environment") $t("api_test.environment.default_environment")
}}</el-radio> }}
</el-radio>
<el-radio label="CUSTOMIZE_ENV" style="margin-top: 7px">{{ <el-radio label="CUSTOMIZE_ENV" style="margin-top: 7px">{{
$t("api_test.environment.choose_new_environment") $t("api_test.environment.choose_new_environment")
}}</el-radio> }}
</el-radio>
</el-radio-group> </el-radio-group>
<el-tag <el-tag
v-show="!pe.showEnvSelect" v-show="!pe.showEnvSelect"
v-for="(itemName, index) in selectedEnvName.get(pe.id)" v-for="(itemName, index) in selectedEnvName.get(pe.id)"
:key="index" :key="index"
size="mini" size="mini"
style="margin-left: 0; margin-right: 2px; margin-top: 8px" style="margin-left: 0; margin-right: 2px; margin-top: 8px"
>{{ itemName }}</el-tag >{{ itemName }}
</el-tag
> >
<el-select <el-select
v-show="pe.showEnvSelect" v-show="pe.showEnvSelect"
v-model="pe['selectEnv']" v-model="pe['selectEnv']"
filterable filterable
:placeholder="$t('api_test.environment.select_environment')" :placeholder="$t('api_test.environment.select_environment')"
style="margin-top: 8px; width: 100%" style="margin-top: 8px; width: 100%"
size="small" size="small"
@change="chooseEnv" @change="chooseEnv"
> >
<el-option <el-option
v-for="(environment, index) in pe.envs" v-for="(environment, index) in pe.envs"
:key="index" :key="index"
:label="environment.name" :label="environment.name"
:value="environment.id" :value="environment.id"
/> />
</el-select> </el-select>
</div> </div>
@ -82,57 +92,57 @@
<div v-show="radio === ENV_TYPE.GROUP"> <div v-show="radio === ENV_TYPE.GROUP">
<div> <div>
<el-select <el-select
v-show="!hasOptionGroup" v-show="!hasOptionGroup"
v-model="envGroupId" v-model="envGroupId"
:placeholder="$t('workspace.env_group.select')" :placeholder="$t('workspace.env_group.select')"
@change="chooseEnvGroup" @change="chooseEnvGroup"
style="margin-top: 8px; width: 100%" style="margin-top: 8px; width: 100%"
size="small" size="small"
> >
<el-option <el-option
v-for="(group, index) in groups" v-for="(group, index) in groups"
:key="index" :key="index"
:label="group.name" :label="group.name"
:value="group.id" :value="group.id"
/> />
</el-select> </el-select>
<el-select <el-select
v-show="hasOptionGroup" v-show="hasOptionGroup"
v-model="envGroupId" v-model="envGroupId"
:placeholder="$t('workspace.env_group.select')" :placeholder="$t('workspace.env_group.select')"
style="margin-top: 8px; width: 100%" style="margin-top: 8px; width: 100%"
@change="chooseEnvGroup" @change="chooseEnvGroup"
size="small" size="small"
clearable clearable
> >
<el-option-group <el-option-group
v-for="group in groups" v-for="group in groups"
:key="group.label" :key="group.label"
:label="group.label" :label="group.label"
> >
<el-option <el-option
v-for="item in group.options" v-for="item in group.options"
:key="item.name" :key="item.name"
:label="item.name" :label="item.name"
:disabled="item.disabled" :disabled="item.disabled"
:value="item.id" :value="item.id"
> >
</el-option> </el-option>
</el-option-group> </el-option-group>
</el-select> </el-select>
</div> </div>
<el-dialog <el-dialog
:visible="visible" :visible="visible"
append-to-body append-to-body
:title="$t('workspace.env_group.name')" :title="$t('workspace.env_group.name')"
@close="visible = false" @close="visible = false"
style="height: 800px" style="height: 800px"
> >
<template> <template>
<environment-group <environment-group
style="overflow-y: auto" style="overflow-y: auto"
:screen-height="'350px'" :screen-height="'350px'"
:read-only="true" :read-only="true"
></environment-group> ></environment-group>
</template> </template>
</el-dialog> </el-dialog>
@ -141,19 +151,16 @@
</template> </template>
<script> <script>
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants"; import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import { import {environmentGetALL, getEnvironmentOptions,} from "metersphere-frontend/src/api/environment";
environmentGetALL,
getEnvironmentOptions,
} from "metersphere-frontend/src/api/environment";
import MsTag from "metersphere-frontend/src/components/MsTag"; import MsTag from "metersphere-frontend/src/components/MsTag";
import EnvironmentGroup from "@/business/plan/env/EnvironmentGroupList"; import EnvironmentGroup from "@/business/plan/env/EnvironmentGroupList";
import { getEnvironmentByProjectId } from "@/api/remote/api/api-environment"; import {getEnvironmentByProjectId} from "@/api/remote/api/api-environment";
import { parseEnvironment } from "metersphere-frontend/src/model/EnvironmentModel"; import {parseEnvironment} from "metersphere-frontend/src/model/EnvironmentModel";
export default { export default {
name: "EnvSelectPopover", name: "EnvSelectPopover",
components: { MsTag, EnvironmentGroup }, components: {MsTag, EnvironmentGroup},
data() { data() {
return { return {
radio: this.environmentType, radio: this.environmentType,
@ -189,6 +196,11 @@ export default {
}, },
projectIds: Set, projectIds: Set,
projectList: Array, projectList: Array,
//
isEnvSaved: {
type: Boolean,
default: true,
},
projectEnvMap: Object, projectEnvMap: Object,
envMap: Map, envMap: Map,
environmentType: String, environmentType: String,
@ -221,7 +233,7 @@ export default {
envRadioChange(val, index) { envRadioChange(val, index) {
this.eventData[index].envRadio = val; this.eventData[index].envRadio = val;
this.eventData[index].showEnvSelect = this.eventData[index].showEnvSelect =
this.eventData[index].envRadio === "CUSTOMIZE_ENV"; this.eventData[index].envRadio === "CUSTOMIZE_ENV";
}, },
viewGroup() { viewGroup() {
this.visible = true; this.visible = true;
@ -233,24 +245,24 @@ export default {
this.groups = data ? data : []; this.groups = data ? data : [];
}); });
} else { } else {
getEnvironmentOptions({ projectIds: [...this.projectIds] }).then( getEnvironmentOptions({projectIds: [...this.projectIds]}).then(
(res) => { (res) => {
let groups = res.data; let groups = res.data;
this.disabledGroups = groups.filter( this.disabledGroups = groups.filter(
(group) => group.disabled === true (group) => group.disabled === true
); );
this.notDisabledGroups = groups.filter( this.notDisabledGroups = groups.filter(
(group) => group.disabled === false (group) => group.disabled === false
); );
this.$set(this.groups, 0, { this.$set(this.groups, 0, {
label: this.$t("workspace.env_group.available_group"), label: this.$t("workspace.env_group.available_group"),
options: this.notDisabledGroups, options: this.notDisabledGroups,
}); });
this.$set(this.groups, 1, { this.$set(this.groups, 1, {
label: this.$t("workspace.env_group.not_available_group"), label: this.$t("workspace.env_group.not_available_group"),
options: this.disabledGroups, options: this.disabledGroups,
}); });
} }
); );
} }
}, },
@ -291,11 +303,11 @@ export default {
let envId = this.envMap.get(id); let envId = this.envMap.get(id);
// //
temp.selectEnv = temp.selectEnv =
envs.filter((e) => e.id === envId).length === 0 ? null : envId; envs.filter((e) => e.id === envId).length === 0 ? null : envId;
} }
if ( if (
this.projectEnvMap && this.projectEnvMap &&
Object.keys(this.projectEnvMap).length > 0 Object.keys(this.projectEnvMap).length > 0
) { ) {
let projectEnvMapElement = this.projectEnvMap[d]; let projectEnvMapElement = this.projectEnvMap[d];
if (projectEnvMapElement.length > 0) { if (projectEnvMapElement.length > 0) {

View File

@ -1,71 +1,71 @@
<template> <template>
<test-case-relevance-base <test-case-relevance-base
@setProject="setProject" @setProject="setProject"
@save="saveCaseRelevance" @save="saveCaseRelevance"
@close="close" @close="close"
:plan-id="planId" :plan-id="planId"
:is-saving="isSaving" :is-saving="isSaving"
ref="baseRelevance" ref="baseRelevance"
> >
<template v-slot:aside> <template v-slot:aside>
<ms-api-module <ms-api-module
class="node-tree" class="node-tree"
:relevance-project-id="projectId" :relevance-project-id="projectId"
@nodeSelectEvent="nodeChange" @nodeSelectEvent="nodeChange"
@protocolChange="handleProtocolChange" @protocolChange="handleProtocolChange"
@refreshTable="refresh" @refreshTable="refresh"
@setModuleOptions="setModuleOptions" @setModuleOptions="setModuleOptions"
:show-case-num="false" :show-case-num="false"
:is-read-only="true" :is-read-only="true"
:is-relevance="true" :is-relevance="true"
ref="nodeTree" ref="nodeTree"
/> />
</template> </template>
<relevance-api-list <relevance-api-list
v-if="isApiListEnable" v-if="isApiListEnable"
:current-protocol="currentProtocol" :current-protocol="currentProtocol"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable" :is-api-list-enable="isApiListEnable"
:project-id="projectId" :project-id="projectId"
:is-test-plan="true" :is-test-plan="true"
:plan-id="planId" :plan-id="planId"
:versionFilters="versionFilters" :versionFilters="versionFilters"
:version-enable="versionEnable" :version-enable="versionEnable"
@isApiListEnableChange="isApiListEnableChange" @isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts" @selectCountChange="setSelectCounts"
ref="apiList" ref="apiList"
> >
<template v-slot:version> <template v-slot:version>
<mx-version-select <mx-version-select
v-xpack v-xpack
:project-id="projectId" :project-id="projectId"
@changeVersion="changeVersion($event, 'api')" @changeVersion="changeVersion($event, 'api')"
margin-left="10" margin-left="10"
/> />
</template> </template>
</relevance-api-list> </relevance-api-list>
<relevance-case-list <relevance-case-list
v-if="!isApiListEnable" v-if="!isApiListEnable"
:current-protocol="currentProtocol" :current-protocol="currentProtocol"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable" :is-api-list-enable="isApiListEnable"
:project-id="projectId" :project-id="projectId"
:is-test-plan="true" :is-test-plan="true"
:versionFilters="versionFilters" :versionFilters="versionFilters"
:version-enable="versionEnable" :version-enable="versionEnable"
:plan-id="planId" :plan-id="planId"
@isApiListEnableChange="isApiListEnableChange" @isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts" @selectCountChange="setSelectCounts"
ref="apiCaseList" ref="apiCaseList"
> >
<template v-slot:version> <template v-slot:version>
<mx-version-select <mx-version-select
v-xpack v-xpack
:project-id="projectId" :project-id="projectId"
@changeVersion="changeVersion($event, 'case')" @changeVersion="changeVersion($event, 'case')"
margin-left="10" margin-left="10"
/> />
</template> </template>
</relevance-case-list> </relevance-case-list>
@ -75,18 +75,12 @@
<script> <script>
import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase"; import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase";
import MxVersionSelect from "metersphere-frontend/src/components/version/MxVersionSelect"; import MxVersionSelect from "metersphere-frontend/src/components/version/MxVersionSelect";
import { import {apiDefinitionListBatch, apiDefinitionRelevance,} from "@/api/remote/api/api-definition";
apiDefinitionListBatch, import {apiTestCaseListBlobs, apiTestCaseRelevance,} from "@/api/remote/api/api-case";
apiDefinitionRelevance,
} from "@/api/remote/api/api-definition";
import {
apiTestCaseListBlobs,
apiTestCaseRelevance,
} from "@/api/remote/api/api-case";
import RelevanceApiList from "@/business/plan/view/comonents/api/RelevanceApiList"; import RelevanceApiList from "@/business/plan/view/comonents/api/RelevanceApiList";
import RelevanceCaseList from "@/business/plan/view/comonents/api/RelevanceCaseList"; import RelevanceCaseList from "@/business/plan/view/comonents/api/RelevanceCaseList";
import MsApiModule from "@/business/plan/view/comonents/api/module/ApiModule"; import MsApiModule from "@/business/plan/view/comonents/api/module/ApiModule";
import { getVersionFilters } from "@/business/utils/sdk-utils"; import {getVersionFilters} from "@/business/utils/sdk-utils";
export default { export default {
name: "TestCaseApiRelevance", name: "TestCaseApiRelevance",
@ -206,51 +200,44 @@ export default {
// //
let params = this.$refs.apiList.getConditions(); let params = this.$refs.apiList.getConditions();
apiDefinitionListBatch(params) apiDefinitionListBatch(params)
.then((response) => { .then((response) => {
let apis = response.data; let apis = response.data;
environmentId = this.$refs.apiList.environmentId; environmentId = this.$refs.apiList.environmentId;
selectIds = Array.from(apis).map((row) => row.id); selectIds = Array.from(apis).map((row) => row.id);
let protocol = this.$refs.apiList.currentProtocol; let protocol = this.$refs.apiList.currentProtocol;
this.postRelevance( this.postRelevance(
apiDefinitionRelevance, apiDefinitionRelevance,
environmentId, environmentId,
selectIds, selectIds,
protocol protocol
); );
}) })
.catch(() => { .catch(() => {
this.isSaving = false; this.isSaving = false;
}); });
} else { } else {
let params = this.$refs.apiCaseList.getConditions(); let params = this.$refs.apiCaseList.getConditions();
apiTestCaseListBlobs(params) apiTestCaseListBlobs(params)
.then((response) => { .then((response) => {
let apiCases = response.data; let apiCases = response.data;
environmentId = this.$refs.apiCaseList.environmentId; environmentId = this.$refs.apiCaseList.environmentId;
selectIds = Array.from(apiCases).map((row) => row.id); selectIds = Array.from(apiCases).map((row) => row.id);
let protocol = this.$refs.apiCaseList.currentProtocol; let protocol = this.$refs.apiCaseList.currentProtocol;
this.postRelevance( this.postRelevance(
apiTestCaseRelevance, apiTestCaseRelevance,
environmentId, environmentId,
selectIds, selectIds,
protocol protocol
); );
}) })
.catch(() => { .catch(() => {
this.isSaving = false; this.isSaving = false;
}); });
} }
}, },
postRelevance(relevanceList, environmentId, selectIds, protocol) { postRelevance(relevanceList, environmentId, selectIds, protocol) {
let param = {}; let param = {};
if (protocol !== "DUBBO") {
if (!environmentId) {
this.isSaving = false;
this.$warning(this.$t("api_test.environment.select_environment"));
return;
}
}
if (selectIds.length < 1) { if (selectIds.length < 1) {
this.isSaving = false; this.isSaving = false;
this.$warning(this.$t("test_track.plan_view.please_choose_test_case")); this.$warning(this.$t("test_track.plan_view.please_choose_test_case"));
@ -260,19 +247,19 @@ export default {
param.selectIds = selectIds; param.selectIds = selectIds;
param.environmentId = environmentId; param.environmentId = environmentId;
relevanceList(param) relevanceList(param)
.then(() => { .then(() => {
this.$success(this.$t("plan.relevance_case_success")); this.$success(this.$t("plan.relevance_case_success"));
this.$emit("refresh"); this.$emit("refresh");
this.refresh(); this.refresh();
this.isSaving = false; this.isSaving = false;
}) })
.catch(() => { .catch(() => {
this.isSaving = false; this.isSaving = false;
}); });
}, },
getVersionOptions() { getVersionOptions() {
getVersionFilters(this.projectId).then( getVersionFilters(this.projectId).then(
(r) => (this.versionFilters = r.data) (r) => (this.versionFilters = r.data)
); );
}, },
changeVersion(currentVersion, type) { changeVersion(currentVersion, type) {

View File

@ -1,33 +1,33 @@
<template> <template>
<test-case-relevance-base <test-case-relevance-base
@setProject="setProject" @setProject="setProject"
@save="saveCaseRelevance" @save="saveCaseRelevance"
@close="close" @close="close"
:plan-id="planId" :plan-id="planId"
ref="baseRelevance" ref="baseRelevance"
:is-saving="isSaving" :is-saving="isSaving"
> >
<template v-slot:aside> <template v-slot:aside>
<ms-api-scenario-module <ms-api-scenario-module
class="node-tree" class="node-tree"
@nodeSelectEvent="nodeChange" @nodeSelectEvent="nodeChange"
@refreshTable="refresh" @refreshTable="refresh"
@setModuleOptions="setModuleOptions" @setModuleOptions="setModuleOptions"
:show-case-num="false" :show-case-num="false"
:relevance-project-id="projectId" :relevance-project-id="projectId"
:is-read-only="true" :is-read-only="true"
ref="nodeTree" ref="nodeTree"
/> />
</template> </template>
<relevance-scenario-list <relevance-scenario-list
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:trash-enable="trashEnable" :trash-enable="trashEnable"
:version-enable="versionEnable" :version-enable="versionEnable"
:plan-id="planId" :plan-id="planId"
:project-id="projectId" :project-id="projectId"
@selectCountChange="setSelectCounts" @selectCountChange="setSelectCounts"
ref="apiScenarioList" ref="apiScenarioList"
/> />
</test-case-relevance-base> </test-case-relevance-base>
</template> </template>
@ -35,15 +35,10 @@
<script> <script>
import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase"; import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase";
import RelevanceScenarioList from "./RelevanceScenarioList"; import RelevanceScenarioList from "./RelevanceScenarioList";
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants"; import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import { import {getCurrentProjectID, getVersionFilters, hasLicense, strMapToObj,} from "@/business/utils/sdk-utils";
getCurrentProjectID, import {testPlanAutoCheck} from "@/api/remote/plan/test-plan";
hasLicense, import {scenarioRelevance} from "@/api/remote/plan/test-plan-scenario";
strMapToObj,
} from "@/business/utils/sdk-utils";
import { getVersionFilters } from "@/business/utils/sdk-utils";
import { testPlanAutoCheck } from "@/api/remote/plan/test-plan";
import { scenarioRelevance } from "@/api/remote/plan/test-plan-scenario";
import MsApiScenarioModule from "@/business/plan/view/comonents/api/module/ApiScenarioModule"; import MsApiScenarioModule from "@/business/plan/view/comonents/api/module/ApiScenarioModule";
export default { export default {
@ -137,11 +132,6 @@ export default {
}, },
async saveCaseRelevance() { async saveCaseRelevance() {
this.isSaving = true; this.isSaving = true;
const sign = await this.$refs.apiScenarioList.checkEnv();
if (!sign) {
this.isSaving = false;
return false;
}
let selectIds = []; let selectIds = [];
let selectRows = this.$refs.apiScenarioList.selectRows; let selectRows = this.$refs.apiScenarioList.selectRows;
const envMap = this.$refs.apiScenarioList.projectEnvMap; const envMap = this.$refs.apiScenarioList.projectEnvMap;
@ -157,15 +147,7 @@ export default {
selectRows.forEach((row) => { selectRows.forEach((row) => {
selectIds.push(row.id); selectIds.push(row.id);
}); });
if (envType === ENV_TYPE.JSON && (!envMap || envMap.size < 1)) {
this.isSaving = false;
this.$warning(this.$t("api_test.environment.select_environment"));
return false;
} else if (envType === ENV_TYPE.GROUP && !envGroupId) {
this.isSaving = false;
this.$warning(this.$t("api_test.environment.select_environment"));
return false;
}
let param = {}; let param = {};
param.planId = this.planId; param.planId = this.planId;
param.mapping = strMapToObj(map); param.mapping = strMapToObj(map);
@ -175,16 +157,16 @@ export default {
param.ids = selectIds; param.ids = selectIds;
param.condition = this.$refs.apiScenarioList.condition; param.condition = this.$refs.apiScenarioList.condition;
scenarioRelevance(param) scenarioRelevance(param)
.then(() => { .then(() => {
this.isSaving = false; this.isSaving = false;
this.$success(this.$t("plan.relevance_case_success")); this.$success(this.$t("plan.relevance_case_success"));
this.$emit("refresh"); this.$emit("refresh");
this.refresh(); this.refresh();
this.autoCheckStatus(); this.autoCheckStatus();
}) })
.catch(() => { .catch(() => {
this.isSaving = false; this.isSaving = false;
}); });
}, },
autoCheckStatus() { autoCheckStatus() {
// //
@ -199,7 +181,7 @@ export default {
getVersionOptions() { getVersionOptions() {
if (hasLicense()) { if (hasLicense()) {
getVersionFilters(getCurrentProjectID()).then( getVersionFilters(getCurrentProjectID()).then(
(r) => (this.versionFilters = r.data) (r) => (this.versionFilters = r.data)
); );
} }
}, },

View File

@ -3,16 +3,16 @@
<ms-test-plan-common-component> <ms-test-plan-common-component>
<template v-slot:aside> <template v-slot:aside>
<ms-api-module <ms-api-module
v-if="model === 'api'" v-if="model === 'api'"
@nodeSelectEvent="nodeChange" @nodeSelectEvent="nodeChange"
@protocolChange="handleProtocolChange" @protocolChange="handleProtocolChange"
@refreshTable="refreshTable" @refreshTable="refreshTable"
@setModuleOptions="setModuleOptions" @setModuleOptions="setModuleOptions"
:plan-id="planId" :plan-id="planId"
:plan-status="planStatus" :plan-status="planStatus"
:is-read-only="true" :is-read-only="true"
:redirectCharType="redirectCharType" :redirectCharType="redirectCharType"
ref="apiNodeTree"> ref="apiNodeTree">
<template v-slot:header> <template v-slot:header>
<div class="model-change-radio"> <div class="model-change-radio">
<el-radio v-model="model" label="api">{{ $t('commons.api_case') }}</el-radio> <el-radio v-model="model" label="api">{{ $t('commons.api_case') }}</el-radio>
@ -22,14 +22,14 @@
</ms-api-module> </ms-api-module>
<ms-api-scenario-module <ms-api-scenario-module
v-if="model === 'scenario'" v-if="model === 'scenario'"
@nodeSelectEvent="nodeChange" @nodeSelectEvent="nodeChange"
@refreshTable="refreshTable" @refreshTable="refreshTable"
@setModuleOptions="setModuleOptions" @setModuleOptions="setModuleOptions"
:is-read-only="true" :is-read-only="true"
:plan-id="planId" :plan-id="planId"
:plan-status="planStatus" :plan-status="planStatus"
ref="scenarioNodeTree"> ref="scenarioNodeTree">
<template v-slot:header> <template v-slot:header>
<div class="model-change-radio"> <div class="model-change-radio">
<el-radio v-model="model" label="api">{{ $t('commons.api_case') }}</el-radio> <el-radio v-model="model" label="api">{{ $t('commons.api_case') }}</el-radio>
@ -43,62 +43,62 @@
<template v-slot:main> <template v-slot:main>
<!--测试用例列表--> <!--测试用例列表-->
<test-plan-api-case-list <test-plan-api-case-list
v-if="model === 'api'" v-if="model === 'api'"
:current-protocol="currentProtocol" :current-protocol="currentProtocol"
:currentRow="currentRow" :currentRow="currentRow"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:trash-enable="trashEnable" :trash-enable="trashEnable"
:is-case-relevance="true" :is-case-relevance="true"
:version-enable="versionEnable" :version-enable="versionEnable"
:model="'plan'" :model="'plan'"
:plan-id="planId" :plan-id="planId"
:plan-status="planStatus" :plan-status="planStatus"
:clickType="clickType" :clickType="clickType"
@refresh="refreshTree" @refresh="refreshTree"
@relevanceCase="openTestCaseRelevanceDialog" @relevanceCase="openTestCaseRelevanceDialog"
ref="apiCaseList"/> ref="apiCaseList"/>
<ms-test-plan-api-scenario-list <ms-test-plan-api-scenario-list
v-if="model === 'scenario'" v-if="model === 'scenario'"
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:trash-enable="trashEnable" :trash-enable="trashEnable"
:version-enable="versionEnable" :version-enable="versionEnable"
:plan-id="planId" :plan-id="planId"
:plan-status="planStatus" :plan-status="planStatus"
:clickType="clickType" :clickType="clickType"
@refresh="refreshTree" @refresh="refreshTree"
@relevanceCase="openTestCaseRelevanceDialog" @relevanceCase="openTestCaseRelevanceDialog"
ref="apiScenarioList"/> ref="apiScenarioList"/>
</template> </template>
<test-case-api-relevance <test-case-api-relevance
@refresh="refresh" @refresh="refresh"
:plan-id="planId" :plan-id="planId"
:model="model" :model="model"
:version-enable="versionEnable" :version-enable="versionEnable"
ref="apiCaseRelevance"/> ref="apiCaseRelevance"/>
<test-case-scenario-relevance <test-case-scenario-relevance
@refresh="refresh" @refresh="refresh"
:plan-id="planId" :plan-id="planId"
:model="model" :model="model"
:version-enable="versionEnable" :version-enable="versionEnable"
ref="scenarioCaseRelevance"/> ref="scenarioCaseRelevance"/>
</ms-test-plan-common-component> </ms-test-plan-common-component>
</template> </template>
<script> <script>
import NodeTree from "metersphere-frontend/src/components/module/MsNodeTree"; import NodeTree from "metersphere-frontend/src/components/module/MsNodeTree";
import MsTestPlanCommonComponent from "../base/TestPlanCommonComponent"; import MsTestPlanCommonComponent from "../base/TestPlanCommonComponent";
import TestPlanApiCaseList from "./TestPlanApiCaseList"; import TestPlanApiCaseList from "./TestPlanApiCaseList";
import TestCaseApiRelevance from "./TestCaseApiRelevance"; import TestCaseApiRelevance from "./TestCaseApiRelevance";
import MsTestPlanApiScenarioList from "./TestPlanApiScenarioList"; import MsTestPlanApiScenarioList from "./TestPlanApiScenarioList";
import TestCaseScenarioRelevance from "./TestCaseScenarioRelevance"; import TestCaseScenarioRelevance from "./TestCaseScenarioRelevance";
import MsApiModule from "@/business/plan/view/comonents/api/module/ApiModule"; import MsApiModule from "@/business/plan/view/comonents/api/module/ApiModule";
import MsApiScenarioModule from "@/business/plan/view/comonents/api/module/ApiScenarioModule"; import MsApiScenarioModule from "@/business/plan/view/comonents/api/module/ApiScenarioModule";
export default { export default {
name: "TestPlanApi", name: "TestPlanApi",
@ -140,19 +140,19 @@ export default {
this.selectNodeIds = []; this.selectNodeIds = [];
this.moduleOptions = {}; this.moduleOptions = {};
}, },
redirectCharType(){ redirectCharType() {
if(this.redirectCharType=='scenario'){ if (this.redirectCharType == 'scenario') {
this.model = 'scenario'; this.model = 'scenario';
}else{ } else {
this.model = 'api'; this.model = 'api';
} }
} }
}, },
methods: { methods: {
checkRedirectCharType(){ checkRedirectCharType() {
if(this.redirectCharType=='scenario'){ if (this.redirectCharType == 'scenario') {
this.model = 'scenario'; this.model = 'scenario';
}else{ } else {
this.model = 'api'; this.model = 'api';
} }
}, },

View File

@ -4,36 +4,36 @@
<slot name="header"></slot> <slot name="header"></slot>
<ms-node-tree <ms-node-tree
v-if="refreshDataOver" v-if="refreshDataOver"
v-loading="loading" v-loading="loading"
:tree-nodes="data" :tree-nodes="data"
:type="isReadOnly ? 'view' : 'edit'" :type="isReadOnly ? 'view' : 'edit'"
:allLabel="$t('api_test.definition.api_all')" :allLabel="$t('api_test.definition.api_all')"
:default-label="$t('api_test.definition.unplanned_api')" :default-label="$t('api_test.definition.unplanned_api')"
:hide-opretor="true" :hide-opretor="true"
local-suffix="api_definition" local-suffix="api_definition"
@refresh="list" @refresh="list"
@filter="filter" @filter="filter"
:show-case-num="showCaseNum" :show-case-num="showCaseNum"
:delete-permission="['PROJECT_API_DEFINITION:READ+DELETE_API']" :delete-permission="['PROJECT_API_DEFINITION:READ+DELETE_API']"
:add-permission="['PROJECT_API_DEFINITION:READ+CREATE_API']" :add-permission="['PROJECT_API_DEFINITION:READ+CREATE_API']"
:update-permission="['PROJECT_API_DEFINITION:READ+EDIT_API']" :update-permission="['PROJECT_API_DEFINITION:READ+EDIT_API']"
@nodeSelectEvent="nodeChange" @nodeSelectEvent="nodeChange"
ref="nodeTree"> ref="nodeTree">
<template v-slot:header> <template v-slot:header>
<api-module-header <api-module-header
:show-operator="showOperator" :show-operator="showOperator"
:condition="condition" :condition="condition"
:current-module="currentModule" :current-module="currentModule"
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:moduleOptions="data" :moduleOptions="data"
:options="options" :options="options"
:total="total" :total="total"
:select-project-id="projectId" :select-project-id="projectId"
@refreshTable="$emit('refreshTable')" @refreshTable="$emit('refreshTable')"
@schedule="$emit('schedule')" @schedule="$emit('schedule')"
@refresh="refresh"/> @refresh="refresh"/>
</template> </template>
</ms-node-tree> </ms-node-tree>
@ -124,6 +124,9 @@ export default {
} }
} }
}, },
mounted() {
this.initProtocol();
},
watch: { watch: {
'condition.filterText'() { 'condition.filterText'() {
this.filter(); this.filter();
@ -200,19 +203,19 @@ export default {
this.loading = true; this.loading = true;
if (this.isPlanModel) { if (this.isPlanModel) {
apiCaseModulePlanList(this.planId, this.condition.protocol) apiCaseModulePlanList(this.planId, this.condition.protocol)
.then((response) => { .then((response) => {
this.getData(response); this.getData(response);
}); });
} else if (this.isRelevanceModel) { } else if (this.isRelevanceModel) {
apiModuleProjectList(this.relevanceProjectId, this.condition.protocol) apiModuleProjectList(this.relevanceProjectId, this.condition.protocol)
.then((response) => { .then((response) => {
this.getData(response); this.getData(response);
}); });
} else { } else {
apiModuleProjectList(getCurrentProjectID()) apiModuleProjectList(getCurrentProjectID())
.then((response) => { .then((response) => {
this.getData(response); this.getData(response);
}); });
} }
}, },
nodeChange(node, nodeIds, pNodes) { nodeChange(node, nodeIds, pNodes) {
@ -228,14 +231,14 @@ export default {
this.loading = true; this.loading = true;
if (this.isPlanModel) { if (this.isPlanModel) {
apiCaseModulePlanList(this.planId, this.condition.protocol) apiCaseModulePlanList(this.planId, this.condition.protocol)
.then((response) => { .then((response) => {
this.getNohupReloadData(response, selectNodeId); this.getNohupReloadData(response, selectNodeId);
}); });
} else if (this.isRelevanceModel) { } else if (this.isRelevanceModel) {
apiModuleProjectList(this.relevanceProjectId, this.condition.protocol) apiModuleProjectList(this.relevanceProjectId, this.condition.protocol)
.then((response) => { .then((response) => {
this.getNohupReloadData(response, selectNodeId); this.getNohupReloadData(response, selectNodeId);
}); });
} }
}, },
getData(response) { getData(response) {

View File

@ -1,46 +1,46 @@
<template> <template>
<test-case-relevance-base <test-case-relevance-base
@setProject="setProject" @setProject="setProject"
@save="saveCaseRelevance" @save="saveCaseRelevance"
@close="close" @close="close"
:plan-id="planId" :plan-id="planId"
:is-saving="isSaving" :is-saving="isSaving"
ref="baseRelevance" ref="baseRelevance"
> >
<template v-slot:aside> <template v-slot:aside>
<ui-scenario-module <ui-scenario-module
class="node-tree" class="node-tree"
@nodeSelectEvent="nodeChange" @nodeSelectEvent="nodeChange"
@refreshTable="refresh" @refreshTable="refresh"
@setModuleOptions="setModuleOptions" @setModuleOptions="setModuleOptions"
:relevance-project-id="projectId" :relevance-project-id="projectId"
:is-read-only="true" :is-read-only="true"
:show-case-num="false" :show-case-num="false"
ref="nodeTree" ref="nodeTree"
/> />
</template> </template>
<relevance-ui-scenario-list <relevance-ui-scenario-list
:select-node-ids="selectNodeIds" :select-node-ids="selectNodeIds"
:trash-enable="trashEnable" :trash-enable="trashEnable"
:version-enable="versionEnable" :version-enable="versionEnable"
:plan-id="planId" :plan-id="planId"
:project-id="projectId" :project-id="projectId"
@selectCountChange="setSelectCounts" @selectCountChange="setSelectCounts"
ref="apiScenarioList" ref="apiScenarioList"
/> />
</test-case-relevance-base> </test-case-relevance-base>
</template> </template>
<script> <script>
import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase"; import TestCaseRelevanceBase from "../base/TestCaseRelevanceBase";
import { strMapToObj } from "metersphere-frontend/src/utils"; import {strMapToObj} from "metersphere-frontend/src/utils";
import { ENV_TYPE } from "metersphere-frontend/src/utils/constants"; import {ENV_TYPE} from "metersphere-frontend/src/utils/constants";
import RelevanceUiScenarioList from "@/business/plan/view/comonents/ui/RelevanceUiScenarioList"; import RelevanceUiScenarioList from "@/business/plan/view/comonents/ui/RelevanceUiScenarioList";
import { testPlanAutoCheck } from "@/api/remote/plan/test-plan"; import {testPlanAutoCheck} from "@/api/remote/plan/test-plan";
import { testPlanUiScenarioRelevanceListIds } from "@/api/remote/ui/test-plan-ui-scenario-case"; import {testPlanUiScenarioRelevanceListIds} from "@/api/remote/ui/test-plan-ui-scenario-case";
import UiScenarioModule from "@/business/plan/view/comonents/ui/UiScenarioModule"; import UiScenarioModule from "@/business/plan/view/comonents/ui/UiScenarioModule";
import { uiAutomationRelevance } from "@/api/remote/ui/api-scenario"; import {uiAutomationRelevance} from "@/api/remote/ui/api-scenario";
export default { export default {
name: "TestCaseUiScenarioRelevance", name: "TestCaseUiScenarioRelevance",
@ -163,12 +163,6 @@ export default {
return; return;
} }
if (!envMap || envMap.size == 0) {
this.isSaving = false;
this.$warning(this.$t("api_test.environment.select_environment"));
return;
}
let param = {}; let param = {};
param.planId = this.planId; param.planId = this.planId;
param.mapping = strMapToObj(map); param.mapping = strMapToObj(map);
@ -184,17 +178,17 @@ export default {
} }
this.loading = true; this.loading = true;
uiAutomationRelevance(param) uiAutomationRelevance(param)
.then(() => { .then(() => {
this.loading = false; this.loading = false;
this.isSaving = false; this.isSaving = false;
this.$success(this.$t("plan.relevance_case_success")); this.$success(this.$t("plan.relevance_case_success"));
this.$emit("refresh"); this.$emit("refresh");
this.refresh(); this.refresh();
this.autoCheckStatus(); this.autoCheckStatus();
}) })
.catch(() => { .catch(() => {
this.isSaving = false; this.isSaving = false;
}); });
}, },
autoCheckStatus() { autoCheckStatus() {
// //