Merge branch 'master' into v1.7

This commit is contained in:
BugKing 2021-01-28 14:37:14 +08:00
commit 90d8161b5e
68 changed files with 914 additions and 460 deletions

View File

@ -158,7 +158,8 @@ public class MsHTTPSamplerProxy extends MsTestElement {
if (!path.startsWith("/")) {
path = "/" + path;
}
path = sampler.getProtocol() + "://" + sampler.getDomain() + ":" + sampler.getPort() + path;
String port = sampler.getPort() != 80 ? ":" + sampler.getPort() : "";
path = sampler.getProtocol() + "://" + sampler.getDomain() + port + path;
}
sampler.setPath(path);
}

View File

@ -24,7 +24,6 @@ import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
import java.util.stream.Collectors;
@Data
@EqualsAndHashCode(callSuper = true)
@ -51,7 +50,7 @@ public class MsJDBCSampler extends MsTestElement {
@JSONField(ordinal = 28)
private String dataSourceId;
@JSONField(ordinal = 29)
private String protocol="SQL";
private String protocol = "SQL";
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
@ -62,7 +61,8 @@ public class MsJDBCSampler extends MsTestElement {
this.getRefElement(this);
}
if (StringUtils.isNotEmpty(dataSourceId)) {
initDataSource();
this.dataSource = null;
this.initDataSource();
}
if (this.dataSource == null) {
MSException.throwException("数据源为空无法执行");
@ -79,14 +79,16 @@ public class MsJDBCSampler extends MsTestElement {
private void initDataSource() {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(this.dataSourceId);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(environmentId);
if (environment != null && environment.getConfig() != null) {
EnvironmentConfig config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
if (CollectionUtils.isNotEmpty(config.getDatabaseConfigs())) {
List<DatabaseConfig> databaseConfigs = config.getDatabaseConfigs().stream().filter((DatabaseConfig d) -> this.dataSourceId.equals(d.getId())).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(databaseConfigs)) {
this.dataSource = databaseConfigs.get(0);
}
EnvironmentConfig envConfig = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
if (CollectionUtils.isNotEmpty(envConfig.getDatabaseConfigs())) {
envConfig.getDatabaseConfigs().forEach(item -> {
if (item.getId().equals(this.dataSourceId)) {
this.dataSource = item;
return;
}
});
}
}
}

View File

@ -227,12 +227,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
LogUtil.error(e.getMessage(), e);
}
}
try {
sendTask(report, reportUrl, testResult);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
}
private static void sendTask(ApiTestReport report, String reportUrl, TestResult testResult) {
@ -297,6 +292,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
requestResult.setHeaders(result.getRequestHeaders());
requestResult.setRequestSize(result.getSentBytes());
requestResult.setStartTime(result.getStartTime());
requestResult.setEndTime(result.getEndTime());
requestResult.setTotalAssertions(result.getAssertionResults().length);
requestResult.setSuccess(result.isSuccessful());
requestResult.setError(result.getErrorCount());
@ -336,7 +332,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
requestResult.addPassAssertions();
}
//xpath 提取错误会添加断言错误
if (StringUtils.isBlank(responseAssertionResult.getMessage()) || !responseAssertionResult.getMessage().contains("The required item type of the first operand of")) {
if (StringUtils.isBlank(responseAssertionResult.getMessage()) || !responseAssertionResult.getName().endsWith("XPath2Extractor")) {
responseResult.getAssertions().add(responseAssertionResult);
}
}

View File

@ -18,6 +18,8 @@ public class RequestResult {
private long startTime;
private long endTime;
private int error;
private boolean success;

View File

@ -217,7 +217,7 @@ public class ApiAutomationService {
ids.add(scenarioId);
deleteApiScenarioReport(ids);
scheduleService.deleteByResourceId(scenarioId);
scheduleService.deleteScheduleAndJobByResourceId(scenarioId,ScheduleGroup.API_SCENARIO_TEST.name());
TestPlanApiScenarioExample example = new TestPlanApiScenarioExample();
example.createCriteria().andApiScenarioIdEqualTo(scenarioId);
List<TestPlanApiScenario> testPlanApiScenarioList = testPlanApiScenarioMapper.selectByExample(example);
@ -282,6 +282,10 @@ public class ApiAutomationService {
public void removeToGc(List<String> apiIds) {
extApiScenarioMapper.removeToGc(apiIds);
//将这些场景的定时任务删除掉
for (String id : apiIds) {
scheduleService.deleteScheduleAndJobByResourceId(id,ScheduleGroup.API_SCENARIO_TEST.name());
}
}
public void reduction(List<SaveApiScenarioRequest> requests) {

View File

@ -323,7 +323,9 @@ public class ApiDefinitionService {
apiDefinitionMapper.updateByPrimaryKeyWithBLOBs(apiDefinition);
}
} else if (StringUtils.equals("incrementalMerge", apiTestImportRequest.getModeId())) {
batchMapper.insert(apiDefinition);
if (CollectionUtils.isEmpty(sameRequest)) {
batchMapper.insert(apiDefinition);
}
} else {
if (CollectionUtils.isEmpty(sameRequest)) {
batchMapper.insert(apiDefinition);

View File

@ -163,6 +163,7 @@ public class ApiScenarioReportService {
String passRate = new DecimalFormat("0%").format((float) scenarioResult.getSuccess() / (scenarioResult.getSuccess() + scenarioResult.getError()));
testPlanApiScenario.setPassRate(passRate);
testPlanApiScenario.setReportId(report.getId());
testPlanApiScenario.setUpdateTime(System.currentTimeMillis());
testPlanApiScenarioMapper.updateByPrimaryKeySelective(testPlanApiScenario);
}
returnReport = report;
@ -220,6 +221,7 @@ public class ApiScenarioReportService {
apiScenarioReportDetailMapper.insert(detail);
testPlanApiScenario.setReportId(report.getId());
testPlanApiScenario.setUpdateTime(System.currentTimeMillis());
testPlanApiScenarioMapper.updateByPrimaryKeySelective(testPlanApiScenario);
lastReport = report;
@ -343,13 +345,41 @@ public class ApiScenarioReportService {
ids = allIds.stream().filter(id -> !reportRequest.getUnSelectIds().contains(id)).collect(Collectors.toList());
}
ApiScenarioReportDetailExample detailExample = new ApiScenarioReportDetailExample();
detailExample.createCriteria().andReportIdIn(ids);
apiScenarioReportDetailMapper.deleteByExample(detailExample);
//为预防数量太多调用删除方法时引起SQL过长的Bug此处采取分批执行的方式
//每次处理的数据数量
int handleCount = 7000;
//每次处理的集合
List<String> handleIdList = new ArrayList<>(handleCount);
while (ids.size() > handleCount){
handleIdList = new ArrayList<>(handleCount);
List<String> otherIdList = new ArrayList<>();
for (int index = 0;index < ids.size();index++){
if(index<handleCount){
handleIdList.add(ids.get(index));
}else{
otherIdList.add(ids.get(index));
}
}
//处理本次的数据
ApiScenarioReportDetailExample detailExample = new ApiScenarioReportDetailExample();
detailExample.createCriteria().andReportIdIn(handleIdList);
apiScenarioReportDetailMapper.deleteByExample(detailExample);
ApiScenarioReportExample apiTestReportExample = new ApiScenarioReportExample();
apiTestReportExample.createCriteria().andIdIn(handleIdList);
apiScenarioReportMapper.deleteByExample(apiTestReportExample);
//转存剩余的数据
ids = otherIdList;
}
ApiScenarioReportExample apiTestReportExample = new ApiScenarioReportExample();
apiTestReportExample.createCriteria().andIdIn(ids);
apiScenarioReportMapper.deleteByExample(apiTestReportExample);
//处理最后剩余的数据
if(!ids.isEmpty()){
ApiScenarioReportDetailExample detailExample = new ApiScenarioReportDetailExample();
detailExample.createCriteria().andReportIdIn(ids);
apiScenarioReportDetailMapper.deleteByExample(detailExample);
ApiScenarioReportExample apiTestReportExample = new ApiScenarioReportExample();
apiTestReportExample.createCriteria().andIdIn(ids);
apiScenarioReportMapper.deleteByExample(apiTestReportExample);
}
}
public long countByProjectID(String projectId) {

View File

@ -134,7 +134,9 @@ public class HistoricalDataUpgradeService {
request1.getBody().setType(Body.FORM_DATA);
}
if ("json".equals(request1.getBody().getFormat())) {
request1.getBody().setType(Body.JSON);
if ("Raw".equals(request1.getBody().getType())) {
request1.getBody().setType(Body.JSON);
}
if (CollectionUtils.isEmpty(request1.getHeaders())) {
List<KeyValue> headers = new LinkedList<>();
headers.add(new KeyValue("Content-Type", "application/json"));

View File

@ -33,5 +33,4 @@ public interface ScheduleMapper {
int updateByPrimaryKeyWithBLOBs(Schedule record);
int updateByPrimaryKey(Schedule record);
}

View File

@ -219,7 +219,8 @@
</if>
<if test="request.name != null">
and (api_definition.name like CONCAT('%', #{request.name},'%')
or api_definition.tags like CONCAT('%', #{request.name},'%'))
or api_definition.tags like CONCAT('%', #{request.name},'%')
or api_definition.num like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.protocol != null">
AND api_definition.protocol = #{request.protocol}

View File

@ -143,7 +143,9 @@
</if>
<if test="request.name != null">
and (api_scenario.name like CONCAT('%', #{request.name},'%') or api_scenario.tags like CONCAT('%', #{request.name},'%'))
and (api_scenario.name like CONCAT('%', #{request.name},'%')
or api_scenario.tags like CONCAT('%', #{request.name},'%')
or api_scenario.num like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.workspaceId != null">
AND project.workspace_id = #{request.workspaceId}

View File

@ -284,7 +284,9 @@
</foreach>
</if>
<if test="request.name != null and request.name!=''">
and (t1.name like CONCAT('%', #{request.name},'%') or t1.tags like CONCAT('%', #{request.name},'%'))
and (t1.name like CONCAT('%', #{request.name},'%')
or t1.tags like CONCAT('%', #{request.name},'%')
or t1.num like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.createTime > 0">
and t1.create_time >= #{request.createTime}

View File

@ -19,7 +19,7 @@
select
t.id, t.environment_id, t.create_time, t.update_time, t.last_result, t.pass_rate, t.report_id,
c.id as case_id, c.project_id, c.user_id,c.api_scenario_module_id, c.module_path, c.name, c.level,
c.status, c.principal, c.step_total, c.follow_people, c.schedule, c.description,
c.status, c.principal, c.step_total, c.follow_people, c.schedule, c.description, c.tags, c.num,
p.name as project_name, p.id as project_id, u.name as user_name
from
test_plan_api_scenario t
@ -44,7 +44,9 @@
</foreach>
</if>
<if test="request.name != null and request.name!=''">
and c.name like CONCAT('%', #{request.name},'%')
and (c.name like CONCAT('%', #{request.name},'%')
or c.num like CONCAT('%', #{request.name},'%')
or c.tags like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.status != null and request.status!=''">
and t.last_result like CONCAT('%', #{request.status},'%')

View File

@ -122,7 +122,7 @@
<select id="list" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
select test_plan_test_case.id as id, test_case.id as caseId, test_case.name, test_case.priority,
test_case.type,test_case.test_id as testId,test_case.node_id,
test_case.type,test_case.test_id as testId,test_case.node_id, test_case.tags,
test_case.node_path, test_case.method, test_case.num, test_plan_test_case.executor, test_plan_test_case.status,
test_plan_test_case.update_time, test_case_node.name as model, project.name as projectName,
test_plan_test_case.plan_id as planId
@ -139,7 +139,7 @@
</if>
<if test="request.name != null">
and (test_case.name like CONCAT('%', #{request.name},'%') or test_case.num like
CONCAT('%',#{request.name},'%'))
CONCAT('%',#{request.name},'%') or test_case.tags like CONCAT('%', #{request.name},'%'))
</if>
<if test="request.id != null">
and test_case.id = #{request.id}

View File

@ -28,6 +28,7 @@ public class ShiroUtils {
filterChainDefinitionMap.put("/authsource/list/allenable", "anon");
filterChainDefinitionMap.put("/sso/signin", "anon");
filterChainDefinitionMap.put("/sso/callback", "anon");
filterChainDefinitionMap.put("/license/valid", "anon");
// for swagger
filterChainDefinitionMap.put("/swagger-ui.html", "anon");

View File

@ -105,25 +105,27 @@ public class TestPlanTestJob extends MsScheduleJob {
//需要更新这里来保证PlanCase的状态能正常更改
apiTestCaseService.run(blobs,UUID.randomUUID().toString(),testPlanReport.getId(),this.resourceId,ApiRunMode.SCHEDULE_API_PLAN.name());
}
LogUtil.info("-------------- testplan schedule ---------- api case over -----------------");
//执行场景执行任务
SchedulePlanScenarioExecuteRequest scenarioRequest = new SchedulePlanScenarioExecuteRequest();
String senarionReportID = UUID.randomUUID().toString();
scenarioRequest.setId(senarionReportID);
scenarioRequest.setReportId(senarionReportID);
scenarioRequest.setProjectId(projectID);
scenarioRequest.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
scenarioRequest.setExecuteType(ExecuteType.Saved.name());
Map<String, Map<String,String>> testPlanScenarioIdMap = new HashMap<>();
testPlanScenarioIdMap.put(resourceId, this.planScenarioIdMap);
scenarioRequest.setTestPlanScenarioIDMap(testPlanScenarioIdMap);
scenarioRequest.setReportUserID(this.userId);
scenarioRequest.setTestPlanID(this.resourceId);
scenarioRequest.setRunMode(ApiRunMode.SCHEDULE_SCENARIO_PLAN.name());
scenarioRequest.setTestPlanReportId(testPlanReport.getId());
testPlanService.runScenarioCase(scenarioRequest);
LogUtil.info("-------------- testplan schedule ---------- scenario case over -----------------");
//执行场景执行任务
if(!planScenarioIdMap.isEmpty()){
LogUtil.info("-------------- testplan schedule ---------- api case over -----------------");
SchedulePlanScenarioExecuteRequest scenarioRequest = new SchedulePlanScenarioExecuteRequest();
String senarionReportID = UUID.randomUUID().toString();
scenarioRequest.setId(senarionReportID);
scenarioRequest.setReportId(senarionReportID);
scenarioRequest.setProjectId(projectID);
scenarioRequest.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
scenarioRequest.setExecuteType(ExecuteType.Saved.name());
Map<String, Map<String,String>> testPlanScenarioIdMap = new HashMap<>();
testPlanScenarioIdMap.put(resourceId, this.planScenarioIdMap);
scenarioRequest.setTestPlanScenarioIDMap(testPlanScenarioIdMap);
scenarioRequest.setReportUserID(this.userId);
scenarioRequest.setTestPlanID(this.resourceId);
scenarioRequest.setRunMode(ApiRunMode.SCHEDULE_SCENARIO_PLAN.name());
scenarioRequest.setTestPlanReportId(testPlanReport.getId());
testPlanService.runScenarioCase(scenarioRequest);
LogUtil.info("-------------- testplan schedule ---------- scenario case over -----------------");
}
//执行性能测试任务
List<String> performaneReportIDList = new ArrayList<>();
for (Map.Entry<String,String> entry: this.performanceIdMap.entrySet()) {

View File

@ -20,10 +20,13 @@ public class MailNoticeSender extends AbstractNoticeSender {
private MailService mailService;
private void sendMail(MessageDetail messageDetail, String context, NoticeModel noticeModel) throws MessagingException {
LogUtil.info("发送邮件开始 ");
JavaMailSenderImpl javaMailSender = mailService.getMailSender();
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom(javaMailSender.getUsername());
LogUtil.info("发件人地址"+javaMailSender.getUsername());
LogUtil.info("helper"+helper);
helper.setSubject("MeterSphere " + noticeModel.getSubject());
List<String> emails = super.getUserEmails(messageDetail.getUserIds());
String[] users = emails.toArray(new String[0]);
@ -38,6 +41,7 @@ public class MailNoticeSender extends AbstractNoticeSender {
String context = super.getHtmlContext(messageDetail, noticeModel);
try {
sendMail(messageDetail, context, noticeModel);
LogUtil.info("发送邮件结束");
} catch (Exception e) {
LogUtil.error(e);
}

View File

@ -53,14 +53,14 @@ public class MailService {
props.put("mail.smtp.starttls.enable", result);
props.put("mail.smtp.starttls.required", result);
break;
case "smtp.anon":
/* case "smtp.anon":
boolean isAnon = BooleanUtils.toBoolean(p.getParamValue());
if (isAnon) {
props.put("mail.smtp.auth", "false");
javaMailSender.setUsername(null);
javaMailSender.setPassword(null);
}
break;
break;*/
default:
break;
}

View File

@ -18,10 +18,7 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.OrderRequest;
import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.ScheduleDao;
import io.metersphere.job.sechedule.ApiScenarioTestJob;
import io.metersphere.job.sechedule.ApiTestJob;
import io.metersphere.job.sechedule.ScheduleManager;
import io.metersphere.job.sechedule.TestPlanTestJob;
import io.metersphere.job.sechedule.*;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
@ -98,6 +95,25 @@ public class ScheduleService {
return scheduleMapper.deleteByExample(scheduleExample);
}
public int deleteScheduleAndJobByResourceId(String resourceId,String group) {
ScheduleExample scheduleExample = new ScheduleExample();
scheduleExample.createCriteria().andResourceIdEqualTo(resourceId);
removeJob(resourceId,group);
return scheduleMapper.deleteByExample(scheduleExample);
}
public void removeJob(String resourceId,String group) {
if(StringUtils.equals(ScheduleGroup.API_SCENARIO_TEST.name(),group)){
scheduleManager.removeJob(ApiScenarioTestJob.getJobKey(resourceId), ApiScenarioTestJob.getTriggerKey(resourceId));
}else if(StringUtils.equals(ScheduleGroup.TEST_PLAN_TEST.name(),group)){
scheduleManager.removeJob(TestPlanTestJob.getJobKey(resourceId), TestPlanTestJob.getTriggerKey(resourceId));
}else if(StringUtils.equals(ScheduleGroup.SWAGGER_IMPORT.name(),group)){
scheduleManager.removeJob(SwaggerUrlImportJob.getJobKey(resourceId), SwaggerUrlImportJob.getTriggerKey(resourceId));
}else{
scheduleManager.removeJob(ApiTestJob.getJobKey(resourceId), ApiTestJob.getTriggerKey(resourceId));
}
}
public List<Schedule> listSchedule() {
ScheduleExample example = new ScheduleExample();
return scheduleMapper.selectByExample(example);

View File

@ -115,6 +115,7 @@ public class TestPlanApiCaseService {
TestPlanApiCase apiCase = new TestPlanApiCase();
apiCase.setId(id);
apiCase.setStatus(status);
apiCase.setUpdateTime(System.currentTimeMillis());
testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase);
}

View File

@ -281,6 +281,18 @@ public class TestPlanReportService {
component.afterBuild(testCaseReportMetricDTO);
});
if (StringUtils.equals(ReportTriggerMode.SCHEDULE.name(),triggerMode)
&&StringUtils.equals(resourceRunMode, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name())) {
//如果是性能测试作为触发由于延迟原因可能会出现报告已经结束但是状态还是进行中的状态
List<TestCaseReportStatusResultDTO> loadResult = testCaseReportMetricDTO.getExecuteResult().getLoadResult();
for (TestCaseReportStatusResultDTO dto: loadResult) {
if(StringUtils.equals(dto.getStatus(),TestPlanTestCaseStatus.Underway.name())){
dto.setStatus(TestPlanTestCaseStatus.Pass.name());
}
}
testCaseReportMetricDTO.getExecuteResult().setLoadResult(loadResult);
}
TestPlanReportDataExample example = new TestPlanReportDataExample();
example.createCriteria().andTestPlanReportIdEqualTo(planReportId);
List<TestPlanReportDataWithBLOBs> testPlanReportDataList = testPlanReportDataMapper.selectByExampleWithBLOBs(example);
@ -437,7 +449,7 @@ public class TestPlanReportService {
if(loadTestReportFromDatabase == null){
//检查错误数据
if(errorDataCheckMap.containsKey(loadTestReportId)){
if(errorDataCheckMap.get(loadTestReportId)>20){
if(errorDataCheckMap.get(loadTestReportId)>10){
performaneReportIDList.remove(loadTestReportId);
}else {
errorDataCheckMap.put(loadTestReportId,errorDataCheckMap.get(loadTestReportId)+1);

View File

@ -28,6 +28,7 @@ import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.ScheduleService;
import io.metersphere.service.SystemParameterService;
import io.metersphere.track.Factory.ReportComponentFactory;
import io.metersphere.track.domain.ReportComponent;
@ -62,6 +63,8 @@ public class TestPlanService {
@Resource
ExtTestPlanMapper extTestPlanMapper;
@Resource
ScheduleService scheduleService;
@Resource
ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper;
@Resource
TestCaseMapper testCaseMapper;
@ -315,6 +318,10 @@ public class TestPlanService {
testPlanProjectService.deleteTestPlanProjectByPlanId(planId);
testPlanApiCaseService.deleteByPlanId(planId);
testPlanScenarioCaseService.deleteByPlanId(planId);
//删除定时任务
scheduleService.deleteScheduleAndJobByResourceId(planId,ScheduleGroup.TEST_PLAN_TEST.name());
int num = testPlanMapper.deleteByPrimaryKey(planId);
List<String> relatedUsers = new ArrayList<>();
AddTestPlanRequest testPlans = new AddTestPlanRequest();

@ -1 +1 @@
Subproject commit 387ca56312b62ae5edb3d7f34afa08946d86d621
Subproject commit 26d36f3f81e889f58eed7c6903252a129b301d98

View File

@ -1,11 +1,11 @@
create table swagger_url_project
(
id varchar(255) not null,
project_id varchar(255) null,
id varchar(30) not null,
project_id varchar(30) null,
swagger_url varchar(255) null,
module_id varchar(255) null,
module_id varchar(30) null,
module_path varchar(255) null,
mode_id varchar(255) null,
mode_id varchar(30) null,
primary key (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;

BIN
frontend/src/assets/info.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

After

Width:  |  Height:  |  Size: 169 KiB

View File

@ -14,7 +14,7 @@
<ms-main-container>
<el-tabs v-model="activeName" @tab-click="addTab" @tab-remove="removeTab">
<el-tab-pane name="default" :label="$t('api_test.automation.scenario_test')">
<el-tab-pane name="default" :label="$t('api_test.automation.scenario_list')">
<ms-api-scenario-list
:module-tree="nodeTree"
:module-options="moduleOptions"
@ -97,6 +97,7 @@
renderComponent: true,
isHide: true,
activeName: 'default',
redirectFlag: 'none',
currentModule: null,
moduleOptions: [],
tabs: [],
@ -150,6 +151,15 @@
},
changeRedirectParam(redirectIDParam) {
this.redirectID = redirectIDParam;
if(redirectIDParam!=null){
if(this.redirectFlag == "none"){
this.activeName = "default";
this.addListener();
this.redirectFlag = "redirected";
}
}else{
this.redirectFlag = "none";
}
},
addTab(tab) {
if (!getCurrentProjectID()) {
@ -227,8 +237,18 @@
this.$refs.apiScenarioList.search(data);
},
refresh(data) {
this.setTabTitle(data);
this.$refs.apiScenarioList.search(data);
},
setTabTitle(data) {
for (let index in this.tabs) {
let tab = this.tabs[index];
if (tab.name === this.activeName) {
tab.label = data.name;
break;
}
}
},
editScenario(row) {
this.addTab({name: 'edit', currentScenario: row});
},

View File

@ -118,6 +118,7 @@
throw e;
}
this.getFails();
this.computeTotalTime();
this.loading = false;
} else {
setTimeout(this.getReport, 2000)
@ -146,12 +147,30 @@
failScenario.requestResults.push(failRequest);
}
})
}
})
}
}
},
computeTotalTime() {
if (this.content.scenarios) {
let startTime = 99991611737506593;
let endTime = 0;
this.content.scenarios.forEach((scenario) => {
scenario.requestResults.forEach((request) => {
if (request.startTime && Number(request.startTime) < startTime) {
startTime = request.startTime;
}
if (request.endTime && Number(request.endTime) > endTime) {
endTime = request.endTime;
}
})
})
if (startTime < endTime) {
this.totalTime = endTime - startTime + 100;
}
}
},
requestResult(requestResult) {
this.active();
this.isRequestResult = false;

View File

@ -1,17 +1,21 @@
<template>
<div class="request-result">
<div>
<el-row :gutter="10" type="flex" align="middle" class="info">
<el-row :gutter="8" type="flex" align="middle" class="info">
<el-col :span="2">
<div class="method">
{{request.method}}
</div>
</el-col>
<el-col :span="10">
<el-col :span="8">
<el-tooltip effect="dark" :content="request.url" placement="bottom" :open-delay="800">
<div class="url">{{request.url}}</div>
</el-tooltip>
</el-col>
<el-col :span="8">
<div class="url"> {{$t('api_report.start_time')}}{{request.startTime | timestampFormatDate(true) }}
</div>
</el-col>
</el-row>
</div>
<el-collapse-transition>
@ -19,7 +23,7 @@
<el-tabs v-model="activeName" v-show="isActive" v-if="hasSub">
<el-tab-pane :label="$t('api_report.sub_result')" name="sub">
<ms-request-sub-result class="sub-result" v-for="(sub, index) in request.subRequestResults"
:key="index" :indexNumber="index" :request="sub"/>
:key="index" :indexNumber="index" :request="sub"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_report.request_result')" name="result">
<ms-response-text :request-type="requestType" :response="request.responseResult" :request="request"/>
@ -43,7 +47,7 @@
export default {
name: "MsRequestResultTail",
components: {MsResponseText, MsRequestText, MsAssertionResults, MsRequestMetric, MsRequestResult,MsRequestSubResult},
components: {MsResponseText, MsRequestText, MsAssertionResults, MsRequestMetric, MsRequestResult, MsRequestSubResult},
props: {
request: Object,
scenarioName: String,

View File

@ -3,7 +3,7 @@
<el-card class="table-card" v-loading="loading">
<template v-slot:header>
<ms-table-header :condition.sync="condition" @search="selectByParam" title=""
:show-create="false"/>
:show-create="false" :tip="$t('commons.search_by_id_name_tag')"/>
</template>
<el-table ref="scenarioTable" border :data="tableData" class="adjust-table ms-select-all-fixed"

View File

@ -137,7 +137,7 @@
</div>
<!-- 场景步骤内容 -->
<div v-loading="loading">
<el-tree node-key="resourceId" :props="props" :data="scenarioDefinition"
<el-tree node-key="resourceId" :props="props" :data="scenarioDefinition" class="ms-tree"
:default-expanded-keys="expandedNode"
:expand-on-click-node="false"
highlight-current
@ -286,8 +286,7 @@
},
response: {}
}
}
,
},
created() {
if (!this.currentScenario.apiScenarioModuleId) {
this.currentScenario.apiScenarioModuleId = "";
@ -848,7 +847,7 @@
if (this.currentScenario.tags instanceof String) {
this.currentScenario.tags = JSON.parse(this.currentScenario.tags);
}
this.$emit('refresh');
this.$emit('refresh', this.currentScenario);
})
}
})
@ -857,6 +856,9 @@
if (this.currentScenario.tags != undefined && !(this.currentScenario.tags instanceof Array)) {
this.currentScenario.tags = JSON.parse(this.currentScenario.tags);
}
if (!this.currentScenario.variables) {
this.currentScenario.variables = [];
}
if (this.currentScenario.id) {
this.result = this.$get("/api/automation/getApiScenario/" + this.currentScenario.id, response => {
if (response.data) {
@ -1037,15 +1039,27 @@
z-index: 1;
}
.ms-expanded >>> .el-tree-node__expand-icon.expanded {
color: #7C3985;
.ms-tree >>> .el-tree-node__expand-icon.expanded {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
.ms-el-icon-caret-right .el-icon-caret-right {
color: #7C3985;
.ms-tree >>> .el-icon-caret-right:before {
content: '\e723';
font-size: 20px;
}
.ms-is-leaf >>> .is-leaf {
.ms-tree >>> .el-tree-node__expand-icon.is-leaf {
color: transparent;
}
.ms-tree >>> .el-tree-node__expand-icon {
color: #7C3985;
}
.ms-tree >>> .el-tree-node__expand-icon.expanded.el-icon-caret-right:before {
color: #7C3985;
content: "\e722";
font-size: 20px;
}
</style>

View File

@ -72,4 +72,11 @@
font-size: 12px;
padding-bottom: 10px;
}
.el-button--text .el-link.el-link--default {
font-size: 12px;
color: #4b1980;
font-weight: 400;
text-decoration: underline;
}
</style>

View File

@ -193,7 +193,10 @@ export default {
}
if (this.currentProtocol != null) {
this.condition.protocol = this.currentProtocol;
}else{
this.condition.protocol = "HTTP";
}
let url = '/api/definition/list/';
if (this.isTestPlan) {
url = '/api/definition/list/relevance/';

View File

@ -37,15 +37,17 @@
</el-tooltip>
</div>
</div>
<div class="header">
<el-collapse-transition>
<div v-if="data.active && showCollapse" :draggable="draggable">
<fieldset :disabled="data.disabled" class="ms-fieldset">
<el-divider></el-divider>
<slot></slot>
</fieldset>
</div>
</el-collapse-transition>
</div>
<el-collapse-transition>
<div v-if="data.active && showCollapse" :draggable="draggable">
<fieldset :disabled="data.disabled" style="border: 0px">
<el-divider></el-divider>
<slot></slot>
</fieldset>
</div>
</el-collapse-transition>
</el-card>
</template>
@ -148,5 +150,12 @@
.enable-switch {
margin-right: 10px;
}
fieldset {
padding: 0px;
margin: 0px;
min-width: 100%;
min-inline-size: 0px;
border: 0px;
}
</style>

View File

@ -76,18 +76,20 @@
</el-select>
<el-input size="small" v-model="controller.whileController.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<span class="ms-span ms-radio">{{$t('loop.timeout')}}</span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="1" :step="1000"/>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="3000" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</div>
<p class="tip">{{$t('api_test.definition.request.res_param')}} </p>
<el-tabs v-model="activeName">
<el-tab-pane :label="item.name" :name="item.name" v-for="(item,index) in requestResult.scenarios" :key="index">
<div v-for="(result,i) in item.requestResults" :key="i" style="margin-bottom: 5px">
<api-response-component :result="result"/>
</div>
</el-tab-pane>
</el-tabs>
<div>
<el-tabs v-model="activeName" closable class="ms-tabs">
<el-tab-pane :label="item.name" :name="item.name" v-for="(item,index) in requestResult.scenarios" :key="index">
<div v-for="(result,i) in item.requestResults" :key="i" style="margin-bottom: 5px">
<api-response-component :result="result"/>
</div>
</el-tab-pane>
</el-tabs>
</div>
</api-base-component>
@ -331,6 +333,11 @@
margin: 20px 0;
}
.ms-tabs >>> .el-icon-close:before {
content: "";
}
.icon.is-active {
transform: rotate(90deg);
}

View File

@ -35,7 +35,6 @@
this.$refs.nameInput.focus();
}
}
}
</script>

View File

@ -8,12 +8,22 @@
<el-form :model="form" :rules="rules" ref="from">
<el-form-item
prop="cronValue">
<el-input :disabled="isReadOnly" v-model="form.cronValue" class="inp"
:placeholder="$t('schedule.please_input_cron_expression')"/>
<el-button :disabled="isReadOnly" type="primary" @click="saveCron" v-tester>{{
$t('commons.save')
}}
</el-button>
<el-row>
<el-col :span="18">
<el-input :disabled="isReadOnly" v-model="form.cronValue" class="inp"
:placeholder="$t('schedule.please_input_cron_expression')"/>
<el-button :disabled="isReadOnly" type="primary" @click="saveCron" v-tester>{{
$t('commons.save')
}}
</el-button>
</el-col>
<el-col :span="6">
<schedule-switch :schedule="schedule" @scheduleChange="scheduleChange"></schedule-switch>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-link :disabled="isReadOnly" type="primary" @click="showCronDialog">
@ -44,6 +54,7 @@ import Crontab from "@/business/components/common/cron/Crontab";
import CrontabResult from "@/business/components/common/cron/CrontabResult";
import {cronValidate} from "@/common/js/cron";
import MsScheduleNotification from "./ScheduleNotification";
import ScheduleSwitch from "@/business/components/api/automation/schedule/ScheduleSwitch";
function defaultCustomValidate() {
return {pass: true};
@ -55,7 +66,7 @@ const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./
export default {
name: "MsScheduleMaintain",
components: {CrontabResult, Crontab, MsScheduleNotification, "NoticeTemplate": noticeTemplate.default},
components: {CrontabResult, ScheduleSwitch,Crontab, MsScheduleNotification, "NoticeTemplate": noticeTemplate.default},
props: {
customValidate: {
@ -100,6 +111,7 @@ export default {
form: {
cronValue: ""
},
paramRow:{},
activeName: 'first',
rules: {
cronValue: [{required: true, validator: validateCron, trigger: 'blur'}],
@ -110,6 +122,35 @@ export default {
currentUser: () => {
return getCurrentUser();
},
scheduleChange(){
let flag = this.schedule.enable;
this.$confirm(this.$t('api_test.home_page.running_task_list.confirm.close_title'), this.$t('commons.prompt'), {
confirmButtonText: this.$t('commons.confirm'),
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
let param = {};
param.taskID = this.schedule.id;
param.enable = flag;
this.updateTask(param);
}).catch(() => {
});
},
updateTask(param){
this.result = this.$post('/api/schedule/updateEnableByPrimyKey', param, response => {
let paramTestId = "";
if (this.paramRow.redirectFrom == 'testPlan') {
paramTestId = this.paramRow.id;
this.scheduleTaskType = "TEST_PLAN_TEST";
} else {
paramTestId = this.paramRow.id;
this.scheduleTaskType = "API_SCENARIO_TEST";
}
this.taskID = paramTestId;
this.findSchedule(paramTestId);
});
},
initUserList() {
let param = {
name: '',
@ -132,6 +173,7 @@ export default {
open(row) {
//
let paramTestId = "";
this.paramRow = row;
if (row.redirectFrom == 'testPlan') {
paramTestId = row.id;
this.scheduleTaskType = "TEST_PLAN_TEST";

View File

@ -0,0 +1,82 @@
<template>
<div class="schedule-config">
<div>
<span class="cron-ico">
<i class="el-icon-date" size="small"></i>
<span class="character">SCHEDULER</span>
</span>
<!-- <el-switch :disabled="!schedule.value || isReadOnly" v-model="schedule.enable" @change="scheduleChange"/>-->
<!-- <el-switch :disabled="!schedule.value || isReadOnly" v-model="schedule.enable" />-->
<el-switch :disabled="!schedule.value" v-model="schedule.enable" @change="scheduleChange"/>
</div>
<div>
<span>
{{ $t('schedule.next_execution_time') }}
<span :class="{'disable-character': !schedule.enable}"
v-if="!schedule.enable">{{ $t('schedule.not_set') }}</span>
<crontab-result v-if="schedule.enable" :enable-simple-mode="true" :ex="schedule.value" ref="crontabResult"/>
</span>
</div>
</div>
</template>
<script>
import CrontabResult from "@/business/components/common/cron/CrontabResult";
export default {
name: "ScheduleSwitch",
components: {CrontabResult},
data() {
return {
}
},
props: {
testId: String,
schedule: Object,
isReadOnly: {
type: Boolean,
default: false
}
},
methods: {
scheduleChange() {
this.$emit('scheduleChange');
},
},
watch: {
}
}
</script>
<style scoped>
.schedule-config {
float: right;
width: 250px;
height: 15px;
line-height: 25px;
}
.el-icon-date {
font-size: 20px;
margin-left: 5px;
}
.character {
font-weight: bold;
margin: 0 5px;
}
.disable-character {
color: #cccccc;
}
.el-switch {
margin: 0 5px;
}
.cron-ico {
cursor: pointer;
}
</style>

View File

@ -263,7 +263,6 @@
this.apiTabs = [];
this.apiDefaultTab = tabs.name;
this.apiTabs.push(tabs);
this.refresh();
},
handleTabRemove(targetName) {
let tabs = this.apiTabs;

View File

@ -23,7 +23,7 @@
</div>
</el-row>
<el-dialog :title="$t('api_test.request.assertions.script')" :visible.sync="visible" width="900px">
<el-dialog :title="$t('api_test.request.assertions.script')" :visible.sync="visible" width="900px" append-to-body>
<el-row type="flex" justify="space-between" align="middle" class="quick-script-block">
<div class="assertion-item input">
<el-input size="small" v-model="assertion.variable"
@ -55,206 +55,206 @@
</template>
<script>
import {AssertionJSR223} from "../../model/ApiTestModel";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor";
import {AssertionJSR223} from "../../model/ApiTestModel";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import MsJsr233Processor from "../../../automation/scenario/common/Jsr233ProcessorContent";
export default {
name: "MsApiAssertionJsr223",
components: {MsJsr233Processor, MsDialogFooter},
props: {
assertion: {
default: () => {
return new AssertionJSR223();
}
},
edit: {
type: Boolean,
default: false
},
index: Number,
list: Array,
callback: Function,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
visible: false,
operators: {
EQ: {
label: "commons.adv_search.operators.equals",
value: "=="
},
NE: {
label: "commons.adv_search.operators.not_equals",
value: "!="
},
CONTAINS: {
label: "commons.adv_search.operators.like",
value: "contains"
},
NOT_CONTAINS: {
label: "commons.adv_search.operators.not_like",
value: "not contains"
},
GT: {
label: "commons.adv_search.operators.gt",
value: ">"
},
LT: {
label: "commons.adv_search.operators.lt",
value: "<"
},
IS_EMPTY: {
label: "commons.adv_search.operators.is_empty",
value: "is empty"
},
IS_NOT_EMPTY: {
label: "commons.adv_search.operators.is_not_empty",
value: "is not empty"
export default {
name: "MsApiAssertionJsr223",
components: {MsJsr233Processor, MsDialogFooter},
props: {
assertion: {
default: () => {
return new AssertionJSR223();
}
},
templates: [
{
title: this.$t('api_test.request.assertions.set_failure_status'),
value: 'AssertionResult.setFailure(true)',
edit: {
type: Boolean,
default: false
},
index: Number,
list: Array,
callback: Function,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
visible: false,
operators: {
EQ: {
label: "commons.adv_search.operators.equals",
value: "=="
},
NE: {
label: "commons.adv_search.operators.not_equals",
value: "!="
},
CONTAINS: {
label: "commons.adv_search.operators.like",
value: "contains"
},
NOT_CONTAINS: {
label: "commons.adv_search.operators.not_like",
value: "not contains"
},
GT: {
label: "commons.adv_search.operators.gt",
value: ">"
},
LT: {
label: "commons.adv_search.operators.lt",
value: "<"
},
IS_EMPTY: {
label: "commons.adv_search.operators.is_empty",
value: "is empty"
},
IS_NOT_EMPTY: {
label: "commons.adv_search.operators.is_not_empty",
value: "is not empty"
}
},
{
title: this.$t('api_test.request.assertions.set_failure_msg'),
value: 'AssertionResult.setFailureMessage("msg")',
templates: [
{
title: this.$t('api_test.request.assertions.set_failure_status'),
value: 'AssertionResult.setFailure(true)',
},
{
title: this.$t('api_test.request.assertions.set_failure_msg'),
value: 'AssertionResult.setFailureMessage("msg")',
}
],
}
},
methods: {
add() {
this.list.push(new AssertionJSR223(this.assertion));
this.callback();
},
remove() {
this.list.splice(this.index, 1);
},
changeOperator(value) {
if (value.indexOf("empty") > 0 && !!this.assertion.value) {
this.assertion.value = "";
}
],
}
},
this.quickScript();
},
quickScript() {
if (this.assertion.variable && this.assertion.operator) {
let variable = this.assertion.variable;
let operator = this.assertion.operator;
let value = this.assertion.value || "";
let desc = "${" + variable + "} " + operator + " '" + value + "'";
let script = "value = vars.get(\"" + variable + "\");\n"
switch (this.assertion.operator) {
case "==":
script += "result = \"" + value + "\".equals(value);\n";
break;
case "!=":
script += "result = !\"" + value + "\".equals(value);\n";
break;
case "contains":
script += "result = value.contains(\"" + value + "\");\n";
break;
case "not contains":
script += "result = !value.contains(\"" + value + "\");\n";
break;
case ">":
desc = "${" + variable + "} " + operator + " " + value;
script += "number = Integer.parseInt(value);\n" +
"result = number > " + value + ";\n";
break;
case "<":
desc = "${" + variable + "} " + operator + " " + value;
script += "number = Integer.parseInt(value);\n" +
"result = number < " + value + ";\n";
break;
case "is empty":
desc = "${" + variable + "} " + operator
script += "result = value == void || value.length() == 0;\n";
break;
case "is not empty":
desc = "${" + variable + "} " + operator
script += "result = value != void && value.length() > 0;\n";
break;
}
let msg = "assertion [" + desc + "]: false;"
script += "if (!result){\n" +
"\tmsg = \"" + msg + "\";\n" +
"\tAssertionResult.setFailureMessage(msg);\n" +
"\tAssertionResult.setFailure(true);\n" +
"}";
methods: {
add() {
this.list.push(new AssertionJSR223(this.assertion));
this.callback();
},
remove() {
this.list.splice(this.index, 1);
},
changeOperator(value) {
if (value.indexOf("empty") > 0 && !!this.assertion.value) {
this.assertion.value = "";
}
this.quickScript();
},
quickScript() {
if (this.assertion.variable && this.assertion.operator) {
let variable = this.assertion.variable;
let operator = this.assertion.operator;
let value = this.assertion.value || "";
let desc = "${" + variable + "} " + operator + " '" + value + "'";
let script = "value = vars.get(\"" + variable + "\");\n"
switch (this.assertion.operator) {
case "==":
script += "result = \"" + value + "\".equals(value);\n";
break;
case "!=":
script += "result = !\"" + value + "\".equals(value);\n";
break;
case "contains":
script += "result = value.contains(\"" + value + "\");\n";
break;
case "not contains":
script += "result = !value.contains(\"" + value + "\");\n";
break;
case ">":
desc = "${" + variable + "} " + operator + " " + value;
script += "number = Integer.parseInt(value);\n" +
"result = number > " + value + ";\n";
break;
case "<":
desc = "${" + variable + "} " + operator + " " + value;
script += "number = Integer.parseInt(value);\n" +
"result = number < " + value + ";\n";
break;
case "is empty":
desc = "${" + variable + "} " + operator
script += "result = value == void || value.length() == 0;\n";
break;
case "is not empty":
desc = "${" + variable + "} " + operator
script += "result = value != void && value.length() > 0;\n";
break;
this.assertion.desc = desc;
this.assertion.script = script;
this.$refs.jsr233.reload();
}
let msg = "assertion [" + desc + "]: false;"
script += "if (!result){\n" +
"\tmsg = \"" + msg + "\";\n" +
"\tAssertionResult.setFailureMessage(msg);\n" +
"\tAssertionResult.setFailure(true);\n" +
"}";
this.assertion.desc = desc;
this.assertion.script = script;
this.$refs.jsr233.reload();
},
detail() {
this.visible = true;
},
close() {
this.visible = false;
},
confirm() {
if (!this.edit) {
this.add();
}
if (!this.assertion.desc) {
this.assertion.desc = this.assertion.script;
}
this.close();
}
},
},
detail() {
this.visible = true;
},
close() {
this.visible = false;
},
confirm() {
if (!this.edit) {
this.add();
computed: {
hasEmptyOperator() {
return !!this.assertion.operator && this.assertion.operator.indexOf("empty") > 0;
}
if (!this.assertion.desc) {
this.assertion.desc = this.assertion.script;
}
this.close();
}
},
computed: {
hasEmptyOperator() {
return !!this.assertion.operator && this.assertion.operator.indexOf("empty") > 0;
}
}
}
</script>
<style scoped>
.assertion-item {
display: inline-block;
}
.assertion-item {
display: inline-block;
}
.assertion-item + .assertion-item {
margin-left: 10px;
}
.assertion-item + .assertion-item {
margin-left: 10px;
}
.assertion-item.input {
width: 100%;
}
.assertion-item.input {
width: 100%;
}
.assertion-item.select {
min-width: 150px;
}
.assertion-item.select {
min-width: 150px;
}
.assertion-item.label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.assertion-item.label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.assertion-item.btn {
min-width: 130px;
}
.assertion-item.btn {
min-width: 130px;
}
.assertion-item.btn.circle {
text-align: right;
min-width: 80px;
}
.assertion-item.btn.circle {
text-align: right;
min-width: 80px;
}
.quick-script-block {
margin-bottom: 10px;
}
.quick-script-block {
margin-bottom: 10px;
}
</style>

View File

@ -3,13 +3,13 @@
<el-dialog
:title="$t('api_test.environment.select_environment')"
:visible.sync="dialogVisible"
width="25%"
width="15%"
:destroy-on-close="true"
@close="handleClose"
>
<el-form label-position="right" label-width="150px" size="medium" ref="form">
<el-form ref="form">
<el-form-item prop="type">
<el-select v-model="environmentId" value-key="id" size="small" class="ms-htt-width"
<el-select v-model="environmentId" value-key="id" class="ms-htt-width"
:placeholder="$t('api_test.definition.request.run_env')"
clearable>
<el-option v-for="(environment, index) in environments" :key="index"
@ -22,6 +22,7 @@
</el-select>
</el-form-item>
</el-form>
<template v-slot:footer>
<!-- <el-button onclick="this.handleClose">{{ $t('commons.cancel') }}</el-button>-->
<el-button type="primary" @click="createPerformance" @keydown.enter.native.prevent>

View File

@ -212,7 +212,8 @@
this.$emit('singleRun', data);
},
copyCase(data) {
let obj = {name: "copy_" + data.name, priority: data.priority, active: true, request: data.request};
let uuid = getUUID();
let obj = {name: "copy_" + data.name, priority: data.priority, active: true, tags: data.tags, request: data.request, uuid: uuid};
this.$emit('copyCase', obj);
},
selectTestCase(item, $event) {

View File

@ -20,7 +20,7 @@
<el-container v-loading="result.loading">
<el-main>
<div v-for="(item,index) in apiCaseList" :key="index">
<div v-for="(item,index) in apiCaseList" :key="item.id ? item.id : item.uuid">
<api-case-item v-loading="singleLoading && singleRunId === item.id || batchLoadingIds.indexOf(item.id) > -1"
@refresh="refresh"
@singleRun="singleRun"
@ -243,7 +243,8 @@
if (!request.hashTree) {
request.hashTree = [];
}
let obj = {apiDefinitionId: this.api.id, name: '', priority: 'P0', active: true, tags: []};
let uuid = getUUID();
let obj = {apiDefinitionId: this.api.id, name: '', priority: 'P0', active: true, tags: [], uuid: uuid};
obj.request = request;
this.apiCaseList.unshift(obj);
}

View File

@ -50,8 +50,9 @@
<el-switch
v-model="swaggerSynchronization"
@click.native="scheduleEdit"
:active-text="$t('api_test.api_import.timing_synchronization')">
>
</el-switch>
<span style="color: #6C317C;cursor: pointer;font-weight: bold;margin-left: 10px" @click="scheduleEditByText">{{$t('api_test.api_import.timing_synchronization')}}</span>
</el-form-item>
</el-col>
<el-col :span="12"
@ -188,6 +189,9 @@ export default {
}
}
},
scheduleEditByText(){
this.$refs.scheduleEdit.open(this.buildParam());
},
open(module) {
this.currentModule = module;
this.visible = true;

View File

@ -5,7 +5,7 @@
@isApiListEnableChange="isApiListEnableChange">
<el-link type="primary" style="float:right;margin-top: 5px" @click="open">{{$t('commons.adv_search.title')}}</el-link>
<el-input placeholder="搜索" @blur="search" @keyup.enter.native="search" class="search-input" size="small"
<el-input :placeholder="$t('commons.search_by_id_name_tag')" @blur="search" @keyup.enter.native="search" class="search-input" size="small"
v-model="condition.name"/>
<el-table v-loading="result.loading"
@ -124,7 +124,7 @@ import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit";
import {API_METHOD_COLOUR, CASE_PRIORITY, REQ_METHOD} from "../../model/JsonData";
import {API_METHOD_COLOUR, CASE_PRIORITY, DUBBO_METHOD, REQ_METHOD, SQL_METHOD, TCP_METHOD} from "../../model/JsonData";
import {getBodyUploadFiles, getCurrentProjectID} from "@/common/js/utils";
import ApiListContainer from "./ApiListContainer";
@ -409,6 +409,15 @@ export default {
// }
},
handleEditBatch() {
if(this.currentProtocol =='HTTP'){
this.valueArr.method = REQ_METHOD;
}else if(this.currentProtocol =='TCP'){
this.valueArr.method = TCP_METHOD;
}else if(this.currentProtocol =='SQL'){
this.valueArr.method = SQL_METHOD;
}else if(this.currentProtocol =='DUBBO'){
this.valueArr.method = DUBBO_METHOD;
}
this.$refs.batchEdit.open();
},
batchEdit(form) {

View File

@ -5,7 +5,7 @@
@isApiListEnableChange="isApiListEnableChange">
<el-link type="primary" @click="open" style="float: right;margin-top: 5px">{{$t('commons.adv_search.title')}}</el-link>
<el-input :placeholder="$t('api_monitor.please_search')" @blur="search" class="search-input" size="small" @keyup.enter.native="search"
<el-input :placeholder="$t('commons.search_by_id_name_tag')" @blur="search" class="search-input" size="small" @keyup.enter.native="search"
v-model="condition.name"/>
<el-table v-loading="result.loading"
@ -162,11 +162,10 @@
import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit";
import {API_METHOD_COLOUR, API_STATUS, REQ_METHOD} from "../../model/JsonData";
import {API_METHOD_COLOUR, API_STATUS, REQ_METHOD,TCP_METHOD,SQL_METHOD,DUBBO_METHOD} from "../../model/JsonData";
import {_filter, _sort, getCurrentProjectID} from "@/common/js/utils";
import {WORKSPACE_ID} from '@/common/js/constants';
import ApiListContainer from "./ApiListContainer";
// import MsTableSelectAll from "../../../../common/components/table/MsTableSelectAll";
import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover";
import ApiStatus from "@/business/components/api/definition/components/list/ApiStatus";
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
@ -274,7 +273,12 @@
},
},
created: function () {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
if (this.trashEnable) {
this.condition.filters = {status: ["Trash"]};
}
else {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
}
this.initTable();
this.getMaintainerOptions();
},
@ -336,10 +340,10 @@
}
if (this.condition.projectId) {
this.result = this.$post("/api/definition/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {
this.genProtocalFilter(this.condition.protocol);
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
this.unSelection = response.data.listObject.map(s => s.id);
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
@ -348,6 +352,48 @@
});
}
},
genProtocalFilter(protocalType){
if(protocalType === "HTTP"){
this.methodFilters = [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
];
}else if(protocalType === "TCP"){
this.methodFilters = [
{text: 'TCP', value: 'TCP'},
];
}else if(protocalType === "SQL"){
this.methodFilters = [
{text: 'SQL', value: 'SQL'},
];
}else if(protocalType === "DUBBO"){
this.methodFilters = [
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
];
}else{
this.methodFilters = [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
{text: 'SQL', value: 'SQL'},
{text: 'TCP', value: 'TCP'},
];
}
},
getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
@ -466,6 +512,15 @@
}
},
handleEditBatch() {
if(this.currentProtocol =='HTTP'){
this.valueArr.method = REQ_METHOD;
}else if(this.currentProtocol =='TCP'){
this.valueArr.method = TCP_METHOD;
}else if(this.currentProtocol =='SQL'){
this.valueArr.method = SQL_METHOD;
}else if(this.currentProtocol =='DUBBO'){
this.valueArr.method = DUBBO_METHOD;
}
this.$refs.batchEdit.open();
},
batchEdit(form) {

View File

@ -180,10 +180,10 @@
}
},
//---使
createRootModel(){
createRootModel() {
let dataArr = this.$refs.nodeTree.extendTreeNodes;
if(dataArr.length>0){
this.$refs.nodeTree.append({},dataArr[0]);
if (dataArr.length > 0) {
this.$refs.nodeTree.append({}, dataArr[0]);
}
},
exportAPI() {
@ -197,7 +197,6 @@
},
refresh() {
this.list();
this.$emit("refreshTable");
},
}
}

View File

@ -64,22 +64,22 @@
</el-form>
</div>
<!--<div v-if="showScript">-->
<!--<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">-->
<!--&lt;!&ndash; 前置脚本 &ndash;&gt;-->
<!--<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"-->
<!--:jsr223-processor="row"/>-->
<!--&lt;!&ndash;后置脚本&ndash;&gt;-->
<!--<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"-->
<!--:jsr223-processor="row"/>-->
<!--&lt;!&ndash;断言规则&ndash;&gt;-->
<!--<div style="margin-top: 10px">-->
<!--<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>-->
<!--</div>-->
<!--&lt;!&ndash;提取规则&ndash;&gt;-->
<!--<div style="margin-top: 10px">-->
<!--<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>-->
<!--</div>-->
<!--</div>-->
<!--<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData" style="margin-left: 20px;width: 100%">-->
<!--&lt;!&ndash; 前置脚本 &ndash;&gt;-->
<!--<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.pre_script')" style-type="color: #B8741A;background-color: #F9F1EA"-->
<!--:jsr223-processor="row"/>-->
<!--&lt;!&ndash;后置脚本&ndash;&gt;-->
<!--<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @copyRow="copyRow" @remove="remove" :is-read-only="false" :title="$t('api_test.definition.request.post_script')" style-type="color: #783887;background-color: #F2ECF3"-->
<!--:jsr223-processor="row"/>-->
<!--&lt;!&ndash;断言规则&ndash;&gt;-->
<!--<div style="margin-top: 10px">-->
<!--<ms-api-assertions v-if="row.type==='Assertions'" @copyRow="copyRow" @remove="remove" :is-read-only="isReadOnly" :assertions="row"/>-->
<!--</div>-->
<!--&lt;!&ndash;提取规则&ndash;&gt;-->
<!--<div style="margin-top: 10px">-->
<!--<ms-api-extract :is-read-only="isReadOnly" @copyRow="copyRow" @remove="remove" v-if="row.type==='Extract'" :extract="row"/>-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
</el-col>
<el-col :span="3" class="ms-left-cell" v-if="showScript">
@ -225,6 +225,9 @@
if (!hasEnvironment) {
this.request.environmentId = undefined;
}
if (!this.request.environmentId) {
this.request.dataSourceId = undefined;
}
this.initDataSource();
});
},
@ -232,15 +235,22 @@
this.$refs.environmentConfig.open(getCurrentProjectID());
},
initDataSource() {
let flag = false;
for (let i in this.environments) {
if (this.environments[i].id === this.request.environmentId) {
this.databaseConfigsOptions = [];
this.environments[i].config.databaseConfigs.forEach(item => {
if (item.id === this.request.dataSourceId) {
flag = true;
}
this.databaseConfigsOptions.push(item);
});
break;
}
}
if (!flag) {
this.request.dataSourceId = undefined;
}
},
setDataSource() {
for (let item of this.databaseConfigsOptions) {

View File

@ -86,10 +86,9 @@
import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "../../ApiVariable";
import {createComponent} from "../../jmeter/components";
import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract";
import {Assertions, Body, Extract, KeyValue} from "../../../model/ApiTestModel";
import {Body, KeyValue} from "../../../model/ApiTestModel";
import {getUUID} from "@/common/js/utils";
import BatchAddParameter from "../../basis/BatchAddParameter";
import MsApiAdvancedConfig from "./ApiAdvancedConfig";

View File

@ -8,7 +8,9 @@
</el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
<pre>{{ response.responseResult.headers }}</pre>
<div style="width: 400px">
<pre>{{ response.responseResult.headers }}</pre>
</div>
</el-tab-pane>
<!--<el-tab-pane label="Cookie" name="cookie" class="pane cookie">-->
<!--<pre>{{response.cookies}}</pre>-->
@ -25,7 +27,9 @@
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
<pre>{{response.responseResult.vars}}</pre>
<div style="width: 400px">
<pre>{{response.responseResult.vars}}</pre>
</div>
</el-tab-pane>
<el-tab-pane :label="$t('api_report.request_body')" name="request_body" class="pane">

View File

@ -44,6 +44,15 @@ export const REQ_METHOD = [
{id: 'HEAD', label: 'HEAD'},
{id: 'CONNECT', label: 'CONNECT'}
]
export const TCP_METHOD = [
{id: 'TCP', label: 'TCP'}
]
export const SQL_METHOD = [
{id: 'SQL', label: 'SQL'}
]
export const DUBBO_METHOD = [
{id: 'dubbo://', label: 'dubbo://'},
]
export const CASE_PRIORITY = [
{id: 'P0', label: 'P0'},

View File

@ -35,8 +35,8 @@
</el-table-column>
</el-table>
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline" @click="add">添加
</el-button>
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline" @click="add">{{$t("commons.add")}}</el-button>
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-files" @click="copy">{{$t("commons.copy")}}</el-button>
</div>
</template>
@ -68,6 +68,16 @@
this.$emit('change', this.hostTable);
},
add: function (r) {
let row = {
ip: '',
domain: '',
status: 'edit',
annotation: '',
uuid: this.uuid(),
}
this.hostTable.push(row);
},
copy: function (r) {
let row = {
ip: '',
domain: '',

View File

@ -15,23 +15,6 @@
props: ['total', 'pageSize'],
data() {
return {
gridData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}]
};
}
}

View File

@ -4,7 +4,7 @@
<span v-if="triggerMode === 'SCHEDULE'">{{$t('commons.trigger_mode.schedule')}}</span>
<span v-if="triggerMode === 'TEST_PLAN_SCHEDULE'">{{$t('commons.trigger_mode.schedule')}}</span>
<span v-if="triggerMode === 'API'">{{$t('commons.trigger_mode.api')}}</span>
<span v-if="triggerMode === 'CASE'">用例触发</span>
<span v-if="triggerMode === 'CASE'">{{$t('commons.trigger_mode.case')}}</span>
</span>
</template>

View File

@ -143,6 +143,7 @@ export default {
{text: this.$t('commons.trigger_mode.manual'), value: 'MANUAL'},
{text: this.$t('commons.trigger_mode.schedule'), value: 'SCHEDULE'},
{text: this.$t('commons.trigger_mode.api'), value: 'API'},
{text: this.$t('commons.trigger_mode.case'), value: 'CASE'},
],
buttons: [
{

View File

@ -14,7 +14,7 @@
<el-tab-pane v-if="hasLicense()" :label="$t('display.title')" name="display">
<ms-display/>
</el-tab-pane>
<el-tab-pane v-if="hasLicense()" :label="'认证设置'" name="auth">
<el-tab-pane v-if="hasLicense()" :label="$t('auth_source.title')" name="auth">
<ms-auth/>
</el-tab-pane>
</el-tabs>

View File

@ -81,7 +81,8 @@
if (size) {
this.size = size;
} else {
this.size = this.$parent.selectRows.size;
// this.size = this.$parent.selectRows.size;
this.size = this.$parent.selectDataCounts;
}
listenGoBack(this.handleClose);
},

View File

@ -15,6 +15,7 @@
:filter-node-method="filterNode"
:expand-on-click-node="false"
highlight-current
style="overflow: auto"
@node-click="nodeClick"
ref="tree"
>

View File

@ -31,14 +31,14 @@
@select="handleSelect"
@cell-mouse-enter="showPopover"
row-key="id"
class="test-content adjust-table ms-select-all"
class="test-content adjust-table ms-select-all-fixed"
ref="table" @row-click="handleEdit">
<el-table-column
width="50"
type="selection"/>
<ms-table-select-all
<ms-table-header-select-popover v-show="total>0"
:page-size="pageSize > total ? total : pageSize"
:total="total"
@selectPageAll="isSelectDataAll(false)"
@ -46,7 +46,7 @@
<el-table-column width="40" :resizable="false" align="center">
<template v-slot:default="scope">
<show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectRows.size"/>
<show-more-btn :is-show="scope.row.showMore" :buttons="buttons" :size="selectDataCounts"/>
</template>
</el-table-column>
<el-table-column
@ -78,6 +78,7 @@
prop="priority"
:filters="priorityFilters"
column-key="priority"
min-width="100px"
:label="$t('test_track.case.priority')"
show-overflow-tooltip>
<template v-slot:default="scope">
@ -98,6 +99,7 @@
prop="method"
column-key="method"
:filters="methodFilters"
min-width="100px"
:label="$t('test_track.case.method')"
show-overflow-tooltip>
<template v-slot:default="scope">
@ -108,6 +110,7 @@
<el-table-column
:filters="statusFilters"
column-key="status"
min-width="100px"
:label="$t('test_track.case.status')">
<template v-slot:default="scope">
<span class="el-dropdown-link">
@ -127,6 +130,7 @@
<el-table-column
prop="nodePath"
:label="$t('test_track.case.module')"
min-width="150px"
show-overflow-tooltip>
</el-table-column>
@ -134,12 +138,13 @@
prop="updateTime"
sortable="custom"
:label="$t('commons.update_time')"
min-width="150px"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
<el-table-column fixed="right"
:label="$t('commons.operating')" min-width="150">
<template v-slot:default="scope">
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
@ -169,6 +174,7 @@
<script>
import MsCreateBox from '../../../settings/CreateBox';
import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover";
import TestCaseImport from '../components/TestCaseImport';
import TestCaseExport from '../components/TestCaseExport';
import MsTablePagination from '../../../../components/common/pagination/TablePagination';
@ -191,7 +197,6 @@ import TestCaseDetail from "./TestCaseDetail";
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
import {getCurrentProjectID} from "../../../../../common/js/utils";
import MsTag from "@/business/components/common/components/MsTag";
import MsTableSelectAll from "../../../common/components/table/MsTableSelectAll";
import {_handleSelect, _handleSelectAll} from "../../../../../common/js/tableUtils";
import BatchMove from "./BatchMove";
@ -199,7 +204,7 @@ export default {
name: "TestCaseList",
components: {
BatchMove,
MsTableSelectAll,
MsTableHeaderSelectPopover,
MsTableButton,
MsTableOperatorButton,
MsTableOperator,
@ -287,6 +292,7 @@ export default {
},
currentCaseId: null,
projectId: "",
selectDataCounts: 0,
}
},
props: {
@ -323,6 +329,7 @@ export default {
this.condition.nodeIds = [];
this.condition.selectAll = false;
this.condition.unSelectIds = [];
this.selectDataCounts = 0;
if (this.planId) {
// param.planId = this.planId;
this.condition.planId = this.planId;
@ -430,6 +437,7 @@ export default {
_handleSelect(this, selection, row, this.selectRows);
this.setUnSelectIds();
},
importTestCase() {
if (!getCurrentProjectID()) {
this.$warning(this.$t('commons.check_project_tip'));
@ -548,6 +556,11 @@ export default {
this.condition.unSelectIds = allIDs.filter(function (val) {
return ids.indexOf(val) === -1
});
if (this.condition.selectAll) {
this.selectDataCounts = this.total - this.condition.unSelectIds.length;
} else {
this.selectDataCounts = this.selectRows.size;
}
},
moveSave(param) {
param.condition = this.condition;

View File

@ -19,7 +19,7 @@
</el-table-column>
<el-table-column prop="tagNames" :label="$t('api_test.automation.tag')" width="200px">
<template v-slot:default="scope">
<div v-for="itemName in scope.row.tagNames" :key="itemName">
<div v-for="itemName in scope.row.tags" :key="itemName">
<ms-tag type="success" effect="plain" :content="itemName"/>
</div>
</template>
@ -89,9 +89,6 @@
selectRows: new Set()
}
},
created() {
this.search();
},
watch: {
selectNodeIds() {
this.search();
@ -102,6 +99,9 @@
},
methods: {
search() {
if (!this.projectId) {
return;
}
this.selectRows = new Set();
this.loading = true;
@ -122,6 +122,11 @@
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
});
});
},
handleSelectAll(selection) {

View File

@ -75,6 +75,9 @@
methods: {
open() {
this.$refs.baseRelevance.open();
if (this.$refs.apiScenarioList) {
this.$refs.apiScenarioList.search();
}
},
setProject(projectId) {
this.projectId = projectId;

View File

@ -15,6 +15,7 @@
<show-more-btn :is-show="isSelect(row)" :buttons="buttons" :size="selectRows.length"/>
</template>
</el-table-column>
<el-table-column prop="num" label="ID"/>
<el-table-column prop="name" :label="$t('api_test.automation.scenario_name')"
show-overflow-tooltip/>
<el-table-column prop="level" :label="$t('api_test.automation.case_level')"
@ -29,7 +30,7 @@
</el-table-column>
<el-table-column prop="tagNames" :label="$t('api_test.automation.tag')" width="200px">
<template v-slot:default="scope">
<div v-for="itemName in scope.row.tagNames" :key="itemName">
<div v-for="(itemName,index) in scope.row.tags" :key="index">
<ms-tag type="success" effect="plain" :content="itemName"/>
</div>
</template>
@ -159,6 +160,11 @@
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
});
this.loading = false;
});
},

View File

@ -3,7 +3,7 @@
:condition="condition"
@search="$emit('refresh')"
:show-create="false"
:tip="$t('commons.search_by_name_or_id')">
:tip="$t('commons.search_by_id_name_tag')">
<template v-slot:title>
场景用例
</template>

View File

@ -76,6 +76,14 @@
</template>
</el-table-column>
<el-table-column prop="tags" :label="$t('commons.tag')">
<template v-slot:default="scope">
<div v-for="(tag, index) in scope.row.showTags" :key="tag + '_' + index">
<ms-tag type="success" effect="plain" :content="tag"/>
</div>
</template>
</el-table-column>
<el-table-column
prop="method"
:filters="methodFilters"
@ -179,8 +187,9 @@
</template>
</el-table-column>
<el-table-column
min-width="100"
:label="$t('commons.operating')">
fixed="right"
min-width="100"
:label="$t('commons.operating')">
<template v-slot:default="scope">
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.edit')" icon="el-icon-edit"
@exec="handleEdit(scope.row)"/>
@ -231,6 +240,7 @@ import ShowMoreBtn from "../../../../case/components/ShowMoreBtn";
import BatchEdit from "../../../../case/components/BatchEdit";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import {hub} from "@/business/components/track/plan/event-bus";
import MsTag from "@/business/components/common/components/MsTag";
export default {
name: "FunctionalTestCaseList",
@ -243,7 +253,7 @@ export default {
StatusTableItem,
PriorityTableItem, StatusEdit, ExecutorEdit, MsTipButton, MsTablePagination,
MsTableHeader, NodeBreadcrumb, MsTableButton, ShowMoreBtn,
BatchEdit
BatchEdit, MsTag
},
data() {
return {
@ -257,7 +267,7 @@ export default {
currentPage: 1,
pageSize: 10,
total: 0,
status:'default',
status: 'default',
selectRows: new Set(),
testPlan: {},
isReadOnly: false,
@ -319,7 +329,7 @@ export default {
planId: {
type: String
},
clickType:String,
clickType: String,
selectNodeIds: {
type: Array
},
@ -354,10 +364,10 @@ export default {
// param.planId = this.planId;
this.condition.planId = this.planId;
}
if(this.clickType){
if(this.status =='default'){
if (this.clickType) {
if (this.status == 'default') {
this.condition.status = this.clickType;
}else{
} else {
this.condition.status = null;
}
this.status = 'all';
@ -373,6 +383,7 @@ export default {
this.tableData = data.listObject;
for (let i = 0; i < this.tableData.length; i++) {
if (this.tableData[i]) {
this.$set(this.tableData[i], "showTags", JSON.parse(this.tableData[i].tags));
this.$set(this.tableData[i], "issuesSize", 0);
this.$get("/issues/get/" + this.tableData[i].caseId).then(response => {
let issues = response.data.data;
@ -381,7 +392,11 @@ export default {
this.$set(this.tableData[i], "issuesContent", issues);
}
}).catch(() => {
this.$set(this.tableData[i], "issuesContent", [{title: '获取缺陷失败',description: '获取缺陷失败',platform: '获取缺陷失败' }]);
this.$set(this.tableData[i], "issuesContent", [{
title: '获取缺陷失败',
description: '获取缺陷失败',
platform: '获取缺陷失败'
}]);
})
}
}
@ -597,4 +612,7 @@ export default {
cursor: pointer;
}
.el-tag {
margin-left: 10px;
}
</style>

View File

@ -32,7 +32,7 @@ export default {
operating: 'Operating',
input_limit: 'Within {0} and {1} characters',
login: 'Sign In',
welcome: 'Welcome back, please enter username and password to log in',
welcome: 'One-stop open source continuous testing platform',
username: 'Username',
password: 'Password',
input_username: 'Please enter username',
@ -150,7 +150,8 @@ export default {
name: "Trigger Mode",
manual: "Manual trigger",
schedule: "Scheduled Task",
api: "API call"
api: "API call",
case: "Case"
},
adv_search: {
title: 'Advanced Search',
@ -602,6 +603,7 @@ export default {
customize_req: "Customize req",
reference_info: "Reference info",
scenario_test: "Scenario test",
scenario_list: "Scenario List",
add_scenario: "Add scenario",
scenario_name: "Scenario name",
case_level: "Case level",
@ -1499,6 +1501,7 @@ export default {
format: "Output format",
},
auth_source: {
delete_prompt: 'This operation will delete the authentication source, do you want to continue? '
delete_prompt: 'This operation will delete the authentication source, do you want to continue? ',
title: 'Auth Source'
}
};

View File

@ -33,7 +33,7 @@ export default {
operating: '操作',
input_limit: '长度在 {0} 到 {1} 个字符',
login: '登录',
welcome: '欢迎回来,请输入用户名和密码登录',
welcome: '一站式开源持续测试平台',
username: '姓名',
password: '密码',
input_username: '请输入用户姓名',
@ -151,7 +151,8 @@ export default {
name: "触发方式",
manual: "手动触发",
schedule: "定时任务",
api: "API调用"
api: "API调用",
case: "用例触发"
},
adv_search: {
title: '高级搜索',
@ -603,6 +604,7 @@ export default {
customize_req: "自定义请求",
reference_info: "请选择接口或用例",
scenario_test: "场景",
scenario_list: "场景列表",
add_scenario: "创建场景",
scenario_name: "场景名称",
case_level: "用例等级",
@ -1502,6 +1504,7 @@ export default {
format: "输出格式",
},
auth_source: {
delete_prompt: '此操作会删除认证源,是否继续?'
delete_prompt: '此操作会删除认证源,是否继续?',
title: '认证设置'
}
};

View File

@ -33,7 +33,7 @@ export default {
operating: '操作',
input_limit: '長度在 {0} 到 {1} 個字符',
login: '登錄',
welcome: '歡迎回來,請輸入用戶名和密碼登錄',
welcome: '壹站式開源持續測試平臺',
username: '姓名',
password: '密碼',
input_username: '請輸入用戶姓名',
@ -151,7 +151,8 @@ export default {
name: "觸發方式",
manual: "手動觸發",
schedule: "定時任務",
api: "API調用"
api: "API調用",
case: "用例觸發"
},
adv_search: {
title: '高級搜索',
@ -602,6 +603,7 @@ export default {
customize_req: "自定義請求",
reference_info: "請選擇接口或用例",
scenario_test: "場景",
scenario_list: "場景列表",
add_scenario: "創建場景",
scenario_name: "場景名稱",
case_level: "用例等級",
@ -1500,6 +1502,7 @@ export default {
format: "輸出格式",
},
auth_source: {
delete_prompt: '此操作會刪除認證源,是否繼續? '
delete_prompt: '此操作會刪除認證源,是否繼續? ',
title: '認證設置'
}
};

View File

@ -2,33 +2,36 @@
<div class="container" v-loading="result.loading" v-if="ready">
<el-row type="flex">
<el-col :span="12">
<el-form :model="form" :rules="rules" ref="form">
<div class="logo">
<img :src="'/display/file/loginLogo'" style="width: 224px;height: 45px;" alt="">
<div class="title">
<div class="title-img">
<img :src="'/display/file/loginLogo'" alt="">
</div>
<div class="title">
<span id="s1">{{ loginTitle }}</span>
</div>
<div class="border"></div>
<div class="welcome">
{{ $t('commons.welcome') }}
<span>Metersphere</span>
<span>{{ $t('commons.welcome') }}</span>
</div>
</div>
<div class="content">
<div class="form">
<el-form-item v-slot:default>
<el-radio-group v-model="form.authenticate" @change="redirectAuth(form.authenticate)">
<el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</el-radio>
<el-radio label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio>
<el-radio :label="auth.id" size="mini" v-for="auth in authSources" :key="auth.id">{{ auth.type }} {{ auth.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="username">
<el-input v-model="form.username" :placeholder="$t('commons.login_username')" autofocus
autocomplete="off"/>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="form.password" :placeholder="$t('commons.password')" show-password autocomplete="off"
maxlength="30" show-word-limit/>
</el-form-item>
<el-form :model="form" :rules="rules" ref="form">
<el-form-item v-slot:default>
<el-radio-group v-model="form.authenticate" @change="redirectAuth(form.authenticate)">
<el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</el-radio>
<el-radio label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio>
<el-radio :label="auth.id" size="mini" v-for="auth in authSources" :key="auth.id">{{ auth.type }} {{ auth.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="username">
<el-input v-model="form.username" :placeholder="$t('commons.login_username')" autofocus
autocomplete="off"/>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="form.password" :placeholder="$t('commons.password')" show-password autocomplete="off"
maxlength="30" show-word-limit/>
</el-form-item>
</el-form>
</div>
<div class="btn">
<el-button type="primary" class="submit" @click="submit('form')">
@ -38,12 +41,17 @@
<div class="msg">
{{ msg }}
</div>
</el-form>
</div>
</el-col>
<div class="divider"/>
<el-col :span="12">
<img :src="'/display/file/loginImage'" style="height: 560px; width: 100%">
<img class="login-image" :src="'/display/file/loginImage'">
</el-col>
</el-row>
</div>
</template>
@ -54,19 +62,11 @@ import {DEFAULT_LANGUAGE} from "@/common/js/constants";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {};
const auth = requireComponent.keys().length > 0 ? requireComponent("./auth/Auth.vue") : {};
const license = requireComponent.keys().length > 0 ? requireComponent("./license/LicenseMessage.vue") : null;
export default {
name: "Login",
data() {
/*let validateEmail = (rule, value, callback) => {
// eslint-disable-next-line no-useless-escape
let EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (!EMAIL_REGEX.test(value)) {
callback(new Error('邮箱格式不正确'));
} else {
callback();
}
};*/
return {
result: {},
form: {
@ -118,6 +118,10 @@ export default {
created: function () {
// ,,
document.addEventListener("keydown", this.watchEnter);
//
if (license.default) {
license.default.valid(this)
}
},
destroyed() {
@ -184,62 +188,51 @@ export default {
<style scoped>
.container {
min-width: 800px;
max-width: 1440px;
height: 560px;
margin: calc((100vh - 560px) / 2) auto 0;
width: 1440px;
height: 810px;
margin: calc((100vh - 810px) / 2) auto 0;
background-color: #FFFFFF;
}
.logo {
margin: 30px 30px 0;
.el-col:nth-child(3) {
align-items: center;
display: flex;
}
.title {
margin-top: 50px;
font-size: 32px;
.title img {
width: 293px;
margin-top: 165px;
}
.title-img {
letter-spacing: 0;
text-align: center;
}
.title > #s1 {
color: #999999;
}
.title > #s2 {
color: #151515;
}
.border {
height: 2px;
margin: 20px auto 20px;
position: relative;
width: 80px;
background: #8B479B;
.login-image {
height: 365px;
width: 567px;
margin: auto;
display: block;
}
.welcome {
margin-top: 50px;
margin-top: 12px;
margin-bottom: 75px;
font-size: 14px;
color: #999999;
letter-spacing: 0;
line-height: 18px;
color: #843697;
line-height: 14px;
text-align: center;
}
.form {
margin-top: 30px;
padding: 0 40px;
}
.btn {
margin-top: 40px;
padding: 0 40px;
.form, .btn {
padding: 0;
width: 443px;
margin: auto;
}
.btn > .submit {
width: 100%;
border-radius: 0;
border-radius: 70px;
border-color: #8B479B;
background-color: #8B479B;
}
@ -254,32 +247,54 @@ export default {
background-color: rgba(139, 71, 155, 0.8);
}
.msg {
margin-top: 10px;
padding: 0 40px;
color: red;
text-align: center;
.el-form-item:first-child {
margin-top: 60px;
}
.image {
background: url(../assets/info.png);
height: 560px;
/deep/ .el-radio__input.is-checked .el-radio__inner {
background-color: #783887;
background: #783887;
border-color: #783887;
}
.login-logo {
background: url(../assets/logo-dark-MeterSphere.svg);
/deep/ .el-radio__input.is-checked + .el-radio__label {
color: #783887;
}
.logo-header {
background: url(../assets/logo-light-MeterSphere.svg);
/deep/ .el-input__inner {
border-radius: 70px !important;
background: #f6f3f8 !important;
border-color: #f6f3f8 !important;
/*谷歌浏览器默认填充的颜色无法替换,使用下列样式填充*/
box-shadow: inset 0 0 0 1000px #f6f3f8 !important;
}
.el-input, .el-button {
width: 443px;
}
/deep/ .el-input__inner:focus {
border: 1px solid #783887 !important;
}
.divider {
border: 1px solid #f6f3f8;
height: 480px;
margin: 165px 0px;
}
.welcome span:first-child {
font-weight: bold;
margin-right: 3px;
}
</style>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Neue Haas Grotesk Text Pro", "Arial Nova", "Segoe UI", "Helvetica Neue", ".PingFang SC", "PingFang SC", "Source Han Sans SC", "Noto Sans CJK SC", "Source Han Sans CN", "Noto Sans SC", "Source Han Sans TC", "Noto Sans CJK TC", "Hiragino Sans GB", sans-serif;
font-size: 14px;
background-color: #F5F5F5;
/*background-color: #F5F5F5;*/
line-height: 26px;
color: #2B415C;
-webkit-font-smoothing: antialiased;

View File

@ -4,6 +4,10 @@ import 'element-ui/lib/theme-chalk/index.css';
import Login from "./Login.vue";
import Ajax from "../common/js/ajax";
import i18n from "../i18n/i18n";
// 引用静态资源,去掉打包将缺失图片
import infoImg from "../assets/info.png";
import loginLogo from "../assets/logo-dark-MeterSphere.svg";
import logoHeader from "../assets/logo-light-MeterSphere.svg";
Vue.config.productionTip = false;