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

View File

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

View File

@ -99,4 +99,10 @@ public class RedisTemplateService {
}
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>
<test-case-relevance-base
:is-across-space="isAcrossSpace"
@setProject="setProject"
:dialog-title="$t('api_test.definition.api_import')"
ref="baseRelevance">
:is-across-space="isAcrossSpace"
@setProject="setProject"
:dialog-title="$t('api_test.definition.api_import')"
ref="baseRelevance">
<template v-slot:aside>
<ms-api-module
class="node-tree"
@ -18,60 +18,60 @@
</template>
<scenario-relevance-api-list
v-if="isApiListEnable"
:project-id="projectId"
:version-filters="versionFilters"
:current-version="currentVersion"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts"
ref="apiList">
v-if="isApiListEnable"
:project-id="projectId"
:version-filters="versionFilters"
:current-version="currentVersion"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts"
ref="apiList">
<template v-slot:version>
<mx-version-select
v-xpack
:project-id="projectId"
:default-version="currentVersion"
@changeVersion="currentVersionChange" />
v-xpack
:project-id="projectId"
:default-version="currentVersion"
@changeVersion="currentVersionChange"/>
</template>
</scenario-relevance-api-list>
<scenario-relevance-case-list
v-if="!isApiListEnable"
:project-id="projectId"
:version-filters="versionFilters"
:current-version="currentVersion"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts"
ref="apiCaseList">
v-if="!isApiListEnable"
:project-id="projectId"
:version-filters="versionFilters"
:current-version="currentVersion"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange"
@selectCountChange="setSelectCounts"
ref="apiCaseList">
<template v-slot:version>
<mx-version-select
v-xpack
:project-id="projectId"
:default-version="currentVersion"
@changeVersion="currentVersionChange" />
v-xpack
:project-id="projectId"
:default-version="currentVersion"
@changeVersion="currentVersionChange"/>
</template>
</scenario-relevance-case-list>
<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">
{{ $t('commons.copy') }}
</el-button>
<el-button
v-if="!isApiListEnable"
type="primary"
:loading="buttonIsWorking"
@click="reference"
size="mini"
@keydown.enter.native.prevent>
v-if="!isApiListEnable"
type="primary"
:loading="buttonIsWorking"
@click="reference"
size="mini"
@keydown.enter.native.prevent>
{{ $t('api_test.scenario.reference') }}
</el-button>
</template>
@ -79,9 +79,9 @@
</template>
<script>
import { getApiCaseWithBLOBs } from '@/api/api-test-case';
import { apiListBatch } from '@/api/definition';
import { getProjectVersions } from '@/api/xpack';
import {getApiCaseWithBLOBs} from '@/api/api-test-case';
import {apiListBatch} from '@/api/definition';
import {getProjectVersions} from '@/api/xpack';
import ScenarioRelevanceCaseList from './RelevanceCaseList';
import MsApiModule from '../../../definition/components/module/ApiModule';
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 RelevanceDialog from '@/business/commons/RelevanceDialog';
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 { operationConfirm } from 'metersphere-frontend/src/utils';
import {operationConfirm} from 'metersphere-frontend/src/utils';
export default {
name: 'ApiRelevance',
@ -166,15 +166,15 @@ export default {
} else {
if (params.condition.selectAll) {
operationConfirm(
this,
this.$t('automation.scenario_step_ref_message') + '',
() => {
this.$emit('save', apis, 'API', reference);
this.$refs.baseRelevance.close();
},
() => {
this.buttonIsWorking = false;
}
this,
this.$t('automation.scenario_step_ref_message') + '',
() => {
this.$emit('save', apis, 'API', reference);
this.$refs.baseRelevance.close();
},
() => {
this.buttonIsWorking = false;
}
);
} else {
this.$emit('save', apis, 'API', reference);
@ -192,15 +192,15 @@ export default {
} else {
if (this.$refs.apiCaseList.condition.selectAll) {
operationConfirm(
this,
this.$t('automation.scenario_step_ref_message') + '',
() => {
this.$emit('save', apiCases, 'CASE', reference);
this.$refs.baseRelevance.close();
},
() => {
this.buttonIsWorking = false;
}
this,
this.$t('automation.scenario_step_ref_message') + '',
() => {
this.$emit('save', apiCases, 'CASE', reference);
this.$refs.baseRelevance.close();
},
() => {
this.buttonIsWorking = false;
}
);
} else {
this.$emit('save', apiCases, 'CASE', reference);
@ -258,13 +258,13 @@ export default {
getProjectVersions(this.projectId).then((response) => {
if (currentVersion) {
this.versionFilters = response.data
.filter((u) => u.id === currentVersion)
.map((u) => {
return { text: u.name, value: u.id };
});
.filter((u) => u.id === currentVersion)
.map((u) => {
return {text: u.name, value: u.id};
});
} else {
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>
<test-case-relevance-base
@setProject="setProject"
@save="save"
:plan-id="planId"
:dialog-title="dialogTitle"
ref="baseRelevance">
@setProject="setProject"
@save="save"
:plan-id="planId"
:dialog-title="dialogTitle"
ref="baseRelevance">
<template v-slot:aside>
<ms-api-module
:options="options"
:relevance-project-id="projectId"
@nodeSelectEvent="nodeChange"
@protocolChange="handleProtocolChange"
@refreshTable="refresh"
@setModuleOptions="setModuleOptions"
:is-read-only="true"
:is-relevance="true"
ref="nodeTree" />
:options="options"
:relevance-project-id="projectId"
@nodeSelectEvent="nodeChange"
@protocolChange="handleProtocolChange"
@refreshTable="refresh"
@setModuleOptions="setModuleOptions"
:is-read-only="true"
:is-relevance="true"
ref="nodeTree"/>
</template>
<relevance-api-list
v-if="isApiListEnable"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
:project-id="projectId"
:is-test-plan="isTestPlan"
:is-script="isScript"
:plan-id="planId"
@isApiListEnableChange="isApiListEnableChange"
ref="apiList" />
v-if="isApiListEnable"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
:project-id="projectId"
:is-test-plan="isTestPlan"
:is-script="isScript"
:plan-id="planId"
@isApiListEnableChange="isApiListEnableChange"
ref="apiList"/>
<relevance-case-list
v-if="!isApiListEnable"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
:project-id="projectId"
:is-test-plan="isTestPlan"
:is-script="isScript"
:plan-id="planId"
@isApiListEnableChange="isApiListEnableChange"
ref="apiCaseList" />
v-if="!isApiListEnable"
:current-protocol="currentProtocol"
:select-node-ids="selectNodeIds"
:is-api-list-enable="isApiListEnable"
:project-id="projectId"
:is-test-plan="isTestPlan"
:is-script="isScript"
:plan-id="planId"
@isApiListEnableChange="isApiListEnableChange"
ref="apiCaseList"/>
</test-case-relevance-base>
</template>
<script>
import { getApiCaseWithBLOBs } from '@/api/api-test-case';
import { apiListBatch } from '@/api/definition';
import {getApiCaseWithBLOBs} from '@/api/api-test-case';
import {apiListBatch} from '@/api/definition';
import RelevanceCaseList from '@/business/automation/scenario/api/RelevanceCaseList';
import RelevanceApiList from '@/business/automation/scenario/api/RelevanceApiList';
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 { parseEnvironment } from '@/business/environment/model/EnvironmentModel';
import {parseEnvironment} from '@/business/environment/model/EnvironmentModel';
export default {
name: 'ApiFuncRelevance',
@ -75,7 +75,7 @@ export default {
condition: {},
currentRow: {},
projectId: '',
options: [{ value: 'HTTP', name: 'HTTP' }],
options: [{value: 'HTTP', name: 'HTTP'}],
};
},
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_TYPE = {
SYSTEM: 'SYSTEM',
WORKSPACE: 'WORKSPACE',
PROJECT: 'PROJECT'
SYSTEM: 'SYSTEM',
WORKSPACE: 'WORKSPACE',
PROJECT: 'PROJECT'
}
export const SCHEDULE_TYPE = {
API_TEST: 'API_TEST',
PERFORMANCE_TEST: 'PERFORMANCE_TEST'
API_TEST: 'API_TEST',
PERFORMANCE_TEST: 'PERFORMANCE_TEST'
}
export const REQUEST_HEADERS = [
{value: 'Accept'},
{value: 'Accept-Charset'},
{value: 'Accept-Language'},
{value: 'Accept-Datetime'},
{value: 'Authorization'},
{value: 'Cache-Control'},
{value: 'Connection'},
{value: 'Cookie'},
{value: 'Content-Length'},
{value: 'Content-MD5'},
{value: 'Content-Type'},
{value: 'Date'},
{value: 'Expect'},
{value: 'From'},
{value: 'Host'},
{value: 'If-Match'},
{value: 'If-Modified-Since'},
{value: 'If-None-Match'},
{value: 'If-Range'},
{value: 'If-Unmodified-Since'},
{value: 'Max-Forwards'},
{value: 'Origin'},
{value: 'Pragma'},
{value: 'Proxy-Authorization'},
{value: 'Range'},
{value: 'Referer'},
{value: 'TE'},
{value: 'User-Agent'},
{value: 'Upgrade'},
{value: 'Via'},
{value: 'Warning'}
{value: 'Accept'},
{value: 'Accept-Charset'},
{value: 'Accept-Language'},
{value: 'Accept-Datetime'},
{value: 'Authorization'},
{value: 'Cache-Control'},
{value: 'Connection'},
{value: 'Cookie'},
{value: 'Content-Length'},
{value: 'Content-MD5'},
{value: 'Content-Type'},
{value: 'Date'},
{value: 'Expect'},
{value: 'From'},
{value: 'Host'},
{value: 'If-Match'},
{value: 'If-Modified-Since'},
{value: 'If-None-Match'},
{value: 'If-Range'},
{value: 'If-Unmodified-Since'},
{value: 'Max-Forwards'},
{value: 'Origin'},
{value: 'Pragma'},
{value: 'Proxy-Authorization'},
{value: 'Range'},
{value: 'Referer'},
{value: 'TE'},
{value: 'User-Agent'},
{value: 'Upgrade'},
{value: 'Via'},
{value: 'Warning'}
]
export const MOCKJS_FUNC = [
{name: '@boolean', des: i18n.t('api_test.request.boolean'), ex: true},
{name: '@natural', des: i18n.t('api_test.request.natural'), ex: 72834},
{name: '@integer', des: i18n.t('api_test.request.integer'), ex: 79750},
{name: '@float', des: i18n.t('api_test.request.float'), ex: 24.2},
{name: '@character', des: i18n.t('api_test.request.character'), ex: "k"},
{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: '@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: '@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: '@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: '@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: '@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: '@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: '@word', des: i18n.t('api_test.request.word'), ex: "shnjlyazvi"},
{name: '@title', des: i18n.t('api_test.request.title'), ex: "Tefsdc Vhs Ujx"},
{name: '@cparagraph', des: i18n.t('api_test.request.cparagraph'), ex: "色青元处才不米拉律消叫别金如上。"},
{name: '@csentence', des: i18n.t('api_test.request.csentence'), ex: "与形府部速她运改织图集料进完。"},
{name: '@cword', des: i18n.t('api_test.request.cword'), ex: "满"},
{name: '@ctitle', des: i18n.t('api_test.request.ctitle'), ex: "运满前省快"},
{name: '@first', des: i18n.t('api_test.request.first'), ex: "Mary"},
{name: '@last', des: i18n.t('api_test.request.last'), ex: "Miller"},
{name: '@name', des: i18n.t('api_test.request.name'), ex: "Robert Lee"},
{name: '@cfirst', des: i18n.t('api_test.request.cfirst'), ex: "龚"},
{name: '@clast', des: i18n.t('api_test.request.clast'), 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: '@domain', des: i18n.t('api_test.request.domain'), ex: "rsh.bt"},
{name: '@protocol', des: i18n.t('api_test.request.protocol'), ex: "rlogin"},
{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: '@ip', des: i18n.t('api_test.request.ip'), ex: "22.151.93.255"},
{name: '@region', des: i18n.t('api_test.request.region'), ex: "东北"},
{name: '@province', des: i18n.t('api_test.request.province'), ex: "陕西省"},
{name: '@city', des: i18n.t('api_test.request.city'), ex: "珠海市"},
{name: '@county', des: i18n.t('api_test.request.county'), ex: "正宁县"},
{name: '@zip', des: i18n.t('api_test.request.zip'), ex: 873247},
{name: '@capitalize', des: i18n.t('api_test.request.capitalize'), 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: '@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: '@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: '@increment', des: i18n.t('api_test.request.increment'), ex: 1}
{name: '@boolean', des: i18n.t('api_test.request.boolean'), ex: true},
{name: '@natural', des: i18n.t('api_test.request.natural'), ex: 72834},
{name: '@integer', des: i18n.t('api_test.request.integer'), ex: 79750},
{name: '@float', des: i18n.t('api_test.request.float'), ex: 24.2},
{name: '@character', des: i18n.t('api_test.request.character'), ex: "k"},
{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: '@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: '@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: '@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: '@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: '@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: '@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: '@word', des: i18n.t('api_test.request.word'), ex: "shnjlyazvi"},
{name: '@title', des: i18n.t('api_test.request.title'), ex: "Tefsdc Vhs Ujx"},
{name: '@cparagraph', des: i18n.t('api_test.request.cparagraph'), ex: "色青元处才不米拉律消叫别金如上。"},
{name: '@csentence', des: i18n.t('api_test.request.csentence'), ex: "与形府部速她运改织图集料进完。"},
{name: '@cword', des: i18n.t('api_test.request.cword'), ex: "满"},
{name: '@ctitle', des: i18n.t('api_test.request.ctitle'), ex: "运满前省快"},
{name: '@first', des: i18n.t('api_test.request.first'), ex: "Mary"},
{name: '@last', des: i18n.t('api_test.request.last'), ex: "Miller"},
{name: '@name', des: i18n.t('api_test.request.name'), ex: "Robert Lee"},
{name: '@cfirst', des: i18n.t('api_test.request.cfirst'), ex: "龚"},
{name: '@clast', des: i18n.t('api_test.request.clast'), 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: '@domain', des: i18n.t('api_test.request.domain'), ex: "rsh.bt"},
{name: '@protocol', des: i18n.t('api_test.request.protocol'), ex: "rlogin"},
{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: '@ip', des: i18n.t('api_test.request.ip'), ex: "22.151.93.255"},
{name: '@region', des: i18n.t('api_test.request.region'), ex: "东北"},
{name: '@province', des: i18n.t('api_test.request.province'), ex: "陕西省"},
{name: '@city', des: i18n.t('api_test.request.city'), ex: "珠海市"},
{name: '@county', des: i18n.t('api_test.request.county'), ex: "正宁县"},
{name: '@zip', des: i18n.t('api_test.request.zip'), ex: 873247},
{name: '@capitalize', des: i18n.t('api_test.request.capitalize'), 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: '@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: '@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: '@increment', des: i18n.t('api_test.request.increment'), ex: 1}
]
export const JMETER_FUNC = [
{type: "Information", name: "${__threadNum}", description: "get thread number"},
{type: "Information", name: "${__threadGroupName}", description: "get thread group name"},
{type: "Information", name: "${__samplerName}", description: "get the sampler name (label)"},
{type: "Information", name: "${__machineIP}", description: "get the local machine IP address"},
{type: "Information", name: "${__machineName}", description: "get the local machine name"},
{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", name: "${__logn}", description: "log (or display) a message (empty return value)"},
{type: "Input", name: "${__StringFromFile}", description: "read a line from a file"},
{type: "Input", name: "${__FileToString}", description: "read an entire file"},
{type: "Input", name: "${__CSVRead}", description: "read from CSV delimited file"},
{type: "Input", name: "${__XPath}", description: "Use an XPath expression to read from a file"},
{type: "Input", name: "${__StringToFile}", description: "write a string to a file"},
{type: "Calculation", name: "${__counter}", description: "generate an incrementing number"},
{type: "Formatting", name: "${__dateTimeConvert}", description: "Convert a date or time from source to target format"},
{type: "Calculation", name: "${__digest}", description: "Generate a digest (SHA-1, SHA-256, MD5...)"},
{type: "Calculation", name: "${__intSum}", description: "add int numbers"},
{type: "Calculation", name: "${__longSum}", description: "add long numbers"},
{type: "Calculation", name: "${__Random}", description: "generate a random number"},
{type: "Calculation", name: "${__RandomDate}", description: "generate random date within a specific date range"},
{type: "Calculation", name: "${__RandomFromMultipleVars}", description: "extracts an element from the values of a set of variables separated by |"},
{type: "Calculation", name: "${__RandomString}", description: "generate a random string"},
{type: "Calculation", name: "${__UUID}", description: "generate a random type 4 UUID"},
{type: "Scripting", name: "${__groovy}", description: "run an Apache Groovy script"},
{type: "Scripting", name: "${__BeanShell}", description: "run a BeanShell script"},
{type: "Scripting", name: "${__javaScript}", description: "process JavaScript (Nashorn)"},
{type: "Scripting", name: "${__jexl2}", description: "evaluate a Commons Jexl2 expression"},
{type: "Scripting", name: "${__jexl3}", description: "evaluate a Commons Jexl3 expression"},
{type: "Properties", name: "${__isPropDefined}", description: "Test if a property exists"},
{type: "Properties", name: "${__property}", description: "read a property"},
{type: "Properties", name: "${__P}", description: "read a property (shorthand method)"},
{type: "Properties", name: "${__setProperty}", description: "set a JMeter property"},
{type: "Variables", name: "${__split}", description: "Split a string into variables"},
{type: "Variables", name: "${__eval}", description: "evaluate a variable expression"},
{type: "Variables", name: "${__evalVar}", description: "evaluate an expression stored in a variable"},
{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"},
{type: "Information", name: "${__threadNum}", description: "get thread number"},
{type: "Information", name: "${__threadGroupName}", description: "get thread group name"},
{type: "Information", name: "${__samplerName}", description: "get the sampler name (label)"},
{type: "Information", name: "${__machineIP}", description: "get the local machine IP address"},
{type: "Information", name: "${__machineName}", description: "get the local machine name"},
{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", name: "${__logn}", description: "log (or display) a message (empty return value)"},
{type: "Input", name: "${__StringFromFile}", description: "read a line from a file"},
{type: "Input", name: "${__FileToString}", description: "read an entire file"},
{type: "Input", name: "${__CSVRead}", description: "read from CSV delimited file"},
{type: "Input", name: "${__XPath}", description: "Use an XPath expression to read from a file"},
{type: "Input", name: "${__StringToFile}", description: "write a string to a file"},
{type: "Calculation", name: "${__counter}", description: "generate an incrementing number"},
{
type: "Formatting",
name: "${__dateTimeConvert}",
description: "Convert a date or time from source to target format"
},
{type: "Calculation", name: "${__digest}", description: "Generate a digest (SHA-1, SHA-256, MD5...)"},
{type: "Calculation", name: "${__intSum}", description: "add int numbers"},
{type: "Calculation", name: "${__longSum}", description: "add long numbers"},
{type: "Calculation", name: "${__Random}", description: "generate a random number"},
{type: "Calculation", name: "${__RandomDate}", description: "generate random date within a specific date range"},
{
type: "Calculation",
name: "${__RandomFromMultipleVars}",
description: "extracts an element from the values of a set of variables separated by |"
},
{type: "Calculation", name: "${__RandomString}", description: "generate a random string"},
{type: "Calculation", name: "${__UUID}", description: "generate a random type 4 UUID"},
{type: "Scripting", name: "${__groovy}", description: "run an Apache Groovy script"},
{type: "Scripting", name: "${__BeanShell}", description: "run a BeanShell script"},
{type: "Scripting", name: "${__javaScript}", description: "process JavaScript (Nashorn)"},
{type: "Scripting", name: "${__jexl2}", description: "evaluate a Commons Jexl2 expression"},
{type: "Scripting", name: "${__jexl3}", description: "evaluate a Commons Jexl3 expression"},
{type: "Properties", name: "${__isPropDefined}", description: "Test if a property exists"},
{type: "Properties", name: "${__property}", description: "read a property"},
{type: "Properties", name: "${__P}", description: "read a property (shorthand method)"},
{type: "Properties", name: "${__setProperty}", description: "set a JMeter property"},
{type: "Variables", name: "${__split}", description: "Split a string into variables"},
{type: "Variables", name: "${__eval}", description: "evaluate a variable expression"},
{type: "Variables", name: "${__evalVar}", description: "evaluate an expression stored in a variable"},
{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';
@ -207,164 +223,341 @@ export const COUNT_NUMBER_SHALLOW = '#CDB9D2';
export const PRIMARY_COLOR = '#783887';
export const CONFIG_TYPE = {
NOT: "NOT",
NORMAL: "NORMAL",
ABNORMAL: "ABNORMAL"
NOT: "NOT",
NORMAL: "NORMAL",
ABNORMAL: "ABNORMAL"
}
export const WORKSTATION={
UPCOMING:"upcoming",
FOCUS:"focus",
NODE:"node"
export const WORKSTATION = {
UPCOMING: "upcoming",
FOCUS: "focus",
NODE: "node"
}
export const ENV_TYPE = {
JSON: "JSON",
GROUP: "GROUP"
DEFAULT: "DEFAULT",
JSON: "JSON",
GROUP: "GROUP"
}
export const DEFAULT_XSS_ATTR = ['style', 'class'];
export const SECOND_LEVEL_ROUTE_PERMISSION_MAP = {
API: [
{router: '/api/home', permission: ['PROJECT_API_HOME:READ']},
{router: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ']},
{router: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ']},
{router: '/api/automation/report', permission: ['PROJECT_API_REPORT:READ']},
],
TRACK: [
{router: '/track/home', permission: ['PROJECT_TRACK_HOME:READ']},
{router: '/track/case/all', permission: ['PROJECT_TRACK_CASE:READ']},
{router: '/track/review/all', permission: ['PROJECT_TRACK_REVIEW:READ']},
{router: '/track/plan/all', permission: ['PROJECT_TRACK_PLAN:READ']},
{router: '/track/issue', permission: ['PROJECT_TRACK_ISSUE:READ']},
{router: '/track/testPlan/reportList', permission: ['PROJECT_TRACK_REPORT:READ']},
],
LOAD: [
{router: '/performance/home', permission: ['PROJECT_PERFORMANCE_HOME:READ']},
{router: '/performance/test/all', permission: ['PROJECT_PERFORMANCE_TEST:READ']},
{router: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ']},
],
UI: [
{router: '/ui/automation', permission: ['PROJECT_UI_SCENARIO:READ']},
{router: '/ui/element', permission: ['PROJECT_UI_ELEMENT:READ']},
{router: '/ui/report', permission: ['PROJECT_UI_REPORT:READ']},
],
REPORT: [
{router: '/report/projectStatistics', permission: ['PROJECT_REPORT_ANALYSIS:READ']},
{
router: '/report/projectReport',
permission: [
'PROJECT_ENTERPRISE_REPORT:READ+EXPORT', 'PROJECT_ENTERPRISE_REPORT:READ+CREATE',
'PROJECT_ENTERPRISE_REPORT:READ+DELETE', 'PROJECT_ENTERPRISE_REPORT:READ+COPY',
'PROJECT_ENTERPRISE_REPORT:READ+SCHEDULE', 'PROJECT_ENTERPRISE_REPORT:READ+EDIT'
]
}
]
API: [
{router: '/api/home', permission: ['PROJECT_API_HOME:READ']},
{router: '/api/definition', permission: ['PROJECT_API_DEFINITION:READ']},
{router: '/api/automation', permission: ['PROJECT_API_SCENARIO:READ']},
{router: '/api/automation/report', permission: ['PROJECT_API_REPORT:READ']},
],
TRACK: [
{router: '/track/home', permission: ['PROJECT_TRACK_HOME:READ']},
{router: '/track/case/all', permission: ['PROJECT_TRACK_CASE:READ']},
{router: '/track/review/all', permission: ['PROJECT_TRACK_REVIEW:READ']},
{router: '/track/plan/all', permission: ['PROJECT_TRACK_PLAN:READ']},
{router: '/track/issue', permission: ['PROJECT_TRACK_ISSUE:READ']},
{router: '/track/testPlan/reportList', permission: ['PROJECT_TRACK_REPORT:READ']},
],
LOAD: [
{router: '/performance/home', permission: ['PROJECT_PERFORMANCE_HOME:READ']},
{router: '/performance/test/all', permission: ['PROJECT_PERFORMANCE_TEST:READ']},
{router: '/performance/report/all', permission: ['PROJECT_PERFORMANCE_REPORT:READ']},
],
UI: [
{router: '/ui/automation', permission: ['PROJECT_UI_SCENARIO:READ']},
{router: '/ui/element', permission: ['PROJECT_UI_ELEMENT:READ']},
{router: '/ui/report', permission: ['PROJECT_UI_REPORT:READ']},
],
REPORT: [
{router: '/report/projectStatistics', permission: ['PROJECT_REPORT_ANALYSIS:READ']},
{
router: '/report/projectReport',
permission: [
'PROJECT_ENTERPRISE_REPORT:READ+EXPORT', 'PROJECT_ENTERPRISE_REPORT:READ+CREATE',
'PROJECT_ENTERPRISE_REPORT:READ+DELETE', 'PROJECT_ENTERPRISE_REPORT:READ+COPY',
'PROJECT_ENTERPRISE_REPORT:READ+SCHEDULE', 'PROJECT_ENTERPRISE_REPORT:READ+EDIT'
]
}
]
}
export const TASK_PATH = [
"/test/case/add",
"/test/case/review/save",
"/test/case/comment/save",
"/test/plan/add",
"/test/plan/relevance",
"issues/add",
"test/case/issues/relate",
"/api/definition/create",
"/api/definition/run/debug",
"/api/testcase/create",
"/share/generate/api/document",
"/api/definition/import",
"/api/automation/create",
"/api/automation/schedule/create",
"/performance/save",
"/share/generate/expired",
"/project/add",
"/project/member/add",
"/setting/user/project/member/add",
"/environment/add",
"/ui/element/add",
"/ui/automation/create",
"/ui/automation/run/debug",
"/test/case/add",
"/test/case/review/save",
"/test/case/comment/save",
"/test/plan/add",
"/test/plan/relevance",
"issues/add",
"test/case/issues/relate",
"/api/definition/create",
"/api/definition/run/debug",
"/api/testcase/create",
"/share/generate/api/document",
"/api/definition/import",
"/api/automation/create",
"/api/automation/schedule/create",
"/performance/save",
"/share/generate/expired",
"/project/add",
"/project/member/add",
"/setting/user/project/member/add",
"/environment/add",
"/ui/element/add",
"/ui/automation/create",
"/ui/automation/run/debug",
];
export const TASK_DATA = [
{
id: 1,
name: "track",
title: "side_task.test_tracking.title",
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'],
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: 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" },
{ 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" },
{ 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" },
{ 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" },
{ 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" },
],
rate: 1,
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
},
{
id: 1,
name: "track",
title: "side_task.test_tracking.title",
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'],
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: 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"
},
{
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"
},
{
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"
},
{
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"
},
{
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"
},
],
rate: 1,
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.ext.BaseApiExecutionQueueMapper;
import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
import io.metersphere.commons.constants.TestPlanLoadCaseStatus;
import io.metersphere.commons.constants.TriggerMode;
import io.metersphere.commons.utils.BeanUtils;
@ -14,6 +15,7 @@ import io.metersphere.constants.RunModeConstants;
import io.metersphere.dto.RunModeConfigDTO;
import io.metersphere.plan.exec.queue.DBTestQueue;
import io.metersphere.request.RunTestPlanRequest;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.utils.LoggerUtil;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
@ -175,13 +177,16 @@ public class PerfQueueService {
return queue;
}
@Resource
private RedisTemplateService redisTemplateService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public DBTestQueue add(Object runObj, String poolId, String reportId, String reportType, String runMode, RunModeConfigDTO config) {
LoggerUtil.info("报告【" + reportId + "】开始生成执行链");
public DBTestQueue add(Object runObj, String poolId, String testPlanReportId, String reportType, String runMode, RunModeConfigDTO config) {
LoggerUtil.info("报告【" + testPlanReportId + "】开始生成执行链");
if (config.getEnvMap() == null) {
config.setEnvMap(new LinkedHashMap<>());
}
ApiExecutionQueue executionQueue = getApiExecutionQueue(poolId, reportId, reportType, runMode, config);
ApiExecutionQueue executionQueue = getApiExecutionQueue(poolId, testPlanReportId, reportType, runMode, config);
queueMapper.insert(executionQueue);
DBTestQueue resQueue = new DBTestQueue();
BeanUtils.copyBean(resQueue, executionQueue);
@ -196,7 +201,9 @@ public class PerfQueueService {
extApiExecutionQueueMapper.sqlInsert(queueDetails);
}
resQueue.setDetailMap(detailMap);
LoggerUtil.info("报告【" + reportId + "】生成执行链结束");
LoggerUtil.info("报告【" + testPlanReportId + "】生成执行链结束");
//移除Redis中的标志
redisTemplateService.unlock(testPlanReportId, TestPlanExecuteCaseType.LOAD_CASE.name(), testPlanReportId);
return resQueue;
}
@ -207,7 +214,7 @@ public class PerfQueueService {
executionQueue.setPoolId(poolId);
executionQueue.setFailure(config.isOnSampleError());
executionQueue.setReportId(reportId);
executionQueue.setReportType(StringUtils.isNotEmpty(reportType) ? reportType : RunModeConstants.INDEPENDENCE.toString());
executionQueue.setReportType(TestPlanExecuteCaseType.LOAD_CASE.name());
executionQueue.setRunMode(runMode);
return executionQueue;
}

View File

@ -5,6 +5,7 @@ import io.metersphere.base.mapper.ApiExecutionQueueMapper;
import io.metersphere.commons.constants.KafkaTopicConstants;
import io.metersphere.plan.service.AutomationCaseExecOverService;
import io.metersphere.plan.service.TestPlanReportService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.utils.LoggerUtil;
import io.metersphere.utils.NamedThreadFactory;
import jakarta.annotation.Resource;
@ -28,6 +29,8 @@ public class ExecReportListener {
@Resource
private TestPlanReportService testPlanReportService;
@Resource
private RedisTemplateService redisTemplateService;
@Resource
private AutomationCaseExecOverService automationCaseExecOverService;
// 线程池维护线程的最少数量
@ -57,6 +60,7 @@ public class ExecReportListener {
task.setApiExecutionQueueDetailMapper(executionQueueDetailMapper);
task.setAutomationCaseExecOverService(automationCaseExecOverService);
task.setTestPlanReportService(testPlanReportService);
task.setRedisTemplateService(redisTemplateService);
task.setRecord(item);
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.mapper.ApiExecutionQueueDetailMapper;
import io.metersphere.base.mapper.ApiExecutionQueueMapper;
import io.metersphere.commons.constants.TestPlanExecuteCaseType;
import io.metersphere.commons.constants.TestPlanReportStatus;
import io.metersphere.plan.service.AutomationCaseExecOverService;
import io.metersphere.plan.service.TestPlanReportService;
import io.metersphere.service.RedisTemplateService;
import io.metersphere.utils.LoggerUtil;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.util.List;
import java.util.stream.Collectors;
@ -25,7 +27,7 @@ public class ExecReportListenerTask implements Runnable {
private ApiExecutionQueueDetailMapper apiExecutionQueueDetailMapper;
private TestPlanReportService testPlanReportService;
private AutomationCaseExecOverService automationCaseExecOverService;
private RedisTemplateService redisTemplateService;
@Override
public void run() {
@ -50,15 +52,15 @@ public class ExecReportListenerTask implements Runnable {
ApiExecutionQueueExample executionQueueExample = new ApiExecutionQueueExample();
executionQueueExample.createCriteria().andReportIdEqualTo(testPlanReportId);
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);
testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name());
} else {
} else if (CollectionUtils.isNotEmpty(queues)) {
List<String> ids = queues.stream().map(ApiExecutionQueue::getId).collect(Collectors.toList());
ApiExecutionQueueDetailExample detailExample = new ApiExecutionQueueDetailExample();
detailExample.createCriteria().andQueueIdIn(ids);
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);
testPlanReportService.testPlanExecuteOver(testPlanReportId, TestPlanReportStatus.COMPLETED.name());
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.mybatis.spring.SqlSessionUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -622,6 +623,7 @@ public class TestPlanReportService {
}
}
@Async
public void testPlanUnExecute(TestPlanReport testPlanReport) {
if (testPlanReport != null && !StringUtils.equalsIgnoreCase(testPlanReport.getStatus(), TestPlanReportStatus.COMPLETED.name())) {
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.TestPlanUiExecuteReportDTO;
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.PlanTestPlanApiCaseService;
import io.metersphere.plan.service.remote.api.PlanTestPlanScenarioCaseService;
@ -495,6 +496,22 @@ public class TestPlanService {
request.setProjectId(request.getProjectId());
}
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)) {
List<String> changeToFinishedIds = new ArrayList<>();
//检查定时任务的设置
@ -556,17 +573,7 @@ public class TestPlanService {
public List<TestPlanDTOWithMetric> selectTestPlanMetricById(List<String> 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) {
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());
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>
<el-dialog
destroy-on-close
:title="$t('load_test.runtime_config')"
width="550px"
style="margin-top: -8.65vh; max-height: 87.3vh"
@close="close"
:visible.sync="runModeVisible"
destroy-on-close
:title="$t('load_test.runtime_config')"
width="550px"
style="margin-top: -8.65vh; max-height: 87.3vh"
@close="close"
:visible.sync="runModeVisible"
>
<div class="env-container">
<div>
<div>{{ $t("commons.environment") }}</div>
<env-select-popover
:project-ids="projectIds"
:project-list="projectList"
:project-env-map="projectEnvListMap"
:environment-type.sync="runConfig.environmentType"
:has-option-group="true"
:group-id="runConfig.environmentGroupId"
@setProjectEnvMap="setProjectEnvMap"
@setDefaultEnv="setDefaultEnv"
@setEnvGroup="setEnvGroup"
ref="envSelectPopover"
class="mode-row"
:project-ids="projectIds"
:project-list="projectList"
:project-env-map="projectEnvListMap"
:environment-type.sync="runConfig.environmentType"
:has-option-group="true"
:is-env-saved="isEnvSaved"
:group-id="runConfig.environmentGroupId"
@setProjectEnvMap="setProjectEnvMap"
@setDefaultEnv="setDefaultEnv"
@setEnvGroup="setEnvGroup"
ref="envSelectPopover"
class="mode-row"
></env-select-popover>
</div>
<div v-if="haveUICase">
<div>{{ $t("ui.browser") }}</div>
<div>
<el-select
size="mini"
v-model="runConfig.browser"
style="width: 100%"
class="mode-row"
size="mini"
v-model="runConfig.browser"
style="width: 100%"
class="mode-row"
>
<el-option
v-for="b in browsers"
:key="b.value"
:value="b.value"
:label="b.label"
v-for="b in browsers"
:key="b.value"
:value="b.value"
:label="b.label"
></el-option>
</el-select>
</div>
@ -46,10 +48,10 @@
<div class="mode-row">{{ $t("run_mode.title") }}</div>
<div>
<el-radio-group
v-model="runConfig.mode"
@change="changeMode"
style="width: 100%"
class="radio-change mode-row"
v-model="runConfig.mode"
@change="changeMode"
style="width: 100%"
class="radio-change mode-row"
>
<el-radio label="serial">{{ $t("run_mode.serial") }}</el-radio>
<el-radio label="parallel">{{ $t("run_mode.parallel") }}</el-radio>
@ -59,51 +61,25 @@
<div>
<div class="mode-row">{{ $t("run_mode.other_config") }}</div>
<div>
<!-- 串行 -->
<!-- 资源池 -->
<div
class="mode-row"
v-if="
runConfig.mode === 'serial' &&
class="mode-row"
v-if="
testType === 'API' &&
haveOtherExecCase
(haveOtherExecCase && !haveUICase)
"
>
<span>{{ $t("run_mode.run_with_resource_pool") }}: </span>
<el-select
v-model="runConfig.resourcePoolId"
size="mini"
style="width: 100%; margin-top: 8px"
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"
: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"
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:value="item.id"
>
</el-option>
</el-select>
@ -112,8 +88,8 @@
<!-- 失败重试 -->
<div class="mode-row">
<el-checkbox
v-model="runConfig.retryEnable"
class="radio-change ms-failure-div-right"
v-model="runConfig.retryEnable"
class="radio-change ms-failure-div-right"
>
{{ $t("run_mode.retry_on_failure") }}
</el-checkbox>
@ -121,19 +97,19 @@
<el-tooltip placement="top" style="margin: 0 4px 0 2px">
<div slot="content">{{ $t("run_mode.retry_message") }}</div>
<i
class="el-icon-question"
style="cursor: pointer"
class="el-icon-question"
style="cursor: pointer"
/> </el-tooltip
><br />
><br/>
<span>
{{ $t("run_mode.retry") }}
<el-input-number
:value="runConfig.retryNum"
v-model="runConfig.retryNum"
:min="1"
:max="10000000"
size="mini"
style="width: 103px; margin-top: 8px"
:value="runConfig.retryNum"
v-model="runConfig.retryNum"
:min="1"
:max="10000000"
size="mini"
style="width: 103px; margin-top: 8px"
/>
&nbsp;
{{ $t("run_mode.retry_frequency") }}
@ -143,14 +119,14 @@
<div class="mode-row" v-if="runConfig.mode === 'serial'">
<el-checkbox v-model="runConfig.onSampleError" class="radio-change"
>{{ $t("api_test.fail_to_stop") }}
>{{ $t("api_test.fail_to_stop") }}
</el-checkbox>
</div>
<div class="mode-row" v-if="haveUICase">
<el-checkbox
v-model="runConfig.headlessEnabled"
class="radio-change"
v-model="runConfig.headlessEnabled"
class="radio-change"
>
{{ $t("ui.performance_mode") }}
</el-checkbox>
@ -164,23 +140,24 @@
<el-button @click="close">{{ $t("commons.cancel") }}</el-button>
<el-dropdown @command="handleCommand" style="margin-left: 5px">
<el-button type="primary">
{{ $t("api_test.run")
{{
$t("api_test.run")
}}<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="run"
>{{ $t("api_test.run") }}
>{{ $t("api_test.run") }}
</el-dropdown-item>
<el-dropdown-item command="runAndSave"
>{{ $t("load_test.save_and_run") }}
>{{ $t("load_test.save_and_run") }}
</el-dropdown-item>
<el-dropdown-item command="save"
>{{ $t("commons.save") }}
>{{ $t("commons.save") }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch" />
<ms-dialog-footer v-else @cancel="close" @confirm="handleRunBatch"/>
</template>
</el-dialog>
</template>
@ -220,7 +197,7 @@ export default {
btnStyle: {
width: "260px",
},
result: { loading: false },
result: {loading: false},
runModeVisible: false,
testType: null,
resourcePools: [],
@ -233,7 +210,7 @@ export default {
resourcePoolId: null,
envMap: new Map(),
environmentGroupId: "",
environmentType: ENV_TYPE.JSON,
environmentType: ENV_TYPE.DEFAULT,
retryEnable: false,
retryNum: 1,
browser: "CHROME",
@ -241,6 +218,8 @@ export default {
},
projectList: [],
projectIds: new Set(),
//
isEnvSaved: true,
options: [
{
value: "confirmAndRun",
@ -279,6 +258,7 @@ export default {
type: Boolean,
default: false,
},
//
haveOtherExecCase: {
type: Boolean,
default: true,
@ -289,11 +269,22 @@ export default {
this.defaultEnvMap = {};
if (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.testPlanDefaultEnvMap = {};
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.testType = testType;
@ -348,7 +339,7 @@ export default {
handleRunBatch() {
if (this.runConfig.resourcePoolId == null && this.haveOtherExecCase) {
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;
}
this.runConfig.testPlanDefaultEnvMap = this.defaultEnvMap;
@ -408,7 +399,7 @@ export default {
this.$refs.envSelectPopover.open();
});
} else if (this.type === "plan") {
param = { id: this.planId };
param = {id: this.planId};
getPlanCaseEnv(param).then((res) => {
let data = res.data;
if (data) {
@ -418,7 +409,7 @@ export default {
}
}
if (this.projectIds.size === 0) {
param = { id: this.planId };
param = {id: this.planId};
getPlanCaseProjectIds(param).then((res) => {
let data = res.data;
if (data) {
@ -436,11 +427,11 @@ export default {
},
handleCommand(command) {
if (
this.runConfig.resourcePoolId == null &&
this.haveOtherExecCase
this.runConfig.resourcePoolId == null &&
this.haveOtherExecCase
) {
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;
}
@ -459,11 +450,12 @@ export default {
</script>
<style scoped>
.env-container{
.env-container {
max-height: 400px;
overflow-y: auto;
padding-bottom: 1px;
}
.env-container .title {
width: 100px;
min-width: 100px;
@ -486,6 +478,7 @@ export default {
.radio-change:deep(.el-radio__input.is-checked + .el-radio__label) {
color: #606266 !important;
}
.radio-change:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
color: #606266 !important;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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