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("/")) { if (!path.startsWith("/")) {
path = "/" + path; 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); sampler.setPath(path);
} }

View File

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

View File

@ -227,12 +227,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
LogUtil.error(e.getMessage(), e); LogUtil.error(e.getMessage(), e);
} }
} }
try {
sendTask(report, reportUrl, testResult); sendTask(report, reportUrl, testResult);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
} }
private static void sendTask(ApiTestReport report, String reportUrl, TestResult testResult) { 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.setHeaders(result.getRequestHeaders());
requestResult.setRequestSize(result.getSentBytes()); requestResult.setRequestSize(result.getSentBytes());
requestResult.setStartTime(result.getStartTime()); requestResult.setStartTime(result.getStartTime());
requestResult.setEndTime(result.getEndTime());
requestResult.setTotalAssertions(result.getAssertionResults().length); requestResult.setTotalAssertions(result.getAssertionResults().length);
requestResult.setSuccess(result.isSuccessful()); requestResult.setSuccess(result.isSuccessful());
requestResult.setError(result.getErrorCount()); requestResult.setError(result.getErrorCount());
@ -336,7 +332,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
requestResult.addPassAssertions(); requestResult.addPassAssertions();
} }
//xpath 提取错误会添加断言错误 //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); responseResult.getAssertions().add(responseAssertionResult);
} }
} }

View File

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

View File

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

View File

