Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
2c47bcc073
|
@ -18,7 +18,8 @@ MeterSphere 是一站式的开源企业级持续测试平台,涵盖测试跟
|
|||
- 性能测试: 兼容 JMeter,支持 Kubernetes 和云环境,轻松支持高并发、分布式的性能测试;
|
||||
- 团队协作: 两级租户体系,天然支持团队协作。
|
||||
|
||||
![产品定位](https://metersphere.io/images/icon/ct-devops.png)
|
||||
![产品定位](https://metersphere.oss-cn-hangzhou.aliyuncs.com/img/ct-devops.png)
|
||||
|
||||
|
||||
> 如需进一步了解 MeterSphere 开源项目,推荐阅读 [MeterSphere 的初心和使命](https://mp.weixin.qq.com/s/DpCt3BNgBTlV3sJ5qtPmZw)
|
||||
|
||||
|
@ -295,7 +296,7 @@ v1.1.0 是 v1.0.0 之后的功能版本。
|
|||
|
||||
## 微信群
|
||||
|
||||
![wechat-group](https://metersphere.io/images/contact/wechat-group.png)
|
||||
![wechat-group](https://metersphere.oss-cn-hangzhou.aliyuncs.com/img/wechat-group.png)
|
||||
|
||||
## License & Copyright
|
||||
|
||||
|
|
|
@ -39,10 +39,12 @@ public class APIReportController {
|
|||
return apiReportService.recentTest(request);
|
||||
}
|
||||
|
||||
@GetMapping("/list/{testId}")
|
||||
public List<APIReportResult> listByTestId(@PathVariable String testId) {
|
||||
@GetMapping("/list/{testId}/{goPage}/{pageSize}")
|
||||
public Pager<List<APIReportResult>> listByTestId(@PathVariable String testId, @PathVariable int goPage, @PathVariable int pageSize) {
|
||||
checkOwnerService.checkApiTestOwner(testId);
|
||||
return apiReportService.listByTestId(testId);
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
return PageUtils.setPageInfo(page, apiReportService.listByTestId(testId));
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/list/{goPage}/{pageSize}")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.metersphere.api.dto.scenario;
|
||||
|
||||
import io.metersphere.api.dto.scenario.assertions.Assertions;
|
||||
import io.metersphere.api.dto.scenario.request.Request;
|
||||
import lombok.Data;
|
||||
|
||||
|
@ -15,6 +16,7 @@ public class Scenario {
|
|||
private List<KeyValue> variables;
|
||||
private List<KeyValue> headers;
|
||||
private List<Request> requests;
|
||||
private Assertions assertions;
|
||||
private DubboConfig dubboConfig;
|
||||
private TCPConfig tcpConfig;
|
||||
private List<DatabaseConfig> databaseConfigs;
|
||||
|
|
|
@ -22,5 +22,7 @@ public class LoadTestReport implements Serializable {
|
|||
|
||||
private String triggerMode;
|
||||
|
||||
private String fileId;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -643,6 +643,76 @@ public class LoadTestReportExample {
|
|||
addCriterion("trigger_mode not between", value1, value2, "triggerMode");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdIsNull() {
|
||||
addCriterion("file_id is null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdIsNotNull() {
|
||||
addCriterion("file_id is not null");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdEqualTo(String value) {
|
||||
addCriterion("file_id =", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotEqualTo(String value) {
|
||||
addCriterion("file_id <>", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdGreaterThan(String value) {
|
||||
addCriterion("file_id >", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdGreaterThanOrEqualTo(String value) {
|
||||
addCriterion("file_id >=", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdLessThan(String value) {
|
||||
addCriterion("file_id <", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdLessThanOrEqualTo(String value) {
|
||||
addCriterion("file_id <=", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdLike(String value) {
|
||||
addCriterion("file_id like", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotLike(String value) {
|
||||
addCriterion("file_id not like", value, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdIn(List<String> values) {
|
||||
addCriterion("file_id in", values, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotIn(List<String> values) {
|
||||
addCriterion("file_id not in", values, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdBetween(String value1, String value2) {
|
||||
addCriterion("file_id between", value1, value2, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
|
||||
public Criteria andFileIdNotBetween(String value1, String value2) {
|
||||
addCriterion("file_id not between", value1, value2, "fileId");
|
||||
return (Criteria) this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Criteria extends GeneratedCriteria {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
<result column="status" jdbcType="VARCHAR" property="status" />
|
||||
<result column="user_id" jdbcType="VARCHAR" property="userId" />
|
||||
<result column="trigger_mode" jdbcType="VARCHAR" property="triggerMode" />
|
||||
<result column="file_id" jdbcType="VARCHAR" property="fileId" />
|
||||
</resultMap>
|
||||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.LoadTestReportWithBLOBs">
|
||||
<result column="description" jdbcType="LONGVARCHAR" property="description" />
|
||||
|
@ -74,7 +75,7 @@
|
|||
</where>
|
||||
</sql>
|
||||
<sql id="Base_Column_List">
|
||||
id, test_id, `name`, create_time, update_time, `status`, user_id, trigger_mode
|
||||
id, test_id, `name`, create_time, update_time, `status`, user_id, trigger_mode, file_id
|
||||
</sql>
|
||||
<sql id="Blob_Column_List">
|
||||
description, load_configuration
|
||||
|
@ -130,12 +131,12 @@
|
|||
<insert id="insert" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
|
||||
INSERT INTO load_test_report (id, test_id, `name`,
|
||||
create_time, update_time, `status`,
|
||||
user_id, trigger_mode, description,
|
||||
load_configuration)
|
||||
user_id, trigger_mode, file_id,
|
||||
description, load_configuration)
|
||||
VALUES (#{id,jdbcType=VARCHAR}, #{testId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
|
||||
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{status,jdbcType=VARCHAR},
|
||||
#{userId,jdbcType=VARCHAR}, #{triggerMode,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR},
|
||||
#{loadConfiguration,jdbcType=LONGVARCHAR})
|
||||
#{userId,jdbcType=VARCHAR}, #{triggerMode,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR},
|
||||
#{description,jdbcType=LONGVARCHAR}, #{loadConfiguration,jdbcType=LONGVARCHAR})
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
|
||||
insert into load_test_report
|
||||
|
@ -164,6 +165,9 @@
|
|||
<if test="triggerMode != null">
|
||||
trigger_mode,
|
||||
</if>
|
||||
<if test="fileId != null">
|
||||
file_id,
|
||||
</if>
|
||||
<if test="description != null">
|
||||
description,
|
||||
</if>
|
||||
|
@ -196,6 +200,9 @@
|
|||
<if test="triggerMode != null">
|
||||
#{triggerMode,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="fileId != null">
|
||||
#{fileId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="description != null">
|
||||
#{description,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
|
@ -237,6 +244,9 @@
|
|||
<if test="record.triggerMode != null">
|
||||
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.fileId != null">
|
||||
file_id = #{record.fileId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.description != null">
|
||||
description = #{record.description,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
|
@ -258,6 +268,7 @@
|
|||
`status` = #{record.status,jdbcType=VARCHAR},
|
||||
user_id = #{record.userId,jdbcType=VARCHAR},
|
||||
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
|
||||
file_id = #{record.fileId,jdbcType=VARCHAR},
|
||||
description = #{record.description,jdbcType=LONGVARCHAR},
|
||||
load_configuration = #{record.loadConfiguration,jdbcType=LONGVARCHAR}
|
||||
<if test="_parameter != null">
|
||||
|
@ -273,7 +284,8 @@
|
|||
update_time = #{record.updateTime,jdbcType=BIGINT},
|
||||
`status` = #{record.status,jdbcType=VARCHAR},
|
||||
user_id = #{record.userId,jdbcType=VARCHAR},
|
||||
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR}
|
||||
trigger_mode = #{record.triggerMode,jdbcType=VARCHAR},
|
||||
file_id = #{record.fileId,jdbcType=VARCHAR}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause" />
|
||||
</if>
|
||||
|
@ -302,6 +314,9 @@
|
|||
<if test="triggerMode != null">
|
||||
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="fileId != null">
|
||||
file_id = #{fileId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="description != null">
|
||||
description = #{description,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
|
@ -312,27 +327,29 @@
|
|||
where id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
<update id="updateByPrimaryKeyWithBLOBs" parameterType="io.metersphere.base.domain.LoadTestReportWithBLOBs">
|
||||
update load_test_report
|
||||
set test_id = #{testId,jdbcType=VARCHAR},
|
||||
UPDATE load_test_report
|
||||
SET test_id = #{testId,jdbcType=VARCHAR},
|
||||
`name` = #{name,jdbcType=VARCHAR},
|
||||
create_time = #{createTime,jdbcType=BIGINT},
|
||||
update_time = #{updateTime,jdbcType=BIGINT},
|
||||
`status` = #{status,jdbcType=VARCHAR},
|
||||
user_id = #{userId,jdbcType=VARCHAR},
|
||||
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
|
||||
file_id = #{fileId,jdbcType=VARCHAR},
|
||||
description = #{description,jdbcType=LONGVARCHAR},
|
||||
load_configuration = #{loadConfiguration,jdbcType=LONGVARCHAR}
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
WHERE id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.LoadTestReport">
|
||||
update load_test_report
|
||||
set test_id = #{testId,jdbcType=VARCHAR},
|
||||
UPDATE load_test_report
|
||||
SET test_id = #{testId,jdbcType=VARCHAR},
|
||||
`name` = #{name,jdbcType=VARCHAR},
|
||||
create_time = #{createTime,jdbcType=BIGINT},
|
||||
update_time = #{updateTime,jdbcType=BIGINT},
|
||||
`status` = #{status,jdbcType=VARCHAR},
|
||||
user_id = #{userId,jdbcType=VARCHAR},
|
||||
trigger_mode = #{triggerMode,jdbcType=VARCHAR}
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
trigger_mode = #{triggerMode,jdbcType=VARCHAR},
|
||||
file_id = #{fileId,jdbcType=VARCHAR}
|
||||
WHERE id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
</mapper>
|
|
@ -24,6 +24,7 @@ import org.apache.commons.collections4.MapUtils;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -99,10 +100,10 @@ public class MailService {
|
|||
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
|
||||
helper.setFrom(javaMailSender.getUsername());
|
||||
if (StringUtils.equals(type, NoticeConstants.API)) {
|
||||
helper.setSubject("MeterSphere平台" + Translator.get("task_notification"));
|
||||
helper.setSubject("MeterSphere平台" + Translator.get("task_notification_jenkins"));
|
||||
}
|
||||
if (StringUtils.equals(type, NoticeConstants.SCHEDULE)) {
|
||||
helper.setSubject("MeterSphere平台" + Translator.get("task_notification_"));
|
||||
helper.setSubject("MeterSphere平台" + Translator.get("task_notification"));
|
||||
}
|
||||
String[] users;
|
||||
List<String> emails = new ArrayList<>();
|
||||
|
@ -113,7 +114,11 @@ public class MailService {
|
|||
users = emails.toArray(new String[0]);
|
||||
helper.setText(getContent(Template, context), true);
|
||||
helper.setTo(users);
|
||||
try {
|
||||
javaMailSender.send(mimeMessage);
|
||||
} catch (MailException e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
}
|
||||
//测试评审
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ import io.metersphere.performance.controller.request.ReportRequest;
|
|||
import io.metersphere.performance.service.ReportService;
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -130,4 +133,13 @@ public class PerformanceReportController {
|
|||
public void deleteReportBatch(@RequestBody DeleteReportRequest reportRequest) {
|
||||
reportService.deleteReportBatch(reportRequest);
|
||||
}
|
||||
|
||||
@GetMapping("/jtl/download/{reportId}")
|
||||
public ResponseEntity<byte[]> downloadJtl(@PathVariable String reportId) {
|
||||
byte[] bytes = reportService.downloadJtl(reportId);
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType("application/octet-stream"))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + reportId + ".jtl\"")
|
||||
.body(bytes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,12 @@ public class PerformanceTestController {
|
|||
return performanceTestService.getLoadConfiguration(testId);
|
||||
}
|
||||
|
||||
@GetMapping("/get-jmx-content/{testId}")
|
||||
public String getJmxContent(@PathVariable String testId) {
|
||||
checkOwnerService.checkPerformanceTestOwner(testId);
|
||||
return performanceTestService.getJmxContent(testId);
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
public void delete(@RequestBody DeleteTestPlanRequest request) {
|
||||
checkOwnerService.checkPerformanceTestOwner(request.getId());
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.apache.commons.collections.CollectionUtils;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class AbstractEngine implements Engine {
|
||||
|
@ -81,9 +82,22 @@ public abstract class AbstractEngine implements Engine {
|
|||
String loadConfiguration = t.getLoadConfiguration();
|
||||
JSONArray jsonArray = JSON.parseArray(loadConfiguration);
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
if (jsonArray.get(i) instanceof Map) {
|
||||
JSONObject o = jsonArray.getJSONObject(i);
|
||||
if (StringUtils.equals(o.getString("key"), "TargetLevel")) {
|
||||
s = o.getInteger("value");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (jsonArray.get(i) instanceof List) {
|
||||
JSONArray o = jsonArray.getJSONArray(i);
|
||||
for (int j = 0; j < o.size(); j++) {
|
||||
JSONObject b = o.getJSONObject(j);
|
||||
if (StringUtils.equals(b.getString("key"), "TargetLevel")) {
|
||||
s += b.getInteger("value");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
|
|
|
@ -10,7 +10,6 @@ public class EngineContext {
|
|||
private String fileType;
|
||||
private String content;
|
||||
private String resourcePoolId;
|
||||
private Long threadNum;
|
||||
private Long startTime;
|
||||
private String reportId;
|
||||
private Integer resourceIndex;
|
||||
|
@ -95,14 +94,6 @@ public class EngineContext {
|
|||
this.resourcePoolId = resourcePoolId;
|
||||
}
|
||||
|
||||
public Long getThreadNum() {
|
||||
return threadNum;
|
||||
}
|
||||
|
||||
public void setThreadNum(Long threadNum) {
|
||||
this.threadNum = threadNum;
|
||||
}
|
||||
|
||||
public Long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import io.metersphere.base.domain.TestResourcePool;
|
|||
import io.metersphere.commons.constants.FileType;
|
||||
import io.metersphere.commons.constants.ResourcePoolTypeEnum;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.config.KafkaProperties;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.performance.engine.docker.DockerTestEngine;
|
||||
|
@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -52,7 +54,7 @@ public class EngineFactory {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static EngineContext createContext(LoadTestWithBLOBs loadTest, String resourceId, long threadNum, long startTime, String reportId, int resourceIndex) {
|
||||
public static EngineContext createContext(LoadTestWithBLOBs loadTest, String resourceId, double ratio, long startTime, String reportId, int resourceIndex) {
|
||||
final List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(loadTest.getId());
|
||||
if (org.springframework.util.CollectionUtils.isEmpty(fileMetadataList)) {
|
||||
MSException.throwException(Translator.get("run_load_test_file_not_found") + loadTest.getId());
|
||||
|
@ -73,7 +75,6 @@ public class EngineFactory {
|
|||
engineContext.setTestName(loadTest.getName());
|
||||
engineContext.setNamespace(loadTest.getProjectId());
|
||||
engineContext.setFileType(jmxFile.getType());
|
||||
engineContext.setThreadNum(threadNum);
|
||||
engineContext.setResourcePoolId(loadTest.getTestResourcePoolId());
|
||||
engineContext.setStartTime(startTime);
|
||||
engineContext.setReportId(reportId);
|
||||
|
@ -90,8 +91,34 @@ public class EngineFactory {
|
|||
final JSONArray jsonArray = JSONObject.parseArray(loadTest.getLoadConfiguration());
|
||||
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
final JSONObject jsonObject = jsonArray.getJSONObject(i);
|
||||
engineContext.addProperty(jsonObject.getString("key"), jsonObject.get("value"));
|
||||
if (jsonArray.get(i) instanceof Map) {
|
||||
JSONObject o = jsonArray.getJSONObject(i);
|
||||
String key = o.getString("key");
|
||||
if ("TargetLevel".equals(key)) {
|
||||
engineContext.addProperty(key, Math.round(((Integer) o.get("value")) * ratio));
|
||||
} else {
|
||||
engineContext.addProperty(key, o.get("value"));
|
||||
}
|
||||
}
|
||||
if (jsonArray.get(i) instanceof List) {
|
||||
JSONArray o = jsonArray.getJSONArray(i);
|
||||
for (int j = 0; j < o.size(); j++) {
|
||||
JSONObject b = o.getJSONObject(j);
|
||||
String key = b.getString("key");
|
||||
Object values = engineContext.getProperty(key);
|
||||
if (values == null) {
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
if (values instanceof List) {
|
||||
Object value = b.get("value");
|
||||
if ("TargetLevel".equals(key)) {
|
||||
value = Math.round(((Integer) b.get("value")) * ratio);
|
||||
}
|
||||
((List<Object>) values).add(value);
|
||||
engineContext.addProperty(key, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
@ -112,8 +139,10 @@ public class EngineFactory {
|
|||
String content = engineSourceParser.parse(engineContext, source);
|
||||
engineContext.setContent(content);
|
||||
} catch (MSException e) {
|
||||
LogUtil.error(e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
MSException.throwException(e);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.metersphere.base.domain.TestResource;
|
|||
import io.metersphere.commons.constants.ResourceStatusEnum;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.controller.ResultHolder;
|
||||
import io.metersphere.dto.NodeDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
|
@ -52,19 +53,21 @@ public class DockerTestEngine extends AbstractEngine {
|
|||
|
||||
for (int i = 0, size = resourceList.size(); i < size; i++) {
|
||||
int ratio = resourceRatio.get(i);
|
||||
double realThreadNum = ((double) ratio / totalThreadNum) * threadNum;
|
||||
runTest(resourceList.get(i), Math.round(realThreadNum), i);
|
||||
// double realThreadNum = ((double) ratio / totalThreadNum) * threadNum;
|
||||
runTest(resourceList.get(i), ((double) ratio / totalThreadNum), i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void runTest(TestResource resource, long realThreadNum, int resourceIndex) {
|
||||
private void runTest(TestResource resource, double ratio, int resourceIndex) {
|
||||
EngineContext context = null;
|
||||
try {
|
||||
context = EngineFactory.createContext(loadTest, resource.getId(), realThreadNum, this.getStartTime(), this.getReportId(), resourceIndex);
|
||||
context = EngineFactory.createContext(loadTest, resource.getId(), ratio, this.getStartTime(), this.getReportId(), resourceIndex);
|
||||
} catch (MSException e) {
|
||||
LogUtil.error(e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
MSException.throwException(e);
|
||||
}
|
||||
|
||||
|
@ -80,6 +83,7 @@ public class DockerTestEngine extends AbstractEngine {
|
|||
TestRequest testRequest = new TestRequest();
|
||||
testRequest.setSize(1);
|
||||
testRequest.setTestId(testId);
|
||||
testRequest.setReportId(getReportId());
|
||||
testRequest.setFileString(content);
|
||||
testRequest.setImage(JMETER_IMAGE);
|
||||
testRequest.setTestData(context.getTestData());
|
||||
|
|
|
@ -7,4 +7,5 @@ import lombok.Setter;
|
|||
@Setter
|
||||
public class BaseRequest {
|
||||
private String testId;
|
||||
private String reportId;
|
||||
}
|
||||
|
|
|
@ -39,34 +39,36 @@ public class PerformanceNoticeTask {
|
|||
private LoadTestReportMapper loadTestReportMapper;
|
||||
|
||||
private final ExecutorService executorService = Executors.newFixedThreadPool(20);
|
||||
private boolean isRunning = true;
|
||||
|
||||
@PreDestroy
|
||||
private boolean isRunning=false;
|
||||
|
||||
/*@PreDestroy
|
||||
public void preDestroy() {
|
||||
isRunning = false;
|
||||
}
|
||||
}*/
|
||||
|
||||
public void registerNoticeTask(LoadTestReportWithBLOBs loadTestReport) {
|
||||
int count = 20;
|
||||
while (count-- > 0) {
|
||||
isRunning=true;
|
||||
executorService.submit(() -> {
|
||||
LogUtil.info("性能测试定时任务");
|
||||
while (isRunning) {
|
||||
LoadTestReportWithBLOBs loadTestReportFromDatabase = loadTestReportMapper.selectByPrimaryKey(loadTestReport.getId());
|
||||
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Completed.name())) {
|
||||
isRunning = false;
|
||||
sendSuccessNotice(loadTestReportFromDatabase);
|
||||
return;
|
||||
isRunning=false;
|
||||
}
|
||||
if (StringUtils.equals(loadTestReportFromDatabase.getStatus(), PerformanceTestStatus.Error.name())) {
|
||||
isRunning = false;
|
||||
sendFailNotice(loadTestReportFromDatabase);
|
||||
return;
|
||||
isRunning=false;
|
||||
}
|
||||
count--;
|
||||
try {
|
||||
Thread.sleep(1000 * 4L);// 每分钟检查 loadtest 的状态
|
||||
//查询定时任务是否关闭
|
||||
Thread.sleep(1000 * 30);// 每分钟检查 loadtest 的状态
|
||||
} catch (InterruptedException e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void sendSuccessNotice(LoadTestReportWithBLOBs loadTestReport) {
|
||||
|
|
|
@ -776,15 +776,12 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
elementProp.setAttribute("name", "ThreadGroup.main_controller");
|
||||
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");
|
||||
threadGroup.appendChild(elementProp);
|
||||
// 持续时长
|
||||
String duration = context.getProperty("duration").toString();
|
||||
String rampUp = context.getProperty("RampUp").toString();
|
||||
int realHold = Integer.parseInt(duration) - Integer.parseInt(rampUp);
|
||||
|
||||
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue"));
|
||||
threadGroup.appendChild(createStringProp(document, "TargetLevel", "2"));
|
||||
threadGroup.appendChild(createStringProp(document, "RampUp", "12"));
|
||||
threadGroup.appendChild(createStringProp(document, "Steps", "2"));
|
||||
threadGroup.appendChild(createStringProp(document, "Hold", String.valueOf(realHold)));
|
||||
threadGroup.appendChild(createStringProp(document, "Hold", "1"));
|
||||
threadGroup.appendChild(createStringProp(document, "LogFilename", ""));
|
||||
// bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空
|
||||
// threadGroup.appendChild(createStringProp(document, "Iterations", "1"));
|
||||
|
@ -803,9 +800,18 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
</collectionProp>
|
||||
</kg.apc.jmeter.timers.VariableThroughputTimer>
|
||||
*/
|
||||
if (context.getProperty("rpsLimitEnable") == null || StringUtils.equals(context.getProperty("rpsLimitEnable").toString(), "false")) {
|
||||
if (context.getProperty("rpsLimitEnable") == null) {
|
||||
return;
|
||||
}
|
||||
Object rpsLimitEnables = context.getProperty("rpsLimitEnable");
|
||||
if (rpsLimitEnables instanceof List) {
|
||||
Object o = ((List<?>) rpsLimitEnables).get(0);
|
||||
((List<?>) rpsLimitEnables).remove(0);
|
||||
if (o == null || "false".equals(o.toString())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Document document = element.getOwnerDocument();
|
||||
|
||||
|
||||
|
@ -866,11 +872,6 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
if (nodeNameEquals(ele, STRING_PROP)) {
|
||||
parseStringProp(ele);
|
||||
}
|
||||
|
||||
// 设置具体的线程数
|
||||
if (nodeNameEquals(ele, STRING_PROP) && "TargetLevel".equals(ele.getAttribute("name"))) {
|
||||
ele.getFirstChild().setNodeValue(context.getThreadNum().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -902,11 +903,28 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
stringPropCount++;
|
||||
} else {
|
||||
stringPropCount = 0;
|
||||
Integer duration = (Integer) context.getProperty("duration");// 传入的是分钟数, 需要转化成秒数
|
||||
Object durations = context.getProperty("duration");// 传入的是分钟数, 需要转化成秒数
|
||||
Integer duration;
|
||||
if (durations instanceof List) {
|
||||
Object o = ((List<?>) durations).get(0);
|
||||
duration = (Integer) o;
|
||||
((List<?>) durations).remove(0);
|
||||
} else {
|
||||
duration = (Integer) durations;
|
||||
}
|
||||
prop.getFirstChild().setNodeValue(String.valueOf(duration * 60));
|
||||
continue;
|
||||
}
|
||||
prop.getFirstChild().setNodeValue(context.getProperty("rpsLimit").toString());
|
||||
Object rpsLimits = context.getProperty("rpsLimit");
|
||||
String rpsLimit;
|
||||
if (rpsLimits instanceof List) {
|
||||
Object o = ((List<?>) rpsLimits).get(0);
|
||||
((List<?>) rpsLimits).remove(0);
|
||||
rpsLimit = o.toString();
|
||||
} else {
|
||||
rpsLimit = rpsLimits.toString();
|
||||
}
|
||||
prop.getFirstChild().setNodeValue(rpsLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -920,8 +938,15 @@ public class JmeterDocumentParser implements DocumentParser {
|
|||
}
|
||||
|
||||
private void parseStringProp(Element stringProp) {
|
||||
if (stringProp.getChildNodes().getLength() > 0 && context.getProperty(stringProp.getAttribute("name")) != null) {
|
||||
stringProp.getFirstChild().setNodeValue(context.getProperty(stringProp.getAttribute("name")).toString());
|
||||
Object threadParams = context.getProperty(stringProp.getAttribute("name"));
|
||||
if (stringProp.getChildNodes().getLength() > 0 && threadParams != null) {
|
||||
if (threadParams instanceof List) {
|
||||
Object o = ((List<?>) threadParams).get(0);
|
||||
((List<?>) threadParams).remove(0);
|
||||
stringProp.getFirstChild().setNodeValue(o.toString());
|
||||
} else {
|
||||
stringProp.getFirstChild().setNodeValue(threadParams.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -207,6 +207,7 @@ public class PerformanceTestService {
|
|||
|
||||
@Transactional(noRollbackFor = MSException.class)// 保存失败的信息
|
||||
public String run(RunTestPlanRequest request) {
|
||||
LogUtil.info("性能测试run测试");
|
||||
final LoadTestWithBLOBs loadTest = loadTestMapper.selectByPrimaryKey(request.getId());
|
||||
if (request.getUserId() != null) {
|
||||
loadTest.setUserId(request.getUserId());
|
||||
|
@ -345,6 +346,17 @@ public class PerformanceTestService {
|
|||
return Optional.ofNullable(loadTestWithBLOBs).orElse(new LoadTestWithBLOBs()).getLoadConfiguration();
|
||||
}
|
||||
|
||||
public String getJmxContent(String testId) {
|
||||
List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(testId);
|
||||
for (FileMetadata metadata : fileMetadataList) {
|
||||
if (FileType.JMX.name().equals(metadata.getType())) {
|
||||
FileContent fileContent = fileService.getFileContent(metadata.getId());
|
||||
return new String(fileContent.getFile());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<LoadTestWithBLOBs> selectByTestResourcePoolId(String resourcePoolId) {
|
||||
LoadTestExample example = new LoadTestExample();
|
||||
example.createCriteria().andTestResourcePoolIdEqualTo(resourcePoolId);
|
||||
|
|
|
@ -13,11 +13,13 @@ import io.metersphere.commons.utils.ServiceUtils;
|
|||
import io.metersphere.controller.request.OrderRequest;
|
||||
import io.metersphere.dto.LogDetailDTO;
|
||||
import io.metersphere.dto.ReportDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.performance.base.*;
|
||||
import io.metersphere.performance.controller.request.DeleteReportRequest;
|
||||
import io.metersphere.performance.controller.request.ReportRequest;
|
||||
import io.metersphere.performance.engine.Engine;
|
||||
import io.metersphere.performance.engine.EngineFactory;
|
||||
import io.metersphere.service.FileService;
|
||||
import io.metersphere.service.TestResourceService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -47,6 +49,8 @@ public class ReportService {
|
|||
private TestResourceService testResourceService;
|
||||
@Resource
|
||||
private LoadTestReportDetailMapper loadTestReportDetailMapper;
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
public List<ReportDTO> getRecentReportList(ReportRequest request) {
|
||||
List<OrderRequest> orders = new ArrayList<>();
|
||||
|
@ -168,7 +172,10 @@ public class ReportService {
|
|||
|
||||
public void checkReportStatus(String reportId) {
|
||||
LoadTestReport loadTestReport = loadTestReportMapper.selectByPrimaryKey(reportId);
|
||||
String reportStatus = loadTestReport.getStatus();
|
||||
String reportStatus = "";
|
||||
if (loadTestReport != null) {
|
||||
reportStatus = loadTestReport.getStatus();
|
||||
}
|
||||
if (StringUtils.equals(PerformanceTestStatus.Error.name(), reportStatus)) {
|
||||
MSException.throwException("Report generation error!");
|
||||
}
|
||||
|
@ -268,4 +275,12 @@ public class ReportService {
|
|||
String content = getContent(id, ReportKeys.ResponseCodeChart);
|
||||
return JSON.parseArray(content, ChartsData.class);
|
||||
}
|
||||
|
||||
public byte[] downloadJtl(String reportId) {
|
||||
LoadTestReportWithBLOBs report = getReport(reportId);
|
||||
if (StringUtils.isBlank(report.getFileId())) {
|
||||
throw new RuntimeException(Translator.get("load_test_report_file_not_exist"));
|
||||
}
|
||||
return fileService.loadFileAsBytes(report.getFileId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ public class CheckOwnerService {
|
|||
}
|
||||
|
||||
public void checkApiTestOwner(String testId) {
|
||||
// 关联为其他时
|
||||
if (StringUtils.equals("other", testId)) {
|
||||
return;
|
||||
}
|
||||
String workspaceId = SessionUtils.getCurrentWorkspaceId();
|
||||
QueryAPITestRequest request = new QueryAPITestRequest();
|
||||
request.setWorkspaceId(workspaceId);
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 24047fea950a74f7848a9fdaa857a22b884c4ce2
|
||||
Subproject commit 57d6f78efa4b0300be188e8b024511ceef0873ed
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE load_test_report
|
||||
ADD file_id VARCHAR(50) NULL;
|
|
@ -48,6 +48,7 @@ related_case_del_fail_prefix=Connected to
|
|||
related_case_del_fail_suffix=TestCase, please disassociate first
|
||||
jmx_content_valid=JMX content is invalid
|
||||
container_delete_fail=The container failed to stop, please try again
|
||||
load_test_report_file_not_exist=There is no JTL file in the current report, please execute it again to get it
|
||||
#workspace
|
||||
workspace_name_is_null=Workspace name cannot be null
|
||||
workspace_name_already_exists=The workspace name already exists
|
||||
|
@ -167,8 +168,8 @@ check_owner_comment=The current user does not have permission to manipulate this
|
|||
upload_content_is_null=Imported content is empty
|
||||
test_plan_notification=Test plan notification
|
||||
task_defect_notification=Task defect notification
|
||||
task_notification=Jenkins Task notification
|
||||
task_notification_=Timing task result notification
|
||||
task_notification_jenkins=Jenkins Task notification
|
||||
task_notification=Result notification
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ related_case_del_fail_prefix=已关联到
|
|||
related_case_del_fail_suffix=测试用例,请先解除关联
|
||||
jmx_content_valid=JMX 内容无效,请检查
|
||||
container_delete_fail=容器由于网络原因停止失败,请重试
|
||||
load_test_report_file_not_exist=当前报告没有JTL文件,请重新执行以便获取
|
||||
#workspace
|
||||
workspace_name_is_null=工作空间名不能为空
|
||||
workspace_name_already_exists=工作空间名已存在
|
||||
|
@ -168,5 +169,5 @@ check_owner_comment=当前用户没有操作此评论的权限
|
|||
upload_content_is_null=导入内容为空
|
||||
test_plan_notification=测试计划通知
|
||||
task_defect_notification=缺陷任务通知
|
||||
task_notification=jenkins任务通知
|
||||
task_notification_=定时任务结果通知
|
||||
task_notification_jenkins=jenkins任务通知
|
||||
task_notification=任务通知
|
|
@ -48,6 +48,7 @@ related_case_del_fail_prefix=已關聯到
|
|||
related_case_del_fail_suffix=測試用例,請先解除關聯
|
||||
jmx_content_valid=JMX 內容無效,請檢查
|
||||
container_delete_fail=容器由於網絡原因停止失敗,請重試
|
||||
load_test_report_file_not_exist=當前報告沒有JTL文件,請重新執行以便獲取
|
||||
#workspace
|
||||
workspace_name_is_null=工作空間名不能為空
|
||||
workspace_name_already_exists=工作空間名已存在
|
||||
|
@ -169,6 +170,6 @@ check_owner_comment=當前用戶沒有操作此評論的權限
|
|||
upload_content_is_null=導入內容為空
|
||||
test_plan_notification=測試計畫通知
|
||||
task_defect_notification=缺陷任務通知
|
||||
task_notification=jenkins任務通知
|
||||
task_notification_=定時任務通知
|
||||
task_notification_jenkins=jenkins任務通知
|
||||
task_notification=任務通知
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
for file in ${TESTS_DIR}/*.jmx; do
|
||||
jmeter -n -t ${file} -Jserver.rmi.ssl.disable=${SSL_DISABLED}
|
||||
jmeter -n -t ${file} -Jserver.rmi.ssl.disable=${SSL_DISABLED} -l ${TESTS_DIR}/${REPORT_ID}.jtl
|
||||
done
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
"nprogress": "^0.2.0",
|
||||
"el-table-infinite-scroll": "^1.0.10",
|
||||
"vue-pdf": "^4.2.0",
|
||||
"diffable-html": "^4.0.0"
|
||||
"diffable-html": "^4.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.0",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
|
@ -126,6 +126,7 @@ export default {
|
|||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
this.selectRows.clear();
|
||||
});
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
|
@ -171,28 +172,13 @@ export default {
|
|||
this.$set(row, "showMore", true);
|
||||
this.selectRows.add(row);
|
||||
}
|
||||
|
||||
let arr = Array.from(this.selectRows);
|
||||
|
||||
// 选中1个以上的用例时显示更多操作
|
||||
if (this.selectRows.size === 1) {
|
||||
this.$set(arr[0], "showMore", false);
|
||||
} else if (this.selectRows.size === 2) {
|
||||
arr.forEach(row => {
|
||||
this.$set(row, "showMore", true);
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
if (selection.length === 1) {
|
||||
this.selectRows.add(selection[0]);
|
||||
} else {
|
||||
this.tableData.forEach(item => {
|
||||
this.$set(item, "showMore", true);
|
||||
this.selectRows.add(item);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.selectRows.clear();
|
||||
this.tableData.forEach(row => {
|
||||
|
|
|
@ -22,16 +22,19 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||
:total="total"/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiReportStatus from "../report/ApiReportStatus";
|
||||
import MsTablePagination from "@/business/components/common/pagination/TablePagination";
|
||||
|
||||
export default {
|
||||
name: "MsApiReportDialog",
|
||||
|
||||
components: {MsApiReportStatus},
|
||||
components: {MsApiReportStatus, MsTablePagination},
|
||||
|
||||
props: ["testId"],
|
||||
|
||||
|
@ -40,7 +43,10 @@
|
|||
reportVisible: false,
|
||||
result: {},
|
||||
tableData: [],
|
||||
loading: false
|
||||
loading: false,
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -48,10 +54,7 @@
|
|||
open() {
|
||||
this.reportVisible = true;
|
||||
|
||||
let url = "/api/report/list/" + this.testId;
|
||||
this.result = this.$get(url, response => {
|
||||
this.tableData = response.data;
|
||||
});
|
||||
this.search();
|
||||
},
|
||||
link(row) {
|
||||
this.reportVisible = false;
|
||||
|
@ -59,7 +62,15 @@
|
|||
this.$router.push({
|
||||
path: '/api/report/view/' + row.id,
|
||||
})
|
||||
}
|
||||
},
|
||||
search() {
|
||||
let url = "/api/report/list/" + this.testId + "/" + this.currentPage + "/" + this.pageSize;
|
||||
this.result = this.$get(url, response => {
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -305,6 +305,7 @@
|
|||
},
|
||||
cancel() {
|
||||
this.$router.push('/api/test/list/all');
|
||||
// console.log(this.test.toJMX().xml);
|
||||
},
|
||||
handleCommand(command) {
|
||||
switch (command) {
|
||||
|
|
|
@ -52,6 +52,9 @@
|
|||
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
|
||||
<ms-tcp-config :config="scenario.tcpConfig" :is-read-only="isReadOnly"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
|
||||
<ms-api-assertions :scenario="scenario" :is-read-only="isReadOnly" :assertions="scenario.assertions"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
|
||||
|
@ -72,6 +75,7 @@ import MsDubboConsumerService from "@/business/components/api/test/components/re
|
|||
import MsDatabaseConfig from "./request/database/DatabaseConfig";
|
||||
import {parseEnvironment} from "../model/EnvironmentModel";
|
||||
import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig";
|
||||
import MsApiAssertions from "@/business/components/api/test/components/assertion/ApiAssertions";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioForm",
|
||||
|
@ -79,7 +83,8 @@ export default {
|
|||
MsTcpConfig,
|
||||
MsDatabaseConfig,
|
||||
MsDubboConsumerService,
|
||||
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue
|
||||
MsDubboConfigCenter, MsDubboRegistryCenter, ApiEnvironmentConfig, MsApiScenarioVariables, MsApiKeyValue,
|
||||
MsApiAssertions
|
||||
},
|
||||
props: {
|
||||
scenario: Scenario,
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
<div class="assertion-add">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="4">
|
||||
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')"
|
||||
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
|
||||
:placeholder="$t('api_test.request.assertions.select_type')"
|
||||
size="small">
|
||||
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
|
||||
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
|
||||
|
@ -14,13 +15,18 @@
|
|||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<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-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"/>
|
||||
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223"
|
||||
:callback="after"/>
|
||||
<el-button v-if="!type" :disabled="true" type="primary" size="small">
|
||||
{{ $t('api_test.request.assertions.add') }}
|
||||
</el-button>
|
||||
|
@ -28,7 +34,7 @@
|
|||
</el-row>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div v-if="!scenario">
|
||||
<el-row :gutter="10" class="json-path-suggest-button">
|
||||
<el-button size="small" type="primary" @click="suggestJsonOpen">
|
||||
{{ $t('api_test.request.assertions.json_path_suggest') }}
|
||||
|
@ -39,7 +45,8 @@
|
|||
</el-row>
|
||||
</div>
|
||||
|
||||
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request" ref="jsonpathSuggestList"/>
|
||||
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request"
|
||||
ref="jsonpathSuggestList"/>
|
||||
|
||||
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
|
||||
</div>
|
||||
|
@ -49,7 +56,7 @@
|
|||
import MsApiAssertionText from "./ApiAssertionText";
|
||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath} from "../../model/ScenarioModel";
|
||||
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath, Scenario} from "../../model/ScenarioModel";
|
||||
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "@/business/components/api/test/components/assertion/ApiAssertionJsr223";
|
||||
|
@ -64,11 +71,13 @@
|
|||
MsApiAssertionJsr223,
|
||||
MsApiJsonpathSuggestList,
|
||||
MsApiAssertionJsonPath,
|
||||
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText},
|
||||
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
|
||||
},
|
||||
|
||||
props: {
|
||||
assertions: Assertions,
|
||||
request: HttpRequest,
|
||||
scenario: Scenario,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Arguments,
|
||||
ConstantTimer as JMXConstantTimer,
|
||||
CookieManager,
|
||||
DNSCacheManager,
|
||||
DubboSample,
|
||||
|
@ -10,22 +11,24 @@ import {
|
|||
HTTPSamplerArguments,
|
||||
HTTPsamplerFiles,
|
||||
HTTPSamplerProxy,
|
||||
IfController as JMXIfController,
|
||||
JDBCDataSource,
|
||||
JDBCSampler,
|
||||
JSONPathAssertion,
|
||||
JSONPostProcessor,
|
||||
JSR223Assertion,
|
||||
JSR223PostProcessor,
|
||||
JSR223PreProcessor,
|
||||
RegexExtractor,
|
||||
ResponseCodeAssertion,
|
||||
ResponseDataAssertion,
|
||||
ResponseHeadersAssertion,
|
||||
TCPSampler,
|
||||
TestElement,
|
||||
TestPlan,
|
||||
ThreadGroup,
|
||||
XPath2Assertion,
|
||||
XPath2Extractor,
|
||||
IfController as JMXIfController,
|
||||
ConstantTimer as JMXConstantTimer, TCPSampler, JSR223Assertion, XPath2Assertion,
|
||||
} from "./JMX";
|
||||
import Mock from "mockjs";
|
||||
import {funcFilters} from "@/common/js/func-filter";
|
||||
|
@ -226,6 +229,7 @@ export class Scenario extends BaseConfig {
|
|||
this.enable = true;
|
||||
this.databaseConfigs = [];
|
||||
this.tcpConfig = undefined;
|
||||
this.assertions = undefined;
|
||||
|
||||
this.set(options);
|
||||
this.sets({
|
||||
|
@ -242,6 +246,7 @@ export class Scenario extends BaseConfig {
|
|||
options.databaseConfigs = options.databaseConfigs || [];
|
||||
options.dubboConfig = new DubboConfig(options.dubboConfig);
|
||||
options.tcpConfig = new TCPConfig(options.tcpConfig);
|
||||
options.assertions = new Assertions(options.assertions);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -1151,6 +1156,9 @@ class JMXGenerator {
|
|||
this.addScenarioCookieManager(threadGroup, scenario);
|
||||
|
||||
this.addJDBCDataSources(threadGroup, scenario);
|
||||
|
||||
this.addAssertion(threadGroup, scenario);
|
||||
|
||||
scenario.requests.forEach(request => {
|
||||
if (request.enable) {
|
||||
if (!request.isValid()) return;
|
||||
|
@ -1175,7 +1183,7 @@ class JMXGenerator {
|
|||
|
||||
this.addRequestExtractor(sampler, request);
|
||||
|
||||
this.addRequestAssertion(sampler, request);
|
||||
this.addAssertion(sampler, request);
|
||||
|
||||
this.addJSR223PreProcessor(sampler, request);
|
||||
|
||||
|
@ -1467,7 +1475,7 @@ class JMXGenerator {
|
|||
httpSamplerProxy.add(new HTTPsamplerFiles(files));
|
||||
}
|
||||
|
||||
addRequestAssertion(httpSamplerProxy, request) {
|
||||
addAssertion(httpSamplerProxy, request) {
|
||||
let assertions = request.assertions;
|
||||
if (assertions.regex.length > 0) {
|
||||
assertions.regex.filter(this.filter).forEach(regex => {
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
<el-button :disabled="isReadOnly" type="info" plain size="mini" @click="handleExport(reportName)">
|
||||
{{ $t('test_track.plan_view.export_report') }}
|
||||
</el-button>
|
||||
<el-button :disabled="isReadOnly" type="warning" plain size="mini" @click="downloadJtl()">
|
||||
{{ $t('report.downloadJtl') }}
|
||||
</el-button>
|
||||
|
||||
<!--<el-button :disabled="isReadOnly" type="warning" plain size="mini">-->
|
||||
<!--{{$t('report.compare')}}-->
|
||||
|
@ -95,6 +98,7 @@ import MsMainContainer from "../../common/components/MsMainContainer";
|
|||
import {checkoutTestManagerOrTestUser, exportPdf} from "@/common/js/utils";
|
||||
import html2canvas from 'html2canvas';
|
||||
import MsPerformanceReportExport from "./PerformanceReportExport";
|
||||
import {Message} from "element-ui";
|
||||
|
||||
|
||||
export default {
|
||||
|
@ -281,6 +285,34 @@ export default {
|
|||
this.reportExportVisible = false;
|
||||
this.result.loading = false;
|
||||
},
|
||||
downloadJtl() {
|
||||
let config = {
|
||||
url: "/performance/report/jtl/download/" + this.reportId,
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
};
|
||||
this.result = this.$request(config).then(response => {
|
||||
const content = response.data;
|
||||
const blob = new Blob([content]);
|
||||
if ("download" in document.createElement("a")) {
|
||||
// 非IE下载
|
||||
// chrome/firefox
|
||||
let aTag = document.createElement('a');
|
||||
aTag.download = this.reportId + ".jtl";
|
||||
aTag.href = URL.createObjectURL(blob);
|
||||
aTag.click();
|
||||
URL.revokeObjectURL(aTag.href)
|
||||
} else {
|
||||
// IE10+下载
|
||||
navigator.msSaveBlob(blob, this.filename)
|
||||
}
|
||||
}).catch(e => {
|
||||
let text = e.response.data.text();
|
||||
text.then((data) => {
|
||||
Message.error({message: JSON.parse(data).message || e.message, showClose: true});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.isReadOnly = false;
|
||||
|
|
|
@ -222,28 +222,13 @@ export default {
|
|||
this.$set(row, "showMore", true);
|
||||
this.selectRows.add(row);
|
||||
}
|
||||
|
||||
let arr = Array.from(this.selectRows);
|
||||
|
||||
// 选中1个以上的用例时显示更多操作
|
||||
if (this.selectRows.size === 1) {
|
||||
this.$set(arr[0], "showMore", false);
|
||||
} else if (this.selectRows.size === 2) {
|
||||
arr.forEach(row => {
|
||||
this.$set(row, "showMore", true);
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if (selection.length > 0) {
|
||||
if (selection.length === 1) {
|
||||
this.selectRows.add(selection[0]);
|
||||
} else {
|
||||
this.tableData.forEach(item => {
|
||||
this.$set(item, "showMore", true);
|
||||
this.selectRows.add(item);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.selectRows.clear();
|
||||
this.tableData.forEach(row => {
|
||||
|
|
|
@ -1,91 +1,73 @@
|
|||
<template>
|
||||
<div v-loading="result.loading" class="pressure-config-container">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<ms-chart class="chart-container" ref="chart1" :options="options" :autoresize="true"></ms-chart>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item :title="threadGroup.attributes.testname" :name="index"
|
||||
v-for="(threadGroup, index) in threadGroups"
|
||||
:key="index">
|
||||
<el-col :span="10">
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item :label="$t('load_test.thread_num')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:placeholder="$t('load_test.input_thread_num')"
|
||||
v-model="threadNumber"
|
||||
@change="calculateChart"
|
||||
v-model="threadGroup.threadNumber"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.duration')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:placeholder="$t('load_test.duration')"
|
||||
v-model="duration"
|
||||
v-model="threadGroup.duration"
|
||||
:min="1"
|
||||
@change="calculateChart"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-switch v-model="rpsLimitEnable" :disabled="true"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="rpsLimitEnable"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
:placeholder="$t('load_test.input_rps_limit')"
|
||||
v-model="rpsLimit"
|
||||
@change="calculateChart"
|
||||
v-model="threadGroup.rpsLimit"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form :inline="true" class="input-bottom-border">
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="duration"
|
||||
v-model="rampUpTime"
|
||||
@change="calculateChart"
|
||||
:max="threadGroup.duration"
|
||||
v-model="threadGroup.rampUpTime"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||
<el-input-number
|
||||
:disabled="true"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="Math.min(threadNumber, rampUpTime)"
|
||||
v-model="step"
|
||||
@change="calculateChart"
|
||||
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||
v-model="threadGroup.step"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
|
||||
<ms-chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></ms-chart>
|
||||
<ms-chart class="chart-container" :options="threadGroup.options" :autoresize="true"></ms-chart>
|
||||
</el-col>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -93,6 +75,7 @@
|
|||
<script>
|
||||
import echarts from "echarts";
|
||||
import MsChart from "@/business/components/common/chart/MsChart";
|
||||
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
|
||||
|
||||
const TARGET_LEVEL = "TargetLevel";
|
||||
const RAMP_UP = "RampUp";
|
||||
|
@ -100,6 +83,14 @@ const STEPS = "Steps";
|
|||
const DURATION = "duration";
|
||||
const RPS_LIMIT = "rpsLimit";
|
||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||
const hexToRgba = function (hex, opacity) {
|
||||
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
|
||||
+ parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
|
||||
}
|
||||
const hexToRgb = function (hex) {
|
||||
return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5))
|
||||
+ ',' + parseInt('0x' + hex.slice(5, 7)) + ')';
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "MsPerformancePressureConfig",
|
||||
|
@ -108,62 +99,91 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
result: {},
|
||||
threadNumber: 10,
|
||||
duration: 10,
|
||||
rampUpTime: 10,
|
||||
step: 10,
|
||||
rpsLimit: 10,
|
||||
threadNumber: 0,
|
||||
duration: 0,
|
||||
rampUpTime: 0,
|
||||
step: 0,
|
||||
rpsLimit: 0,
|
||||
rpsLimitEnable: false,
|
||||
orgOptions: {},
|
||||
options: {},
|
||||
resourcePool: null,
|
||||
resourcePools: [],
|
||||
activeNames: ["0"],
|
||||
threadGroups: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getLoadConfig();
|
||||
// this.getJmxContent();
|
||||
},
|
||||
methods: {
|
||||
calculateLoadConfiguration: function (data) {
|
||||
data.forEach(d => {
|
||||
switch (d.key) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let d = data[i];
|
||||
if (d instanceof Array) {
|
||||
d.forEach(item => {
|
||||
switch (item.key) {
|
||||
case TARGET_LEVEL:
|
||||
this.threadNumber = d.value;
|
||||
this.threadGroups[i].threadNumber = item.value;
|
||||
break;
|
||||
case RAMP_UP:
|
||||
this.rampUpTime = d.value;
|
||||
this.threadGroups[i].rampUpTime = item.value;
|
||||
break;
|
||||
case DURATION:
|
||||
this.duration = d.value;
|
||||
this.threadGroups[i].duration = item.value;
|
||||
break;
|
||||
case STEPS:
|
||||
this.step = d.value;
|
||||
this.threadGroups[i].step = item.value;
|
||||
break;
|
||||
case RPS_LIMIT:
|
||||
this.rpsLimit = d.value;
|
||||
this.threadGroups[i].rpsLimit = item.value;
|
||||
break;
|
||||
case RPS_LIMIT_ENABLE:
|
||||
this.threadGroups[i].rpsLimitEnable = item.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.threadNumber = this.threadNumber || 10;
|
||||
this.duration = this.duration || 30;
|
||||
this.rampUpTime = this.rampUpTime || 12;
|
||||
this.step = this.step || 3;
|
||||
this.rpsLimit = this.rpsLimit || 10;
|
||||
|
||||
this.calculateChart();
|
||||
})
|
||||
this.calculateChart(this.threadGroups[i]);
|
||||
} else {
|
||||
switch (d.key) {
|
||||
case TARGET_LEVEL:
|
||||
this.threadGroups[0].threadNumber = d.value;
|
||||
break;
|
||||
case RAMP_UP:
|
||||
this.threadGroups[0].rampUpTime = d.value;
|
||||
break;
|
||||
case DURATION:
|
||||
this.threadGroups[0].duration = d.value;
|
||||
break;
|
||||
case STEPS:
|
||||
this.threadGroups[0].step = d.value;
|
||||
break;
|
||||
case RPS_LIMIT:
|
||||
this.threadGroups[0].rpsLimit = d.value;
|
||||
break;
|
||||
case RPS_LIMIT_ENABLE:
|
||||
this.threadGroups[0].rpsLimitEnable = d.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.calculateChart(this.threadGroups[0]);
|
||||
}
|
||||
}
|
||||
},
|
||||
getLoadConfig() {
|
||||
if (!this.report.id) {
|
||||
return;
|
||||
}
|
||||
this.$get("/performance/report/" + this.report.id, res => {
|
||||
this.result = this.$get("/performance/report/" + this.report.id, res => {
|
||||
let data = res.data;
|
||||
if (data) {
|
||||
if (data.loadConfiguration) {
|
||||
let d = JSON.parse(data.loadConfiguration);
|
||||
this.calculateLoadConfiguration(d);
|
||||
} else {
|
||||
this.$get('/performance/get-load-config/' + this.report.testId, (response) => {
|
||||
this.$get('/performance/get-load-config/' + this.report.id, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
this.calculateLoadConfiguration(data);
|
||||
|
@ -175,14 +195,127 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
calculateChart() {
|
||||
if (this.duration < this.rampUpTime) {
|
||||
this.rampUpTime = this.duration;
|
||||
getJmxContent() {
|
||||
console.log(this.report.testId);
|
||||
if (!this.report.testId) {
|
||||
return;
|
||||
}
|
||||
if (this.rampUpTime < this.step) {
|
||||
this.step = this.rampUpTime;
|
||||
this.result = this.$get('/performance/get-jmx-content/' + this.report.testId, (response) => {
|
||||
if (response.data) {
|
||||
this.threadGroups = findThreadGroup(response.data);
|
||||
this.threadGroups.forEach(tg => {
|
||||
tg.options = {};
|
||||
});
|
||||
this.getLoadConfig();
|
||||
}
|
||||
this.orgOptions = {
|
||||
});
|
||||
},
|
||||
calculateTotalChart() {
|
||||
let handler = this;
|
||||
if (handler.duration < handler.rampUpTime) {
|
||||
handler.rampUpTime = handler.duration;
|
||||
}
|
||||
if (handler.rampUpTime < handler.step) {
|
||||
handler.step = handler.rampUpTime;
|
||||
}
|
||||
handler.options = {
|
||||
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: []
|
||||
};
|
||||
|
||||
|
||||
for (let i = 0; i < handler.threadGroups.length; i++) {
|
||||
let seriesData = {
|
||||
name: handler.threadGroups[i].attributes.testname,
|
||||
data: [],
|
||||
type: 'line',
|
||||
step: 'start',
|
||||
smooth: false,
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: hexToRgba(handler.options.color[i], 0.3),
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: hexToRgba(handler.options.color[i], 0),
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: hexToRgb(handler.options.color[i]),
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let tg = handler.threadGroups[i];
|
||||
|
||||
let timePeriod = Math.floor(tg.rampUpTime / tg.step);
|
||||
let timeInc = timePeriod;
|
||||
|
||||
let threadPeriod = Math.floor(tg.threadNumber / tg.step);
|
||||
let threadInc1 = Math.floor(tg.threadNumber / tg.step);
|
||||
let threadInc2 = Math.ceil(tg.threadNumber / tg.step);
|
||||
let inc2count = tg.threadNumber - tg.step * threadInc1;
|
||||
for (let j = 0; j <= tg.duration; j++) {
|
||||
|
||||
if (j > timePeriod) {
|
||||
timePeriod += timeInc;
|
||||
if (inc2count > 0) {
|
||||
threadPeriod = threadPeriod + threadInc2;
|
||||
inc2count--;
|
||||
} else {
|
||||
threadPeriod = threadPeriod + threadInc1;
|
||||
}
|
||||
if (threadPeriod > tg.threadNumber) {
|
||||
threadPeriod = tg.threadNumber;
|
||||
}
|
||||
}
|
||||
// x 轴
|
||||
let xAxis = handler.options.xAxis.data;
|
||||
if (xAxis.indexOf(j) < 0) {
|
||||
xAxis.push(j);
|
||||
}
|
||||
seriesData.data.push(threadPeriod);
|
||||
}
|
||||
handler.options.series.push(seriesData);
|
||||
}
|
||||
},
|
||||
calculateChart(threadGroup) {
|
||||
let handler = this;
|
||||
if (threadGroup) {
|
||||
handler = threadGroup;
|
||||
}
|
||||
if (handler.duration < handler.rampUpTime) {
|
||||
handler.rampUpTime = handler.duration;
|
||||
}
|
||||
if (handler.rampUpTime < handler.step) {
|
||||
handler.step = handler.rampUpTime;
|
||||
}
|
||||
handler.options = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
|
@ -235,16 +368,16 @@ export default {
|
|||
},
|
||||
}]
|
||||
};
|
||||
let timePeriod = Math.floor(this.rampUpTime / this.step);
|
||||
let timePeriod = Math.floor(handler.rampUpTime / handler.step);
|
||||
let timeInc = timePeriod;
|
||||
|
||||
let threadPeriod = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc1 = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc2 = Math.ceil(this.threadNumber / this.step);
|
||||
let inc2count = this.threadNumber - this.step * threadInc1;
|
||||
for (let i = 0; i <= this.duration; i++) {
|
||||
let threadPeriod = Math.floor(handler.threadNumber / handler.step);
|
||||
let threadInc1 = Math.floor(handler.threadNumber / handler.step);
|
||||
let threadInc2 = Math.ceil(handler.threadNumber / handler.step);
|
||||
let inc2count = handler.threadNumber - handler.step * threadInc1;
|
||||
for (let i = 0; i <= handler.duration; i++) {
|
||||
// x 轴
|
||||
this.orgOptions.xAxis.data.push(i);
|
||||
handler.options.xAxis.data.push(i);
|
||||
if (i > timePeriod) {
|
||||
timePeriod += timeInc;
|
||||
if (inc2count > 0) {
|
||||
|
@ -253,25 +386,22 @@ export default {
|
|||
} else {
|
||||
threadPeriod = threadPeriod + threadInc1;
|
||||
}
|
||||
if (threadPeriod > this.threadNumber) {
|
||||
threadPeriod = this.threadNumber;
|
||||
if (threadPeriod > handler.threadNumber) {
|
||||
threadPeriod = handler.threadNumber;
|
||||
}
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
handler.options.series[0].data.push(threadPeriod);
|
||||
} else {
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
handler.options.series[0].data.push(threadPeriod);
|
||||
}
|
||||
}
|
||||
this.calculateTotalChart();
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
report: {
|
||||
handler(val) {
|
||||
if (!val.testId) {
|
||||
return;
|
||||
}
|
||||
this.getLoadConfig();
|
||||
'report.testId': {
|
||||
handler() {
|
||||
this.getJmxContent();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,6 +425,7 @@ export default {
|
|||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.el-col .el-form {
|
||||
|
|
|
@ -2,8 +2,8 @@ import MsProject from "@/business/components/project/MsProject";
|
|||
|
||||
const PerformanceTest = () => import('@/business/components/performance/PerformanceTest')
|
||||
const PerformanceTestHome = () => import('@/business/components/performance/home/PerformanceTestHome')
|
||||
const EditPerformanceTestPlan = () => import('@/business/components/performance/test/EditPerformanceTestPlan')
|
||||
const PerformanceTestPlan = () => import('@/business/components/performance/test/PerformanceTestPlan')
|
||||
const EditPerformanceTest = () => import('@/business/components/performance/test/EditPerformanceTest')
|
||||
const PerformanceTestList = () => import('@/business/components/performance/test/PerformanceTestList')
|
||||
const PerformanceTestReport = () => import('@/business/components/performance/report/PerformanceTestReport')
|
||||
const PerformanceChart = () => import('@/business/components/performance/report/components/PerformanceChart')
|
||||
const PerformanceReportView = () => import('@/business/components/performance/report/PerformanceReportView')
|
||||
|
@ -24,12 +24,12 @@ export default {
|
|||
{
|
||||
path: 'test/create',
|
||||
name: "createPerTest",
|
||||
component: EditPerformanceTestPlan,
|
||||
component: EditPerformanceTest,
|
||||
},
|
||||
{
|
||||
path: "test/edit/:testId",
|
||||
name: "editPerTest",
|
||||
component: EditPerformanceTestPlan,
|
||||
component: EditPerformanceTest,
|
||||
props: {
|
||||
content: (route) => {
|
||||
return {
|
||||
|
@ -41,7 +41,7 @@ export default {
|
|||
{
|
||||
path: "test/:projectId",
|
||||
name: "perPlan",
|
||||
component: PerformanceTestPlan
|
||||
component: PerformanceTestList
|
||||
},
|
||||
{
|
||||
path: "project/:type",
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
<el-card v-loading="result.loading">
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
<el-input :disabled="isReadOnly" :placeholder="$t('load_test.input_name')" v-model="testPlan.name"
|
||||
<el-input :disabled="isReadOnly" :placeholder="$t('load_test.input_name')" v-model="test.name"
|
||||
class="input-with-select"
|
||||
maxlength="30" show-word-limit
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<el-select filterable v-model="testPlan.projectId"
|
||||
<el-select filterable v-model="test.projectId"
|
||||
:placeholder="$t('load_test.select_project')">
|
||||
<el-option
|
||||
v-for="item in projects"
|
||||
|
@ -29,7 +29,7 @@
|
|||
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{ $t('commons.cancel') }}
|
||||
</el-button>
|
||||
|
||||
<ms-schedule-config :schedule="testPlan.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
|
||||
<ms-schedule-config :schedule="test.schedule" :save="saveCronExpression" @scheduleChange="saveSchedule"
|
||||
:check-open="checkScheduleEdit" :test-id="testId" :custom-validate="durationValidate"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
@ -37,10 +37,11 @@
|
|||
|
||||
<el-tabs class="testplan-config" v-model="active" type="border-card" :stretch="true">
|
||||
<el-tab-pane :label="$t('load_test.basic_config')">
|
||||
<performance-basic-config :is-read-only="isReadOnly" :test-plan="testPlan" ref="basicConfig"/>
|
||||
<performance-basic-config :is-read-only="isReadOnly" :test="test" ref="basicConfig"
|
||||
@fileChange="fileChange"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('load_test.pressure_config')">
|
||||
<performance-pressure-config :is-read-only="isReadOnly" :test-plan="testPlan" :test-id="testId"
|
||||
<performance-pressure-config :is-read-only="isReadOnly" :test="test" :test-id="testId"
|
||||
ref="pressureConfig" @changeActive="changeTabActive"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
|
||||
|
@ -63,7 +64,7 @@ import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
|||
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
|
||||
|
||||
export default {
|
||||
name: "EditPerformanceTestPlan",
|
||||
name: "EditPerformanceTest",
|
||||
components: {
|
||||
MsScheduleConfig,
|
||||
PerformancePressureConfig,
|
||||
|
@ -75,7 +76,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
result: {},
|
||||
testPlan: {schedule: {}},
|
||||
test: {schedule: {}},
|
||||
listProjectPath: "/project/listAll",
|
||||
savePath: "/performance/save",
|
||||
editPath: "/performance/edit",
|
||||
|
@ -136,8 +137,8 @@ export default {
|
|||
importAPITest() {
|
||||
let apiTest = this.$store.state.api.test;
|
||||
if (apiTest && apiTest.name) {
|
||||
this.$set(this.testPlan, "projectId", apiTest.projectId);
|
||||
this.$set(this.testPlan, "name", apiTest.name);
|
||||
this.$set(this.test, "projectId", apiTest.projectId);
|
||||
this.$set(this.test, "name", apiTest.name);
|
||||
let blob = new Blob([apiTest.jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], apiTest.jmx.name);
|
||||
this.$refs.basicConfig.beforeUpload(file);
|
||||
|
@ -151,9 +152,9 @@ export default {
|
|||
this.testId = testId;
|
||||
this.result = this.$get('/performance/get/' + testId, response => {
|
||||
if (response.data) {
|
||||
this.testPlan = response.data;
|
||||
if (!this.testPlan.schedule) {
|
||||
this.testPlan.schedule = {};
|
||||
this.test = response.data;
|
||||
if (!this.test.schedule) {
|
||||
this.test.schedule = {};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -165,7 +166,7 @@ export default {
|
|||
})
|
||||
},
|
||||
save() {
|
||||
if (!this.validTestPlan()) {
|
||||
if (!this.validTest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -180,16 +181,16 @@ export default {
|
|||
});
|
||||
},
|
||||
saveAndRun() {
|
||||
if (!this.validTestPlan()) {
|
||||
if (!this.validTest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = this.getSaveOption();
|
||||
|
||||
this.result = this.$request(options, (response) => {
|
||||
this.testPlan.id = response.data;
|
||||
this.test.id = response.data;
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.result = this.$post(this.runPath, {id: this.testPlan.id, triggerMode: 'MANUAL'}, (response) => {
|
||||
this.result = this.$post(this.runPath, {id: this.test.id, triggerMode: 'MANUAL'}, (response) => {
|
||||
let reportId = response.data;
|
||||
this.$router.push({path: '/performance/report/view/' + reportId})
|
||||
// 发送广播,刷新 head 上的最新列表
|
||||
|
@ -199,7 +200,7 @@ export default {
|
|||
},
|
||||
getSaveOption() {
|
||||
let formData = new FormData();
|
||||
let url = this.testPlan.id ? this.editPath : this.savePath;
|
||||
let url = this.test.id ? this.editPath : this.savePath;
|
||||
|
||||
if (this.$refs.basicConfig.uploadList.length > 0) {
|
||||
this.$refs.basicConfig.uploadList.forEach(f => {
|
||||
|
@ -207,15 +208,15 @@ export default {
|
|||
});
|
||||
}
|
||||
// 基本配置
|
||||
this.testPlan.updatedFileList = this.$refs.basicConfig.updatedFileList();
|
||||
this.test.updatedFileList = this.$refs.basicConfig.updatedFileList();
|
||||
// 压力配置
|
||||
this.testPlan.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
|
||||
this.testPlan.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
|
||||
this.test.loadConfiguration = JSON.stringify(this.$refs.pressureConfig.convertProperty());
|
||||
this.test.testResourcePoolId = this.$refs.pressureConfig.resourcePool;
|
||||
// 高级配置
|
||||
this.testPlan.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
|
||||
this.test.advancedConfiguration = JSON.stringify(this.$refs.advancedConfig.configurations());
|
||||
|
||||
// file属性不需要json化
|
||||
let requestJson = JSON.stringify(this.testPlan, function (key, value) {
|
||||
let requestJson = JSON.stringify(this.test, function (key, value) {
|
||||
return key === "file" ? undefined : value
|
||||
});
|
||||
|
||||
|
@ -235,13 +236,13 @@ export default {
|
|||
cancel() {
|
||||
this.$router.push({path: '/performance/test/all'})
|
||||
},
|
||||
validTestPlan() {
|
||||
if (!this.testPlan.name) {
|
||||
validTest() {
|
||||
if (!this.test.name) {
|
||||
this.$error(this.$t('load_test.test_name_is_null'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.testPlan.projectId) {
|
||||
if (!this.test.projectId) {
|
||||
this.$error(this.$t('load_test.project_is_null'));
|
||||
return false;
|
||||
}
|
||||
|
@ -268,26 +269,26 @@ export default {
|
|||
});
|
||||
},
|
||||
saveCronExpression(cronExpression) {
|
||||
this.testPlan.schedule.enable = true;
|
||||
this.testPlan.schedule.value = cronExpression;
|
||||
this.test.schedule.enable = true;
|
||||
this.test.schedule.value = cronExpression;
|
||||
this.saveSchedule();
|
||||
},
|
||||
saveSchedule() {
|
||||
this.checkScheduleEdit();
|
||||
let param = {};
|
||||
param = this.testPlan.schedule;
|
||||
param.resourceId = this.testPlan.id;
|
||||
param = this.test.schedule;
|
||||
param.resourceId = this.test.id;
|
||||
let url = '/performance/schedule/create';
|
||||
if (param.id) {
|
||||
url = '/performance/schedule/update';
|
||||
}
|
||||
this.$post(url, param, response => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.getTest(this.testPlan.id);
|
||||
this.getTest(this.test.id);
|
||||
});
|
||||
},
|
||||
checkScheduleEdit() {
|
||||
if (!this.testPlan.id) {
|
||||
if (!this.test.id) {
|
||||
this.$message(this.$t('api_test.environment.please_save_test'));
|
||||
return false;
|
||||
}
|
||||
|
@ -304,6 +305,18 @@ export default {
|
|||
return {
|
||||
pass: true
|
||||
}
|
||||
},
|
||||
fileChange(threadGroups) {
|
||||
let handler = this.$refs.pressureConfig;
|
||||
handler.threadGroups = threadGroups;
|
||||
threadGroups.forEach(tg => {
|
||||
tg.threadNumber = tg.threadNumber || 10;
|
||||
tg.duration = tg.duration || 10;
|
||||
tg.rampUpTime = tg.rampUpTime || 5;
|
||||
tg.step = tg.step || 5;
|
||||
tg.rpsLimit = tg.rpsLimit || 10;
|
||||
handler.calculateChart(tg);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ import MsContainer from "../../common/components/MsContainer";
|
|||
import MsMainContainer from "../../common/components/MsMainContainer";
|
||||
import MsPerformanceTestStatus from "./PerformanceTestStatus";
|
||||
import MsTableOperators from "../../common/components/MsTableOperators";
|
||||
import {_filter, _sort} from "../../../../common/js/utils";
|
||||
import {_filter, _sort} from "@/common/js/utils";
|
||||
import MsTableHeader from "../../common/components/MsTableHeader";
|
||||
import {TEST_CONFIGS} from "../../common/components/search/search-components";
|
||||
import {LIST_CHANGE, PerformanceEvent} from "@/business/components/common/head/ListEvent";
|
||||
|
@ -164,30 +164,30 @@ export default {
|
|||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val;
|
||||
},
|
||||
handleEdit(testPlan) {
|
||||
handleEdit(test) {
|
||||
this.$router.push({
|
||||
path: '/performance/test/edit/' + testPlan.id,
|
||||
path: '/performance/test/edit/' + test.id,
|
||||
})
|
||||
},
|
||||
handleCopy(testPlan) {
|
||||
this.result = this.$post("/performance/copy", {id: testPlan.id}, () => {
|
||||
handleCopy(test) {
|
||||
this.result = this.$post("/performance/copy", {id: test.id}, () => {
|
||||
this.$success(this.$t('commons.copy_success'));
|
||||
this.search();
|
||||
});
|
||||
},
|
||||
handleDelete(testPlan) {
|
||||
this.$alert(this.$t('load_test.delete_confirm') + testPlan.name + "?", '', {
|
||||
handleDelete(test) {
|
||||
this.$alert(this.$t('load_test.delete_confirm') + test.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(testPlan);
|
||||
this._handleDelete(test);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleDelete(testPlan) {
|
||||
_handleDelete(test) {
|
||||
let data = {
|
||||
id: testPlan.id
|
||||
id: test.id
|
||||
};
|
||||
|
||||
this.result = this.$post(this.deletePath, data, () => {
|
|
@ -56,11 +56,12 @@
|
|||
|
||||
<script>
|
||||
import {Message} from "element-ui";
|
||||
import {findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
|
||||
|
||||
export default {
|
||||
name: "PerformanceBasicConfig",
|
||||
props: {
|
||||
testPlan: {
|
||||
test: {
|
||||
type: Object
|
||||
},
|
||||
isReadOnly: {
|
||||
|
@ -81,23 +82,36 @@ export default {
|
|||
};
|
||||
},
|
||||
created() {
|
||||
if (this.testPlan.id) {
|
||||
this.getFileMetadata(this.testPlan)
|
||||
if (this.test.id) {
|
||||
this.getFileMetadata(this.test)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testPlan() {
|
||||
if (this.testPlan.id) {
|
||||
this.getFileMetadata(this.testPlan)
|
||||
test() {
|
||||
if (this.test.id) {
|
||||
this.getFileMetadata(this.test)
|
||||
}
|
||||
},
|
||||
uploadList() {
|
||||
let self = this;
|
||||
let fileList = self.uploadList.filter(f => f.name.endsWith(".jmx"));
|
||||
if (fileList.length > 0) {
|
||||
let file = fileList[0];
|
||||
let jmxReader = new FileReader();
|
||||
jmxReader.onload = function (event) {
|
||||
let threadGroups = findThreadGroup(event.target.result);
|
||||
self.$emit('fileChange', threadGroups);
|
||||
};
|
||||
jmxReader.readAsText(file);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFileMetadata(testPlan) {
|
||||
getFileMetadata(test) {
|
||||
this.fileList = [];
|
||||
this.tableData = [];
|
||||
this.uploadList = [];
|
||||
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
|
||||
this.result = this.$get(this.getFileMetadataPath + "/" + test.id, response => {
|
||||
let files = response.data;
|
||||
|
||||
if (!files) {
|
||||
|
|
|
@ -1,91 +1,9 @@
|
|||
<template>
|
||||
<div v-loading="result.loading" class="pressure-config-container">
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
<el-col>
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{ $t('load_test.thread_num') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:placeholder="$t('load_test.input_thread_num')"
|
||||
v-model="threadNumber"
|
||||
@change="calculateChart"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{ $t('load_test.duration') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:placeholder="$t('load_test.duration')"
|
||||
v-model="duration"
|
||||
:min="1"
|
||||
@change="calculateChart"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form :inline="true">
|
||||
<el-form-item>
|
||||
<el-form-item>
|
||||
<div class="config-form-label">{{ $t('load_test.rps_limit') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-switch v-model="rpsLimitEnable"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
:disabled="isReadOnly || !rpsLimitEnable"
|
||||
:placeholder="$t('load_test.input_rps_limit')"
|
||||
v-model="rpsLimit"
|
||||
@change="calculateChart"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form :inline="true" class="input-bottom-border">
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.ramp_up_time_within') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="duration"
|
||||
v-model="rampUpTime"
|
||||
@change="calculateChart"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.ramp_up_time_minutes') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="Math.min(threadNumber, rampUpTime)"
|
||||
v-model="step"
|
||||
@change="calculateChart"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.ramp_up_time_times') }}</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-form :inline="true" class="input-bottom-border">
|
||||
<el-form-item>
|
||||
<div>{{ $t('load_test.select_resource_pool') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item :label="$t('load_test.select_resource_pool')">
|
||||
<el-select v-model="resourcePool" :disabled="isReadOnly" size="mini">
|
||||
<el-option
|
||||
v-for="item in resourcePools"
|
||||
|
@ -96,11 +14,77 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<ms-chart class="chart-container" ref="chart1" :options="options" :autoresize="true"></ms-chart>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item :title="threadGroup.attributes.testname" :name="index"
|
||||
v-for="(threadGroup, index) in threadGroups"
|
||||
:key="index">
|
||||
<el-col :span="10">
|
||||
<el-form :inline="true">
|
||||
<el-form-item :label="$t('load_test.thread_num')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:placeholder="$t('load_test.input_thread_num')"
|
||||
v-model="threadGroup.threadNumber"
|
||||
@change="calculateChart(threadGroup)"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.duration')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
:placeholder="$t('load_test.duration')"
|
||||
v-model="threadGroup.duration"
|
||||
:min="1"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.rps_limit')">
|
||||
<el-switch v-model="threadGroup.rpsLimitEnable" @change="calculateTotalChart()"/>
|
||||
|
||||
<el-input-number
|
||||
:disabled="isReadOnly || !threadGroup.rpsLimitEnable"
|
||||
:placeholder="$t('load_test.input_rps_limit')"
|
||||
v-model="threadGroup.rpsLimit"
|
||||
@change="calculateChart(threadGroup)"
|
||||
:min="1"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<br>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_within')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="threadGroup.duration"
|
||||
v-model="threadGroup.rampUpTime"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_minutes')">
|
||||
<el-input-number
|
||||
:disabled="isReadOnly"
|
||||
placeholder=""
|
||||
:min="1"
|
||||
:max="Math.min(threadGroup.threadNumber, threadGroup.rampUpTime)"
|
||||
v-model="threadGroup.step"
|
||||
@change="calculateChart(threadGroup)"
|
||||
size="mini"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('load_test.ramp_up_time_times')"/>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<div class="title">{{ $t('load_test.pressure_prediction_chart') }}</div>
|
||||
<ms-chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></ms-chart>
|
||||
<ms-chart class="chart-container" :options="threadGroup.options" :autoresize="true"></ms-chart>
|
||||
</el-col>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -108,6 +92,7 @@
|
|||
<script>
|
||||
import echarts from "echarts";
|
||||
import MsChart from "@/business/components/common/chart/MsChart";
|
||||
import {findTestPlan, findThreadGroup} from "@/business/components/performance/test/model/ThreadGroup";
|
||||
|
||||
const TARGET_LEVEL = "TargetLevel";
|
||||
const RAMP_UP = "RampUp";
|
||||
|
@ -115,12 +100,22 @@ const STEPS = "Steps";
|
|||
const DURATION = "duration";
|
||||
const RPS_LIMIT = "rpsLimit";
|
||||
const RPS_LIMIT_ENABLE = "rpsLimitEnable";
|
||||
const HOLD = "Hold";
|
||||
|
||||
const hexToRgba = function (hex, opacity) {
|
||||
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ','
|
||||
+ parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')';
|
||||
}
|
||||
const hexToRgb = function (hex) {
|
||||
return 'rgb(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5))
|
||||
+ ',' + parseInt('0x' + hex.slice(5, 7)) + ')';
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "PerformancePressureConfig",
|
||||
components: {MsChart},
|
||||
props: {
|
||||
testPlan: {
|
||||
test: {
|
||||
type: Object
|
||||
},
|
||||
testId: {
|
||||
|
@ -134,35 +129,38 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
result: {},
|
||||
threadNumber: 10,
|
||||
duration: 10,
|
||||
rampUpTime: 10,
|
||||
step: 10,
|
||||
rpsLimit: 10,
|
||||
threadNumber: 0,
|
||||
duration: 0,
|
||||
rampUpTime: 0,
|
||||
step: 0,
|
||||
rpsLimit: 0,
|
||||
rpsLimitEnable: false,
|
||||
orgOptions: {},
|
||||
options: {},
|
||||
resourcePool: null,
|
||||
resourcePools: [],
|
||||
activeNames: ["0"],
|
||||
threadGroups: [],
|
||||
serializeThreadgroups: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.testId) {
|
||||
this.getLoadConfig();
|
||||
this.getJmxContent();
|
||||
} else {
|
||||
this.calculateChart();
|
||||
this.calculateTotalChart();
|
||||
}
|
||||
this.resourcePool = this.testPlan.testResourcePoolId;
|
||||
this.resourcePool = this.test.testResourcePoolId;
|
||||
this.getResourcePools();
|
||||
},
|
||||
watch: {
|
||||
testPlan(n) {
|
||||
test(n) {
|
||||
this.resourcePool = n.testResourcePoolId;
|
||||
},
|
||||
testId() {
|
||||
if (this.testId) {
|
||||
this.getLoadConfig();
|
||||
this.getJmxContent();
|
||||
} else {
|
||||
this.calculateChart();
|
||||
this.calculateTotalChart();
|
||||
}
|
||||
this.getResourcePools();
|
||||
},
|
||||
|
@ -178,56 +176,191 @@ export default {
|
|||
})
|
||||
},
|
||||
getLoadConfig() {
|
||||
if (this.testId) {
|
||||
|
||||
this.$get('/performance/get-load-config/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let data = JSON.parse(response.data);
|
||||
|
||||
data.forEach(d => {
|
||||
switch (d.key) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let d = data[i];
|
||||
if (d instanceof Array) {
|
||||
d.forEach(item => {
|
||||
switch (item.key) {
|
||||
case TARGET_LEVEL:
|
||||
this.threadNumber = d.value;
|
||||
this.threadGroups[i].threadNumber = item.value;
|
||||
break;
|
||||
case RAMP_UP:
|
||||
this.rampUpTime = d.value;
|
||||
this.threadGroups[i].rampUpTime = item.value;
|
||||
break;
|
||||
case DURATION:
|
||||
this.duration = d.value;
|
||||
this.threadGroups[i].duration = item.value;
|
||||
break;
|
||||
case STEPS:
|
||||
this.step = d.value;
|
||||
this.threadGroups[i].step = item.value;
|
||||
break;
|
||||
case RPS_LIMIT:
|
||||
this.rpsLimit = d.value;
|
||||
this.threadGroups[i].rpsLimit = item.value;
|
||||
break;
|
||||
case RPS_LIMIT_ENABLE:
|
||||
this.rpsLimitEnable = d.value;
|
||||
this.threadGroups[i].rpsLimitEnable = item.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
this.calculateChart(this.threadGroups[i]);
|
||||
} else {
|
||||
switch (d.key) {
|
||||
case TARGET_LEVEL:
|
||||
this.threadGroups[0].threadNumber = d.value;
|
||||
break;
|
||||
case RAMP_UP:
|
||||
this.threadGroups[0].rampUpTime = d.value;
|
||||
break;
|
||||
case DURATION:
|
||||
this.threadGroups[0].duration = d.value;
|
||||
break;
|
||||
case STEPS:
|
||||
this.threadGroups[0].step = d.value;
|
||||
break;
|
||||
case RPS_LIMIT:
|
||||
this.threadGroups[0].rpsLimit = d.value;
|
||||
break;
|
||||
case RPS_LIMIT_ENABLE:
|
||||
this.threadGroups[0].rpsLimitEnable = d.value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.calculateChart(this.threadGroups[0]);
|
||||
}
|
||||
}
|
||||
this.calculateTotalChart();
|
||||
}
|
||||
});
|
||||
|
||||
this.threadNumber = this.threadNumber || 10;
|
||||
this.duration = this.duration || 30;
|
||||
this.rampUpTime = this.rampUpTime || 12;
|
||||
this.step = this.step || 3;
|
||||
this.rpsLimit = this.rpsLimit || 10;
|
||||
|
||||
this.calculateChart();
|
||||
},
|
||||
getJmxContent() {
|
||||
if (this.testId) {
|
||||
this.$get('/performance/get-jmx-content/' + this.testId, (response) => {
|
||||
if (response.data) {
|
||||
let testPlan = findTestPlan(response.data);
|
||||
testPlan.elements.forEach(e => {
|
||||
if (e.attributes.name === 'TestPlan.serialize_threadgroups') {
|
||||
this.serializeThreadgroups = Boolean(e.elements[0].text);
|
||||
}
|
||||
});
|
||||
this.threadGroups = findThreadGroup(response.data);
|
||||
this.threadGroups.forEach(tg => {
|
||||
tg.options = {};
|
||||
});
|
||||
this.getLoadConfig();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
calculateChart() {
|
||||
if (this.duration < this.rampUpTime) {
|
||||
this.rampUpTime = this.duration;
|
||||
calculateTotalChart() {
|
||||
let handler = this;
|
||||
if (handler.duration < handler.rampUpTime) {
|
||||
handler.rampUpTime = handler.duration;
|
||||
}
|
||||
if (this.rampUpTime < this.step) {
|
||||
this.step = this.rampUpTime;
|
||||
if (handler.rampUpTime < handler.step) {
|
||||
handler.step = handler.rampUpTime;
|
||||
}
|
||||
this.orgOptions = {
|
||||
handler.options = {
|
||||
color: ['#60acfc', '#32d3eb', '#5bc49f', '#feb64d', '#ff7c7c', '#9287e7', '#ca8622', '#bda29a', '#6e7074', '#546570', '#c4ccd3'],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: []
|
||||
};
|
||||
|
||||
for (let i = 0; i < handler.threadGroups.length; i++) {
|
||||
let seriesData = {
|
||||
name: handler.threadGroups[i].attributes.testname,
|
||||
data: [],
|
||||
type: 'line',
|
||||
step: 'start',
|
||||
smooth: false,
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: hexToRgba(handler.options.color[i], 0.3),
|
||||
}, {
|
||||
offset: 0.8,
|
||||
color: hexToRgba(handler.options.color[i], 0),
|
||||
}], false),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: hexToRgb(handler.options.color[i]),
|
||||
borderColor: 'rgba(137,189,2,0.27)',
|
||||
borderWidth: 12
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let tg = handler.threadGroups[i];
|
||||
|
||||
let timePeriod = Math.floor(tg.rampUpTime / tg.step);
|
||||
let timeInc = timePeriod;
|
||||
|
||||
let threadPeriod = Math.floor(tg.threadNumber / tg.step);
|
||||
let threadInc1 = Math.floor(tg.threadNumber / tg.step);
|
||||
let threadInc2 = Math.ceil(tg.threadNumber / tg.step);
|
||||
let inc2count = tg.threadNumber - tg.step * threadInc1;
|
||||
for (let j = 0; j <= tg.duration; j++) {
|
||||
|
||||
if (j > timePeriod) {
|
||||
timePeriod += timeInc;
|
||||
if (inc2count > 0) {
|
||||
threadPeriod = threadPeriod + threadInc2;
|
||||
inc2count--;
|
||||
} else {
|
||||
threadPeriod = threadPeriod + threadInc1;
|
||||
}
|
||||
if (threadPeriod > tg.threadNumber) {
|
||||
threadPeriod = tg.threadNumber;
|
||||
}
|
||||
}
|
||||
// x 轴
|
||||
let xAxis = handler.options.xAxis.data;
|
||||
if (xAxis.indexOf(j) < 0) {
|
||||
xAxis.push(j);
|
||||
}
|
||||
seriesData.data.push(threadPeriod);
|
||||
}
|
||||
handler.options.series.push(seriesData);
|
||||
}
|
||||
},
|
||||
calculateChart(threadGroup) {
|
||||
let handler = this;
|
||||
if (threadGroup) {
|
||||
handler = threadGroup;
|
||||
}
|
||||
if (handler.duration < handler.rampUpTime) {
|
||||
handler.rampUpTime = handler.duration;
|
||||
}
|
||||
if (handler.rampUpTime < handler.step) {
|
||||
handler.step = handler.rampUpTime;
|
||||
}
|
||||
handler.options = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
|
@ -280,16 +413,16 @@ export default {
|
|||
},
|
||||
}]
|
||||
};
|
||||
let timePeriod = Math.floor(this.rampUpTime / this.step);
|
||||
let timePeriod = Math.floor(handler.rampUpTime / handler.step);
|
||||
let timeInc = timePeriod;
|
||||
|
||||
let threadPeriod = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc1 = Math.floor(this.threadNumber / this.step);
|
||||
let threadInc2 = Math.ceil(this.threadNumber / this.step);
|
||||
let inc2count = this.threadNumber - this.step * threadInc1;
|
||||
for (let i = 0; i <= this.duration; i++) {
|
||||
let threadPeriod = Math.floor(handler.threadNumber / handler.step);
|
||||
let threadInc1 = Math.floor(handler.threadNumber / handler.step);
|
||||
let threadInc2 = Math.ceil(handler.threadNumber / handler.step);
|
||||
let inc2count = handler.threadNumber - handler.step * threadInc1;
|
||||
for (let i = 0; i <= handler.duration; i++) {
|
||||
// x 轴
|
||||
this.orgOptions.xAxis.data.push(i);
|
||||
handler.options.xAxis.data.push(i);
|
||||
if (i > timePeriod) {
|
||||
timePeriod += timeInc;
|
||||
if (inc2count > 0) {
|
||||
|
@ -298,14 +431,15 @@ export default {
|
|||
} else {
|
||||
threadPeriod = threadPeriod + threadInc1;
|
||||
}
|
||||
if (threadPeriod > this.threadNumber) {
|
||||
threadPeriod = this.threadNumber;
|
||||
if (threadPeriod > handler.threadNumber) {
|
||||
threadPeriod = handler.threadNumber;
|
||||
}
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
handler.options.series[0].data.push(threadPeriod);
|
||||
} else {
|
||||
this.orgOptions.series[0].data.push(threadPeriod);
|
||||
handler.options.series[0].data.push(threadPeriod);
|
||||
}
|
||||
}
|
||||
this.calculateTotalChart();
|
||||
},
|
||||
validConfig() {
|
||||
if (!this.resourcePool) {
|
||||
|
@ -315,24 +449,32 @@ export default {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!this.threadNumber || !this.duration || !this.rampUpTime || !this.step || !this.rpsLimit) {
|
||||
for (let i = 0; i < this.threadGroups.length; i++) {
|
||||
if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration
|
||||
|| !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step || !this.threadGroups[i].rpsLimit) {
|
||||
this.$warning(this.$t('load_test.pressure_config_params_is_empty'));
|
||||
this.$emit('changeActive', '1');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
convertProperty() {
|
||||
/// todo:下面4个属性是jmeter ConcurrencyThreadGroup plugin的属性,这种硬编码不太好吧,在哪能转换这种属性?
|
||||
return [
|
||||
{key: TARGET_LEVEL, value: this.threadNumber},
|
||||
{key: RAMP_UP, value: this.rampUpTime},
|
||||
{key: STEPS, value: this.step},
|
||||
{key: DURATION, value: this.duration},
|
||||
{key: RPS_LIMIT, value: this.rpsLimit},
|
||||
{key: RPS_LIMIT_ENABLE, value: this.rpsLimitEnable},
|
||||
];
|
||||
let result = [];
|
||||
for (let i = 0; i < this.threadGroups.length; i++) {
|
||||
result.push([
|
||||
{key: TARGET_LEVEL, value: this.threadGroups[i].threadNumber},
|
||||
{key: RAMP_UP, value: this.threadGroups[i].rampUpTime},
|
||||
{key: STEPS, value: this.threadGroups[i].step},
|
||||
{key: DURATION, value: this.threadGroups[i].duration},
|
||||
{key: RPS_LIMIT, value: this.threadGroups[i].rpsLimit},
|
||||
{key: RPS_LIMIT_ENABLE, value: this.threadGroups[i].rpsLimitEnable},
|
||||
{key: HOLD, value: this.threadGroups[i].duration - this.threadGroups[i].rampUpTime},
|
||||
]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -356,6 +498,7 @@ export default {
|
|||
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.el-col .el-form {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import {xml2json} from "xml-js";
|
||||
|
||||
let travel = function (elements, threadGroups) {
|
||||
if (!elements) {
|
||||
return;
|
||||
}
|
||||
for (let element of elements) {
|
||||
if (element.name === 'ThreadGroup') {
|
||||
threadGroups.push(element);
|
||||
}
|
||||
travel(element.elements, threadGroups)
|
||||
}
|
||||
}
|
||||
|
||||
export function findThreadGroup(jmxContent) {
|
||||
let jmxJson = JSON.parse(xml2json(jmxContent));
|
||||
let threadGroups = [];
|
||||
travel(jmxJson.elements, threadGroups);
|
||||
return threadGroups;
|
||||
}
|
||||
|
||||
|
||||
export function findTestPlan(jmxContent) {
|
||||
let jmxJson = JSON.parse(xml2json(jmxContent));
|
||||
for (let element of jmxJson.elements[0].elements[0].elements) {
|
||||
if (element.name === 'TestPlan') {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<div class="header-title" v-loading="result.loading">
|
||||
<div>
|
||||
<div>{{ $t('organization.integration.select_defect_platform') }}</div>
|
||||
<el-radio-group v-model="platform" style="margin-top: 10px" @change="change">
|
||||
<el-radio label="Tapd">
|
||||
<img class="platform" src="../../../../assets/tapd.png" alt="Tapd"/>
|
||||
</el-radio>
|
||||
<el-radio label="Jira">
|
||||
<img class="platform" src="../../../../assets/jira.png" alt="Jira"/>
|
||||
</el-radio>
|
||||
<el-radio label="Zentao">
|
||||
<img class="platform" src="../../../../assets/zentao.jpg" alt="Zentao"/>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<tapd-setting v-if="tapdEnable" ref="tapdSetting"/>
|
||||
<jira-setting v-if="jiraEnable" ref="jiraSetting"/>
|
||||
<zentao-setting v-if="zentaoEnable" ref="zentaoSetting"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TapdSetting from "@/business/components/settings/organization/components/TapdSetting";
|
||||
import JiraSetting from "@/business/components/settings/organization/components/JiraSetting";
|
||||
import ZentaoSetting from "@/business/components/settings/organization/components/ZentaoSetting";
|
||||
import {JIRA, TAPD, ZEN_TAO} from "@/common/js/constants";
|
||||
|
||||
export default {
|
||||
name: "BugManagement",
|
||||
components: {TapdSetting, JiraSetting, ZentaoSetting},
|
||||
data() {
|
||||
return {
|
||||
tapdEnable: true,
|
||||
jiraEnable: false,
|
||||
zentaoEnable: false,
|
||||
result: {},
|
||||
platform: TAPD
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(platform) {
|
||||
if (platform === TAPD) {
|
||||
this.tapdEnable = true;
|
||||
this.jiraEnable = false;
|
||||
this.zentaoEnable = false;
|
||||
} else if (platform === JIRA) {
|
||||
this.tapdEnable = false;
|
||||
this.jiraEnable = true;
|
||||
this.zentaoEnable = false;
|
||||
} else if (platform === ZEN_TAO) {
|
||||
this.tapdEnable = false;
|
||||
this.jiraEnable = false;
|
||||
this.zentaoEnable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-title {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
.platform {
|
||||
height: 90px;
|
||||
vertical-align: middle
|
||||
}
|
||||
</style>
|
|
@ -2,7 +2,7 @@
|
|||
<el-card>
|
||||
<el-tabs class="system-setting" v-model="activeName">
|
||||
<el-tab-pane :label="$t('organization.defect_manage')" name="defect">
|
||||
<defect-management/>
|
||||
<bug-management/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
@ -10,12 +10,12 @@
|
|||
|
||||
<script>
|
||||
|
||||
import DefectManagement from "./IssuesManagement";
|
||||
import BugManagement from "./BugManagement";
|
||||
|
||||
export default {
|
||||
name: "ServiceIntegration",
|
||||
components: {
|
||||
DefectManagement
|
||||
BugManagement
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div style="margin-left: 120px">
|
||||
<el-button type="primary" size="mini" :disabled="!show" @click="testConnection">
|
||||
{{ $t('ldap.test_connect') }}
|
||||
</el-button>
|
||||
<el-button v-if="showEdit" size="mini" @click="edit">
|
||||
{{ $t('commons.edit') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-if="showSave" size="mini" @click="save">
|
||||
{{ $t('commons.save') }}
|
||||
</el-button>
|
||||
<el-button v-if="showCancel" size="mini" @click="cancelEdit">
|
||||
{{ $t('organization.integration.cancel_edit') }}
|
||||
</el-button>
|
||||
<el-button type="info" size="mini" :disabled="!show" @click="cancelIntegration">
|
||||
{{ $t('organization.integration.cancel_integration') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "BugManageBtn",
|
||||
data() {
|
||||
return {
|
||||
showEdit: true,
|
||||
showSave: false,
|
||||
showCancel: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
testConnection() {
|
||||
this.$emit("testConnection");
|
||||
},
|
||||
edit() {
|
||||
this.$emit("update:show", false);
|
||||
this.showEdit = false;
|
||||
this.showSave = true;
|
||||
this.showCancel = true;
|
||||
},
|
||||
cancelEdit() {
|
||||
this.showEdit = true;
|
||||
this.showCancel = false;
|
||||
this.showSave = false;
|
||||
this.$emit("update:show", true);
|
||||
this.init();
|
||||
},
|
||||
init() {
|
||||
this.$emit("init");
|
||||
},
|
||||
save() {
|
||||
this.$emit("save");
|
||||
},
|
||||
cancelIntegration() {
|
||||
this.$emit("cancelIntegration");
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,59 +1,39 @@
|
|||
<template>
|
||||
<div class="header-title" v-loading="result.loading">
|
||||
<div>
|
||||
<div>{{ $t('organization.integration.select_defect_platform') }}</div>
|
||||
<el-radio-group v-model="platform" style="margin-top: 10px" @change="change">
|
||||
<el-radio label="Tapd">
|
||||
<img class="platform" src="../../../../assets/tapd.png" alt="Tapd"/>
|
||||
</el-radio>
|
||||
<el-radio label="Jira">
|
||||
<img class="platform" src="../../../../assets/jira.png" alt="Jira"/>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<div style="width: 500px">
|
||||
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
|
||||
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
|
||||
<el-form-item :label="$t('organization.integration.api_account')" prop="account">
|
||||
<el-form-item :label="$t('organization.integration.account')" prop="account">
|
||||
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('organization.integration.api_password')" prop="password">
|
||||
<el-form-item :label="$t('organization.integration.password')" prop="password">
|
||||
<el-input v-model="form.password" auto-complete="new-password"
|
||||
:placeholder="$t('organization.integration.input_api_password')" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('organization.integration.jira_url')" prop="url" v-if="platform === 'Jira'">
|
||||
<el-form-item :label="$t('organization.integration.jira_url')" prop="url">
|
||||
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_jira_url')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('organization.integration.jira_issuetype')" prop="issuetype"
|
||||
v-if="platform === 'Jira'">
|
||||
<el-form-item :label="$t('organization.integration.jira_issuetype')" prop="issuetype">
|
||||
<el-input v-model="form.issuetype" :placeholder="$t('organization.integration.input_jira_issuetype')"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div style="margin-left: 120px">
|
||||
<el-button type="primary" size="mini" :disabled="!show" @click="testConnection">{{ $t('ldap.test_connect') }}
|
||||
</el-button>
|
||||
<el-button v-if="showEdit" size="mini" @click="edit">{{ $t('commons.edit') }}</el-button>
|
||||
<el-button type="primary" v-if="showSave" size="mini" @click="save('form')">{{ $t('commons.save') }}</el-button>
|
||||
<el-button v-if="showCancel" size="mini" @click="cancelEdit">{{ $t('organization.integration.cancel_edit') }}
|
||||
</el-button>
|
||||
<el-button type="info" size="mini" @click="cancelIntegration('form')" :disabled="!show">
|
||||
{{ $t('organization.integration.cancel_integration') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<bug-manage-btn @save="save"
|
||||
@init="init"
|
||||
@testConnection="testConnection"
|
||||
@cancelIntegration="cancelIntegration"
|
||||
:form="form"
|
||||
:show.sync="show"
|
||||
ref="bugBtn"/>
|
||||
|
||||
<div class="defect-tip">
|
||||
<div>{{ $t('organization.integration.use_tip') }}</div>
|
||||
<div>
|
||||
1. {{ $t('organization.integration.use_tip_tapd') }}
|
||||
1. {{ $t('organization.integration.use_tip_jira') }}
|
||||
</div>
|
||||
<div>
|
||||
2. {{ $t('organization.integration.use_tip_jira') }}
|
||||
</div>
|
||||
<div>
|
||||
3. {{ $t('organization.integration.use_tip_two') }}
|
||||
2. {{ $t('organization.integration.use_tip_two') }}
|
||||
<router-link to="/track/project/all" style="margin-left: 5px">
|
||||
{{ $t('organization.integration.link_the_project_now') }}
|
||||
</router-link>
|
||||
|
@ -63,20 +43,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {getCurrentUser} from "../../../../common/js/utils";
|
||||
import BugManageBtn from "@/business/components/settings/organization/components/BugManageBtn";
|
||||
import {getCurrentUser} from "@/common/js/utils";
|
||||
import {JIRA} from "@/common/js/constants";
|
||||
|
||||
export default {
|
||||
name: "IssuesManagement",
|
||||
name: "JiraSetting",
|
||||
components: {BugManageBtn},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
result: {},
|
||||
platform: '',
|
||||
orgId: '',
|
||||
show: true,
|
||||
showEdit: true,
|
||||
showSave: false,
|
||||
showCancel: false,
|
||||
form: {},
|
||||
rules: {
|
||||
account: {
|
||||
required: true,
|
||||
|
@ -101,17 +81,14 @@ export default {
|
|||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init(this.platform);
|
||||
},
|
||||
methods: {
|
||||
init(platform) {
|
||||
init() {
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
let param = {};
|
||||
param.platform = platform;
|
||||
param.orgId = getCurrentUser().lastOrganizationId;
|
||||
this.result = this.$post("service/integration/type", param, response => {
|
||||
param.platform = JIRA;
|
||||
param.orgId = lastOrganizationId;
|
||||
this.$parent.result = this.$post("service/integration/type", param, response => {
|
||||
let data = response.data;
|
||||
this.platform = data.platform;
|
||||
if (data.configuration) {
|
||||
let config = JSON.parse(data.configuration);
|
||||
this.$set(this.form, 'account', config.account);
|
||||
|
@ -123,54 +100,13 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
edit() {
|
||||
this.show = false;
|
||||
this.showEdit = false;
|
||||
this.showSave = true;
|
||||
this.showCancel = true;
|
||||
},
|
||||
cancelEdit() {
|
||||
this.showEdit = true;
|
||||
this.showCancel = false;
|
||||
this.showSave = false;
|
||||
this.show = true;
|
||||
this.init(this.platform);
|
||||
},
|
||||
cancelIntegration() {
|
||||
if (this.form.account && this.form.password && this.platform) {
|
||||
|
||||
this.$alert(this.$t('organization.integration.cancel_confirm') + this.platform + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
let param = {};
|
||||
param.orgId = getCurrentUser().lastOrganizationId;
|
||||
param.platform = this.platform;
|
||||
this.result = this.$post("service/integration/delete", param, () => {
|
||||
this.$success(this.$t('organization.integration.successful_operation'));
|
||||
this.init('');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||
}
|
||||
},
|
||||
save(form) {
|
||||
if (!this.platform) {
|
||||
this.$warning(this.$t('organization.integration.choose_platform'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.$refs[form].validate(valid => {
|
||||
save() {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
|
||||
let formatUrl = this.form.url.trim();
|
||||
if (!formatUrl.endsWith('/')) {
|
||||
formatUrl = formatUrl + '/';
|
||||
}
|
||||
|
||||
let param = {};
|
||||
let auth = {
|
||||
account: this.form.account,
|
||||
|
@ -178,16 +114,16 @@ export default {
|
|||
url: formatUrl,
|
||||
issuetype: this.form.issuetype
|
||||
};
|
||||
param.organizationId = getCurrentUser().lastOrganizationId;
|
||||
param.platform = this.platform;
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
param.organizationId = lastOrganizationId;
|
||||
param.platform = JIRA;
|
||||
param.configuration = JSON.stringify(auth);
|
||||
|
||||
this.result = this.$post("service/integration/save", param, () => {
|
||||
this.$parent.result = this.$post("service/integration/save", param, () => {
|
||||
this.show = true;
|
||||
this.showEdit = true;
|
||||
this.showSave = false;
|
||||
this.showCancel = false;
|
||||
this.init(this.platform);
|
||||
this.$refs.bugBtn.showEdit = true;
|
||||
this.$refs.bugBtn.showSave = false;
|
||||
this.$refs.bugBtn.showCancel = false;
|
||||
this.init();
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
});
|
||||
} else {
|
||||
|
@ -195,27 +131,6 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
change(platform) {
|
||||
this.show = true;
|
||||
this.showEdit = true;
|
||||
this.showCancel = false;
|
||||
this.showSave = false;
|
||||
let param = {};
|
||||
param.orgId = getCurrentUser().lastOrganizationId;
|
||||
param.platform = platform;
|
||||
this.result = this.$post("service/integration/type", param, response => {
|
||||
let data = response.data;
|
||||
if (data.configuration) {
|
||||
let config = JSON.parse(data.configuration);
|
||||
this.$set(this.form, 'account', config.account);
|
||||
this.$set(this.form, 'password', config.password);
|
||||
this.$set(this.form, 'url', config.url);
|
||||
this.$set(this.form, 'issuetype', config.issuetype);
|
||||
} else {
|
||||
this.clear();
|
||||
}
|
||||
})
|
||||
},
|
||||
clear() {
|
||||
this.$set(this.form, 'account', '');
|
||||
this.$set(this.form, 'password', '');
|
||||
|
@ -226,24 +141,41 @@ export default {
|
|||
});
|
||||
},
|
||||
testConnection() {
|
||||
if (this.form.account && this.form.password && this.platform) {
|
||||
this.result = this.$get("issues/auth/" + this.platform, () => {
|
||||
if (this.form.account && this.form.password) {
|
||||
this.$parent.result = this.$get("issues/auth/" + JIRA, () => {
|
||||
this.$success(this.$t('organization.integration.verified'));
|
||||
});
|
||||
} else {
|
||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
cancelIntegration() {
|
||||
if (this.form.account && this.form.password) {
|
||||
this.$alert(this.$t('organization.integration.cancel_confirm') + JIRA + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
let param = {};
|
||||
param.orgId = lastOrganizationId;
|
||||
param.platform = JIRA;
|
||||
this.$parent.result = this.$post("service/integration/delete", param, () => {
|
||||
this.$success(this.$t('organization.integration.successful_operation'));
|
||||
this.init('');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-title {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
.defect-tip {
|
||||
background: #EDEDED;
|
||||
border: solid #E1E1E1 1px;
|
||||
|
@ -251,9 +183,4 @@ export default {
|
|||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.platform {
|
||||
height: 90px;
|
||||
vertical-align: middle
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="width: 500px">
|
||||
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
|
||||
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
|
||||
<el-form-item :label="$t('organization.integration.api_account')" prop="account">
|
||||
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('organization.integration.api_password')" prop="password">
|
||||
<el-input v-model="form.password" auto-complete="new-password"
|
||||
:placeholder="$t('organization.integration.input_api_password')" show-password/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<bug-manage-btn @save="save"
|
||||
@init="init"
|
||||
@testConnection="testConnection"
|
||||
@cancelIntegration="cancelIntegration"
|
||||
:form="form"
|
||||
:show.sync="show"
|
||||
ref="bugBtn"/>
|
||||
|
||||
<div class="defect-tip">
|
||||
<div>{{ $t('organization.integration.use_tip') }}</div>
|
||||
<div>
|
||||
1. {{ $t('organization.integration.use_tip_tapd') }}
|
||||
</div>
|
||||
<div>
|
||||
2. {{ $t('organization.integration.use_tip_two') }}
|
||||
<router-link to="/track/project/all" style="margin-left: 5px">
|
||||
{{ $t('organization.integration.link_the_project_now') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BugManageBtn from "@/business/components/settings/organization/components/BugManageBtn";
|
||||
import {getCurrentUser} from "@/common/js/utils";
|
||||
import {TAPD} from "@/common/js/constants";
|
||||
|
||||
export default {
|
||||
name: "TapdSetting.vue",
|
||||
components: {
|
||||
BugManageBtn
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
form: {},
|
||||
rules: {
|
||||
account: {
|
||||
required: true,
|
||||
message: this.$t('organization.integration.input_api_account'),
|
||||
trigger: ['change', 'blur']
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: this.$t('organization.integration.input_api_password'),
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
let param = {};
|
||||
param.platform = TAPD;
|
||||
param.orgId = lastOrganizationId;
|
||||
this.$parent.result = this.$post("service/integration/type", param, response => {
|
||||
let data = response.data;
|
||||
if (data.configuration) {
|
||||
let config = JSON.parse(data.configuration);
|
||||
this.$set(this.form, 'account', config.account);
|
||||
this.$set(this.form, 'password', config.password);
|
||||
} else {
|
||||
this.clear();
|
||||
}
|
||||
})
|
||||
},
|
||||
save() {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
|
||||
let param = {};
|
||||
let auth = {
|
||||
account: this.form.account,
|
||||
password: this.form.password,
|
||||
};
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
param.organizationId = lastOrganizationId;
|
||||
param.platform = TAPD;
|
||||
param.configuration = JSON.stringify(auth);
|
||||
|
||||
this.$parent.result = this.$post("service/integration/save", param, () => {
|
||||
this.show = true;
|
||||
this.$refs.bugBtn.showEdit = true;
|
||||
this.$refs.bugBtn.showSave = false;
|
||||
this.$refs.bugBtn.showCancel = false;
|
||||
this.init();
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
},
|
||||
clear() {
|
||||
this.$set(this.form, 'account', '');
|
||||
this.$set(this.form, 'password', '');
|
||||
this.$nextTick(() => {
|
||||
this.$refs.form.clearValidate();
|
||||
});
|
||||
},
|
||||
testConnection() {
|
||||
if (this.form.account && this.form.password) {
|
||||
this.$parent.result = this.$get("issues/auth/" + TAPD, () => {
|
||||
this.$success(this.$t('organization.integration.verified'));
|
||||
});
|
||||
} else {
|
||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
cancelIntegration() {
|
||||
if (this.form.account && this.form.password) {
|
||||
this.$alert(this.$t('organization.integration.cancel_confirm') + TAPD + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
let param = {};
|
||||
param.orgId = lastOrganizationId;
|
||||
param.platform = TAPD;
|
||||
this.$parent.result = this.$post("service/integration/delete", param, () => {
|
||||
this.$success(this.$t('organization.integration.successful_operation'));
|
||||
this.init('');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.defect-tip {
|
||||
background: #EDEDED;
|
||||
border: solid #E1E1E1 1px;
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,164 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="width: 500px">
|
||||
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
|
||||
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
|
||||
<el-form-item :label="$t('organization.integration.app_name')" prop="account">
|
||||
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_app_name')"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('organization.integration.app_key')" prop="password">
|
||||
<el-input v-model="form.password" auto-complete="new-password"
|
||||
:placeholder="$t('organization.integration.input_app_key')" show-password/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<bug-manage-btn @save="save"
|
||||
@init="init"
|
||||
@testConnection="testConnection"
|
||||
@cancelIntegration="cancelIntegration"
|
||||
:form="form"
|
||||
:show.sync="show"
|
||||
ref="bugBtn"/>
|
||||
|
||||
<div class="defect-tip">
|
||||
<div>{{ $t('organization.integration.use_tip') }}</div>
|
||||
<div>
|
||||
1. {{ $t('organization.integration.use_tip_zentao') }}
|
||||
</div>
|
||||
<div>
|
||||
2. {{ $t('organization.integration.use_tip_two') }}
|
||||
<router-link to="/track/project/all" style="margin-left: 5px">
|
||||
{{ $t('organization.integration.link_the_project_now') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BugManageBtn from "@/business/components/settings/organization/components/BugManageBtn";
|
||||
import {getCurrentUser} from "@/common/js/utils";
|
||||
import {ZEN_TAO} from "@/common/js/constants";
|
||||
|
||||
export default {
|
||||
name: "ZentaoSetting",
|
||||
components: {
|
||||
BugManageBtn
|
||||
},
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
form: {},
|
||||
rules: {
|
||||
account: {
|
||||
required: true,
|
||||
message: this.$t('organization.integration.input_app_name'),
|
||||
trigger: ['change', 'blur']
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: this.$t('organization.integration.input_app_key'),
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
let param = {};
|
||||
let auth = {
|
||||
account: this.form.account,
|
||||
password: this.form.password,
|
||||
};
|
||||
param.organizationId = lastOrganizationId;
|
||||
param.platform = ZEN_TAO;
|
||||
param.configuration = JSON.stringify(auth);
|
||||
|
||||
this.$parent.result = this.$post("service/integration/save", param, () => {
|
||||
this.show = true;
|
||||
this.$refs.bugBtn.showEdit = true;
|
||||
this.$refs.bugBtn.showSave = false;
|
||||
this.$refs.bugBtn.showCancel = false;
|
||||
this.init();
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
},
|
||||
init() {
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
let param = {};
|
||||
param.platform = ZEN_TAO;
|
||||
param.orgId = lastOrganizationId;
|
||||
this.$parent.result = this.$post("service/integration/type", param, response => {
|
||||
let data = response.data;
|
||||
if (data.configuration) {
|
||||
let config = JSON.parse(data.configuration);
|
||||
this.$set(this.form, 'account', config.account);
|
||||
this.$set(this.form, 'password', config.password);
|
||||
} else {
|
||||
this.clear();
|
||||
}
|
||||
})
|
||||
},
|
||||
clear() {
|
||||
this.$set(this.form, 'account', '');
|
||||
this.$set(this.form, 'password', '');
|
||||
this.$nextTick(() => {
|
||||
this.$refs.form.clearValidate();
|
||||
});
|
||||
},
|
||||
testConnection() {
|
||||
if (this.form.account && this.form.password) {
|
||||
this.$parent.result = this.$get("issues/auth/" + ZEN_TAO, () => {
|
||||
this.$success(this.$t('organization.integration.verified'));
|
||||
});
|
||||
} else {
|
||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||
return false;
|
||||
}
|
||||
},
|
||||
cancelIntegration() {
|
||||
if (this.form.account && this.form.password) {
|
||||
this.$alert(this.$t('organization.integration.cancel_confirm') + ZEN_TAO + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
const {lastOrganizationId} = getCurrentUser();
|
||||
let param = {};
|
||||
param.orgId = lastOrganizationId;
|
||||
param.platform = ZEN_TAO;
|
||||
this.$parent.result = this.$post("service/integration/delete", param, () => {
|
||||
this.$success(this.$t('organization.integration.successful_operation'));
|
||||
this.init('');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$warning(this.$t('organization.integration.not_integrated'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.defect-tip {
|
||||
background: #EDEDED;
|
||||
border: solid #E1E1E1 1px;
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
|
@ -128,7 +128,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('commons.operating')" min-width="100">
|
||||
:label="$t('commons.operating')" min-width="150">
|
||||
<template v-slot:default="scope">
|
||||
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
||||
@deleteClick="handleDelete(scope.row)">
|
||||
|
@ -175,6 +175,7 @@
|
|||
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
|
||||
import TestCaseDetail from "./TestCaseDetail";
|
||||
import ReviewStatus from "@/business/components/track/case/components/ReviewStatus";
|
||||
|
||||
export default {
|
||||
name: "TestCaseList",
|
||||
components: {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<related-test-plan-list ref="relatedTestPlanList"/>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<review-list title="我的评审" ref="caseReviewList"/>
|
||||
<review-list :title="$t('review.my_review')" ref="caseReviewList"/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
min-width="150"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
||||
|
|
|
@ -183,6 +183,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
min-width="100"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.edit')" icon="el-icon-edit"
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
|
||||
<el-tabs class="test-config" v-model="active" type="border-card" :stretch="true">
|
||||
<el-tab-pane :label="$t('load_test.basic_config')">
|
||||
<performance-basic-config :is-read-only="true" :test-plan="test" ref="basicConfig"/>
|
||||
<performance-basic-config :is-read-only="true" :test="test" ref="basicConfig"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('load_test.pressure_config')">
|
||||
<performance-pressure-config :is-read-only="true" :test-plan="test" :test-id="id" ref="pressureConfig"/>
|
||||
<performance-pressure-config :is-read-only="true" :test="test" :test-id="id" ref="pressureConfig"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('load_test.advanced_config')" class="advanced-config">
|
||||
<performance-advanced-config :read-only="true" :test-id="id" ref="advancedConfig"/>
|
||||
|
@ -40,6 +40,7 @@
|
|||
import PerformanceBasicConfig from "../../../../../performance/test/components/PerformanceBasicConfig";
|
||||
import PerformancePressureConfig from "../../../../../performance/test/components/PerformancePressureConfig";
|
||||
import PerformanceAdvancedConfig from "../../../../../performance/test/components/PerformanceAdvancedConfig";
|
||||
|
||||
export default {
|
||||
name: "PerformanceTestDetail",
|
||||
components: {
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
min-width="100"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<ms-table-operator :is-tester-permission="true" @editClick="handleEdit(scope.row)"
|
||||
|
|
|
@ -124,6 +124,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
min-width="100"
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<ms-table-operator-button :is-tester-permission="true" :tip="$t('commons.edit')" icon="el-icon-edit"
|
||||
|
|
|
@ -65,13 +65,13 @@ body {
|
|||
background-color: white;
|
||||
}
|
||||
|
||||
.adjust-table th:hover:after {
|
||||
.adjust-table th:not([class*='el-table-column--selection']):hover:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
right: 0;
|
||||
height: 50%;
|
||||
width: 3px;
|
||||
width: 2px;
|
||||
background-color: #EBEEF5;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ export const ZH_CN = 'zh_CN';
|
|||
export const ZH_TW = 'zh_TW';
|
||||
export const EN_US = 'en_US';
|
||||
|
||||
export const TAPD = 'Tapd';
|
||||
export const JIRA = 'Jira';
|
||||
export const ZEN_TAO = 'Zentao';
|
||||
|
||||
export const SCHEDULE_TYPE = {
|
||||
API_TEST: 'API_TEST',
|
||||
PERFORMANCE_TEST: 'PERFORMANCE_TEST'
|
||||
|
|
|
@ -250,15 +250,22 @@ export default {
|
|||
basic_auth_info: 'Basic Auth account information:',
|
||||
api_account: 'API account',
|
||||
api_password: 'API password',
|
||||
app_name: 'APP name',
|
||||
app_key: 'APP key',
|
||||
account: 'Account',
|
||||
password: 'Password',
|
||||
jira_url: 'JIRA url',
|
||||
jira_issuetype: 'JIRA issuetype',
|
||||
input_api_account: 'please enter account',
|
||||
input_api_password: 'Please enter password',
|
||||
input_app_name: 'Please enter the application code',
|
||||
input_app_key: 'Please enter the key',
|
||||
input_jira_url: 'Please enter Jira address, for example: https://metersphere.atlassian.net/',
|
||||
input_jira_issuetype: 'Please enter the question type',
|
||||
use_tip: 'Usage guidelines:',
|
||||
use_tip_tapd: 'Basic Auth account information is queried in "Company Management-Security and Integration-Open Platform"',
|
||||
use_tip_jira: 'Jira software server authentication information is account password, Jira software cloud authentication information is account + token (account settings-security-create API token)',
|
||||
use_tip_zentao: 'Log in to ZenTao as a super administrator user, enter the background-secondary development-application, click [Add Application] to add an application',
|
||||
use_tip_two: 'After saving the Basic Auth account information, you need to manually associate the ID/key in the Metersphere project',
|
||||
link_the_project_now: 'Link the project now',
|
||||
cancel_edit: 'Cancel edit',
|
||||
|
@ -353,6 +360,7 @@ export default {
|
|||
test_stop_now_confirm: 'Are you sure you want to stop the current test immediately?',
|
||||
test_rerun_confirm: 'Are you sure you want to rerun the current test immediately?',
|
||||
test_stop_success: 'Test stop successfully',
|
||||
downloadJtl: 'Download JTL',
|
||||
test_execute_again: 'Test Execute Again',
|
||||
export: 'Export',
|
||||
compare: 'Compare',
|
||||
|
|
|
@ -250,15 +250,22 @@ export default {
|
|||
basic_auth_info: 'Basic Auth 账号信息:',
|
||||
api_account: 'API 账号',
|
||||
api_password: 'API 口令',
|
||||
app_name: '应用代号',
|
||||
app_key: '密钥',
|
||||
account: '账号',
|
||||
password: '密码',
|
||||
jira_url: 'JIRA 地址',
|
||||
jira_issuetype: '问题类型',
|
||||
input_api_account: '请输入账号',
|
||||
input_api_password: '请输入口令',
|
||||
input_app_name: '请输入应用代号',
|
||||
input_app_key: '请输入密钥',
|
||||
input_jira_url: '请输入Jira地址,例:https://metersphere.atlassian.net/',
|
||||
input_jira_issuetype: '请输入问题类型',
|
||||
use_tip: '使用指引:',
|
||||
use_tip_tapd: 'Tapd Basic Auth 账号信息在"公司管理-安全与集成-开放平台"中查询',
|
||||
use_tip_jira: 'Jira software server 认证信息为 账号密码,Jira software cloud 认证信息为 账号+令牌(账户设置-安全-创建API令牌)',
|
||||
use_tip_zentao: '用超级管理员用户登录禅道,进入后台-二次开发-应用,点击【添加应用】新增一个应用',
|
||||
use_tip_two: '保存 Basic Auth 账号信息后,需要在 Metersphere 项目中手动关联 ID/key',
|
||||
link_the_project_now: '马上关联项目',
|
||||
cancel_edit: '取消编辑',
|
||||
|
@ -353,6 +360,7 @@ export default {
|
|||
test_rerun_confirm: '确定要再次执行当前测试吗?',
|
||||
test_stop_success: '停止成功',
|
||||
test_execute_again: '再次执行',
|
||||
downloadJtl: '下载JTL',
|
||||
export: '导出',
|
||||
compare: '比较',
|
||||
generation_error: '报告生成错误, 无法查看, 请检查日志详情!',
|
||||
|
|
|
@ -252,15 +252,22 @@ export default {
|
|||
basic_auth_info: 'Basic Auth 賬號信息:',
|
||||
api_account: 'API 賬號',
|
||||
api_password: 'API 口令',
|
||||
app_name: '應用代號',
|
||||
app_key: '密鑰',
|
||||
account: '賬號',
|
||||
password: '密碼',
|
||||
jira_url: 'JIRA 地址',
|
||||
jira_issuetype: '問題類型',
|
||||
input_api_account: '請輸入賬號',
|
||||
input_api_password: '請輸入口令',
|
||||
input_app_name: '請輸入應用代號',
|
||||
input_app_key: '請輸入密鑰',
|
||||
input_jira_url: '請輸入Jira地址,例:https://metersphere.atlassian.net/',
|
||||
input_jira_issuetype: '請輸入問題類型',
|
||||
use_tip: '使用指引:',
|
||||
use_tip_tapd: 'Tapd Basic Auth 賬號信息在"公司管理-安全與集成-開放平臺"中查詢',
|
||||
use_tip_jira: 'Jira software server 認證信息為 賬號密碼,Jira software cloud 認證信息為 賬號+令牌(賬戶設置-安全-創建API令牌)',
|
||||
use_tip_zentao: '用超級管理員用戶登錄禪道,進入後台-二次開發-應用,點擊【添加應用】添加一個應用',
|
||||
use_tip_two: '保存 Basic Auth 賬號信息後,需要在 Metersphere 項目中手動關聯 ID/key',
|
||||
link_the_project_now: '馬上關聯項目',
|
||||
cancel_edit: '取消編輯',
|
||||
|
@ -353,6 +360,7 @@ export default {
|
|||
test_stop_now: '立即停止',
|
||||
test_stop_now_confirm: '確定要立即停止當前測試嗎?',
|
||||
test_rerun_confirm: '確定要再次執行當前測試嗎?',
|
||||
downloadJtl: '下載JTL',
|
||||
test_stop_success: '停止成功',
|
||||
test_execute_again: '再次執行',
|
||||
export: '導出',
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="form.password" :placeholder="$t('commons.password')" show-password autocomplete="off"
|
||||
maxlength="20" show-word-limit/>
|
||||
maxlength="30" show-word-limit/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="btn">
|
||||
|
@ -81,7 +81,7 @@ export default {
|
|||
],
|
||||
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'}
|
||||
{min: 6, max: 30, message: this.$t('commons.input_limit', [6, 30]), trigger: 'blur'}
|
||||
]
|
||||
},
|
||||
msg: '',
|
||||
|
|
Loading…
Reference in New Issue