Merge branch 'master' into local-api-delimit
This commit is contained in:
commit
8404272115
14
ROADMAP.md
14
ROADMAP.md
|
@ -38,9 +38,21 @@
|
|||
- [x] 其他:消息通知
|
||||
- [x] 其他:报告导出
|
||||
|
||||
## v1.4 (已发布)
|
||||
- [x] 测试跟踪模块编辑测试用例支持上传附件
|
||||
- [x] 支持上传并引用自定义Jar包
|
||||
- [x] 接口测试支持TCP协议请求
|
||||
- [x] 全新的消息通知设置,支持企业微信、钉钉机器人通知
|
||||
|
||||
## v1.5 (开发中)
|
||||
- [ ] 性能测试:优化并发数、持续时间等压力配置方式
|
||||
- [ ] 性能测试:支持使用了额外插件的 JMX 文件
|
||||
- [ ] 性能测试:自动修改 csv 等数据文件引用路径
|
||||
- [ ] 性能测试:优化性能测试报告展示
|
||||
- [ ] 测试跟踪:支持对接禅道同步缺陷
|
||||
- [ ] 其他:Jenkins 插件支持 pipeline 方式调用
|
||||
|
||||
## 规划中
|
||||
- [ ] 接口测试支持添加 TCP 协议请求
|
||||
- [ ] 接口测试支持添加 WebSocket 协议请求
|
||||
- [ ] 接口管理功能
|
||||
- [ ] 集成云平台动态管理测试资源池
|
||||
|
|
|
@ -250,7 +250,7 @@
|
|||
<dependency>
|
||||
<groupId>io.metersphere</groupId>
|
||||
<artifactId>jmeter-plugins-dubbo</artifactId>
|
||||
<version>2.7.11</version>
|
||||
<version>2.7.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- LDAP Module -->
|
||||
|
@ -362,14 +362,6 @@
|
|||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -104,9 +104,8 @@ public class APITestController {
|
|||
|
||||
@PostMapping("/delete")
|
||||
public void delete(@RequestBody DeleteAPITestRequest request) {
|
||||
String testId = request.getId();
|
||||
checkownerService.checkApiTestOwner(testId);
|
||||
apiTestService.delete(testId);
|
||||
checkownerService.checkApiTestOwner(request.getId());
|
||||
apiTestService.delete(request);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/run")
|
||||
|
|
|
@ -8,4 +8,8 @@ import lombok.Setter;
|
|||
public class DeleteAPITestRequest {
|
||||
|
||||
private String id;
|
||||
/**
|
||||
* 是否强制删除(删除项目时不检查关联关系,强制删除资源)
|
||||
*/
|
||||
private boolean forceDelete = false;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ public class AssertionType {
|
|||
public final static String JSON_PATH = "JSONPath";
|
||||
public final static String JSR223 = "JSR223";
|
||||
public final static String TEXT = "Text";
|
||||
public final static String XPATH2 = "XPath2";
|
||||
|
||||
private String type;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package io.metersphere.api.dto.scenario.assertions;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class AssertionXPath2 extends AssertionType {
|
||||
private String expression;
|
||||
public AssertionXPath2() {
|
||||
setType(AssertionType.XPATH2);
|
||||
}
|
||||
}
|
|
@ -9,5 +9,6 @@ public class Assertions {
|
|||
private List<AssertionRegex> regex;
|
||||
private List<AssertionJsonPath> jsonPath;
|
||||
private List<AssertionJSR223> jsr223;
|
||||
private List<AssertionXPath2> xpath2;
|
||||
private AssertionDuration duration;
|
||||
}
|
||||
|
|
|
@ -305,9 +305,11 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
|
|||
|
||||
private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) {
|
||||
ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult();
|
||||
responseAssertionResult.setMessage(assertionResult.getFailureMessage());
|
||||
responseAssertionResult.setName(assertionResult.getName());
|
||||
responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError());
|
||||
if (!responseAssertionResult.isPass()) {
|
||||
responseAssertionResult.setMessage(assertionResult.getFailureMessage());
|
||||
}
|
||||
return responseAssertionResult;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ public class MsParser extends ApiImportAbstractParser {
|
|||
Object body = requestObject.get("body");
|
||||
if (body instanceof JSONArray) {
|
||||
JSONArray bodies = requestObject.getJSONArray("body");
|
||||
if (StringUtils.equalsIgnoreCase(requestObject.getString("method"), "POST") && bodies != null) {
|
||||
if (bodies != null) {
|
||||
StringBuilder bodyStr = new StringBuilder();
|
||||
for (int i = 0; i < bodies.size(); i++) {
|
||||
String tmp = bodies.getString(i);
|
||||
|
@ -74,7 +74,7 @@ public class MsParser extends ApiImportAbstractParser {
|
|||
}
|
||||
} else if (body instanceof JSONObject) {
|
||||
JSONObject bodyObj = requestObject.getJSONObject("body");
|
||||
if (StringUtils.equalsIgnoreCase(requestObject.getString("method"), "POST") && bodyObj != null) {
|
||||
if (bodyObj != null) {
|
||||
JSONArray kvs = new JSONArray();
|
||||
bodyObj.keySet().forEach(key -> {
|
||||
JSONObject kv = new JSONObject();
|
||||
|
|
|
@ -193,8 +193,11 @@ public class APITestService {
|
|||
return extApiTestMapper.getApiTestByProjectId(projectId);
|
||||
}
|
||||
|
||||
public void delete(String testId) {
|
||||
testCaseService.checkIsRelateTest(testId);
|
||||
public void delete(DeleteAPITestRequest request) {
|
||||
String testId = request.getId();
|
||||
if (!request.isForceDelete()) {
|
||||
testCaseService.checkIsRelateTest(testId);
|
||||
}
|
||||
deleteFileByTestId(testId);
|
||||
apiReportService.deleteByTestId(testId);
|
||||
scheduleService.deleteByResourceId(testId);
|
||||
|
|
|
@ -8,7 +8,7 @@ import org.apache.ibatis.annotations.Param;
|
|||
import java.util.List;
|
||||
|
||||
public interface ExtTestPlanMapper {
|
||||
List<TestPlanDTO> list(@Param("request") QueryTestPlanRequest params);
|
||||
List<TestPlanDTOWithMetric> list(@Param("request") QueryTestPlanRequest params);
|
||||
|
||||
List<TestPlanDTOWithMetric> listRelate(@Param("request") QueryTestPlanRequest params);
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
</if>
|
||||
</sql>
|
||||
|
||||
<select id="list" resultMap="BaseResultMap"
|
||||
<select id="list" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric"
|
||||
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
|
||||
select DISTINCT test_plan.*, user.name as user_name from test_plan
|
||||
LEFT JOIN user ON user.id = test_plan.principal
|
||||
|
|
|
@ -31,4 +31,11 @@ public interface ExtTestPlanTestCaseMapper {
|
|||
|
||||
List<String> getTestPlanTestCaseIds(String testId);
|
||||
|
||||
/**
|
||||
* 根据项目 ids 查询 TestPlanCaseDTO 列表
|
||||
* @param ids project id list
|
||||
* @return List<TestPlanCaseDTO>
|
||||
*/
|
||||
List<TestPlanCaseDTO> listTestCaseByProjectIds(@Param("ids") List<String> ids);
|
||||
|
||||
}
|
||||
|
|
|
@ -210,6 +210,14 @@
|
|||
</foreach>
|
||||
</if>
|
||||
</select>
|
||||
<select id="listTestCaseByProjectIds" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
|
||||
select distinct * from test_plan_test_case, test_case
|
||||
where test_plan_test_case.case_id = test_case.id
|
||||
and test_case.project_id in
|
||||
<foreach collection="ids" item="id" index="index" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
<select id="listByNode" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
|
||||
select test_plan_test_case.*, test_case.*
|
||||
from test_plan_test_case
|
||||
|
|
|
@ -12,4 +12,11 @@ public interface ExtTestReviewCaseMapper {
|
|||
List<TestReviewCaseDTO> list(@Param("request") QueryCaseReviewRequest request);
|
||||
List<String> getStatusByReviewId(String reviewId);
|
||||
List<String> findRelateTestReviewId(@Param("userId") String userId, @Param("workspaceId") String workspaceId);
|
||||
|
||||
/**
|
||||
* 根据项目 ids 查询 TestReviewCaseDTO 列表
|
||||
* @param ids project id list
|
||||
* @return List<TestReviewCaseDTO>
|
||||
*/
|
||||
List<TestReviewCaseDTO> listTestCaseByProjectIds(@Param("ids") List<String> ids);
|
||||
}
|
||||
|
|
|
@ -195,4 +195,12 @@
|
|||
where test_case_review_test_case.review_id = #{userId}
|
||||
and project.workspace_id = #{workspaceId}
|
||||
</select>
|
||||
<select id="listTestCaseByProjectIds" resultType="io.metersphere.track.dto.TestReviewCaseDTO">
|
||||
select distinct * from test_case_review_test_case, test_case
|
||||
where test_case_review_test_case.case_id = test_case.id
|
||||
and test_case.project_id in
|
||||
<foreach collection="ids" item="id" index="index" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
</mapper>
|
|
@ -3,7 +3,7 @@ package io.metersphere.commons.constants;
|
|||
public enum FileType {
|
||||
JMX(".jmx"), CSV(".csv"), JSON(".json"), PDF(".pdf"),
|
||||
JPG(".jpg"), PNG(".png"), JPEG(".jpeg"), DOC(".doc"),
|
||||
XLSX(".xlsx"), DOCX(".docx");
|
||||
XLSX(".xlsx"), DOCX(".docx"), JAR(".jar");
|
||||
|
||||
// 保存后缀
|
||||
private String suffix;
|
||||
|
|
|
@ -28,7 +28,7 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix="sso",name = "mode", havingValue = "local", matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = "sso", name = "mode", havingValue = "local", matchIfMissing = true)
|
||||
public class ShiroConfig implements EnvironmentAware {
|
||||
private Environment env;
|
||||
|
||||
|
@ -43,6 +43,8 @@ public class ShiroConfig implements EnvironmentAware {
|
|||
shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
|
||||
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
|
||||
ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
|
||||
filterChainDefinitionMap.put("/display/info", "anon");
|
||||
filterChainDefinitionMap.put("/display/file/*", "anon");
|
||||
filterChainDefinitionMap.put("/**", "apikey, authc");
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ public class UserController {
|
|||
* 组织成员列表不分页
|
||||
*/
|
||||
@PostMapping("/org/member/list/all")
|
||||
@RequiresRoles(value = {RoleConstants.ORG_ADMIN, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
@RequiresRoles(value = {RoleConstants.ORG_ADMIN, RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
|
||||
public List<User> getOrgMemberList(@RequestBody QueryOrgMemberRequest request) {
|
||||
return userService.getOrgMemberList(request);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.dingtalk.api.request.OapiRobotSendRequest;
|
|||
import com.dingtalk.api.response.OapiRobotSendResponse;
|
||||
import com.taobao.api.ApiException;
|
||||
import io.metersphere.commons.constants.NoticeConstants;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.notice.domain.MessageDetail;
|
||||
import io.metersphere.notice.domain.UserDetail;
|
||||
import io.metersphere.service.UserService;
|
||||
|
@ -62,7 +63,7 @@ public class DingTaskService {
|
|||
try {
|
||||
response = client.execute(request);
|
||||
} catch (ApiException e) {
|
||||
e.printStackTrace();
|
||||
LogUtil.error(e);
|
||||
}
|
||||
System.out.println(response.getErrcode());
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package io.metersphere.notice.service;
|
||||
|
||||
import io.metersphere.base.domain.ApiTestReport;
|
||||
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
|
||||
import io.metersphere.base.domain.SystemParameter;
|
||||
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.UserMapper;
|
||||
import io.metersphere.commons.constants.APITestStatus;
|
||||
import io.metersphere.commons.constants.NoticeConstants;
|
||||
import io.metersphere.commons.constants.ParamConstants;
|
||||
|
@ -46,6 +44,8 @@ public class MailService {
|
|||
private UserService userService;
|
||||
@Resource
|
||||
private SystemParameterService systemParameterService;
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
//接口和性能测试
|
||||
public void sendLoadNotification(MessageDetail messageDetail, LoadTestReportWithBLOBs loadTestReport, String eventType) {
|
||||
|
@ -144,7 +144,7 @@ public class MailService {
|
|||
context.put("testCaseName", testCaseWithBLOBs.getName());
|
||||
context.put("description", request.getDescription());
|
||||
context.put("url", baseSystemConfigDTO.getUrl());
|
||||
context.put("id", testCaseWithBLOBs.getId());
|
||||
context.put("id", request.getReviewId());
|
||||
try {
|
||||
String commentTemplate = IOUtils.toString(this.getClass().getResource("/mail/ReviewComments.html"), StandardCharsets.UTF_8);
|
||||
sendReviewNotice(addresseeIdList(messageDetail, userIds, eventType), context, commentTemplate);
|
||||
|
@ -297,7 +297,8 @@ public class MailService {
|
|||
Map<String, String> context = new HashMap<>();
|
||||
BaseSystemConfigDTO baseSystemConfigDTO = systemParameterService.getBaseInfo();
|
||||
context.put("url", baseSystemConfigDTO.getUrl());
|
||||
context.put("creator", reviewRequest.getCreator());
|
||||
User user = userMapper.selectByPrimaryKey(reviewRequest.getCreator());
|
||||
context.put("creator", user.getName());
|
||||
context.put("reviewName", reviewRequest.getName());
|
||||
context.put("start", start);
|
||||
context.put("end", end);
|
||||
|
@ -328,6 +329,8 @@ public class MailService {
|
|||
context.put("start", start);
|
||||
context.put("end", end);
|
||||
context.put("id", testPlan.getId());
|
||||
User user = userMapper.selectByPrimaryKey(testPlan.getCreator());
|
||||
context.put("creator", user.getName());
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.metersphere.notice.service;
|
||||
|
||||
import io.metersphere.commons.constants.NoticeConstants;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.notice.domain.MessageDetail;
|
||||
import io.metersphere.notice.domain.UserDetail;
|
||||
import io.metersphere.notice.message.TextMessage;
|
||||
|
@ -55,7 +56,7 @@ public class WxChatTaskService {
|
|||
SendResult result = WxChatbotClient.send(Webhook, message);
|
||||
System.out.println(result);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LogUtil.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ public class EngineContext {
|
|||
private Map<String, Object> properties = new HashMap<>();
|
||||
private Map<String, String> testData = new HashMap<>();
|
||||
private Map<String, String> env = new HashMap<>();
|
||||
private Map<String, byte[]> testJars = new HashMap<>();
|
||||
|
||||
public String getTestId() {
|
||||
return testId;
|
||||
|
@ -126,4 +127,13 @@ public class EngineContext {
|
|||
public void setResourceIndex(Integer resourceIndex) {
|
||||
this.resourceIndex = resourceIndex;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, byte[]> getTestJars() {
|
||||
return testJars;
|
||||
}
|
||||
|
||||
public void setTestJars(Map<String, byte[]> testJars) {
|
||||
this.testJars = testJars;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ public class EngineFactory {
|
|||
});
|
||||
|
||||
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
|
||||
List<FileMetadata> jarFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JAR.name())).collect(Collectors.toList());
|
||||
final FileContent fileContent = fileService.getFileContent(jmxFile.getId());
|
||||
if (fileContent == null) {
|
||||
MSException.throwException(Translator.get("run_load_test_file_content_not_found") + loadTest.getId());
|
||||
|
@ -125,6 +126,15 @@ public class EngineFactory {
|
|||
engineContext.setTestData(data);
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(jarFiles)) {
|
||||
Map<String, byte[]> data = new HashMap<>();
|
||||
jarFiles.forEach(jf -> {
|
||||
FileContent content = fileService.getFileContent(jf.getId());
|
||||
data.put(jf.getName(), content.getFile());
|
||||
});
|
||||
engineContext.setTestJars(data);
|
||||
}
|
||||
|
||||
return engineContext;
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ public class DockerTestEngine extends AbstractEngine {
|
|||
testRequest.setFileString(content);
|
||||
testRequest.setImage(JMETER_IMAGE);
|
||||
testRequest.setTestData(context.getTestData());
|
||||
testRequest.setTestJars(context.getTestJars());
|
||||
testRequest.setEnv(context.getEnv());
|
||||
|
||||
|
||||
|
|
|
@ -14,4 +14,5 @@ public class TestRequest extends BaseRequest {
|
|||
private String image;
|
||||
private Map<String, String> testData = new HashMap<>();
|
||||
private Map<String, String> env = new HashMap<>();
|
||||
private Map<String, byte[]> testJars = new HashMap<>();
|
||||
}
|
||||
|
|
|
@ -47,27 +47,26 @@ public class PerformanceNoticeTask {
|
|||
}
|
||||
|
||||
public void registerNoticeTask(LoadTestReportWithBLOBs loadTestReport) {
|
||||
executorService.submit(() -> {
|
||||
while (isRunning) {
|
||||
LoadTestReportWithBLOBs loadTestReportFromDatabase = loadTestReportMapper.selectByPrimaryKey(loadTestReport.getId());
|
||||
|
||||
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Completed.name())) {
|
||||
isRunning = false;
|
||||
sendSuccessNotice(loadTestReportFromDatabase);
|
||||
return;
|
||||
}
|
||||
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Error.name())) {
|
||||
isRunning = false;
|
||||
sendFailNotice(loadTestReportFromDatabase);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000 * 60);// 每分钟检查 loadtest 的状态
|
||||
} catch (InterruptedException e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
int count = 20;
|
||||
while (count-- > 0) {
|
||||
LoadTestReportWithBLOBs loadTestReportFromDatabase = loadTestReportMapper.selectByPrimaryKey(loadTestReport.getId());
|
||||
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Completed.name())) {
|
||||
isRunning = false;
|
||||
sendSuccessNotice(loadTestReportFromDatabase);
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Error.name())) {
|
||||
isRunning = false;
|
||||
sendFailNotice(loadTestReportFromDatabase);
|
||||
return;
|
||||
}
|
||||
count--;
|
||||
try {
|
||||
Thread.sleep(1000 * 4L);// 每分钟检查 loadtest 的状态
|
||||
} catch (InterruptedException e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendSuccessNotice(LoadTestReportWithBLOBs loadTestReport) {
|
||||
|
|
|
@ -19,7 +19,6 @@ import io.metersphere.dto.LoadTestDTO;
|
|||
import io.metersphere.dto.ScheduleDao;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.job.sechedule.PerformanceTestJob;
|
||||
import io.metersphere.notice.domain.NoticeDetail;
|
||||
import io.metersphere.performance.engine.Engine;
|
||||
import io.metersphere.performance.engine.EngineFactory;
|
||||
import io.metersphere.performance.notice.PerformanceNoticeTask;
|
||||
|
@ -86,7 +85,9 @@ public class PerformanceTestService {
|
|||
public void delete(DeleteTestPlanRequest request) {
|
||||
String testId = request.getId();
|
||||
|
||||
testCaseService.checkIsRelateTest(testId);
|
||||
if (!request.isForceDelete()) {
|
||||
testCaseService.checkIsRelateTest(testId);
|
||||
}
|
||||
|
||||
LoadTestReportExample loadTestReportExample = new LoadTestReportExample();
|
||||
loadTestReportExample.createCriteria().andTestIdEqualTo(testId);
|
||||
|
@ -233,9 +234,6 @@ public class PerformanceTestService {
|
|||
if (StringUtils.equals(NoticeConstants.API, loadTestReport.getTriggerMode()) || StringUtils.equals(NoticeConstants.SCHEDULE, loadTestReport.getTriggerMode())) {
|
||||
performanceNoticeTask.registerNoticeTask(loadTestReport);
|
||||
}
|
||||
/*if (StringUtils.equals(NoticeConstants.API, loadTestReport.getTriggerMode()) || StringUtils.equals(NoticeConstants.SCHEDULE, "SCHEDULE")) {
|
||||
performanceNoticeTask.registerNoticeTask(loadTestReport);
|
||||
}*/
|
||||
return engine.getReportId();
|
||||
}
|
||||
|
||||
|
@ -305,15 +303,10 @@ public class PerformanceTestService {
|
|||
} catch (MSException e) {
|
||||
// 启动失败之后清理任务
|
||||
engine.stop();
|
||||
|
||||
LogUtil.error(e);
|
||||
loadTest.setStatus(PerformanceTestStatus.Error.name());
|
||||
loadTest.setDescription(e.getMessage());
|
||||
loadTestMapper.updateByPrimaryKeySelective(loadTest);
|
||||
LoadTestReportWithBLOBs loadTestReport = loadTestReportMapper.selectByPrimaryKey(engine.getReportId());
|
||||
if (StringUtils.equals(NoticeConstants.API, loadTestReport.getTriggerMode()) || StringUtils.equals(NoticeConstants.SCHEDULE, loadTestReport.getTriggerMode())) {
|
||||
performanceNoticeTask.registerNoticeTask(loadTestReport);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -427,29 +420,28 @@ public class PerformanceTestService {
|
|||
if (forceStop) {
|
||||
reportService.deleteReport(reportId);
|
||||
} else {
|
||||
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
|
||||
LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(loadTestReport.getTestId());
|
||||
final Engine engine = EngineFactory.createEngine(loadTest);
|
||||
if (engine == null) {
|
||||
MSException.throwException(String.format("Stop report fail. create engine fail,report ID:%s", reportId));
|
||||
}
|
||||
reportService.stopEngine(loadTest, engine);
|
||||
stopEngine(reportId);
|
||||
// 停止测试之后设置报告的状态
|
||||
reportService.updateStatus(reportId, PerformanceTestStatus.Completed.name());
|
||||
List<NoticeDetail> noticeList = null;
|
||||
if (loadTestReport.getTriggerMode().equals("SCHEDULE")) {
|
||||
/* try {
|
||||
noticeList = noticeService.queryNotice(loadTest.getId());
|
||||
mailService.sendPerformanceNotification(noticeList, loadTestReport.getStatus(), loadTest, loadTestReport.getId());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void stopErrorTest(String reportId) {
|
||||
stopEngine(reportId);
|
||||
// 停止测试之后设置报告的状态
|
||||
reportService.updateStatus(reportId, PerformanceTestStatus.Error.name());
|
||||
}
|
||||
|
||||
private void stopEngine(String reportId) {
|
||||
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
|
||||
LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(loadTestReport.getTestId());
|
||||
final Engine engine = EngineFactory.createEngine(loadTest);
|
||||
if (engine == null) {
|
||||
MSException.throwException(String.format("Stop report fail. create engine fail,report ID:%s", reportId));
|
||||
}
|
||||
reportService.stopEngine(loadTest, engine);
|
||||
}
|
||||
|
||||
public List<ScheduleDao> listSchedule(QueryScheduleRequest request) {
|
||||
request.setEnable(true);
|
||||
List<ScheduleDao> schedules = scheduleService.list(request);
|
||||
|
|
|
@ -241,7 +241,7 @@ public class ReportService {
|
|||
}
|
||||
}
|
||||
|
||||
public LoadTestReport getReport(String reportId) {
|
||||
public LoadTestReportWithBLOBs getReport(String reportId) {
|
||||
return loadTestReportMapper.selectByPrimaryKey(reportId);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import io.metersphere.base.mapper.ext.*;
|
|||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import io.metersphere.dto.LoadTestDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.track.dto.TestPlanDTO;
|
||||
import io.metersphere.track.dto.TestPlanDTOWithMetric;
|
||||
import io.metersphere.track.request.testplan.QueryTestPlanRequest;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -81,7 +81,7 @@ public class CheckOwnerService {
|
|||
io.metersphere.track.request.testcase.QueryTestPlanRequest request = new io.metersphere.track.request.testcase.QueryTestPlanRequest();
|
||||
request.setWorkspaceId(workspaceId);
|
||||
request.setId(planId);
|
||||
List<TestPlanDTO> list = extTestPlanMapper.list(request);
|
||||
List<TestPlanDTOWithMetric> list = extTestPlanMapper.list(request);
|
||||
if (CollectionUtils.size(list) != 1) {
|
||||
throw new UnauthorizedException(Translator.get("check_owner_plan"));
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -141,4 +142,12 @@ public class FileService {
|
|||
example.createCriteria().andIdIn(fileIds);
|
||||
return fileMetadataMapper.selectByExample(example);
|
||||
}
|
||||
|
||||
public void deleteFileById(String fileId) {
|
||||
deleteFileByIds(Collections.singletonList(fileId));
|
||||
}
|
||||
|
||||
public FileMetadata getFileMetadataById(String fileId) {
|
||||
return fileMetadataMapper.selectByPrimaryKey(fileId);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import io.metersphere.api.dto.DeleteAPITestRequest;
|
||||
import io.metersphere.api.dto.QueryAPITestRequest;
|
||||
import io.metersphere.api.service.APITestService;
|
||||
import io.metersphere.base.domain.LoadTest;
|
||||
|
@ -96,6 +97,7 @@ public class ProjectService {
|
|||
loadTestIdList.forEach(loadTestId -> {
|
||||
DeleteTestPlanRequest deleteTestPlanRequest = new DeleteTestPlanRequest();
|
||||
deleteTestPlanRequest.setId(loadTestId);
|
||||
deleteTestPlanRequest.setForceDelete(true);
|
||||
performanceTestService.delete(deleteTestPlanRequest);
|
||||
});
|
||||
|
||||
|
@ -122,7 +124,10 @@ public class ProjectService {
|
|||
QueryAPITestRequest request = new QueryAPITestRequest();
|
||||
request.setProjectId(projectId);
|
||||
apiTestService.list(request).forEach(test -> {
|
||||
apiTestService.delete(test.getId());
|
||||
DeleteAPITestRequest deleteAPITestRequest = new DeleteAPITestRequest();
|
||||
deleteAPITestRequest.setId(test.getId());
|
||||
deleteAPITestRequest.setForceDelete(true);
|
||||
apiTestService.delete(deleteAPITestRequest);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -73,8 +73,14 @@ public class TestResourcePoolService {
|
|||
LoadTestExample example = new LoadTestExample();
|
||||
example.createCriteria()
|
||||
.andTestResourcePoolIdEqualTo(testResourcePoolId);
|
||||
if (loadTestMapper.countByExample(example) > 0) {
|
||||
MSException.throwException(Translator.get("test_resource_pool_is_use"));
|
||||
List<LoadTest> loadTests = loadTestMapper.selectByExample(example);
|
||||
StringBuilder loadTestNames = new StringBuilder();
|
||||
if (loadTests.size() > 0) {
|
||||
for (LoadTest loadTest : loadTests) {
|
||||
loadTestNames = loadTestNames.append(loadTest.getName()).append(",");
|
||||
}
|
||||
String str = loadTestNames.substring(0, loadTestNames.length() - 1);
|
||||
MSException.throwException(Translator.get("load_test") + " " + str + " " + Translator.get("test_resource_pool_is_use"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ public class TestPlanController {
|
|||
CheckOwnerService checkOwnerService;
|
||||
|
||||
@PostMapping("/list/{goPage}/{pageSize}")
|
||||
public Pager<List<TestPlanDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) {
|
||||
public Pager<List<TestPlanDTOWithMetric>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) {
|
||||
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
|
||||
request.setWorkspaceId(currentWorkspaceId);
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
package io.metersphere.track.request.testplan;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DeleteTestPlanRequest extends TestPlanRequest {
|
||||
/**
|
||||
* 是否强制删除(删除项目时不检查关联关系,强制删除资源)
|
||||
*/
|
||||
private boolean forceDelete = false;
|
||||
}
|
||||
|
|
|
@ -7,5 +7,6 @@ import lombok.Setter;
|
|||
@Getter
|
||||
@Setter
|
||||
public class SaveCommentRequest extends TestCaseComment {
|
||||
private String reviewId;
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import io.metersphere.base.mapper.ext.ExtTestCaseReviewMapper;
|
|||
import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper;
|
||||
import io.metersphere.commons.constants.NoticeConstants;
|
||||
import io.metersphere.commons.constants.TestCaseReviewStatus;
|
||||
import io.metersphere.commons.constants.TestPlanStatus;
|
||||
import io.metersphere.commons.constants.TestReviewCaseStatus;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.user.SessionUser;
|
||||
|
@ -184,28 +185,31 @@ public class TestCaseReviewService {
|
|||
testCaseReview.setUpdateTime(System.currentTimeMillis());
|
||||
checkCaseReviewExist(testCaseReview);
|
||||
testCaseReviewMapper.updateByPrimaryKeySelective(testCaseReview);
|
||||
List<String> userIds=new ArrayList<>();
|
||||
List<String> userIds = new ArrayList<>();
|
||||
userIds.addAll(testCaseReview.getUserIds());
|
||||
try {
|
||||
String context = getReviewContext(testCaseReview, NoticeConstants.CREATE);
|
||||
MessageSettingDetail messageSettingDetail = noticeService.searchMessage();
|
||||
List<MessageDetail> taskList = messageSettingDetail.getReviewTask();
|
||||
taskList.forEach(r -> {
|
||||
switch (r.getType()) {
|
||||
case NoticeConstants.NAIL_ROBOT:
|
||||
dingTaskService.sendNailRobot(r, userIds, context, NoticeConstants.CREATE);
|
||||
break;
|
||||
case NoticeConstants.WECHAT_ROBOT:
|
||||
wxChatTaskService.sendWechatRobot(r, userIds, context, NoticeConstants.CREATE);
|
||||
break;
|
||||
case NoticeConstants.EMAIL:
|
||||
mailService.sendReviewerNotice(r, userIds, testCaseReview, NoticeConstants.CREATE);
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
if (StringUtils.equals(TestPlanStatus.Completed.name(), testCaseReview.getStatus())) {
|
||||
try {
|
||||
String context = getReviewContext(testCaseReview, NoticeConstants.UPDATE);
|
||||
MessageSettingDetail messageSettingDetail = noticeService.searchMessage();
|
||||
List<MessageDetail> taskList = messageSettingDetail.getReviewTask();
|
||||
taskList.forEach(r -> {
|
||||
switch (r.getType()) {
|
||||
case NoticeConstants.NAIL_ROBOT:
|
||||
dingTaskService.sendNailRobot(r, userIds, context, NoticeConstants.UPDATE);
|
||||
break;
|
||||
case NoticeConstants.WECHAT_ROBOT:
|
||||
wxChatTaskService.sendWechatRobot(r, userIds, context, NoticeConstants.UPDATE);
|
||||
break;
|
||||
case NoticeConstants.EMAIL:
|
||||
mailService.sendReviewerNotice(r, userIds, testCaseReview, NoticeConstants.UPDATE);
|
||||
break;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void editCaseReviewer(SaveTestCaseReviewRequest testCaseReview) {
|
||||
|
@ -480,7 +484,7 @@ public class TestCaseReviewService {
|
|||
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
|
||||
request.setReviewIds(extTestReviewCaseMapper.findRelateTestReviewId(user.getId(), SessionUtils.getCurrentWorkspaceId()));
|
||||
|
||||
List<String> projectIds = extProjectMapper.getProjectIdByWorkspaceId(SessionUtils.getCurrentOrganizationId());
|
||||
List<String> projectIds = extProjectMapper.getProjectIdByWorkspaceId(SessionUtils.getCurrentWorkspaceId());
|
||||
|
||||
List<TestReviewDTOWithMetric> testReviews = extTestCaseReviewMapper.listRelate(request);
|
||||
|
||||
|
@ -549,14 +553,17 @@ public class TestCaseReviewService {
|
|||
return name;
|
||||
}
|
||||
|
||||
public List<TestReviewCaseDTO> listTestCaseByProjectIds(List<String> projectIds) {
|
||||
QueryCaseReviewRequest request = new QueryCaseReviewRequest();
|
||||
request.setProjectIds(projectIds);
|
||||
return extTestReviewCaseMapper.list(request);
|
||||
private List<TestReviewCaseDTO> listTestCaseByProjectIds(List<String> projectIds) {
|
||||
if (CollectionUtils.isEmpty(projectIds)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return extTestReviewCaseMapper.listTestCaseByProjectIds(projectIds);
|
||||
}
|
||||
|
||||
/*编辑,新建,完成,删除通知内容*/
|
||||
private static String getReviewContext(SaveTestCaseReviewRequest reviewRequest, String type) {
|
||||
private String getReviewContext(SaveTestCaseReviewRequest reviewRequest, String type) {
|
||||
|
||||
User user = userMapper.selectByPrimaryKey(reviewRequest.getCreator());
|
||||
Long startTime = reviewRequest.getCreateTime();
|
||||
Long endTime = reviewRequest.getEndTime();
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
@ -572,11 +579,11 @@ public class TestCaseReviewService {
|
|||
}
|
||||
String context = "";
|
||||
if (StringUtils.equals(NoticeConstants.CREATE, type)) {
|
||||
context = "测试评审任务通知:" + reviewRequest.getCreator() + "发起的" + "'" + reviewRequest.getName() + "'" + "待开始,计划开始时间是" + start + "计划结束时间为" + end + "请跟进";
|
||||
context = "测试评审任务通知:" + user.getName() + "发起的" + "'" + reviewRequest.getName() + "'" + "待开始,计划开始时间是" + start + "计划结束时间为" + end + "请跟进";
|
||||
} else if (StringUtils.equals(NoticeConstants.UPDATE, type)) {
|
||||
context = "测试评审任务通知:" + reviewRequest.getCreator() + "发起的" + "'" + reviewRequest.getName() + "'" + "已完成,计划开始时间是" + start + "计划结束时间为" + end + "已完成";
|
||||
context = "测试评审任务通知:" + user.getName() + "发起的" + "'" + reviewRequest.getName() + "'" + "已完成,计划开始时间是" + start + "计划结束时间为" + end + "已完成";
|
||||
} else if (StringUtils.equals(NoticeConstants.DELETE, type)) {
|
||||
context = "测试评审任务通知:" + reviewRequest.getCreator() + "发起的" + "'" + reviewRequest.getName() + "'" + "计划开始时间是" + start + "计划结束时间为" + end + "已删除";
|
||||
context = "测试评审任务通知:" + user.getName() + "发起的" + "'" + reviewRequest.getName() + "'" + "计划开始时间是" + start + "计划结束时间为" + end + "已删除";
|
||||
}
|
||||
|
||||
return context;
|
||||
|
|
|
@ -457,7 +457,7 @@ public class TestCaseService {
|
|||
if (t.getMethod().equals("manual")) {
|
||||
String steps = t.getSteps();
|
||||
String setp = "";
|
||||
if (steps.contains("null")) {
|
||||
if (steps.contains("null") && !steps.contains("\"null\"")) {
|
||||
setp = steps.replace("null", "\"\"");
|
||||
} else {
|
||||
setp = steps;
|
||||
|
@ -546,7 +546,7 @@ public class TestCaseService {
|
|||
for (TestCase testCase : testCases) {
|
||||
caseName = caseName.append(testCase.getName()).append(",");
|
||||
}
|
||||
String str = caseName.toString().substring(0, caseName.length() - 1);
|
||||
String str = caseName.substring(0, caseName.length() - 1);
|
||||
MSException.throwException(Translator.get("related_case_del_fail_prefix") + " " + str + " " + Translator.get("related_case_del_fail_suffix"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,8 @@ public class TestPlanService {
|
|||
DingTaskService dingTaskService;
|
||||
@Resource
|
||||
WxChatTaskService wxChatTaskService;
|
||||
@Resource
|
||||
UserMapper userMapper;
|
||||
|
||||
public void addTestPlan(AddTestPlanRequest testPlan) {
|
||||
if (getTestPlanByName(testPlan.getName()).size() > 0) {
|
||||
|
@ -165,20 +167,20 @@ public class TestPlanService {
|
|||
//已完成,写入实际完成时间
|
||||
testPlan.setActualEndTime(System.currentTimeMillis());
|
||||
try {
|
||||
BeanUtils.copyBean(testPlans, testPlan);
|
||||
String context = getTestPlanContext(testPlans, NoticeConstants.CREATE);
|
||||
BeanUtils.copyBean(testPlans, getTestPlan(testPlan.getId()));
|
||||
String context = getTestPlanContext(testPlans, NoticeConstants.UPDATE);
|
||||
MessageSettingDetail messageSettingDetail = noticeService.searchMessage();
|
||||
List<MessageDetail> taskList = messageSettingDetail.getReviewTask();
|
||||
List<MessageDetail> taskList = messageSettingDetail.getTestCasePlanTask();
|
||||
taskList.forEach(r -> {
|
||||
switch (r.getType()) {
|
||||
case NoticeConstants.NAIL_ROBOT:
|
||||
dingTaskService.sendNailRobot(r, userIds, context, NoticeConstants.CREATE);
|
||||
dingTaskService.sendNailRobot(r, userIds, context, NoticeConstants.UPDATE);
|
||||
break;
|
||||
case NoticeConstants.WECHAT_ROBOT:
|
||||
wxChatTaskService.sendWechatRobot(r, userIds, context, NoticeConstants.CREATE);
|
||||
wxChatTaskService.sendWechatRobot(r, userIds, context, NoticeConstants.UPDATE);
|
||||
break;
|
||||
case NoticeConstants.EMAIL:
|
||||
mailService.sendTestPlanStartNotice(r, userIds, testPlans, NoticeConstants.CREATE);
|
||||
mailService.sendTestPlanStartNotice(r, userIds, testPlans, NoticeConstants.UPDATE);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
@ -279,9 +281,46 @@ public class TestPlanService {
|
|||
testPlanTestCaseMapper.deleteByExample(testPlanTestCaseExample);
|
||||
}
|
||||
|
||||
public List<TestPlanDTO> listTestPlan(QueryTestPlanRequest request) {
|
||||
private void calcTestPlanRate(List<TestPlanDTOWithMetric> testPlans) {
|
||||
List<String> projectIds = extProjectMapper.getProjectIdByWorkspaceId(SessionUtils.getCurrentWorkspaceId());
|
||||
Map<String, List<TestPlanCaseDTO>> testCaseMap = new HashMap<>();
|
||||
listTestCaseByProjectIds(projectIds).forEach(testCase -> {
|
||||
List<TestPlanCaseDTO> list = testCaseMap.get(testCase.getPlanId());
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
list.add(testCase);
|
||||
testCaseMap.put(testCase.getPlanId(), list);
|
||||
} else {
|
||||
list.add(testCase);
|
||||
}
|
||||
});
|
||||
testPlans.forEach(testPlan -> {
|
||||
List<TestPlanCaseDTO> testCases = testCaseMap.get(testPlan.getId());
|
||||
testPlan.setTested(0);
|
||||
testPlan.setPassed(0);
|
||||
testPlan.setTotal(0);
|
||||
if (testCases != null) {
|
||||
testPlan.setTotal(testCases.size());
|
||||
testCases.forEach(testCase -> {
|
||||
if (!StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Prepare.name())
|
||||
&& !StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Underway.name())) {
|
||||
testPlan.setTested(testPlan.getTested() + 1);
|
||||
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) {
|
||||
testPlan.setPassed(testPlan.getPassed() + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
testPlan.setPassRate(MathUtils.getPercentWithDecimal(testPlan.getTested() == 0 ? 0 : testPlan.getPassed() * 1.0 / testPlan.getTested()));
|
||||
testPlan.setTestRate(MathUtils.getPercentWithDecimal(testPlan.getTotal() == 0 ? 0 : testPlan.getTested() * 1.0 / testPlan.getTotal()));
|
||||
});
|
||||
}
|
||||
|
||||
public List<TestPlanDTOWithMetric> listTestPlan(QueryTestPlanRequest request) {
|
||||
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
||||
return extTestPlanMapper.list(request);
|
||||
List<TestPlanDTOWithMetric> testPlans = extTestPlanMapper.list(request);
|
||||
calcTestPlanRate(testPlans);
|
||||
return testPlans;
|
||||
}
|
||||
|
||||
public List<TestPlanDTO> listTestPlanByProject(QueryTestPlanRequest request) {
|
||||
|
@ -359,49 +398,12 @@ public class TestPlanService {
|
|||
|
||||
public List<TestPlanDTOWithMetric> listRelateAllPlan() {
|
||||
SessionUser user = SessionUtils.getUser();
|
||||
|
||||
QueryTestPlanRequest request = new QueryTestPlanRequest();
|
||||
request.setPrincipal(user.getId());
|
||||
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
|
||||
request.setPlanIds(extTestPlanTestCaseMapper.findRelateTestPlanId(user.getId(), SessionUtils.getCurrentWorkspaceId()));
|
||||
|
||||
List<String> projectIds = extProjectMapper.getProjectIdByWorkspaceId(SessionUtils.getCurrentOrganizationId());
|
||||
|
||||
List<TestPlanDTOWithMetric> testPlans = extTestPlanMapper.listRelate(request);
|
||||
|
||||
Map<String, List<TestPlanCaseDTO>> testCaseMap = new HashMap<>();
|
||||
listTestCaseByProjectIds(projectIds).forEach(testCase -> {
|
||||
List<TestPlanCaseDTO> list = testCaseMap.get(testCase.getPlanId());
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
list.add(testCase);
|
||||
testCaseMap.put(testCase.getPlanId(), list);
|
||||
} else {
|
||||
list.add(testCase);
|
||||
}
|
||||
});
|
||||
|
||||
testPlans.forEach(testPlan -> {
|
||||
List<TestPlanCaseDTO> testCases = testCaseMap.get(testPlan.getId());
|
||||
testPlan.setTested(0);
|
||||
testPlan.setPassed(0);
|
||||
testPlan.setTotal(0);
|
||||
if (testCases != null) {
|
||||
testPlan.setTotal(testCases.size());
|
||||
testCases.forEach(testCase -> {
|
||||
if (!StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Prepare.name())
|
||||
&& !StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Underway.name())) {
|
||||
testPlan.setTested(testPlan.getTested() + 1);
|
||||
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) {
|
||||
testPlan.setPassed(testPlan.getPassed() + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
testPlan.setPassRate(MathUtils.getPercentWithDecimal(testPlan.getTested() == 0 ? 0 : testPlan.getPassed() * 1.0 / testPlan.getTested()));
|
||||
testPlan.setTestRate(MathUtils.getPercentWithDecimal(testPlan.getTotal() == 0 ? 0 : testPlan.getTested() * 1.0 / testPlan.getTotal()));
|
||||
});
|
||||
|
||||
calcTestPlanRate(testPlans);
|
||||
return testPlans;
|
||||
}
|
||||
|
||||
|
@ -411,10 +413,11 @@ public class TestPlanService {
|
|||
return testPlanTestCaseService.list(request);
|
||||
}
|
||||
|
||||
public List<TestPlanCaseDTO> listTestCaseByProjectIds(List<String> projectIds) {
|
||||
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
|
||||
request.setProjectIds(projectIds);
|
||||
return extTestPlanTestCaseMapper.list(request);
|
||||
private List<TestPlanCaseDTO> listTestCaseByProjectIds(List<String> projectIds) {
|
||||
if (CollectionUtils.isEmpty(projectIds)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return extTestPlanTestCaseMapper.listTestCaseByProjectIds(projectIds);
|
||||
}
|
||||
|
||||
public TestCaseReportMetricDTO getMetric(String planId) {
|
||||
|
@ -527,13 +530,14 @@ public class TestPlanService {
|
|||
for (Project project : projects) {
|
||||
stringBuilder.append(project.getName()).append("、");
|
||||
}
|
||||
projectName = stringBuilder.toString().substring(0, stringBuilder.length() - 1);
|
||||
projectName = stringBuilder.substring(0, stringBuilder.length() - 1);
|
||||
}
|
||||
|
||||
return projectName;
|
||||
}
|
||||
|
||||
private static String getTestPlanContext(AddTestPlanRequest testPlan, String type) {
|
||||
private String getTestPlanContext(AddTestPlanRequest testPlan, String type) {
|
||||
User user = userMapper.selectByPrimaryKey(testPlan.getCreator());
|
||||
Long startTime = testPlan.getPlannedStartTime();
|
||||
Long endTime = testPlan.getPlannedEndTime();
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
@ -553,11 +557,11 @@ public class TestPlanService {
|
|||
}
|
||||
String context = "";
|
||||
if (StringUtils.equals(NoticeConstants.CREATE, type)) {
|
||||
context = "测试计划任务通知:" + testPlan.getCreator() + "创建的" + "'" + testPlan.getName() + "'" + "待开始,计划开始时间是" + start + "计划结束时间为" + end + "请跟进";
|
||||
context = "测试计划任务通知:" + user.getName() + "创建的" + "'" + testPlan.getName() + "'" + "待开始,计划开始时间是" + start + "计划结束时间为" + end + "请跟进";
|
||||
} else if (StringUtils.equals(NoticeConstants.UPDATE, type)) {
|
||||
context = "测试计划任务通知:" + testPlan.getCreator() + "创建的" + "'" + testPlan.getName() + "'" + "已完成,计划开始时间是" + start + "计划结束时间为" + end + "已完成";
|
||||
context = "测试计划任务通知:" + user.getName() + "创建的" + "'" + testPlan.getName() + "'" + "已完成,计划开始时间是" + start + "计划结束时间为" + end + "已完成";
|
||||
} else if (StringUtils.equals(NoticeConstants.DELETE, type)) {
|
||||
context = "测试计划任务通知:" + testPlan.getCreator() + "创建的" + "'" + testPlan.getName() + "'" + "计划开始时间是" + start + "计划结束时间为" + end + "已删除";
|
||||
context = "测试计划任务通知:" + user.getName() + "创建的" + "'" + testPlan.getName() + "'" + "计划开始时间是" + start + "计划结束时间为" + end + "已删除";
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package io.metersphere.websocket;
|
||||
|
||||
import io.metersphere.base.domain.LoadTestReport;
|
||||
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
|
||||
import io.metersphere.commons.constants.PerformanceTestStatus;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.performance.service.PerformanceTestService;
|
||||
import io.metersphere.performance.service.ReportService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -18,12 +19,18 @@ import java.io.IOException;
|
|||
public class ReportWebSocket {
|
||||
|
||||
private static ReportService reportService;
|
||||
private static PerformanceTestService performanceTestService;
|
||||
|
||||
@Resource
|
||||
public void setReportService(ReportService reportService) {
|
||||
ReportWebSocket.reportService = reportService;
|
||||
}
|
||||
|
||||
@Resource
|
||||
public void setPerformanceTestService(PerformanceTestService performanceTestService) {
|
||||
ReportWebSocket.performanceTestService = performanceTestService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启连接的操作
|
||||
*/
|
||||
|
@ -78,12 +85,19 @@ public class ReportWebSocket {
|
|||
public void run() {
|
||||
while (stopMe) {
|
||||
try {
|
||||
LoadTestReport report = reportService.getReport(reportId);
|
||||
if (report == null || StringUtils.equalsAny(report.getStatus(), PerformanceTestStatus.Completed.name(), PerformanceTestStatus.Error.name())) {
|
||||
LoadTestReportWithBLOBs report = reportService.getReport(reportId);
|
||||
if (report == null || StringUtils.equalsAny(report.getStatus(), PerformanceTestStatus.Completed.name())) {
|
||||
this.stopMe();
|
||||
session.close();
|
||||
break;
|
||||
}
|
||||
if (StringUtils.equals(report.getStatus(), PerformanceTestStatus.Error.name())) {
|
||||
this.stopMe();
|
||||
session.getBasicRemote().sendText("Error: " + report.getDescription());
|
||||
performanceTestService.stopErrorTest(reportId);
|
||||
session.close();
|
||||
break;
|
||||
}
|
||||
if (!session.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit ee74568be0beba46da19616f5832e83f9164c688
|
||||
Subproject commit 24047fea950a74f7848a9fdaa857a22b884c4ce2
|
|
@ -1,2 +1,2 @@
|
|||
alter table message_task
|
||||
add create_time bigint(13) null;
|
||||
ALTER TABLE message_task
|
||||
ADD create_time bigint(13) DEFAULT 0;
|
|
@ -1,3 +0,0 @@
|
|||
alter table message_task
|
||||
alter column create_time set default 0;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
INSERT INTO system_parameter (param_key, param_value, type, sort)
|
||||
VALUES ('ui.loginImage', NULL, 'file', 3);
|
||||
INSERT INTO system_parameter (param_key, param_value, type, sort)
|
||||
VALUES ('ui.loginLogo', NULL, 'file', 2);
|
||||
INSERT INTO system_parameter (param_key, param_value, type, sort)
|
||||
VALUES ('ui.loginTitle', '', 'text', 4);
|
||||
INSERT INTO system_parameter (param_key, param_value, type, sort)
|
||||
VALUES ('ui.logo', NULL, 'file', 1);
|
||||
INSERT INTO system_parameter (param_key, param_value, type, sort)
|
||||
VALUES ('ui.title', '', 'text', 5);
|
|
@ -57,7 +57,8 @@ workspace_not_exists=Workspace is not exists
|
|||
test_resource_pool_id_is_null=Test Resource Pool ID cannot be null
|
||||
test_resource_pool_name_is_null=Test Resource Pool name cannot be null
|
||||
test_resource_pool_name_already_exists=The test resource pool name already exists
|
||||
test_resource_pool_is_use=The test resource pool is in use and cannot be deleted
|
||||
load_test=Load Test
|
||||
test_resource_pool_is_use=This resource pool is in use and cannot be deleted
|
||||
#project
|
||||
project_name_is_null=Project name cannot be null
|
||||
project_name_already_exists=The project name already exists
|
||||
|
|
|
@ -57,7 +57,8 @@ workspace_not_exists=工作空间不存在
|
|||
test_resource_pool_id_is_null=资源池ID不能为空
|
||||
test_resource_pool_name_is_null=资源池名称不能为空
|
||||
test_resource_pool_name_already_exists=资源池名称已存在
|
||||
test_resource_pool_is_use=资源池正在使用中,无法删除
|
||||
load_test=性能测试
|
||||
test_resource_pool_is_use=正在使用此资源池,无法删除
|
||||
#project
|
||||
project_name_is_null=项目名称不能为空
|
||||
project_name_already_exists=项目名称已存在
|
||||
|
|
|
@ -57,7 +57,8 @@ workspace_not_exists=工作空間不存在
|
|||
test_resource_pool_id_is_null=資源池ID不能為空
|
||||
test_resource_pool_name_is_null=資源池名稱不能為空
|
||||
test_resource_pool_name_already_exists=資源池名稱已存在
|
||||
test_resource_pool_is_use=資源池正在使用中,無法刪除
|
||||
load_test=性能測試
|
||||
test_resource_pool_is_use=正在使用此資源池,無法刪除
|
||||
#project
|
||||
project_name_is_null=項目名稱不能為空
|
||||
project_name_already_exists=項目名稱已存在
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
${testCaseName}<br/>
|
||||
添加评论:${description}<br/>
|
||||
点击下面链接进入用例评审页面</p>
|
||||
<a href="${url}/#/track/review/view">${url}/#/track/review/view</a>
|
||||
<a href="${url}/#/track/review/view/${id}">${url}/#/track/review/view/${id}</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1215,7 +1215,7 @@ view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundar
|
|||
# All entries will be added to the class path of the system class loader
|
||||
# and also to the path of the JMeter internal loader.
|
||||
# Paths with spaces may cause problems for the JVM
|
||||
#user.classpath=../classes;../lib
|
||||
user.classpath=/test/
|
||||
|
||||
# List of directories (separated by ;) that JMeter will search for utility
|
||||
# and plugin dependency classes.
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
</el-row>
|
||||
<el-row id="header-top" type="flex" justify="space-between" align="middle">
|
||||
<el-col :span="12">
|
||||
<a class="logo"/>
|
||||
<img v-if="logoId" :src="'/display/file/' + logoId" style="width: 156px;height: 37px;" alt="">
|
||||
<a v-else class="logo"/>
|
||||
<ms-top-menus/>
|
||||
</el-col>
|
||||
|
||||
|
@ -24,107 +25,113 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsTopMenus from "./components/common/head/HeaderTopMenus";
|
||||
import MsView from "./components/common/router/View";
|
||||
import MsUser from "./components/common/head/HeaderUser";
|
||||
import MsHeaderOrgWs from "./components/common/head/HeaderOrgWs";
|
||||
import MsLanguageSwitch from "./components/common/head/LanguageSwitch";
|
||||
import {saveLocalStorage} from "../common/js/utils";
|
||||
import MsTopMenus from "./components/common/head/HeaderTopMenus";
|
||||
import MsView from "./components/common/router/View";
|
||||
import MsUser from "./components/common/head/HeaderUser";
|
||||
import MsHeaderOrgWs from "./components/common/head/HeaderOrgWs";
|
||||
import MsLanguageSwitch from "./components/common/head/LanguageSwitch";
|
||||
import {saveLocalStorage} from "@/common/js/utils";
|
||||
|
||||
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
|
||||
const header = requireComponent.keys().length > 0 ? requireComponent("./license/LicenseMessage.vue") : {};
|
||||
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
|
||||
const header = requireComponent.keys().length > 0 ? requireComponent("./license/LicenseMessage.vue") : {};
|
||||
const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {};
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
licenseHeader: null,
|
||||
auth: false,
|
||||
header: {},
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$get("/isLogin").then(response => {
|
||||
if (response.data.success) {
|
||||
this.$setLang(response.data.data.language);
|
||||
saveLocalStorage(response.data);
|
||||
this.auth = true;
|
||||
// 是否显示校验信息
|
||||
if (header.default !== undefined) {
|
||||
this.licenseHeader = "LicenseMessage";
|
||||
}
|
||||
} else {
|
||||
window.location.href = "/login"
|
||||
}
|
||||
}).catch(() => {
|
||||
window.location.href = "/login"
|
||||
});
|
||||
},
|
||||
components: {
|
||||
MsLanguageSwitch,
|
||||
MsUser,
|
||||
MsView,
|
||||
MsTopMenus,
|
||||
MsHeaderOrgWs,
|
||||
"LicenseMessage": header.default
|
||||
export default {
|
||||
name: 'app',
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
licenseHeader: null,
|
||||
auth: false,
|
||||
header: {},
|
||||
logoId: '_blank',
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$get("/isLogin").then(response => {
|
||||
if (response.data.success) {
|
||||
this.$setLang(response.data.data.language);
|
||||
saveLocalStorage(response.data);
|
||||
this.auth = true;
|
||||
// 是否显示校验信息
|
||||
if (header.default !== undefined) {
|
||||
this.licenseHeader = "LicenseMessage";
|
||||
}
|
||||
// 是否显示校验信息
|
||||
if (display.default !== undefined) {
|
||||
display.default.valid(this);
|
||||
}
|
||||
} else {
|
||||
window.location.href = "/login"
|
||||
}
|
||||
}).catch(() => {
|
||||
window.location.href = "/login"
|
||||
});
|
||||
},
|
||||
components: {
|
||||
MsLanguageSwitch,
|
||||
MsUser,
|
||||
MsView,
|
||||
MsTopMenus,
|
||||
MsHeaderOrgWs,
|
||||
"LicenseMessage": header.default
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
#header-top {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
background-color: rgb(44, 42, 72);
|
||||
color: rgb(245, 245, 245);
|
||||
font-size: 14px;
|
||||
}
|
||||
#header-top {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
background-color: rgb(44, 42, 72);
|
||||
color: rgb(245, 245, 245);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 156px;
|
||||
margin-bottom: 0;
|
||||
border: 0;
|
||||
margin-right: 20px;
|
||||
display: inline-block;
|
||||
line-height: 37px;
|
||||
background-size: 156px 30px;
|
||||
box-sizing: border-box;
|
||||
height: 37px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% center;
|
||||
background-image: url("../assets/logo-light-MeterSphere.svg");
|
||||
}
|
||||
.logo {
|
||||
width: 156px;
|
||||
margin-bottom: 0;
|
||||
border: 0;
|
||||
margin-right: 20px;
|
||||
display: inline-block;
|
||||
line-height: 37px;
|
||||
background-size: 156px 30px;
|
||||
box-sizing: border-box;
|
||||
height: 37px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% center;
|
||||
background-image: url("../assets/logo-light-MeterSphere.svg");
|
||||
}
|
||||
|
||||
.menus > * {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
max-width: 180px;
|
||||
white-space: pre;
|
||||
cursor: pointer;
|
||||
line-height: 40px;
|
||||
}
|
||||
.menus > * {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
max-width: 180px;
|
||||
white-space: pre;
|
||||
cursor: pointer;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.header-top-menus {
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
}
|
||||
.header-top-menus {
|
||||
display: inline-block;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.menus > a {
|
||||
padding-right: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.menus > a {
|
||||
padding-right: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
float: right;
|
||||
}
|
||||
.align-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.license-head {
|
||||
height: 30px;
|
||||
background: #BA331B;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
}
|
||||
.license-head {
|
||||
height: 30px;
|
||||
background: #BA331B;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,15 +4,25 @@
|
|||
<el-row :gutter="10" type="flex" align="middle" class="info">
|
||||
<el-col :span="4">
|
||||
<div class="method">
|
||||
{{request.method}}
|
||||
{{ request.method }}
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<div class="name">{{request.name}}</div>
|
||||
<el-col :span="15">
|
||||
<div class="name">{{ request.name }}</div>
|
||||
<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-col>
|
||||
<el-col :span="5">
|
||||
<div class="success">
|
||||
<el-tag size="mini" type="success" v-if="request.success">
|
||||
{{ $t('api_report.success') }}
|
||||
</el-tag>
|
||||
<el-tag size="mini" type="danger" v-else>
|
||||
{{ $t('api_report.fail') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
|
||||
<el-col>
|
||||
<el-input :disabled="isReadOnly" v-model="xPath2.expression" maxlength="200" size="small" show-word-limit
|
||||
:placeholder="$t('api_test.request.extract.xpath_expression')"/>
|
||||
</el-col>
|
||||
<el-col class="assertion-btn">
|
||||
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove" v-if="edit"/>
|
||||
<el-button :disabled="isReadOnly" type="primary" size="small" @click="add" v-else>
|
||||
{{ $t('api_test.request.assertions.add') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {XPath2} from "../../model/ScenarioModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertionXPath2",
|
||||
|
||||
props: {
|
||||
xPath2: {
|
||||
type: XPath2,
|
||||
default: () => {
|
||||
return new XPath2();
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
index: Number,
|
||||
list: Array,
|
||||
callback: Function,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
add: function () {
|
||||
this.list.push(this.getXPath2());
|
||||
this.callback();
|
||||
},
|
||||
remove: function () {
|
||||
this.list.splice(this.index, 1);
|
||||
},
|
||||
getXPath2() {
|
||||
return new XPath2(this.xPath2);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.assertion-select {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.assertion-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.assertion-btn {
|
||||
text-align: center;
|
||||
width: 60px;
|
||||
}
|
||||
</style>
|
|
@ -8,6 +8,7 @@
|
|||
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
|
||||
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
|
||||
<el-option :label="'JSONPath'" :value="options.JSON_PATH"/>
|
||||
<el-option :label="'XPath'" :value="options.XPATH2"/>
|
||||
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.DURATION"/>
|
||||
<el-option :label="$t('api_test.request.assertions.jsr223')" :value="options.JSR223"/>
|
||||
</el-select>
|
||||
|
@ -16,6 +17,7 @@
|
|||
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
|
||||
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
|
||||
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath" v-if="type === options.JSON_PATH" :callback="after"/>
|
||||
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2" v-if="type === options.XPATH2" :callback="after"/>
|
||||
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
|
||||
v-if="type === options.DURATION" :callback="after"/>
|
||||
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223" :callback="after"/>
|
||||
|
@ -52,11 +54,13 @@
|
|||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
||||
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
|
||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertions",
|
||||
|
||||
components: {
|
||||
MsApiAssertionXPath2,
|
||||
MsApiAssertionJsr223,
|
||||
MsApiJsonpathSuggestList,
|
||||
MsApiAssertionJsonPath,
|
||||
|
|
|
@ -20,6 +20,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assertion-item-editing x_path" v-if="assertions.xpath2.length > 0">
|
||||
<div>
|
||||
{{ 'XPath' }}
|
||||
</div>
|
||||
<div class="regex-item" v-for="(xPath, index) in assertions.xpath2" :key="index">
|
||||
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2"
|
||||
:x-path2="xPath" :edit="true" :index="index"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assertion-item-editing jsr223" v-if="assertions.jsr223.length > 0">
|
||||
<div>
|
||||
{{ $t("api_test.request.assertions.script") }}
|
||||
|
@ -47,11 +57,14 @@ import MsApiAssertionDuration from "./ApiAssertionDuration";
|
|||
import {Assertions} from "../../model/ScenarioModel";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertionsEdit",
|
||||
|
||||
components: {MsApiAssertionJsr223, MsApiAssertionJsonPath, MsApiAssertionDuration, MsApiAssertionRegex},
|
||||
components: {
|
||||
MsApiAssertionXPath2,
|
||||
MsApiAssertionJsr223, MsApiAssertionJsonPath, MsApiAssertionDuration, MsApiAssertionRegex},
|
||||
|
||||
props: {
|
||||
assertions: Assertions,
|
||||
|
@ -92,6 +105,10 @@ export default {
|
|||
border-left: 2px solid #1FDD02;
|
||||
}
|
||||
|
||||
.assertion-item-editing.x_path {
|
||||
border-left: 2px solid #fca130;
|
||||
}
|
||||
|
||||
.regex-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
|
|
@ -162,9 +162,11 @@ export default {
|
|||
break;
|
||||
}
|
||||
},
|
||||
getColor(enable, method) {
|
||||
if (enable) {
|
||||
getColor(requestEnable, method) {
|
||||
if (this.scenario.enable && requestEnable) {
|
||||
return this.methodColorMap.get(method);
|
||||
} else {
|
||||
return '#909399';
|
||||
}
|
||||
},
|
||||
select(request) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
export default {
|
||||
name: "MsApiRequestForm",
|
||||
components: {MsApiSqlRequestForm, MsRequestResultTail, MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
|
||||
components: {MsApiSqlRequestForm, MsRequestResultTail, MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm, MsApiTcpRequestForm},
|
||||
props: {
|
||||
scenario: Scenario,
|
||||
request: Request,
|
||||
|
|
|
@ -439,6 +439,16 @@ export class JSONPathAssertion extends DefaultTestElement {
|
|||
}
|
||||
}
|
||||
|
||||
export class XPath2Assertion extends DefaultTestElement {
|
||||
constructor(testName, xPath) {
|
||||
super('XPath2Assertion', 'XPath2AssertionGui', 'XPath2Assertion', testName);
|
||||
this.xPath = xPath || {};
|
||||
this.stringProp('XPath.xpath', this.xPath.expression);
|
||||
this.stringProp('XPath.namespace');
|
||||
this.boolProp('XPath.negate', false);
|
||||
}
|
||||
}
|
||||
|
||||
export class ResponseCodeAssertion extends ResponseAssertion {
|
||||
constructor(testName, type, value, assumeSuccess, message) {
|
||||
let assertion = {
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
ThreadGroup,
|
||||
XPath2Extractor,
|
||||
IfController as JMXIfController,
|
||||
ConstantTimer as JMXConstantTimer, TCPSampler, JSR223Assertion,
|
||||
ConstantTimer as JMXConstantTimer, TCPSampler, JSR223Assertion, XPath2Assertion,
|
||||
} from "./JMX";
|
||||
import Mock from "mockjs";
|
||||
import {funcFilters} from "@/common/js/func-filter";
|
||||
|
@ -96,6 +96,7 @@ export const ASSERTION_TYPE = {
|
|||
JSON_PATH: "JSON",
|
||||
DURATION: "Duration",
|
||||
JSR223: "JSR223",
|
||||
XPATH2: "XPath2",
|
||||
}
|
||||
|
||||
export const ASSERTION_REGEX_SUBJECT = {
|
||||
|
@ -741,10 +742,11 @@ export class Assertions extends BaseConfig {
|
|||
this.regex = [];
|
||||
this.jsonPath = [];
|
||||
this.jsr223 = [];
|
||||
this.xpath2 = [];
|
||||
this.duration = undefined;
|
||||
|
||||
this.set(options);
|
||||
this.sets({text: Text, regex: Regex, jsonPath: JSONPath, jsr223: AssertionJSR223}, options);
|
||||
this.sets({text: Text, regex: Regex, jsonPath: JSONPath, jsr223: AssertionJSR223, xpath2: XPath2}, options);
|
||||
}
|
||||
|
||||
initOptions(options) {
|
||||
|
@ -826,6 +828,23 @@ export class JSONPath extends AssertionType {
|
|||
}
|
||||
}
|
||||
|
||||
export class XPath2 extends AssertionType {
|
||||
constructor(options) {
|
||||
super(ASSERTION_TYPE.XPATH2);
|
||||
this.expression = undefined;
|
||||
this.description = undefined;
|
||||
this.set(options);
|
||||
}
|
||||
|
||||
// setJSONPathDescription() {
|
||||
// this.description = this.expression + " expect: " + (this.expect ? this.expect : '');
|
||||
// }
|
||||
|
||||
isValid() {
|
||||
return !!this.expression;
|
||||
}
|
||||
}
|
||||
|
||||
export class Duration extends AssertionType {
|
||||
constructor(options) {
|
||||
super(ASSERTION_TYPE.DURATION);
|
||||
|
@ -1001,7 +1020,8 @@ class JMXHttpRequest {
|
|||
this.domain = environment.config.httpConfig.domain;
|
||||
this.port = environment.config.httpConfig.port;
|
||||
this.protocol = environment.config.httpConfig.protocol;
|
||||
let envPath = environment.config.httpConfig.protocol + "://" + environment.config.httpConfig.socket;
|
||||
let url = new URL(environment.config.httpConfig.protocol + "://" + environment.config.httpConfig.socket);
|
||||
let envPath = url.pathname === '/' ? '' : url.pathname;
|
||||
this.path = this.getPostQueryParameters(request, decodeURIComponent(envPath + (request.path ? request.path : '')));
|
||||
}
|
||||
this.connectTimeout = request.connectTimeout;
|
||||
|
@ -1139,17 +1159,18 @@ class JMXGenerator {
|
|||
sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig));
|
||||
} else if (request instanceof HttpRequest) {
|
||||
sampler = new HTTPSamplerProxy(request.name || "", new JMXHttpRequest(request, scenario.environment));
|
||||
this.addRequestHeader(sampler, request);
|
||||
this.addRequestHeader(sampler, request, scenario);
|
||||
this.addRequestArguments(sampler, request);
|
||||
this.addRequestBody(sampler, request, testId);
|
||||
} else if (request instanceof SqlRequest) {
|
||||
request.dataSource = scenario.databaseConfigMap.get(request.dataSource);
|
||||
sampler = new JDBCSampler(request.name || "", request);
|
||||
this.addRequestVariables(sampler, request);
|
||||
} else if (request instanceof TCPRequest) {
|
||||
sampler = new TCPSampler(request.name || "", new JMXTCPRequest(request, scenario));
|
||||
}
|
||||
|
||||
this.addRequestVariables(sampler, request, scenario);
|
||||
|
||||
this.addDNSCacheManager(sampler, scenario.environment, request.useEnvironment);
|
||||
|
||||
this.addRequestExtractor(sampler, request);
|
||||
|
@ -1177,29 +1198,31 @@ class JMXGenerator {
|
|||
}
|
||||
|
||||
addEnvironments(environments, target) {
|
||||
let keys = new Set();
|
||||
let targetMap = new Map();
|
||||
target.forEach(item => {
|
||||
keys.add(item.name);
|
||||
if (item.name) {
|
||||
targetMap.set(item.name, item.enable);
|
||||
}
|
||||
});
|
||||
let envArray = environments;
|
||||
if (!(envArray instanceof Array)) {
|
||||
envArray = JSON.parse(environments);
|
||||
}
|
||||
envArray.forEach(item => {
|
||||
if (item.name && !keys.has(item.name)) {
|
||||
let targetItem = targetMap.get(item.name);
|
||||
let hasItem = undefined;
|
||||
if (targetItem) {
|
||||
hasItem = (targetItem.enable === false ? false : true);
|
||||
} else {
|
||||
hasItem = false;
|
||||
}
|
||||
if (item.enable != false && item.name && !hasItem) {
|
||||
target.push(new KeyValue({name: item.name, value: item.value}));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addScenarioVariables(threadGroup, scenario) {
|
||||
if (scenario.environment) {
|
||||
let config = scenario.environment.config;
|
||||
if (!(scenario.environment.config instanceof Object)) {
|
||||
config = JSON.parse(scenario.environment.config);
|
||||
}
|
||||
this.addEnvironments(config.commonConfig.variables, scenario.variables)
|
||||
}
|
||||
let args = this.filterKV(scenario.variables);
|
||||
if (args.length > 0) {
|
||||
let name = scenario.name + " Variables";
|
||||
|
@ -1207,11 +1230,23 @@ class JMXGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
addRequestVariables(httpSamplerProxy, request) {
|
||||
addRequestVariables(httpSamplerProxy, request, scenario) {
|
||||
if (request.useEnvironment && scenario.environment) {
|
||||
let config = scenario.environment.config;
|
||||
if (!(scenario.environment.config instanceof Object)) {
|
||||
config = JSON.parse(scenario.environment.config);
|
||||
}
|
||||
if (!request.variables) {
|
||||
request.variables = [];
|
||||
}
|
||||
this.addEnvironments(config.commonConfig.variables, request.variables)
|
||||
}
|
||||
let name = request.name + " Variables";
|
||||
let variables = this.filterKV(request.variables);
|
||||
if (variables && variables.length > 0) {
|
||||
httpSamplerProxy.put(new Arguments(name, variables));
|
||||
if (request.variables) {
|
||||
let variables = this.filterKV(request.variables);
|
||||
if (variables && variables.length > 0) {
|
||||
httpSamplerProxy.put(new Arguments(name, variables));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1272,13 +1307,6 @@ class JMXGenerator {
|
|||
}
|
||||
|
||||
addScenarioHeaders(threadGroup, scenario) {
|
||||
if (scenario.environment) {
|
||||
let config = scenario.environment.config;
|
||||
if (!(scenario.environment.config instanceof Object)) {
|
||||
config = JSON.parse(scenario.environment.config);
|
||||
}
|
||||
this.addEnvironments(config.httpConfig.headers, scenario.headers)
|
||||
}
|
||||
let headers = this.filterKV(scenario.headers);
|
||||
if (headers.length > 0) {
|
||||
let name = scenario.name + " Headers";
|
||||
|
@ -1286,7 +1314,17 @@ class JMXGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
addRequestHeader(httpSamplerProxy, request) {
|
||||
addRequestHeader(httpSamplerProxy, request, scenario) {
|
||||
if (request.useEnvironment && scenario.environment) {
|
||||
let config = scenario.environment.config;
|
||||
if (!(scenario.environment.config instanceof Object)) {
|
||||
config = JSON.parse(scenario.environment.config);
|
||||
}
|
||||
this.addEnvironments(config.httpConfig.headers, request.headers);
|
||||
if (request.doMultipartPost) {
|
||||
this.removeContentType(request);
|
||||
}
|
||||
}
|
||||
let name = request.name + " Headers";
|
||||
this.addBodyFormat(request);
|
||||
let headers = this.filterKV(request.headers);
|
||||
|
@ -1363,15 +1401,29 @@ class JMXGenerator {
|
|||
}
|
||||
|
||||
addContentType(request, type) {
|
||||
let hasContentType = false;
|
||||
for (let index in request.headers) {
|
||||
if (request.headers.hasOwnProperty(index)) {
|
||||
if (request.headers[index].name === 'Content-Type') {
|
||||
if (request.headers[index].name === 'Content-Type' && request.headers[index].enable != false) {
|
||||
hasContentType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasContentType) {
|
||||
request.headers.push(new KeyValue({name: 'Content-Type', value: type}));
|
||||
}
|
||||
}
|
||||
|
||||
removeContentType(request) {
|
||||
for (let index in request.headers) {
|
||||
if (request.headers.hasOwnProperty(index)) {
|
||||
if (request.headers[index].name === 'Content-Type' && request.headers[index].enable != false) {
|
||||
request.headers.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
request.headers.push(new KeyValue({name: 'Content-Type', value: type}));
|
||||
}
|
||||
|
||||
addRequestArguments(httpSamplerProxy, request) {
|
||||
|
@ -1387,8 +1439,10 @@ class JMXGenerator {
|
|||
body = this.filterKV(request.body.kvs);
|
||||
this.addRequestBodyFile(httpSamplerProxy, request, testId);
|
||||
} else {
|
||||
httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
|
||||
body.push({name: '', value: request.body.raw, encode: false, enable: true});
|
||||
if (request.body.raw) {
|
||||
httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
|
||||
body.push({name: '', value: request.body.raw, encode: false, enable: true});
|
||||
}
|
||||
}
|
||||
|
||||
if (request.method !== 'GET') {
|
||||
|
@ -1427,6 +1481,12 @@ class JMXGenerator {
|
|||
})
|
||||
}
|
||||
|
||||
if (assertions.xpath2.length > 0) {
|
||||
assertions.xpath2.filter(this.filter).forEach(item => {
|
||||
httpSamplerProxy.put(this.getXpathAssertion(item));
|
||||
})
|
||||
}
|
||||
|
||||
if (assertions.jsr223.length > 0) {
|
||||
assertions.jsr223.filter(this.filter).forEach(item => {
|
||||
httpSamplerProxy.put(this.getJSR223Assertion(item));
|
||||
|
@ -1449,6 +1509,11 @@ class JMXGenerator {
|
|||
return new JSR223Assertion(name, item);
|
||||
}
|
||||
|
||||
getXpathAssertion(item) {
|
||||
let name = item.expression;
|
||||
return new XPath2Assertion(name, item);
|
||||
}
|
||||
|
||||
getResponseAssertion(regex) {
|
||||
let name = regex.description;
|
||||
let type = JMX_ASSERTION_CONDITION.CONTAINS; // 固定用Match,自己写正则
|
||||
|
|
|
@ -23,11 +23,11 @@
|
|||
{{ $t('report.test_execute_again') }}
|
||||
</el-button>
|
||||
<el-button :disabled="isReadOnly" type="info" plain size="mini" @click="handleExport(reportName)">
|
||||
{{$t('test_track.plan_view.export_report')}}
|
||||
{{ $t('test_track.plan_view.export_report') }}
|
||||
</el-button>
|
||||
|
||||
<!--<el-button :disabled="isReadOnly" type="warning" plain size="mini">-->
|
||||
<!--{{$t('report.compare')}}-->
|
||||
<!--{{$t('report.compare')}}-->
|
||||
<!--</el-button>-->
|
||||
</el-row>
|
||||
</el-col>
|
||||
|
@ -65,7 +65,8 @@
|
|||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<ms-performance-report-export :title="reportName" id="performanceReportExport" v-show="reportExportVisible" :report="report"/>
|
||||
<ms-performance-report-export :title="reportName" id="performanceReportExport" v-show="reportExportVisible"
|
||||
:report="report"/>
|
||||
|
||||
</el-card>
|
||||
<el-dialog :title="$t('report.test_stop_now_confirm')" :visible.sync="dialogFormVisible" width="30%">
|
||||
|
@ -91,8 +92,7 @@ import MsPerformancePressureConfig from "./components/PerformancePressureConfig"
|
|||
import MsContainer from "../../common/components/MsContainer";
|
||||
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||
|
||||
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
|
||||
import {exportPdf} from "../../../../common/js/utils";
|
||||
import {checkoutTestManagerOrTestUser, exportPdf} from "@/common/js/utils";
|
||||
import html2canvas from 'html2canvas';
|
||||
import MsPerformanceReportExport from "./PerformanceReportExport";
|
||||
|
||||
|
@ -188,7 +188,8 @@ export default {
|
|||
checkReportStatus(status) {
|
||||
switch (status) {
|
||||
case 'Error':
|
||||
this.$warning(this.$t('report.generation_error'));
|
||||
// this.$warning(this.$t('report.generation_error'));
|
||||
this.active = '4';
|
||||
break;
|
||||
case 'Starting':
|
||||
this.$alert(this.$t('report.start_status'));
|
||||
|
@ -240,6 +241,11 @@ export default {
|
|||
},
|
||||
onMessage(e) {
|
||||
this.$set(this.report, "refresh", e.data); // 触发刷新
|
||||
if (e.data.startsWith('Error')) {
|
||||
this.$set(this.report, "status", 'Error');
|
||||
this.$warning(e.data);
|
||||
return;
|
||||
}
|
||||
this.$set(this.report, "status", 'Running');
|
||||
this.status = 'Running';
|
||||
this.initReportTimeInfo();
|
||||
|
@ -264,7 +270,7 @@ export default {
|
|||
setTimeout(() => {
|
||||
html2canvas(document.getElementById('performanceReportExport'), {
|
||||
scale: 2
|
||||
}).then(function(canvas) {
|
||||
}).then(function (canvas) {
|
||||
exportPdf(name, [canvas]);
|
||||
reset();
|
||||
});
|
||||
|
@ -332,18 +338,18 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
|
||||
.ms-report-view-btns {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.ms-report-view-btns {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.ms-report-time-desc {
|
||||
text-align: left;
|
||||
display: block;
|
||||
color: #5C7878;
|
||||
}
|
||||
.ms-report-time-desc {
|
||||
text-align: left;
|
||||
display: block;
|
||||
color: #5C7878;
|
||||
}
|
||||
|
||||
.report-export .el-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.report-export .el-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -180,10 +180,7 @@ export default {
|
|||
this.multipleSelection = val;
|
||||
},
|
||||
handleEdit(report) {
|
||||
if (report.status === "Error") {
|
||||
this.$warning(this.$t('report.generation_error'));
|
||||
return false
|
||||
} else if (report.status === "Starting") {
|
||||
if (report.status === "Starting") {
|
||||
this.$info(this.$t('report.being_generated'))
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ export default {
|
|||
this.avgResponseTime = '0';
|
||||
this.responseTime90 = '0';
|
||||
this.avgBandwidth = '0';
|
||||
this.$warning(this.$t('report.generation_error'));
|
||||
// this.$warning(this.$t('report.generation_error'));
|
||||
})
|
||||
this.getLoadChart();
|
||||
this.getResChart();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-loading="result.loading">
|
||||
<el-upload
|
||||
accept=".jmx,.csv"
|
||||
accept=".jmx,.csv,.jar"
|
||||
drag
|
||||
action=""
|
||||
:limit="fileNumLimit"
|
||||
|
@ -77,7 +77,7 @@ export default {
|
|||
fileList: [],
|
||||
tableData: [],
|
||||
uploadList: [],
|
||||
fileNumLimit: 5,
|
||||
fileNumLimit: 10,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
@ -197,7 +197,7 @@ export default {
|
|||
return this.fileList;// 表示修改了已经上传的文件列表
|
||||
},
|
||||
validConfig() {
|
||||
let newJmxNum = 0, oldJmxNum = 0, newCsvNum = 0, oldCsvNum = 0;
|
||||
let newJmxNum = 0, oldJmxNum = 0, newCsvNum = 0, oldCsvNum = 0, newJarNum = 0, oldJarNum = 0;
|
||||
if (this.uploadList.length > 0) {
|
||||
this.uploadList.forEach(f => {
|
||||
if (f.name.toLowerCase().endsWith(".jmx")) {
|
||||
|
@ -206,6 +206,9 @@ export default {
|
|||
if (f.name.toLowerCase().endsWith(".csv")) {
|
||||
newCsvNum++;
|
||||
}
|
||||
if (f.name.toLowerCase().endsWith(".jar")) {
|
||||
newJarNum++;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.fileList.length > 0) {
|
||||
|
@ -216,9 +219,12 @@ export default {
|
|||
if (f.name.toLowerCase().endsWith(".csv")) {
|
||||
oldCsvNum++;
|
||||
}
|
||||
if (f.name.toLowerCase().endsWith(".jar")) {
|
||||
oldJarNum++;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (newCsvNum + oldCsvNum > this.fileNumLimit - 1) {
|
||||
if (newCsvNum + oldCsvNum + newJarNum + oldJarNum > this.fileNumLimit - 1) {
|
||||
this.handleExceed();
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -204,7 +204,11 @@ export default {
|
|||
})
|
||||
},
|
||||
removeRowTask(index, data) { //移除
|
||||
data.splice(index, 1)
|
||||
if (!data[index].identification) {
|
||||
data.splice(index, 1)
|
||||
} else {
|
||||
data[index].isSet = false
|
||||
}
|
||||
},
|
||||
deleteRowTask(index, data) { //删除
|
||||
this.result = this.$get("/notice/delete/message/" + data.identification, response => {
|
||||
|
|
|
@ -205,7 +205,12 @@ export default {
|
|||
})
|
||||
},
|
||||
removeRowTask(index, data) { //移除
|
||||
data.splice(index, 1)
|
||||
if (!data[index].identification) {
|
||||
data.splice(index, 1)
|
||||
} else {
|
||||
data[index].isSet = false
|
||||
}
|
||||
|
||||
},
|
||||
deleteRowTask(index, data) { //删除
|
||||
this.result = this.$get("/notice/delete/message/" + data.identification, response => {
|
||||
|
|
|
@ -196,7 +196,11 @@ export default {
|
|||
})
|
||||
},
|
||||
removeRowTask(index, data) { //移除
|
||||
data.splice(index, 1)
|
||||
if (!data[index].identification) {
|
||||
data.splice(index, 1)
|
||||
} else {
|
||||
data[index].isSet = false
|
||||
}
|
||||
},
|
||||
deleteRowTask(index, data) { //删除
|
||||
this.result = this.$get("/notice/delete/message/" + data.identification, response => {
|
||||
|
|
|
@ -210,7 +210,11 @@ export default {
|
|||
})
|
||||
},
|
||||
removeRowTask(index, data) { //移除
|
||||
data.splice(index, 1)
|
||||
if (!data[index].identification) {
|
||||
data.splice(index, 1)
|
||||
} else {
|
||||
data[index].isSet = false
|
||||
}
|
||||
},
|
||||
deleteRowTask(index, data) { //删除
|
||||
this.result = this.$get("/notice/delete/message/" + data.identification, response => {
|
||||
|
|
|
@ -211,7 +211,11 @@ export default {
|
|||
})
|
||||
},
|
||||
removeRowTask(index, data) { //移除
|
||||
data.splice(index, 1)
|
||||
if (!data[index].identification) {
|
||||
data.splice(index, 1)
|
||||
} else {
|
||||
data[index].isSet = false
|
||||
}
|
||||
},
|
||||
deleteRowTask(index, data) { //删除
|
||||
this.result = this.$get("/notice/delete/message/" + data.identification, response => {
|
||||
|
|
|
@ -32,7 +32,9 @@ export default {
|
|||
component: () => import('@/business/components/settings/system/SystemParameterSetting'),
|
||||
meta: {system: true, title: 'commons.system_parameter_setting'}
|
||||
},
|
||||
...requireContext.keys().map(key => requireContext(key).system),...requireContext.keys().map(key => requireContext(key).license),
|
||||
...requireContext.keys().map(key => requireContext(key).system),
|
||||
...requireContext.keys().map(key => requireContext(key).license),
|
||||
...requireContext.keys().map(key => requireContext(key).display),
|
||||
{
|
||||
path: 'organizationmember',
|
||||
component: () => import('@/business/components/settings/organization/OrganizationMember'),
|
||||
|
|
|
@ -295,7 +295,6 @@ export default {
|
|||
this.infoList = resources;
|
||||
},
|
||||
del(row) {
|
||||
window.console.log(row);
|
||||
this.$confirm(this.$t('test_resource_pool.delete_prompt'), this.$t('commons.prompt'), {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
cancelButtonText: this.$t('commons.cancel'),
|
||||
|
|
|
@ -368,8 +368,9 @@ export default {
|
|||
}
|
||||
],
|
||||
phone: [
|
||||
{required: true, message: this.$t('user.input_phone'), trigger: 'blur'},
|
||||
{
|
||||
required: false,
|
||||
required: true,
|
||||
pattern: '^1(3|4|5|7|8)\\d{9}$',
|
||||
message: this.$t('user.mobile_number_format_is_incorrect'),
|
||||
trigger: 'blur'
|
||||
|
|
|
@ -63,6 +63,15 @@
|
|||
<plan-stage-table-item :stage="scope.row.stage"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
:label="$t('test_track.home.test_rate')"
|
||||
min-width="100"
|
||||
show-overflow-tooltip>
|
||||
<template v-slot:default="scope">
|
||||
<el-progress :percentage="scope.row.testRate"></el-progress>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
:label="$t('test_track.plan.plan_project')"
|
||||
|
|
|
@ -286,12 +286,13 @@
|
|||
},
|
||||
getProject() {
|
||||
if (this.planId) {
|
||||
this.$post("/test/plan/project/", {planId: this.planId}, res => {
|
||||
this.result = this.$post("/test/plan/project/", {planId: this.planId}, res => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
this.projects = data;
|
||||
this.projectId = data[0].id;
|
||||
this.projectName = data[0].name;
|
||||
this.search();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -57,24 +57,24 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsMainContainer from "../../../../../common/components/MsMainContainer";
|
||||
import MsContainer from "../../../../../common/components/MsContainer";
|
||||
import LogDetails from "../../../../../performance/report/components/LogDetails";
|
||||
import ErrorLog from "../../../../../performance/report/components/ErrorLog";
|
||||
import RequestStatistics from "../../../../../performance/report/components/RequestStatistics";
|
||||
import TestOverview from "../../../../../performance/report/components/TestOverview";
|
||||
import MsMainContainer from "../../../../../common/components/MsMainContainer";
|
||||
import MsContainer from "../../../../../common/components/MsContainer";
|
||||
import LogDetails from "../../../../../performance/report/components/LogDetails";
|
||||
import ErrorLog from "../../../../../performance/report/components/ErrorLog";
|
||||
import RequestStatistics from "../../../../../performance/report/components/RequestStatistics";
|
||||
import TestOverview from "../../../../../performance/report/components/TestOverview";
|
||||
|
||||
export default {
|
||||
name: "PerformanceTestResult",
|
||||
components: {
|
||||
TestOverview,
|
||||
RequestStatistics,
|
||||
ErrorLog,
|
||||
LogDetails,
|
||||
MsContainer,
|
||||
MsMainContainer
|
||||
},
|
||||
data() {
|
||||
export default {
|
||||
name: "PerformanceTestResult",
|
||||
components: {
|
||||
TestOverview,
|
||||
RequestStatistics,
|
||||
ErrorLog,
|
||||
LogDetails,
|
||||
MsContainer,
|
||||
MsMainContainer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
active: '0',
|
||||
|
@ -145,7 +145,7 @@
|
|||
}
|
||||
switch (this.report.status) {
|
||||
case 'Error':
|
||||
this.$warning(this.$t('report.generation_error'));
|
||||
// this.$warning(this.$t('report.generation_error'));
|
||||
break;
|
||||
case 'Starting':
|
||||
this.$warning(this.$t('report.start_status'));
|
||||
|
|
|
@ -40,7 +40,8 @@ export default {
|
|||
components: {ReviewCommentItem},
|
||||
props: {
|
||||
caseId: String,
|
||||
comments: Array
|
||||
comments: Array,
|
||||
reviewId:String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -53,6 +54,7 @@ export default {
|
|||
let comment = {};
|
||||
comment.caseId = this.caseId;
|
||||
comment.description = this.textarea;
|
||||
comment.reviewId=this.reviewId;
|
||||
if (!this.textarea) {
|
||||
this.$warning(this.$t('test_track.comment.description_is_null'));
|
||||
return;
|
||||
|
|
|
@ -229,7 +229,7 @@
|
|||
<i class="el-icon-refresh" @click="getComments(testCase)"
|
||||
style="margin-left:10px;font-size: 14px; cursor: pointer"/>
|
||||
</template>
|
||||
<review-comment :comments="comments" :case-id="testCase.caseId" @getComments="getComments"/>
|
||||
<review-comment :comments="comments" :case-id="testCase.caseId" :review-id="testCase.reviewId" @getComments="getComments"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</div>
|
||||
|
|
|
@ -300,6 +300,7 @@ export default {
|
|||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
handleEdit(testCase, index) {
|
||||
console.log(testCase)
|
||||
this.isReadOnly = false;
|
||||
if (!checkoutTestManagerOrTestUser()) {
|
||||
this.isReadOnly = true;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit cc38137a69a0f20fadece9c0f9f50a9468c4ace9
|
||||
Subproject commit eb237fb6bfeba8d99e4db52450ae92f3cdd4ea33
|
|
@ -180,6 +180,14 @@ export default {
|
|||
invalid: 'invalid',
|
||||
expired: 'expired',
|
||||
},
|
||||
display: {
|
||||
title: 'Theme',
|
||||
logo: 'System LOGO',
|
||||
loginLogo: 'Picture on the right side of the login page',
|
||||
loginImage: 'Login page upper left corner LOGO',
|
||||
loginTitle: 'Login page prompt information',
|
||||
pageTitle: 'Page Title',
|
||||
},
|
||||
system_config: {
|
||||
base_config: 'Base Config',
|
||||
base: {
|
||||
|
@ -348,7 +356,7 @@ export default {
|
|||
test_execute_again: 'Test Execute Again',
|
||||
export: 'Export',
|
||||
compare: 'Compare',
|
||||
generation_error: 'Report generation error, cannot be viewed!',
|
||||
generation_error: 'Report generation error, unable to view, please check log details!',
|
||||
being_generated: 'Report is being generated...',
|
||||
delete_confirm: 'Confirm delete: ',
|
||||
start_status: 'The test is in the beginning state, we will automatically display it on the page after we generate the report!',
|
||||
|
@ -389,7 +397,7 @@ export default {
|
|||
file_status: 'File Status',
|
||||
last_modify_time: 'Modify time',
|
||||
upload_tips: 'Drag files here, or <em> click to upload </em>',
|
||||
upload_type: 'Only JMX/CSV files can be uploaded',
|
||||
upload_type: 'Only JMX/CSV/JAR files can be uploaded',
|
||||
related_file_not_found: "No related test file found!",
|
||||
delete_file_confirm: 'Confirm delete file:',
|
||||
file_size_limit: "The number of files exceeds the limit!",
|
||||
|
|
|
@ -180,6 +180,14 @@ export default {
|
|||
invalid: '无效',
|
||||
expired: '已过期',
|
||||
},
|
||||
display: {
|
||||
title: '显示设置',
|
||||
logo: '系统 LOGO',
|
||||
loginLogo: '登陆页面右侧图片',
|
||||
loginImage: '登录页左上角 LOGO',
|
||||
loginTitle: '登陆页面提示信息',
|
||||
pageTitle: '页面 Title',
|
||||
},
|
||||
system_config: {
|
||||
base_config: '基本配置',
|
||||
base: {
|
||||
|
@ -347,7 +355,7 @@ export default {
|
|||
test_execute_again: '再次执行',
|
||||
export: '导出',
|
||||
compare: '比较',
|
||||
generation_error: '报告生成错误,无法查看!',
|
||||
generation_error: '报告生成错误, 无法查看, 请检查日志详情!',
|
||||
being_generated: '报告正在生成中...',
|
||||
delete_confirm: '确认删除报告: ',
|
||||
start_status: '测试处于开始状态, 我们生成报告后会自动展示到页面上!',
|
||||
|
@ -387,7 +395,7 @@ export default {
|
|||
file_status: '文件状态',
|
||||
last_modify_time: '修改时间',
|
||||
upload_tips: '将文件拖到此处,或<em>点击上传</em>',
|
||||
upload_type: '只能上传JMX/CSV文件',
|
||||
upload_type: '只能上传JMX/CSV/JAR文件',
|
||||
related_file_not_found: "未找到关联的测试文件!",
|
||||
delete_file_confirm: '确认删除文件: ',
|
||||
file_size_limit: "文件个数超出限制!",
|
||||
|
|
|
@ -180,6 +180,14 @@ export default {
|
|||
invalid: '無效',
|
||||
expired: '已過期',
|
||||
},
|
||||
display: {
|
||||
title: '顯示設置',
|
||||
logo: '系統 LOGO',
|
||||
loginLogo: '登陸頁面右側圖片',
|
||||
loginImage: '登錄頁左上角 LOGO',
|
||||
loginTitle: '登陸頁面提示信息',
|
||||
pageTitle: '頁面 Title',
|
||||
},
|
||||
system_config: {
|
||||
base_config: '基本配置',
|
||||
base: {
|
||||
|
@ -347,7 +355,7 @@ export default {
|
|||
test_execute_again: '再次執行',
|
||||
export: '導出',
|
||||
compare: '比較',
|
||||
generation_error: '報告生成錯誤,無法查看!',
|
||||
generation_error: '報告生成錯誤, 無法查看, 請檢查日誌詳情!',
|
||||
being_generated: '報告正在生成中...',
|
||||
delete_confirm: '確認刪除報告: ',
|
||||
start_status: '測試處於開始狀態, 我們生成報告後會自動展示到頁面上!',
|
||||
|
@ -387,7 +395,7 @@ export default {
|
|||
file_status: '文件狀態',
|
||||
last_modify_time: '修改時間',
|
||||
upload_tips: '將文件拖到此處,或<em>點擊上傳</em>',
|
||||
upload_type: '只能上傳JMX/CSV文件',
|
||||
upload_type: '只能上傳JMX/CSV/JAR文件',
|
||||
related_file_not_found: "未找到關聯的測試文件!",
|
||||
delete_file_confirm: '確認刪除文件: ',
|
||||
file_size_limit: "文件個數超出限制!",
|
||||
|
|
|
@ -4,15 +4,16 @@
|
|||
<el-col :span="12">
|
||||
<el-form :model="form" :rules="rules" ref="form">
|
||||
<div class="logo">
|
||||
<img src="../assets/logo-dark-MeterSphere.svg" style="width: 224px" alt="">
|
||||
<img v-if="loginLogoId" :src="'/display/file/' + loginLogoId" style="width: 224px;height: 45px;" alt="">
|
||||
<img v-else src="../assets/logo-dark-MeterSphere.svg" style="width: 224px; " alt="">
|
||||
</div>
|
||||
<div class="title">
|
||||
<span id="s1">{{$t('commons.login')}}</span>
|
||||
<span id="s1">{{ $t('commons.login') }}</span>
|
||||
<span id="s2">MeterSphere</span>
|
||||
</div>
|
||||
<div class="border"></div>
|
||||
<div class="welcome">
|
||||
{{$t('commons.welcome')}}
|
||||
{{ $t('commons.welcome') }}
|
||||
</div>
|
||||
<div class="form">
|
||||
<el-form-item v-slot:default>
|
||||
|
@ -32,241 +33,252 @@
|
|||
</div>
|
||||
<div class="btn">
|
||||
<el-button type="primary" class="submit" @click="submit('form')">
|
||||
{{$t('commons.login')}}
|
||||
{{ $t('commons.login') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="msg">
|
||||
{{msg}}
|
||||
{{ msg }}
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="12" class="image">
|
||||
<div></div>
|
||||
<el-col :span="12">
|
||||
<img v-if="loginImageId" :src="'/display/file/' + loginImageId" style="height: 560px; width: 100%">
|
||||
<img v-else src="../assets/info.png" style="height: 560px; width: 100%">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {saveLocalStorage} from '../common/js/utils';
|
||||
import {DEFAULT_LANGUAGE} from "../common/js/constants";
|
||||
import {saveLocalStorage} from '@/common/js/utils';
|
||||
import {DEFAULT_LANGUAGE} from "@/common/js/constants";
|
||||
|
||||
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
|
||||
const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {};
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
data() {
|
||||
/*let validateEmail = (rule, value, callback) => {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
let EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
if (!EMAIL_REGEX.test(value)) {
|
||||
callback(new Error('邮箱格式不正确'));
|
||||
export default {
|
||||
name: "Login",
|
||||
data() {
|
||||
/*let validateEmail = (rule, value, callback) => {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
let EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
if (!EMAIL_REGEX.test(value)) {
|
||||
callback(new Error('邮箱格式不正确'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};*/
|
||||
return {
|
||||
result: {},
|
||||
form: {
|
||||
username: '',
|
||||
password: '',
|
||||
authenticate: 'LOCAL'
|
||||
},
|
||||
rules: {
|
||||
username: [
|
||||
{required: true, message: this.$t('commons.input_login_username'), trigger: 'blur'},
|
||||
],
|
||||
password: [
|
||||
{required: true, message: this.$t('commons.input_password'), trigger: 'blur'},
|
||||
{min: 6, max: 20, message: this.$t('commons.input_limit', [6, 20]), trigger: 'blur'}
|
||||
]
|
||||
},
|
||||
msg: '',
|
||||
ready: false,
|
||||
openLdap: false,
|
||||
loginLogoId: '_blank',
|
||||
loginImageId: '_blank',
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.result = this.$get("/isLogin").then(response => {
|
||||
|
||||
if (display.default !== undefined) {
|
||||
display.default.valid(this);
|
||||
}
|
||||
|
||||
if (!response.data.success) {
|
||||
if (response.data.message === 'sso') {
|
||||
window.location.href = "/sso/login"
|
||||
} else {
|
||||
callback();
|
||||
this.ready = true;
|
||||
}
|
||||
};*/
|
||||
return {
|
||||
result: {},
|
||||
form: {
|
||||
username: '',
|
||||
password: '',
|
||||
authenticate: 'LOCAL'
|
||||
},
|
||||
rules: {
|
||||
username: [
|
||||
{required: true, message: this.$t('commons.input_login_username'), trigger: 'blur'},
|
||||
],
|
||||
password: [
|
||||
{required: true, message: this.$t('commons.input_password'), trigger: 'blur'},
|
||||
{min: 6, max: 20, message: this.$t('commons.input_limit', [6, 20]), trigger: 'blur'}
|
||||
]
|
||||
},
|
||||
msg: '',
|
||||
ready: false,
|
||||
openLdap: false
|
||||
} else {
|
||||
let user = response.data.data;
|
||||
saveLocalStorage(response.data);
|
||||
this.getLanguage(user.language);
|
||||
window.location.href = "/"
|
||||
}
|
||||
});
|
||||
this.$get("/ldap/open", response => {
|
||||
this.openLdap = response.data;
|
||||
})
|
||||
},
|
||||
created: function () {
|
||||
// 主页添加键盘事件,注意,不能直接在焦点事件上添加回车
|
||||
document.addEventListener("keydown", this.watchEnter);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
//移除监听回车按键
|
||||
document.removeEventListener("keydown", this.watchEnter);
|
||||
},
|
||||
methods: {
|
||||
//监听回车按钮事件
|
||||
watchEnter(e) {
|
||||
let keyNum = e.which; //获取被按下的键值
|
||||
//判断如果用户按下了回车键(keycody=13)
|
||||
if (keyNum === 13) {
|
||||
// 按下回车按钮要做的事
|
||||
this.submit('form');
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$get("/isLogin").then(response => {
|
||||
if (!response.data.success) {
|
||||
if (response.data.message === 'sso') {
|
||||
window.location.href = "/sso/login"
|
||||
} else {
|
||||
this.ready = true;
|
||||
submit(form) {
|
||||
this.$refs[form].validate((valid) => {
|
||||
if (valid) {
|
||||
switch (this.form.authenticate) {
|
||||
case "LOCAL":
|
||||
this.normalLogin();
|
||||
break;
|
||||
case "LDAP":
|
||||
this.ldapLogin();
|
||||
break;
|
||||
default:
|
||||
this.normalLogin();
|
||||
}
|
||||
} else {
|
||||
let user = response.data.data;
|
||||
saveLocalStorage(response.data);
|
||||
this.getLanguage(user.language);
|
||||
window.location.href = "/"
|
||||
return false;
|
||||
}
|
||||
});
|
||||
this.$get("/ldap/open", response => {
|
||||
this.openLdap = response.data;
|
||||
})
|
||||
},
|
||||
created: function () {
|
||||
// 主页添加键盘事件,注意,不能直接在焦点事件上添加回车
|
||||
document.addEventListener("keydown", this.watchEnter);
|
||||
normalLogin() {
|
||||
this.result = this.$post("signin", this.form, response => {
|
||||
saveLocalStorage(response);
|
||||
sessionStorage.setItem('loginSuccess', 'true');
|
||||
this.getLanguage(response.data.language);
|
||||
});
|
||||
},
|
||||
destroyed() {
|
||||
//移除监听回车按键
|
||||
document.removeEventListener("keydown", this.watchEnter);
|
||||
ldapLogin() {
|
||||
this.result = this.$post("ldap/signin", this.form, response => {
|
||||
saveLocalStorage(response);
|
||||
sessionStorage.setItem('loginSuccess', 'true');
|
||||
this.getLanguage(response.data.language);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
//监听回车按钮事件
|
||||
watchEnter(e) {
|
||||
let keyNum = e.which; //获取被按下的键值
|
||||
//判断如果用户按下了回车键(keycody=13)
|
||||
if (keyNum === 13) {
|
||||
// 按下回车按钮要做的事
|
||||
this.submit('form');
|
||||
}
|
||||
},
|
||||
submit(form) {
|
||||
this.$refs[form].validate((valid) => {
|
||||
if (valid) {
|
||||
switch (this.form.authenticate) {
|
||||
case "LOCAL":
|
||||
this.normalLogin();
|
||||
break;
|
||||
case "LDAP":
|
||||
this.ldapLogin();
|
||||
break;
|
||||
default:
|
||||
this.normalLogin();
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
normalLogin() {
|
||||
this.result = this.$post("signin", this.form, response => {
|
||||
saveLocalStorage(response);
|
||||
sessionStorage.setItem('loginSuccess', 'true');
|
||||
this.getLanguage(response.data.language);
|
||||
});
|
||||
},
|
||||
ldapLogin() {
|
||||
this.result = this.$post("ldap/signin", this.form, response => {
|
||||
saveLocalStorage(response);
|
||||
sessionStorage.setItem('loginSuccess', 'true');
|
||||
this.getLanguage(response.data.language);
|
||||
});
|
||||
},
|
||||
getLanguage(language) {
|
||||
if (!language) {
|
||||
this.$get("language", response => {
|
||||
language = response.data;
|
||||
localStorage.setItem(DEFAULT_LANGUAGE, language);
|
||||
window.location.href = "/"
|
||||
})
|
||||
} else {
|
||||
getLanguage(language) {
|
||||
if (!language) {
|
||||
this.$get("language", response => {
|
||||
language = response.data;
|
||||
localStorage.setItem(DEFAULT_LANGUAGE, language);
|
||||
window.location.href = "/"
|
||||
}
|
||||
})
|
||||
} else {
|
||||
window.location.href = "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
min-width: 800px;
|
||||
max-width: 1440px;
|
||||
height: 560px;
|
||||
margin: calc((100vh - 560px) / 2) auto 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.container {
|
||||
min-width: 800px;
|
||||
max-width: 1440px;
|
||||
height: 560px;
|
||||
margin: calc((100vh - 560px) / 2) auto 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 30px 30px 0;
|
||||
}
|
||||
.logo {
|
||||
margin: 30px 30px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 50px;
|
||||
font-size: 32px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.title {
|
||||
margin-top: 50px;
|
||||
font-size: 32px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title > #s1 {
|
||||
color: #999999;
|
||||
}
|
||||
.title > #s1 {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.title > #s2 {
|
||||
color: #151515;
|
||||
}
|
||||
.title > #s2 {
|
||||
color: #151515;
|
||||
}
|
||||
|
||||
.border {
|
||||
height: 2px;
|
||||
margin: 20px auto 20px;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
background: #8B479B;
|
||||
}
|
||||
.border {
|
||||
height: 2px;
|
||||
margin: 20px auto 20px;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
background: #8B479B;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
margin-top: 50px;
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
letter-spacing: 0;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
.welcome {
|
||||
margin-top: 50px;
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
letter-spacing: 0;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-top: 30px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
.form {
|
||||
margin-top: 30px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 40px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
.btn {
|
||||
margin-top: 40px;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.btn > .submit {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
border-color: #8B479B;
|
||||
background-color: #8B479B;
|
||||
}
|
||||
.btn > .submit {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
border-color: #8B479B;
|
||||
background-color: #8B479B;
|
||||
}
|
||||
|
||||
.btn > .submit:hover {
|
||||
border-color: rgba(139, 71, 155, 0.9);
|
||||
background-color: rgba(139, 71, 155, 0.9);
|
||||
}
|
||||
.btn > .submit:hover {
|
||||
border-color: rgba(139, 71, 155, 0.9);
|
||||
background-color: rgba(139, 71, 155, 0.9);
|
||||
}
|
||||
|
||||
.btn > .submit:active {
|
||||
border-color: rgba(139, 71, 155, 0.8);
|
||||
background-color: rgba(139, 71, 155, 0.8);
|
||||
}
|
||||
.btn > .submit:active {
|
||||
border-color: rgba(139, 71, 155, 0.8);
|
||||
background-color: rgba(139, 71, 155, 0.8);
|
||||
}
|
||||
|
||||
.msg {
|
||||
margin-top: 10px;
|
||||
padding: 0 40px;
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
.msg {
|
||||
margin-top: 10px;
|
||||
padding: 0 40px;
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
background: url(../assets/info.png);
|
||||
height: 560px;
|
||||
}
|
||||
.image {
|
||||
background: url(../assets/info.png);
|
||||
height: 560px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Neue Haas Grotesk Text Pro", "Arial Nova", "Segoe UI", "Helvetica Neue", ".PingFang SC", "PingFang SC", "Source Han Sans SC", "Noto Sans CJK SC", "Source Han Sans CN", "Noto Sans SC", "Source Han Sans TC", "Noto Sans CJK TC", "Hiragino Sans GB", sans-serif;
|
||||
font-size: 14px;
|
||||
background-color: #F5F5F5;
|
||||
line-height: 26px;
|
||||
color: #2B415C;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Neue Haas Grotesk Text Pro", "Arial Nova", "Segoe UI", "Helvetica Neue", ".PingFang SC", "PingFang SC", "Source Han Sans SC", "Noto Sans CJK SC", "Source Han Sans CN", "Noto Sans SC", "Source Han Sans TC", "Noto Sans CJK TC", "Hiragino Sans GB", sans-serif;
|
||||
font-size: 14px;
|
||||
background-color: #F5F5F5;
|
||||
line-height: 26px;
|
||||
color: #2B415C;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form .el-input > .el-input__inner {
|
||||
border-radius: 0;
|
||||
}
|
||||
.form .el-input > .el-input__inner {
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
Loading…
Reference in New Issue