@ -323,7 +323,9 @@ public class ApiDefinitionService {
apiDefinitionMapper.updateByPrimaryKeyWithBLOBs(apiDefinition); apiDefinitionMapper.updateByPrimaryKeyWithBLOBs(apiDefinition);
} }
} else if (StringUtils.equals("incrementalMerge", apiTestImportRequest.getModeId())) { } else if (StringUtils.equals("incrementalMerge", apiTestImportRequest.getModeId())) {
batchMapper.insert(apiDefinition); if (CollectionUtils.isEmpty(sameRequest)) {
batchMapper.insert(apiDefinition);
}
} else { } else {
if (CollectionUtils.isEmpty(sameRequest)) { if (CollectionUtils.isEmpty(sameRequest)) {
batchMapper.insert(apiDefinition); 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())); String passRate = new DecimalFormat("0%").format((float) scenarioResult.getSuccess() / (scenarioResult.getSuccess() + scenarioResult.getError()));
testPlanApiScenario.setPassRate(passRate); testPlanApiScenario.setPassRate(passRate);
testPlanApiScenario.setReportId(report.getId()); testPlanApiScenario.setReportId(report.getId());
testPlanApiScenario.setUpdateTime(System.currentTimeMillis());
testPlanApiScenarioMapper.updateByPrimaryKeySelective(testPlanApiScenario); testPlanApiScenarioMapper.updateByPrimaryKeySelective(testPlanApiScenario);
} }
returnReport = report; returnReport = report;
@ -220,6 +221,7 @@ public class ApiScenarioReportService {
apiScenarioReportDetailMapper.insert(detail); apiScenarioReportDetailMapper.insert(detail);
testPlanApiScenario.setReportId(report.getId()); testPlanApiScenario.setReportId(report.getId());
testPlanApiScenario.setUpdateTime(System.currentTimeMillis());
testPlanApiScenarioMapper.updateByPrimaryKeySelective(testPlanApiScenario); testPlanApiScenarioMapper.updateByPrimaryKeySelective(testPlanApiScenario);
lastReport = report; lastReport = report;
@ -343,13 +345,41 @@ public class ApiScenarioReportService {
ids = allIds.stream().filter(id -> !reportRequest.getUnSelectIds().contains(id)).collect(Collectors.toList()); ids = allIds.stream().filter(id -> !reportRequest.getUnSelectIds().contains(id)).collect(Collectors.toList());
} }
ApiScenarioReportDetailExample detailExample = new ApiScenarioReportDetailExample(); //为预防数量太多调用删除方法时引起SQL过长的Bug此处采取分批执行的方式
detailExample.createCriteria().andReportIdIn(ids); //每次处理的数据数量
apiScenarioReportDetailMapper.deleteByExample(detailExample); 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); if(!ids.isEmpty()){
apiScenarioReportMapper.deleteByExample(apiTestReportExample); 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) { public long countByProjectID(String projectId) {

View File

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

View File

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

View File

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

View File

@ -143,7 +143,9 @@
</if> </if>
<if test="request.name != null"> <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>
<if test="request.workspaceId != null"> <if test="request.workspaceId != null">
AND project.workspace_id = #{request.workspaceId} AND project.workspace_id = #{request.workspaceId}

View File

@ -284,7 +284,9 @@
</foreach> </foreach>
</if> </if>
<if test="request.name != null and request.name!=''"> <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>
<if test="request.createTime > 0"> <if test="request.createTime > 0">
and t1.create_time >= #{request.createTime} and t1.create_time >= #{request.createTime}

View File

@ -19,7 +19,7 @@
select select
t.id, t.environment_id, t.create_time, t.update_time, t.last_result, t.pass_rate, t.report_id, 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.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 p.name as project_name, p.id as project_id, u.name as user_name
from from
test_plan_api_scenario t test_plan_api_scenario t
@ -44,7 +44,9 @@
</foreach> </foreach>
</if> </if>
<if test="request.name != null and request.name!=''"> <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>
<if test="request.status != null and request.status!=''"> <if test="request.status != null and request.status!=''">
and t.last_result like CONCAT('%', #{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 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, 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_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.update_time, test_case_node.name as model, project.name as projectName,
test_plan_test_case.plan_id as planId test_plan_test_case.plan_id as planId
@ -139,7 +139,7 @@
</if> </if>
<if test="request.name != null"> <if test="request.name != null">
and (test_case.name like CONCAT('%', #{request.name},'%') or test_case.num like 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>
<if test="request.id != null"> <if test="request.id != null">
and test_case.id = #{request.id} and test_case.id = #{request.id}

View File

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

View File

@ -105,25 +105,27 @@ public class TestPlanTestJob extends MsScheduleJob {
//需要更新这里来保证PlanCase的状态能正常更改 //需要更新这里来保证PlanCase的状态能正常更改
apiTestCaseService.run(blobs,UUID.randomUUID().toString(),testPlanReport.getId(),this.resourceId,ApiRunMode.SCHEDULE_API_PLAN.name()); 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<>(); List<String> performaneReportIDList = new ArrayList<>();
for (Map.Entry<String,String> entry: this.performanceIdMap.entrySet()) { for (Map.Entry<String,String> entry: this.performanceIdMap.entrySet()) {

View File

@ -20,10 +20,13 @@ public class MailNoticeSender extends AbstractNoticeSender {
private MailService mailService; private MailService mailService;
private void sendMail(MessageDetail messageDetail, String context, NoticeModel noticeModel) throws MessagingException { private void sendMail(MessageDetail messageDetail, String context, NoticeModel noticeModel) throws MessagingException {
LogUtil.info("发送邮件开始 ");
JavaMailSenderImpl javaMailSender = mailService.getMailSender(); JavaMailSenderImpl javaMailSender = mailService.getMailSender();
MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom(javaMailSender.getUsername()); helper.setFrom(javaMailSender.getUsername());
LogUtil.info("发件人地址"+javaMailSender.getUsername());
LogUtil.info("helper"+helper);
helper.setSubject("MeterSphere " + noticeModel.getSubject()); helper.setSubject("MeterSphere " + noticeModel.getSubject());
List<String> emails = super.getUserEmails(messageDetail.getUserIds()); List<String> emails = super.getUserEmails(messageDetail.getUserIds());
String[] users = emails.toArray(new String[0]); String[] users = emails.toArray(new String[0]);
@ -38,6 +41,7 @@ public class MailNoticeSender extends AbstractNoticeSender {
String context = super.getHtmlContext(messageDetail, noticeModel); String context = super.getHtmlContext(messageDetail, noticeModel);
try { try {
sendMail(messageDetail, context, noticeModel); sendMail(messageDetail, context, noticeModel);
LogUtil.info("发送邮件结束");
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(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.enable", result);
props.put("mail.smtp.starttls.required", result); props.put("mail.smtp.starttls.required", result);
break; break;
case "smtp.anon": /* case "smtp.anon":
boolean isAnon = BooleanUtils.toBoolean(p.getParamValue()); boolean isAnon = BooleanUtils.toBoolean(p.getParamValue());
if (isAnon) { if (isAnon) {
props.put("mail.smtp.auth", "false"); props.put("mail.smtp.auth", "false");
javaMailSender.setUsername(null); javaMailSender.setUsername(null);
javaMailSender.setPassword(null); javaMailSender.setPassword(null);
} }
break; break;*/
default: default:
break; break;
} }

View File

@ -18,10 +18,7 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.OrderRequest; import io.metersphere.controller.request.OrderRequest;
import io.metersphere.controller.request.QueryScheduleRequest; import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.ScheduleDao; import io.metersphere.dto.ScheduleDao;
import io.metersphere.job.sechedule.ApiScenarioTestJob; import io.metersphere.job.sechedule.*;
import io.metersphere.job.sechedule.ApiTestJob;
import io.metersphere.job.sechedule.ScheduleManager;
import io.metersphere.job.sechedule.TestPlanTestJob;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.quartz.JobKey; import org.quartz.JobKey;
import org.quartz.SchedulerException; import org.quartz.SchedulerException;
@ -98,6 +95,25 @@ public class ScheduleService {
return scheduleMapper.deleteByExample(scheduleExample); 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() { public List<Schedule> listSchedule() {
ScheduleExample example = new ScheduleExample(); ScheduleExample example = new ScheduleExample();
return scheduleMapper.selectByExample(example); return scheduleMapper.selectByExample(example);

View File

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

View File

@ -281,6 +281,18 @@ public class TestPlanReportService {
component.afterBuild(testCaseReportMetricDTO); 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(); TestPlanReportDataExample example = new TestPlanReportDataExample();
example.createCriteria().andTestPlanReportIdEqualTo(planReportId); example.createCriteria().andTestPlanReportIdEqualTo(planReportId);
List<TestPlanReportDataWithBLOBs> testPlanReportDataList = testPlanReportDataMapper.selectByExampleWithBLOBs(example); List<TestPlanReportDataWithBLOBs> testPlanReportDataList = testPlanReportDataMapper.selectByExampleWithBLOBs(example);
@ -437,7 +449,7 @@ public class TestPlanReportService {
if(loadTestReportFromDatabase == null){ if(loadTestReportFromDatabase == null){
//检查错误数据 //检查错误数据
if(errorDataCheckMap.containsKey(loadTestReportId)){ if(errorDataCheckMap.containsKey(loadTestReportId)){
if(errorDataCheckMap.get(loadTestReportId)>20){ if(errorDataCheckMap.get(loadTestReportId)>10){
performaneReportIDList.remove(loadTestReportId); performaneReportIDList.remove(loadTestReportId);
}else { }else {
errorDataCheckMap.put(loadTestReportId,errorDataCheckMap.get(loadTestReportId)+1); 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.i18n.Translator;
import io.metersphere.notice.sender.NoticeModel; import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService; import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.ScheduleService;
import io.metersphere.service.SystemParameterService; import io.metersphere.service.SystemParameterService;
import io.metersphere.track.Factory.ReportComponentFactory; import io.metersphere.track.Factory.ReportComponentFactory;
import io.metersphere.track.domain.ReportComponent; import io.metersphere.track.domain.ReportComponent;
@ -62,6 +63,8 @@ public class TestPlanService {
@Resource @Resource
ExtTestPlanMapper extTestPlanMapper; ExtTestPlanMapper extTestPlanMapper;
@Resource @Resource
ScheduleService scheduleService;
@Resource
ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper; ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper;
@Resource @Resource
TestCaseMapper testCaseMapper; TestCaseMapper testCaseMapper;
@ -315,6 +318,10 @@ public class TestPlanService {
testPlanProjectService.deleteTestPlanProjectByPlanId(planId); testPlanProjectService.deleteTestPlanProjectByPlanId(planId);
testPlanApiCaseService.deleteByPlanId(planId); testPlanApiCaseService.deleteByPlanId(planId);
testPlanScenarioCaseService.deleteByPlanId(planId); testPlanScenarioCaseService.deleteByPlanId(planId);
//删除定时任务
scheduleService.deleteScheduleAndJobByResourceId(planId,ScheduleGroup.TEST_PLAN_TEST.name());
int num = testPlanMapper.deleteByPrimaryKey(planId); int num = testPlanMapper.deleteByPrimaryKey(planId);
List<String> relatedUsers = new ArrayList<>(); List<String> relatedUsers = new ArrayList<>();
AddTestPlanRequest testPlans = new AddTestPlanRequest(); AddTestPlanRequest testPlans = new AddTestPlanRequest();

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

View File

@ -1,11 +1,11 @@
create table swagger_url_project create table swagger_url_project
( (
id varchar(255) not null, id varchar(30) not null,
project_id varchar(255) null, project_id varchar(30) null,
swagger_url varchar(255) null, swagger_url varchar(255) null,
module_id varchar(255) null, module_id varchar(30) null,
module_path varchar(255) null, module_path varchar(255) null,
mode_id varchar(255) null, mode_id varchar(30) null,
primary key (id) primary key (id)
) ENGINE = InnoDB ) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4; 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> <ms-main-container>
<el-tabs v-model="activeName" @tab-click="addTab" @tab-remove="removeTab"> <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 <ms-api-scenario-list
:module-tree="nodeTree" :module-tree="nodeTree"
:module-options="moduleOptions" :module-options="moduleOptions"
@ -97,6 +97,7 @@
renderComponent: true, renderComponent: true,
isHide: true, isHide: true,
activeName: 'default', activeName: 'default',
redirectFlag: 'none',
currentModule: null, currentModule: null,
moduleOptions: [], moduleOptions: [],
tabs: [], tabs: [],
@ -150,6 +151,15 @@
}, },
changeRedirectParam(redirectIDParam) { changeRedirectParam(redirectIDParam) {
this.redirectID = 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) { addTab(tab) {
if (!getCurrentProjectID()) { if (!getCurrentProjectID()) {
@ -227,8 +237,18 @@
this.$refs.apiScenarioList.search(data); this.$refs.apiScenarioList.search(data);
}, },
refresh(data) { refresh(data) {
this.setTabTitle(data);
this.$refs.apiScenarioList.search(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) { editScenario(row) {
this.addTab({name: 'edit', currentScenario: row}); this.addTab({name: 'edit', currentScenario: row});
}, },

View File

@ -118,6 +118,7 @@
throw e; throw e;
} }
this.getFails(); this.getFails();
this.computeTotalTime();
this.loading = false; this.loading = false;
} else { } else {
setTimeout(this.getReport, 2000) setTimeout(this.getReport, 2000)
@ -146,12 +147,30 @@
failScenario.requestResults.push(failRequest); 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) { requestResult(requestResult) {
this.active(); this.active();
this.isRequestResult = false; this.isRequestResult = false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,15 +37,17 @@
</el-tooltip> </el-tooltip>
</div> </div>
</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> </el-card>
</template> </template>
@ -148,5 +150,12 @@
.enable-switch { .enable-switch {
margin-right: 10px; margin-right: 10px;
} }
fieldset {
padding: 0px;
margin: 0px;
min-width: 100%;
min-inline-size: 0px;
border: 0px;
}
</style> </style>

View File

@ -76,18 +76,20 @@
</el-select> </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"/> <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> <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> <span class="ms-span ms-radio">ms</span>
</div> </div>
<p class="tip">{{$t('api_test.definition.request.res_param')}} </p> <p class="tip">{{$t('api_test.definition.request.res_param')}} </p>
<el-tabs v-model="activeName"> <div>
<el-tab-pane :label="item.name" :name="item.name" v-for="(item,index) in requestResult.scenarios" :key="index"> <el-tabs v-model="activeName" closable class="ms-tabs">
<div v-for="(result,i) in item.requestResults" :key="i" style="margin-bottom: 5px"> <el-tab-pane :label="item.name" :name="item.name" v-for="(item,index) in requestResult.scenarios" :key="index">
<api-response-component :result="result"/> <div v-for="(result,i) in item.requestResults" :key="i" style="margin-bottom: 5px">
</div> <api-response-component :result="result"/>
</el-tab-pane> </div>
</el-tabs> </el-tab-pane>
</el-tabs>
</div>
</api-base-component> </api-base-component>
@ -331,6 +333,11 @@
margin: 20px 0; margin: 20px 0;
} }
.ms-tabs >>> .el-icon-close:before {
content: "";
}
.icon.is-active { .icon.is-active {
transform: rotate(90deg); transform: rotate(90deg);
} }

View File

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

View File

@ -8,12 +8,22 @@
<el-form :model="form" :rules="rules" ref="from"> <el-form :model="form" :rules="rules" ref="from">
<el-form-item <el-form-item
prop="cronValue"> prop="cronValue">
<el-input :disabled="isReadOnly" v-model="form.cronValue" class="inp" <el-row>
:placeholder="$t('schedule.please_input_cron_expression')"/> <el-col :span="18">
<el-button :disabled="isReadOnly" type="primary" @click="saveCron" v-tester>{{ <el-input :disabled="isReadOnly" v-model="form.cronValue" class="inp"
$t('commons.save') :placeholder="$t('schedule.please_input_cron_expression')"/>
}} <el-button :disabled="isReadOnly" type="primary" @click="saveCron" v-tester>{{
</el-button> $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-form-item> <el-form-item>
<el-link :disabled="isReadOnly" type="primary" @click="showCronDialog"> <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 CrontabResult from "@/business/components/common/cron/CrontabResult";
import {cronValidate} from "@/common/js/cron"; import {cronValidate} from "@/common/js/cron";
import MsScheduleNotification from "./ScheduleNotification"; import MsScheduleNotification from "./ScheduleNotification";
import ScheduleSwitch from "@/business/components/api/automation/schedule/ScheduleSwitch";
function defaultCustomValidate() { function defaultCustomValidate() {
return {pass: true}; return {pass: true};
@ -55,7 +66,7 @@ const noticeTemplate = requireComponent.keys().length > 0 ? requireComponent("./
export default { export default {
name: "MsScheduleMaintain", name: "MsScheduleMaintain",
components: {CrontabResult, Crontab, MsScheduleNotification, "NoticeTemplate": noticeTemplate.default}, components: {CrontabResult, ScheduleSwitch,Crontab, MsScheduleNotification, "NoticeTemplate": noticeTemplate.default},
props: { props: {
customValidate: { customValidate: {
@ -100,6 +111,7 @@ export default {
form: { form: {
cronValue: "" cronValue: ""
}, },
paramRow:{},
activeName: 'first', activeName: 'first',
rules: { rules: {
cronValue: [{required: true, validator: validateCron, trigger: 'blur'}], cronValue: [{required: true, validator: validateCron, trigger: 'blur'}],
@ -110,6 +122,35 @@ export default {
currentUser: () => { currentUser: () => {
return getCurrentUser(); 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() { initUserList() {
let param = { let param = {
name: '', name: '',
@ -132,6 +173,7 @@ export default {
open(row) { open(row) {
// //
let paramTestId = ""; let paramTestId = "";
this.paramRow = row;
if (row.redirectFrom == 'testPlan') { if (row.redirectFrom == 'testPlan') {
paramTestId = row.id; paramTestId = row.id;
this.scheduleTaskType = "TEST_PLAN_TEST"; 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.apiTabs = [];
this.apiDefaultTab = tabs.name; this.apiDefaultTab = tabs.name;
this.apiTabs.push(tabs); this.apiTabs.push(tabs);
this.refresh();
}, },
handleTabRemove(targetName) { handleTabRemove(targetName) {
let tabs = this.apiTabs; let tabs = this.apiTabs;

View File

@ -23,7 +23,7 @@
</div> </div>
</el-row> </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"> <el-row type="flex" justify="space-between" align="middle" class="quick-script-block">
<div class="assertion-item input"> <div class="assertion-item input">
<el-input size="small" v-model="assertion.variable" <el-input size="small" v-model="assertion.variable"
@ -55,206 +55,206 @@
</template> </template>
<script> <script>
import {AssertionJSR223} from "../../model/ApiTestModel"; import {AssertionJSR223} from "../../model/ApiTestModel";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter"; import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
import MsJsr233Processor from "../../../automation/scenario/component/Jsr233Processor"; import MsJsr233Processor from "../../../automation/scenario/common/Jsr233ProcessorContent";
export default { export default {
name: "MsApiAssertionJsr223", name: "MsApiAssertionJsr223",
components: {MsJsr233Processor, MsDialogFooter}, components: {MsJsr233Processor, MsDialogFooter},
props: { props: {
assertion: { assertion: {
default: () => { default: () => {
return new AssertionJSR223(); 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"
} }
}, },
templates: [ edit: {
{ type: Boolean,
title: this.$t('api_test.request.assertions.set_failure_status'), default: false
value: 'AssertionResult.setFailure(true)', },
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"
}
}, },
{ templates: [
title: this.$t('api_test.request.assertions.set_failure_msg'), {
value: 'AssertionResult.setFailureMessage("msg")', 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: { this.assertion.desc = desc;
add() { this.assertion.script = script;
this.list.push(new AssertionJSR223(this.assertion)); this.$refs.jsr233.reload();
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" +
"}";
this.assertion.desc = desc; },
this.assertion.script = script; detail() {
this.$refs.jsr233.reload(); 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();
} }
},
}, computed: {
detail() { hasEmptyOperator() {
this.visible = true; return !!this.assertion.operator && this.assertion.operator.indexOf("empty") > 0;
},
close() {
this.visible = false;
},
confirm() {
if (!this.edit) {
this.add();
} }
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> </script>
<style scoped> <style scoped>
.assertion-item { .assertion-item {
display: inline-block; display: inline-block;
} }
.assertion-item + .assertion-item { .assertion-item + .assertion-item {
margin-left: 10px; margin-left: 10px;
} }
.assertion-item.input { .assertion-item.input {
width: 100%; width: 100%;
} }
.assertion-item.select { .assertion-item.select {
min-width: 150px; min-width: 150px;
} }
.assertion-item.label { .assertion-item.label {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.assertion-item.btn { .assertion-item.btn {
min-width: 130px; min-width: 130px;
} }
.assertion-item.btn.circle { .assertion-item.btn.circle {
text-align: right; text-align: right;
min-width: 80px; min-width: 80px;
} }
.quick-script-block { .quick-script-block {
margin-bottom: 10px; margin-bottom: 10px;
} }
</style> </style>

View File

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

View File

@ -212,7 +212,8 @@
this.$emit('singleRun', data); this.$emit('singleRun', data);
}, },
copyCase(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); this.$emit('copyCase', obj);
}, },
selectTestCase(item, $event) { selectTestCase(item, $event) {

View File

@ -20,7 +20,7 @@
<el-container v-loading="result.loading"> <el-container v-loading="result.loading">
<el-main> <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" <api-case-item v-loading="singleLoading && singleRunId === item.id || batchLoadingIds.indexOf(item.id) > -1"
@refresh="refresh" @refresh="refresh"
@singleRun="singleRun" @singleRun="singleRun"
@ -243,7 +243,8 @@
if (!request.hashTree) { if (!request.hashTree) {
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; obj.request = request;
this.apiCaseList.unshift(obj); this.apiCaseList.unshift(obj);
} }

View File

@ -50,8 +50,9 @@
<el-switch <el-switch
v-model="swaggerSynchronization" v-model="swaggerSynchronization"
@click.native="scheduleEdit" @click.native="scheduleEdit"
:active-text="$t('api_test.api_import.timing_synchronization')"> >
</el-switch> </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-form-item>
</el-col> </el-col>
<el-col :span="12" <el-col :span="12"
@ -188,6 +189,9 @@ export default {
} }
} }
}, },
scheduleEditByText(){
this.$refs.scheduleEdit.open(this.buildParam());
},
open(module) { open(module) {
this.currentModule = module; this.currentModule = module;
this.visible = true; this.visible = true;

View File

@ -5,7 +5,7 @@
@isApiListEnableChange="isApiListEnableChange"> @isApiListEnableChange="isApiListEnableChange">
<el-link type="primary" style="float:right;margin-top: 5px" @click="open">{{$t('commons.adv_search.title')}}</el-link> <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"/> v-model="condition.name"/>
<el-table v-loading="result.loading" <el-table v-loading="result.loading"
@ -124,7 +124,7 @@ import MsContainer from "../../../../common/components/MsContainer";
import MsBottomContainer from "../BottomContainer"; import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn"; import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit"; 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 {getBodyUploadFiles, getCurrentProjectID} from "@/common/js/utils";
import ApiListContainer from "./ApiListContainer"; import ApiListContainer from "./ApiListContainer";
@ -409,6 +409,15 @@ export default {
// } // }
}, },
handleEditBatch() { 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(); this.$refs.batchEdit.open();
}, },
batchEdit(form) { batchEdit(form) {

View File

@ -5,7 +5,7 @@
@isApiListEnableChange="isApiListEnableChange"> @isApiListEnableChange="isApiListEnableChange">
<el-link type="primary" @click="open" style="float: right;margin-top: 5px">{{$t('commons.adv_search.title')}}</el-link> <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"/> v-model="condition.name"/>
<el-table v-loading="result.loading" <el-table v-loading="result.loading"
@ -162,11 +162,10 @@
import MsBottomContainer from "../BottomContainer"; import MsBottomContainer from "../BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn"; import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../basis/BatchEdit"; 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 {_filter, _sort, getCurrentProjectID} from "@/common/js/utils";
import {WORKSPACE_ID} from '@/common/js/constants'; import {WORKSPACE_ID} from '@/common/js/constants';
import ApiListContainer from "./ApiListContainer"; import ApiListContainer from "./ApiListContainer";
// import MsTableSelectAll from "../../../../common/components/table/MsTableSelectAll";
import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover"; import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover";
import ApiStatus from "@/business/components/api/definition/components/list/ApiStatus"; import ApiStatus from "@/business/components/api/definition/components/list/ApiStatus";
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar"; import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
@ -274,7 +273,12 @@
}, },
}, },
created: function () { 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.initTable();
this.getMaintainerOptions(); this.getMaintainerOptions();
}, },
@ -336,10 +340,10 @@
} }
if (this.condition.projectId) { if (this.condition.projectId) {
this.result = this.$post("/api/definition/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => { 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.total = response.data.itemCount;
this.tableData = response.data.listObject; this.tableData = response.data.listObject;
this.unSelection = response.data.listObject.map(s => s.id); this.unSelection = response.data.listObject.map(s => s.id);
this.tableData.forEach(item => { this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) { if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags); 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() { getMaintainerOptions() {
let workspaceId = localStorage.getItem(WORKSPACE_ID); let workspaceId = localStorage.getItem(WORKSPACE_ID);
this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => { this.$post('/user/ws/member/tester/list', {workspaceId: workspaceId}, response => {
@ -466,6 +512,15 @@
} }
}, },
handleEditBatch() { 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(); this.$refs.batchEdit.open();
}, },
batchEdit(form) { batchEdit(form) {

View File

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

View File

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

View File

@ -86,10 +86,9 @@
import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect"; import ApiRequestMethodSelect from "../../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants"; import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "../../ApiVariable"; import MsApiVariable from "../../ApiVariable";
import {createComponent} from "../../jmeter/components";
import MsApiAssertions from "../../assertion/ApiAssertions"; import MsApiAssertions from "../../assertion/ApiAssertions";
import MsApiExtract from "../../extract/ApiExtract"; 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 {getUUID} from "@/common/js/utils";
import BatchAddParameter from "../../basis/BatchAddParameter"; import BatchAddParameter from "../../basis/BatchAddParameter";
import MsApiAdvancedConfig from "./ApiAdvancedConfig"; import MsApiAdvancedConfig from "./ApiAdvancedConfig";

View File

@ -8,7 +8,9 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="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>
<!--<el-tab-pane label="Cookie" name="cookie" class="pane cookie">--> <!--<el-tab-pane label="Cookie" name="cookie" class="pane cookie">-->
<!--<pre>{{response.cookies}}</pre>--> <!--<pre>{{response.cookies}}</pre>-->
@ -25,7 +27,9 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="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>
<el-tab-pane :label="$t('api_report.request_body')" name="request_body" class="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: 'HEAD', label: 'HEAD'},
{id: 'CONNECT', label: 'CONNECT'} {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 = [ export const CASE_PRIORITY = [
{id: 'P0', label: 'P0'}, {id: 'P0', label: 'P0'},

View File

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

View File

@ -15,23 +15,6 @@
props: ['total', 'pageSize'], props: ['total', 'pageSize'],
data() { data() {
return { 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 === 'SCHEDULE'">{{$t('commons.trigger_mode.schedule')}}</span>
<span v-if="triggerMode === 'TEST_PLAN_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 === '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> </span>
</template> </template>

View File

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

View File

@ -14,7 +14,7 @@
<el-tab-pane v-if="hasLicense()" :label="$t('display.title')" name="display"> <el-tab-pane v-if="hasLicense()" :label="$t('display.title')" name="display">
<ms-display/> <ms-display/>
</el-tab-pane> </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/> <ms-auth/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ export default {
operating: 'Operating', operating: 'Operating',
input_limit: 'Within {0} and {1} characters', input_limit: 'Within {0} and {1} characters',
login: 'Sign In', login: 'Sign In',
welcome: 'Welcome back, please enter username and password to log in', welcome: 'One-stop open source continuous testing platform',
username: 'Username', username: 'Username',
password: 'Password', password: 'Password',
input_username: 'Please enter username', input_username: 'Please enter username',
@ -150,7 +150,8 @@ export default {
name: "Trigger Mode", name: "Trigger Mode",
manual: "Manual trigger", manual: "Manual trigger",
schedule: "Scheduled Task", schedule: "Scheduled Task",
api: "API call" api: "API call",
case: "Case"
}, },
adv_search: { adv_search: {
title: 'Advanced Search', title: 'Advanced Search',
@ -602,6 +603,7 @@ export default {
customize_req: "Customize req", customize_req: "Customize req",
reference_info: "Reference info", reference_info: "Reference info",
scenario_test: "Scenario test", scenario_test: "Scenario test",
scenario_list: "Scenario List",
add_scenario: "Add scenario", add_scenario: "Add scenario",
scenario_name: "Scenario name", scenario_name: "Scenario name",
case_level: "Case level", case_level: "Case level",
@ -1499,6 +1501,7 @@ export default {
format: "Output format", format: "Output format",
}, },
auth_source: { 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: '操作', operating: '操作',
input_limit: '长度在 {0} 到 {1} 个字符', input_limit: '长度在 {0} 到 {1} 个字符',
login: '登录', login: '登录',
welcome: '欢迎回来,请输入用户名和密码登录', welcome: '一站式开源持续测试平台',
username: '姓名', username: '姓名',
password: '密码', password: '密码',
input_username: '请输入用户姓名', input_username: '请输入用户姓名',
@ -151,7 +151,8 @@ export default {
name: "触发方式", name: "触发方式",
manual: "手动触发", manual: "手动触发",
schedule: "定时任务", schedule: "定时任务",
api: "API调用" api: "API调用",
case: "用例触发"
}, },
adv_search: { adv_search: {
title: '高级搜索', title: '高级搜索',
@ -603,6 +604,7 @@ export default {
customize_req: "自定义请求", customize_req: "自定义请求",
reference_info: "请选择接口或用例", reference_info: "请选择接口或用例",
scenario_test: "场景", scenario_test: "场景",
scenario_list: "场景列表",
add_scenario: "创建场景", add_scenario: "创建场景",
scenario_name: "场景名称", scenario_name: "场景名称",
case_level: "用例等级", case_level: "用例等级",
@ -1502,6 +1504,7 @@ export default {
format: "输出格式", format: "输出格式",
}, },
auth_source: { auth_source: {
delete_prompt: '此操作会删除认证源,是否继续?' delete_prompt: '此操作会删除认证源,是否继续?',
title: '认证设置'
} }
}; };

View File

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

View File

@ -2,33 +2,36 @@
<div class="container" v-loading="result.loading" v-if="ready"> <div class="container" v-loading="result.loading" v-if="ready">
<el-row type="flex"> <el-row type="flex">
<el-col :span="12"> <el-col :span="12">
<el-form :model="form" :rules="rules" ref="form">
<div class="logo"> <div class="title">
<img :src="'/display/file/loginLogo'" style="width: 224px;height: 45px;" alt=""> <div class="title-img">
<img :src="'/display/file/loginLogo'" alt="">
</div> </div>
<div class="title">
<span id="s1">{{ loginTitle }}</span>
</div>
<div class="border"></div>
<div class="welcome"> <div class="welcome">
{{ $t('commons.welcome') }} <span>Metersphere</span>
<span>{{ $t('commons.welcome') }}</span>
</div> </div>
</div>
<div class="content">
<div class="form"> <div class="form">
<el-form-item v-slot:default> <el-form :model="form" :rules="rules" ref="form">
<el-radio-group v-model="form.authenticate" @change="redirectAuth(form.authenticate)"> <el-form-item v-slot:default>
<el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</el-radio> <el-radio-group v-model="form.authenticate" @change="redirectAuth(form.authenticate)">
<el-radio label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio> <el-radio label="LDAP" size="mini" v-if="openLdap">LDAP</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 label="LOCAL" size="mini" v-if="openLdap">普通登录</el-radio>
</el-radio-group> <el-radio :label="auth.id" size="mini" v-for="auth in authSources" :key="auth.id">{{ auth.type }} {{ auth.name }}</el-radio>
</el-form-item> </el-radio-group>
<el-form-item prop="username"> </el-form-item>
<el-input v-model="form.username" :placeholder="$t('commons.login_username')" autofocus <el-form-item prop="username">
autocomplete="off"/> <el-input v-model="form.username" :placeholder="$t('commons.login_username')" autofocus
</el-form-item> autocomplete="off"/>
<el-form-item prop="password"> </el-form-item>
<el-input v-model="form.password" :placeholder="$t('commons.password')" show-password autocomplete="off" <el-form-item prop="password">
maxlength="30" show-word-limit/> <el-input v-model="form.password" :placeholder="$t('commons.password')" show-password autocomplete="off"
</el-form-item> maxlength="30" show-word-limit/>
</el-form-item>
</el-form>
</div> </div>
<div class="btn"> <div class="btn">
<el-button type="primary" class="submit" @click="submit('form')"> <el-button type="primary" class="submit" @click="submit('form')">
@ -38,12 +41,17 @@
<div class="msg"> <div class="msg">
{{ msg }} {{ msg }}
</div> </div>
</el-form> </div>
</el-col> </el-col>
<div class="divider"/>
<el-col :span="12"> <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-col>
</el-row> </el-row>
</div> </div>
</template> </template>
@ -54,19 +62,11 @@ import {DEFAULT_LANGUAGE} from "@/common/js/constants";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/); const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {}; const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {};
const auth = requireComponent.keys().length > 0 ? requireComponent("./auth/Auth.vue") : {}; const auth = requireComponent.keys().length > 0 ? requireComponent("./auth/Auth.vue") : {};
const license = requireComponent.keys().length > 0 ? requireComponent("./license/LicenseMessage.vue") : null;
export default { export default {
name: "Login", name: "Login",
data() { 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 { return {
result: {}, result: {},
form: { form: {
@ -118,6 +118,10 @@ export default {
created: function () { created: function () {
// ,, // ,,
document.addEventListener("keydown", this.watchEnter); document.addEventListener("keydown", this.watchEnter);
//
if (license.default) {
license.default.valid(this)
}
}, },
destroyed() { destroyed() {
@ -184,62 +188,51 @@ export default {
<style scoped> <style scoped>
.container { .container {
min-width: 800px; width: 1440px;
max-width: 1440px; height: 810px;
height: 560px; margin: calc((100vh - 810px) / 2) auto 0;
margin: calc((100vh - 560px) / 2) auto 0;
background-color: #FFFFFF; background-color: #FFFFFF;
} }
.logo { .el-col:nth-child(3) {
margin: 30px 30px 0; align-items: center;
display: flex;
} }
.title { .title img {
margin-top: 50px; width: 293px;
font-size: 32px; margin-top: 165px;
}
.title-img {
letter-spacing: 0; letter-spacing: 0;
text-align: center; text-align: center;
} }
.title > #s1 { .login-image {
color: #999999; height: 365px;
} width: 567px;
margin: auto;
.title > #s2 { display: block;
color: #151515;
}
.border {
height: 2px;
margin: 20px auto 20px;
position: relative;
width: 80px;
background: #8B479B;
} }
.welcome { .welcome {
margin-top: 50px; margin-top: 12px;
margin-bottom: 75px;
font-size: 14px; font-size: 14px;
color: #999999; color: #843697;
letter-spacing: 0; line-height: 14px;
line-height: 18px;
text-align: center; text-align: center;
} }
.form { .form, .btn {
margin-top: 30px; padding: 0;
padding: 0 40px; width: 443px;
} margin: auto;
.btn {
margin-top: 40px;
padding: 0 40px;
} }
.btn > .submit { .btn > .submit {
width: 100%; border-radius: 70px;
border-radius: 0;
border-color: #8B479B; border-color: #8B479B;
background-color: #8B479B; background-color: #8B479B;
} }
@ -254,32 +247,54 @@ export default {
background-color: rgba(139, 71, 155, 0.8); background-color: rgba(139, 71, 155, 0.8);
} }
.msg { .el-form-item:first-child {
margin-top: 10px; margin-top: 60px;
padding: 0 40px;
color: red;
text-align: center;
} }
.image { /deep/ .el-radio__input.is-checked .el-radio__inner {
background: url(../assets/info.png); background-color: #783887;
height: 560px; background: #783887;
border-color: #783887;
} }
.login-logo { /deep/ .el-radio__input.is-checked + .el-radio__label {
background: url(../assets/logo-dark-MeterSphere.svg); color: #783887;
} }
.logo-header { /deep/ .el-input__inner {
background: url(../assets/logo-light-MeterSphere.svg); 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>
<style> <style>
body { 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-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; font-size: 14px;
background-color: #F5F5F5; /*background-color: #F5F5F5;*/
line-height: 26px; line-height: 26px;
color: #2B415C; color: #2B415C;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;

View File

@ -4,6 +4,10 @@ import 'element-ui/lib/theme-chalk/index.css';
import Login from "./Login.vue"; import Login from "./Login.vue";
import Ajax from "../common/js/ajax"; import Ajax from "../common/js/ajax";
import i18n from "../i18n/i18n"; 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; Vue.config.productionTip = false;