Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
d2817334b7
|
@ -1,4 +1,4 @@
|
|||
FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre
|
||||
FROM registry.fit2cloud.com/metersphere/fabric8-java-alpine-openjdk8-jre
|
||||
|
||||
MAINTAINER FIT2CLOUD <support@fit2cloud.com>
|
||||
|
||||
|
@ -10,6 +10,5 @@ ENV JAVA_APP_JAR=/opt/apps/backend-1.0.jar
|
|||
|
||||
ENV AB_OFF=true
|
||||
|
||||
ENV JAVA_OPTIONS=-Dfile.encoding=utf-8
|
||||
|
||||
ENV JAVA_OPTIONS="-Dfile.encoding=utf-8 -Djava.awt.headless=true"
|
||||
CMD ["/deployments/run-java.sh"]
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.metersphere.base.mapper.ext;
|
|||
import io.metersphere.base.domain.TestCase;
|
||||
import io.metersphere.controller.request.ReportRequest;
|
||||
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.controller.request.testplancase.QueryTestPlanCaseRequest;
|
||||
import io.metersphere.dto.ReportDTO;
|
||||
import io.metersphere.dto.TestPlanCaseDTO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
@ -13,5 +14,5 @@ public interface ExtTestCaseMapper {
|
|||
|
||||
List<TestCase> getTestCaseNames(@Param("request") QueryTestCaseRequest request);
|
||||
|
||||
List<TestPlanCaseDTO> getTestPlanTestCases(@Param("request") QueryTestCaseRequest request);
|
||||
List<TestPlanCaseDTO> getTestPlanTestCases(@Param("request") QueryTestPlanCaseRequest request);
|
||||
}
|
||||
|
|
|
@ -37,5 +37,8 @@
|
|||
<if test="request.name != null">
|
||||
and t2.name like CONCAT('%', #{request.name},'%')
|
||||
</if>
|
||||
<if test="request.executor != null">
|
||||
and t1.executor = #{request.executor}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
|
@ -2,6 +2,7 @@ package io.metersphere.config;
|
|||
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.i18n.I18nManager;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
@ -20,6 +21,12 @@ public class I18nConfig {
|
|||
return new I18nManager(dirs);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Translator translator() {
|
||||
return new Translator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CommonBeanFactory commonBeanFactory() {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package io.metersphere.controller;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("anonymous")
|
||||
public class HelloController {
|
||||
@Resource
|
||||
MessageSource messageSource;
|
||||
|
||||
@GetMapping("hello")
|
||||
public String hello() {
|
||||
return messageSource.getMessage("max_thread_insufficient", null, "默认值", LocaleContextHolder.getLocale());
|
||||
}
|
||||
}
|
|
@ -3,13 +3,13 @@ package io.metersphere.controller;
|
|||
import io.metersphere.base.domain.UserRole;
|
||||
import io.metersphere.controller.request.LoginRequest;
|
||||
import io.metersphere.dto.UserDTO;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.service.UserService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.authc.*;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -26,7 +26,7 @@ public class LoginController {
|
|||
@GetMapping(value = "/isLogin")
|
||||
public ResultHolder isLogin() {
|
||||
if (SecurityUtils.getSubject().isAuthenticated()) {
|
||||
return ResultHolder.success(Translator.getLangDes());
|
||||
return ResultHolder.success(LocaleContextHolder.getLocale());
|
||||
}
|
||||
return ResultHolder.error("");
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import io.metersphere.base.domain.TestPlanTestCase;
|
|||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.controller.request.testcase.TestCaseBatchRequest;
|
||||
import io.metersphere.controller.request.testplancase.QueryTestPlanCaseRequest;
|
||||
import io.metersphere.dto.TestPlanCaseDTO;
|
||||
import io.metersphere.service.TestPlanTestCaseService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -22,7 +24,7 @@ public class TestPlanTestCaseController {
|
|||
TestPlanTestCaseService testPlanTestCaseService;
|
||||
|
||||
@PostMapping("/list/{goPage}/{pageSize}")
|
||||
public Pager<List<TestPlanCaseDTO>> getTestPlanCases(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request){
|
||||
public Pager<List<TestPlanCaseDTO>> getTestPlanCases(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanCaseRequest request){
|
||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||
return PageUtils.setPageInfo(page, testPlanTestCaseService.getTestPlanCases(request));
|
||||
}
|
||||
|
@ -32,11 +34,14 @@ public class TestPlanTestCaseController {
|
|||
testPlanTestCaseService.editTestCase(testPlanTestCase);
|
||||
}
|
||||
|
||||
@PostMapping("/batch/edit")
|
||||
public void editTestCaseBath(@RequestBody TestCaseBatchRequest request){
|
||||
testPlanTestCaseService.editTestCaseBath(request);
|
||||
}
|
||||
|
||||
@PostMapping("/delete/{id}")
|
||||
public int deleteTestCase(@PathVariable Integer id){
|
||||
return testPlanTestCaseService.deleteTestCase(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package io.metersphere.controller.request.testcase;
|
||||
|
||||
import io.metersphere.base.domain.TestCase;
|
||||
import io.metersphere.base.domain.TestPlanTestCase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TestCaseBatchRequest extends TestPlanTestCase {
|
||||
|
||||
private List<Integer> ids;
|
||||
|
||||
public List<Integer> getIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public void setIds(List<Integer> ids) {
|
||||
this.ids = ids;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package io.metersphere.controller.request.testplancase;
|
||||
|
||||
import io.metersphere.base.domain.TestCase;
|
||||
import io.metersphere.base.domain.TestPlanTestCase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class QueryTestPlanCaseRequest extends TestPlanTestCase {
|
||||
|
||||
private List<Integer> nodeIds;
|
||||
|
||||
private String workspaceId;
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<Integer> getNodeIds() {
|
||||
return nodeIds;
|
||||
}
|
||||
|
||||
public void setNodeIds(List<Integer> nodeIds) {
|
||||
this.nodeIds = nodeIds;
|
||||
}
|
||||
|
||||
public String getWorkspaceId() {
|
||||
return workspaceId;
|
||||
}
|
||||
|
||||
public void setWorkspaceId(String workspaceId) {
|
||||
this.workspaceId = workspaceId;
|
||||
}
|
||||
}
|
|
@ -1,271 +1,22 @@
|
|||
package io.metersphere.i18n;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.JavaBeanSerializer;
|
||||
import com.alibaba.fastjson.serializer.ObjectSerializer;
|
||||
import com.alibaba.fastjson.serializer.SerializeConfig;
|
||||
import io.metersphere.commons.constants.I18nConstants;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.service.SystemParameterService;
|
||||
import io.metersphere.user.SessionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.collections4.map.PassiveExpiringMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.commons.text.StringSubstitutor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
public class Translator {
|
||||
private static MessageSource messageSource;
|
||||
|
||||
public static final String PREFIX = "$[{";
|
||||
public static final String SUFFIX = "}]";
|
||||
private static final String JSON_SYMBOL = "\":";
|
||||
|
||||
private static final HashSet<String> IGNORE_KEYS = new HashSet<>(Arrays.asList("id", "password", "passwd"));
|
||||
|
||||
private static Map<String, String> langCache4Thread = Collections.synchronizedMap(new PassiveExpiringMap(1, TimeUnit.MINUTES));
|
||||
|
||||
public static String getLangDes() {
|
||||
return getLang().getDesc();
|
||||
@Resource
|
||||
public void setMessageSource(MessageSource messageSource) {
|
||||
Translator.messageSource = messageSource;
|
||||
}
|
||||
|
||||
public static Lang getLang() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return getLang(request);
|
||||
}
|
||||
|
||||
public static Object gets(Object keys) {
|
||||
return gets(getLang(), keys);
|
||||
}
|
||||
|
||||
public static Object gets(Lang lang, Object keys) {
|
||||
Map<String, String> context = I18nManager.getI18nMap().get(lang.getDesc().toLowerCase());
|
||||
return translateObject(keys, context);
|
||||
}
|
||||
|
||||
// 单Key翻译
|
||||
/**
|
||||
* 单Key翻译
|
||||
*/
|
||||
public static String get(String key) {
|
||||
return get(getLang(), key);
|
||||
}
|
||||
|
||||
public static String get(Lang lang, String key) {
|
||||
if (StringUtils.isBlank(key)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
return translateKey(key, I18nManager.getI18nMap().get(lang.getDesc().toLowerCase()));
|
||||
}
|
||||
|
||||
public static String toI18nKey(String key) {
|
||||
return String.format("%s%s%s", PREFIX, key, SUFFIX);
|
||||
}
|
||||
|
||||
private static HttpServletRequest getRequest() {
|
||||
try {
|
||||
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
} catch (NullPointerException npe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Lang getLang(HttpServletRequest request) {
|
||||
String preferLang = Lang.zh_CN.getDesc();
|
||||
|
||||
try {
|
||||
if (request != null) {
|
||||
Object sessionLang = request.getSession(true).getAttribute(I18nConstants.LANG_COOKIE_NAME);
|
||||
if (sessionLang != null && StringUtils.isNotBlank(sessionLang.toString())) {
|
||||
return Lang.getLang(sessionLang.toString());
|
||||
}
|
||||
preferLang = getSystemParameterLanguage(preferLang);
|
||||
if (StringUtils.isNotBlank(request.getHeader(HttpHeaders.ACCEPT_LANGUAGE))) {
|
||||
String preferLangWithComma = StringUtils.substringBefore(request.getHeader(HttpHeaders.ACCEPT_LANGUAGE), ";");
|
||||
String acceptLanguage = StringUtils.replace(StringUtils.substringBefore(preferLangWithComma, ","), "-", "_");
|
||||
if (Lang.getLangWithoutDefault(acceptLanguage) != null) {
|
||||
preferLang = acceptLanguage;
|
||||
}
|
||||
}
|
||||
if (request.getCookies() != null && request.getCookies().length > 0) {
|
||||
for (Cookie cookie : request.getCookies()) {
|
||||
if (StringUtils.equalsIgnoreCase(cookie.getName(), I18nConstants.LANG_COOKIE_NAME)) {
|
||||
preferLang = cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (SessionUtils.getUser() != null && StringUtils.isNotBlank(SessionUtils.getUser().getLanguage())) {
|
||||
preferLang = SessionUtils.getUser().getLanguage();
|
||||
}
|
||||
request.getSession(true).setAttribute(I18nConstants.LANG_COOKIE_NAME, preferLang);
|
||||
} else {
|
||||
preferLang = getSystemParameterLanguage(preferLang);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("Fail to getLang.", e);
|
||||
}
|
||||
|
||||
return Lang.getLang(preferLang);
|
||||
}
|
||||
|
||||
private static String getSystemParameterLanguage(String defaultLang) {
|
||||
String result = defaultLang;
|
||||
try {
|
||||
String cachedLang = langCache4Thread.get(I18nConstants.LANG_COOKIE_NAME);
|
||||
if (StringUtils.isNotBlank(cachedLang)) {
|
||||
return cachedLang;
|
||||
}
|
||||
String systemLanguage = Objects.requireNonNull(CommonBeanFactory.getBean(SystemParameterService.class)).getSystemLanguage();
|
||||
if (StringUtils.isNotBlank(systemLanguage)) {
|
||||
result = systemLanguage;
|
||||
}
|
||||
langCache4Thread.put(I18nConstants.LANG_COOKIE_NAME, result);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private static Object translateObject(Object javaObject, final Map<String, String> context) {
|
||||
if (MapUtils.isEmpty(context)) {
|
||||
return javaObject;
|
||||
}
|
||||
if (javaObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (javaObject instanceof String) {
|
||||
String rawString = javaObject.toString();
|
||||
if (StringUtils.contains(rawString, JSON_SYMBOL)) {
|
||||
try {
|
||||
Object jsonObject = JSON.parse(rawString);
|
||||
Object a = translateObject(jsonObject, context);
|
||||
return JSON.toJSONString(a);
|
||||
} catch (Exception e) {
|
||||
LogUtil.warn("Failed to translate object " + rawString + ". Error: " + ExceptionUtils.getStackTrace(e));
|
||||
return translateRawString(null, rawString, context);
|
||||
}
|
||||
|
||||
} else {
|
||||
return translateRawString(null, rawString, context);
|
||||
}
|
||||
}
|
||||
|
||||
if (javaObject instanceof Map) {
|
||||
Map<Object, Object> map = (Map<Object, Object>) javaObject;
|
||||
|
||||
for (Map.Entry<Object, Object> entry : map.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
if (StringUtils.contains(entry.getValue().toString(), JSON_SYMBOL)) {
|
||||
map.put(entry.getKey(), translateObject(entry.getValue(), context));
|
||||
} else {
|
||||
map.put(entry.getKey(), translateRawString(entry.getKey().toString(), entry.getValue().toString(), context));
|
||||
}
|
||||
} else {
|
||||
translateObject(entry.getValue(), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (javaObject instanceof Collection) {
|
||||
Collection<Object> collection = (Collection<Object>) javaObject;
|
||||
for (Object item : collection) {
|
||||
translateObject(item, context);
|
||||
}
|
||||
}
|
||||
|
||||
if (javaObject.getClass().isArray()) {
|
||||
for (int i = 0; i < Array.getLength(javaObject); ++i) {
|
||||
Object item = Array.get(javaObject, i);
|
||||
Array.set(javaObject, i, translateObject(item, context));
|
||||
}
|
||||
}
|
||||
|
||||
ObjectSerializer serializer = SerializeConfig.globalInstance.getObjectWriter(javaObject.getClass());
|
||||
if (serializer instanceof JavaBeanSerializer) {
|
||||
JavaBeanSerializer javaBeanSerializer = (JavaBeanSerializer) serializer;
|
||||
|
||||
try {
|
||||
Map<String, Object> values = javaBeanSerializer.getFieldValuesMap(javaObject);
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
if (StringUtils.contains(entry.getValue().toString(), JSON_SYMBOL)) {
|
||||
BeanUtils.setFieldValueByName(javaObject, entry.getKey(), translateObject(entry.getValue(), context), String.class);
|
||||
} else {
|
||||
BeanUtils.setFieldValueByName(javaObject, entry.getKey(), translateRawString(entry.getKey(), entry.getValue().toString(), context), String.class);
|
||||
}
|
||||
} else {
|
||||
translateObject(entry.getValue(), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
MSException.throwException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return javaObject;
|
||||
} catch (StackOverflowError stackOverflowError) {
|
||||
try {
|
||||
return JSON.parseObject(translateRawString(null, JSON.toJSONString(javaObject), context).toString(), javaObject.getClass());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("Failed to translate object " + javaObject.toString(), e);
|
||||
return javaObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Object translateRawString(String key, String rawString, Map<String, String> context) {
|
||||
if (StringUtils.isBlank(rawString)) {
|
||||
return rawString;
|
||||
}
|
||||
for (String ignoreKey : IGNORE_KEYS) {
|
||||
if (StringUtils.containsIgnoreCase(key, ignoreKey)) {
|
||||
return rawString;
|
||||
}
|
||||
}
|
||||
if (StringUtils.contains(rawString, PREFIX)) {
|
||||
rawString = new StringSubstitutor(context, PREFIX, SUFFIX).replace(rawString);
|
||||
if (StringUtils.contains(rawString, PREFIX)) {
|
||||
String[] unTrans = StringUtils.substringsBetween(rawString, PREFIX, SUFFIX);
|
||||
if (unTrans != null) {
|
||||
for (String unTran : unTrans) {
|
||||
rawString = StringUtils.replace(rawString, PREFIX + unTran + SUFFIX, unTran);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key != null) {
|
||||
String desc = context.get(rawString);
|
||||
if (StringUtils.isNotBlank(desc)) {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
return rawString;
|
||||
}
|
||||
|
||||
private static String translateKey(String key, Map<String, String> context) {
|
||||
if (MapUtils.isEmpty(context)) {
|
||||
return key;
|
||||
}
|
||||
String desc = context.get(StringUtils.replace(StringUtils.replace(key, PREFIX, StringUtils.EMPTY), SUFFIX, StringUtils.EMPTY));
|
||||
if (StringUtils.isNotBlank(desc)) {
|
||||
return desc;
|
||||
}
|
||||
return key;
|
||||
return messageSource.getMessage(key, null, "Not Support Key", LocaleContextHolder.getLocale());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,21 @@ package io.metersphere.report;
|
|||
import com.opencsv.bean.CsvToBean;
|
||||
import com.opencsv.bean.CsvToBeanBuilder;
|
||||
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
|
||||
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
|
||||
import io.metersphere.report.base.*;
|
||||
import io.metersphere.report.dto.ErrorsTop5DTO;
|
||||
import io.metersphere.report.dto.RequestStatisticsDTO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.report.core.Sample;
|
||||
import org.apache.jmeter.report.core.SampleMetadata;
|
||||
import org.apache.jmeter.report.dashboard.JsonizerVisitor;
|
||||
import org.apache.jmeter.report.processor.*;
|
||||
import org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer;
|
||||
import org.apache.jmeter.report.processor.graph.impl.ActiveThreadsGraphConsumer;
|
||||
import org.apache.jmeter.report.processor.graph.impl.HitsPerSecondGraphConsumer;
|
||||
import org.apache.jmeter.report.processor.graph.impl.ResponseTimeOverTimeGraphConsumer;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
|
@ -332,99 +343,116 @@ public class JtlResolver {
|
|||
return testOverview;
|
||||
}
|
||||
|
||||
|
||||
public static List<ChartsData> getLoadChartData(String jtlString) {
|
||||
List<ChartsData> chartsDataList = new ArrayList<>();
|
||||
List<Metric> totalMetricList = JtlResolver.resolver(jtlString);
|
||||
|
||||
if (totalMetricList != null) {
|
||||
for (Metric metric : totalMetricList) {
|
||||
metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp()));
|
||||
}
|
||||
}
|
||||
Map<String, List<Metric>> collect = Objects.requireNonNull(totalMetricList).stream().collect(Collectors.groupingBy(Metric::getTimestamp));
|
||||
List<Map.Entry<String, List<Metric>>> entries = new ArrayList<>(collect.entrySet());
|
||||
|
||||
for (Map.Entry<String, List<Metric>> entry : entries) {
|
||||
int failSize = 0;
|
||||
List<Metric> metrics = entry.getValue();
|
||||
Map<String, List<Metric>> metricsMap = metrics.stream().collect(Collectors.groupingBy(Metric::getThreadName));
|
||||
int maxUsers = metricsMap.size();
|
||||
for (Metric metric : metrics) {
|
||||
String isSuccess = metric.getSuccess();
|
||||
if (!"true".equals(isSuccess)) {
|
||||
failSize++;
|
||||
}
|
||||
}
|
||||
|
||||
String timeStamp = "";
|
||||
try {
|
||||
timeStamp = formatDate(entry.getKey());
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
ChartsData chartsData = new ChartsData();
|
||||
chartsData.setxAxis(timeStamp);
|
||||
chartsData.setGroupName("hits");
|
||||
chartsData.setyAxis(new BigDecimal(metrics.size() * 1.0 / maxUsers));
|
||||
chartsDataList.add(chartsData);
|
||||
|
||||
chartsData = new ChartsData();
|
||||
chartsData.setxAxis(timeStamp);
|
||||
chartsData.setGroupName("users");
|
||||
chartsData.setyAxis(new BigDecimal(maxUsers));
|
||||
chartsDataList.add(chartsData);
|
||||
|
||||
chartsData = new ChartsData();
|
||||
chartsData.setxAxis(timeStamp);
|
||||
chartsData.setGroupName("errors");
|
||||
chartsData.setyAxis(new BigDecimal(failSize));
|
||||
chartsDataList.add(chartsData);
|
||||
|
||||
}
|
||||
|
||||
return chartsDataList;
|
||||
Map<String, Object> activeThreadMap = getResultDataMap(jtlString, new ActiveThreadsGraphConsumer());
|
||||
Map<String, Object> hitsMap = getResultDataMap(jtlString, new HitsPerSecondGraphConsumer());
|
||||
List<ChartsData> activeThreadList = new ArrayList<>();
|
||||
List<ChartsData> hitsList = new ArrayList<>();
|
||||
mapResolver(activeThreadMap, activeThreadList, "users");
|
||||
mapResolver(hitsMap, hitsList, "hits");
|
||||
activeThreadList.addAll(hitsList);
|
||||
return activeThreadList;
|
||||
}
|
||||
|
||||
public static List<ChartsData> getResponseTimeChartData(String jtlString) {
|
||||
List<ChartsData> chartsDataList = new ArrayList<>();
|
||||
List<Metric> totalMetricList = JtlResolver.resolver(jtlString);
|
||||
Map<String, Object> activeThreadMap = getResultDataMap(jtlString, new ActiveThreadsGraphConsumer());
|
||||
Map<String, Object> responseTimeMap = getResultDataMap(jtlString, new ResponseTimeOverTimeGraphConsumer());
|
||||
List<ChartsData> activeThreadList = new ArrayList<>();
|
||||
List<ChartsData> responseTimeList = new ArrayList<>();
|
||||
mapResolver(activeThreadMap, activeThreadList, "users");
|
||||
mapResolver(responseTimeMap, responseTimeList, "responseTime");
|
||||
activeThreadList.addAll(responseTimeList);
|
||||
return activeThreadList;
|
||||
|
||||
totalMetricList.forEach(metric -> {
|
||||
metric.setTimestamp(stampToDate(DATE_TIME_PATTERN, metric.getTimestamp()));
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, List<Metric>> metricMap = totalMetricList.stream().collect(Collectors.groupingBy(Metric::getTimestamp));
|
||||
List<Map.Entry<String, List<Metric>>> entries = new ArrayList<>(metricMap.entrySet());
|
||||
public static void mapResolver(Map<String, Object> map, List list, String seriesName) {
|
||||
// ThreadGroup-1
|
||||
for (String key : map.keySet()) {
|
||||
MapResultData mapResultData = (MapResultData) map.get(key);
|
||||
ResultData maxY = mapResultData.getResult("maxY");
|
||||
ListResultData series = (ListResultData) mapResultData.getResult("series");
|
||||
if (series.getSize() > 0) {
|
||||
for (int j = 0; j < series.getSize(); j++) {
|
||||
MapResultData resultData = (MapResultData) series.get(j);
|
||||
// data, isOverall, label, isController
|
||||
ListResultData data = (ListResultData) resultData.getResult("data");
|
||||
ValueResultData label = (ValueResultData) resultData.getResult("label");
|
||||
|
||||
for (Map.Entry<String, List<Metric>> entry : entries) {
|
||||
List<Metric> metricList = entry.getValue();
|
||||
Map<String, List<Metric>> metricsMap = metricList.stream().collect(Collectors.groupingBy(Metric::getThreadName));
|
||||
int maxUsers = metricsMap.size();
|
||||
int sumElapsedTime = metricList.stream().mapToInt(metric -> Integer.parseInt(metric.getElapsed())).sum();
|
||||
String timeStamp = "";
|
||||
if (data.getSize() > 0) {
|
||||
for (int i = 0; i < data.getSize(); i++) {
|
||||
ListResultData listResultData = (ListResultData) data.get(i);
|
||||
String result = listResultData.accept(new JsonizerVisitor());
|
||||
result = result.substring(1, result.length() - 1);
|
||||
String[] split = result.split(",");
|
||||
ChartsData chartsData = new ChartsData();
|
||||
BigDecimal bigDecimal = new BigDecimal(split[0]);
|
||||
String timeStamp = bigDecimal.toPlainString();
|
||||
String time = null;
|
||||
try {
|
||||
timeStamp = formatDate(entry.getKey());
|
||||
time = formatDate(stampToDate(DATE_TIME_PATTERN, timeStamp));
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
ChartsData chartsData = new ChartsData();
|
||||
chartsData.setxAxis(timeStamp);
|
||||
chartsData.setGroupName("users");
|
||||
chartsData.setyAxis(new BigDecimal(maxUsers));
|
||||
chartsDataList.add(chartsData);
|
||||
|
||||
ChartsData chartsData2 = new ChartsData();
|
||||
chartsData2.setxAxis(timeStamp);
|
||||
chartsData2.setGroupName("responseTime");
|
||||
chartsData2.setyAxis(new BigDecimal(sumElapsedTime * 1.0 / metricList.size()));
|
||||
chartsDataList.add(chartsData2);
|
||||
|
||||
chartsData.setxAxis(time);
|
||||
chartsData.setyAxis(new BigDecimal(split[1].trim()));
|
||||
if (series.getSize() == 1) {
|
||||
chartsData.setGroupName(seriesName);
|
||||
} else {
|
||||
chartsData.setGroupName((String) label.getValue());
|
||||
}
|
||||
list.add(chartsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chartsDataList;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> getResultDataMap(String jtlString, AbstractOverTimeGraphConsumer timeGraphConsumer) {
|
||||
int row = 0;
|
||||
AbstractOverTimeGraphConsumer abstractOverTimeGraphConsumer = timeGraphConsumer;
|
||||
abstractOverTimeGraphConsumer.setGranularity(60000);
|
||||
// 这个路径不存在
|
||||
JMeterUtils.loadJMeterProperties("jmeter.properties");
|
||||
SampleMetadata sampleMetaData = createTestMetaData();
|
||||
SampleContext sampleContext = new SampleContext();
|
||||
abstractOverTimeGraphConsumer.setSampleContext(sampleContext);
|
||||
abstractOverTimeGraphConsumer.initialize();
|
||||
abstractOverTimeGraphConsumer.startConsuming();
|
||||
StringTokenizer tokenizer = new StringTokenizer(jtlString, "\n");
|
||||
// 去掉第一行
|
||||
tokenizer.nextToken();
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String line = tokenizer.nextToken();
|
||||
String[] data = line.split(",", -1);
|
||||
Sample sample = new Sample(row++, sampleMetaData, data);
|
||||
abstractOverTimeGraphConsumer.consume(sample, 0);
|
||||
}
|
||||
abstractOverTimeGraphConsumer.stopConsuming();
|
||||
return sampleContext.getData();
|
||||
}
|
||||
|
||||
// Create a static SampleMetadataObject
|
||||
private static SampleMetadata createTestMetaData() {
|
||||
String columnsString = "timeStamp,elapsed,label,responseCode,responseMessage,threadName,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect";
|
||||
columnsString = "timeStamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect";
|
||||
|
||||
String[] columns = new String[17];
|
||||
int lastComa = 0;
|
||||
int columnIndex = 0;
|
||||
for (int i = 0; i < columnsString.length(); i++) {
|
||||
if (columnsString.charAt(i) == ',') {
|
||||
columns[columnIndex] = columnsString.substring(lastComa, i);
|
||||
lastComa = i + 1;
|
||||
columnIndex++;
|
||||
} else if (i + 1 == columnsString.length()) {
|
||||
columns[columnIndex] = columnsString.substring(lastComa, i + 1);
|
||||
}
|
||||
}
|
||||
return new SampleMetadata(',', columns);
|
||||
}
|
||||
|
||||
public static ReportTimeInfo getReportTimeInfo(String jtlString) {
|
||||
|
@ -434,7 +462,7 @@ public class JtlResolver {
|
|||
totalLineList.sort(Comparator.comparing(t0 -> Long.valueOf(t0.getTimestamp())));
|
||||
|
||||
String startTimeStamp = totalLineList.get(0).getTimestamp();
|
||||
String endTimeStamp = totalLineList.get(totalLineList.size()-1).getTimestamp();
|
||||
String endTimeStamp = totalLineList.get(totalLineList.size() - 1).getTimestamp();
|
||||
|
||||
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
|
||||
String startTime = dtf.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(startTimeStamp)), ZoneId.systemDefault()));
|
||||
|
@ -445,7 +473,7 @@ public class JtlResolver {
|
|||
Date startDate = new Date(Long.parseLong(startTimeStamp));
|
||||
Date endDate = new Date(Long.parseLong(endTimeStamp));
|
||||
long timestamp = endDate.getTime() - startDate.getTime();
|
||||
reportTimeInfo.setDuration(String.valueOf(timestamp*1.0 / 1000 / 60));
|
||||
reportTimeInfo.setDuration(String.valueOf(timestamp * 1.0 / 1000 / 60));
|
||||
|
||||
// todo 时间问题
|
||||
long seconds = Duration.between(Instant.ofEpochMilli(Long.parseLong(startTimeStamp)), Instant.ofEpochMilli(Long.parseLong(endTimeStamp))).getSeconds();
|
||||
|
|
|
@ -31,7 +31,7 @@ import java.util.stream.Collectors;
|
|||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class PerformanceTestService {
|
||||
private static final String HEADERS = "timestamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect";
|
||||
public static final String HEADERS = "timeStamp,elapsed,label,responseCode,responseMessage,threadName,dataType,success,failureMessage,bytes,sentBytes,grpThreads,allThreads,URL,Latency,IdleTime,Connect";
|
||||
|
||||
@Resource
|
||||
private LoadTestMapper loadTestMapper;
|
||||
|
|
|
@ -224,7 +224,7 @@ public class TestCaseService {
|
|||
}
|
||||
|
||||
private List<TestCaseExcelData> generateExportTemplate() {
|
||||
List<TestCaseExcelData> list = new ArrayList<TestCaseExcelData>();
|
||||
List<TestCaseExcelData> list = new ArrayList<>();
|
||||
StringBuilder path = new StringBuilder("");
|
||||
List<String> types = Arrays.asList("functional", "performance", "api");
|
||||
List<String> methods = Arrays.asList("manual", "auto");
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
import io.metersphere.base.domain.TestPlanTestCase;
|
||||
import io.metersphere.base.domain.TestPlanTestCaseExample;
|
||||
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
|
||||
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.controller.request.testcase.TestCaseBatchRequest;
|
||||
import io.metersphere.controller.request.testplancase.QueryTestPlanCaseRequest;
|
||||
import io.metersphere.dto.TestPlanCaseDTO;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -21,11 +26,10 @@ public class TestPlanTestCaseService {
|
|||
@Resource
|
||||
ExtTestCaseMapper extTestCaseMapper;
|
||||
|
||||
public List<TestPlanCaseDTO> getTestPlanCases(QueryTestCaseRequest request) {
|
||||
public List<TestPlanCaseDTO> getTestPlanCases(QueryTestPlanCaseRequest request) {
|
||||
return extTestCaseMapper.getTestPlanTestCases(request);
|
||||
}
|
||||
|
||||
|
||||
public void editTestCase(TestPlanTestCase testPlanTestCase) {
|
||||
testPlanTestCase.setUpdateTime(System.currentTimeMillis());
|
||||
testPlanTestCaseMapper.updateByPrimaryKeySelective(testPlanTestCase);
|
||||
|
@ -34,4 +38,15 @@ public class TestPlanTestCaseService {
|
|||
public int deleteTestCase(Integer id) {
|
||||
return testPlanTestCaseMapper.deleteByPrimaryKey(id);
|
||||
}
|
||||
|
||||
public void editTestCaseBath(TestCaseBatchRequest request) {
|
||||
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
|
||||
testPlanTestCaseExample.createCriteria().andIdIn(request.getIds());
|
||||
|
||||
TestPlanTestCase testPlanTestCase = new TestPlanTestCase();
|
||||
BeanUtils.copyBean(testPlanTestCase, request);
|
||||
testPlanTestCaseMapper.updateByExampleSelective(
|
||||
testPlanTestCase,
|
||||
testPlanTestCaseExample);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,3 +35,5 @@ spring.flyway.table=metersphere_version
|
|||
spring.flyway.baseline-version=0
|
||||
spring.flyway.encoding=UTF-8
|
||||
spring.flyway.validate-on-migrate=false
|
||||
|
||||
spring.messages.basename=i18n/messages
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"error_lang_invalid": "Invalid language parameter",
|
||||
"load_test_already_exists": "Duplicate load test name",
|
||||
"project_name_is_null": "Project name cannot be null",
|
||||
"project_name_already_exists": "The project name already exists",
|
||||
"workspace_name_is_null": "Workspace name cannot be null",
|
||||
"workspace_name_already_exists": "The workspace name already exists",
|
||||
"workspace_does_not_belong_to_user": "The current workspace does not belong to the current user",
|
||||
"organization_does_not_belong_to_user": "The current organization does not belong to the current user",
|
||||
"file_cannot_be_null": "File cannot be empty!",
|
||||
"edit_load_test_not_found": "Cannot edit test, test not found:",
|
||||
"run_load_test_not_found": "Cannot run test, test not found:",
|
||||
"run_load_test_file_not_found": "Unable to run test, unable to get test file meta information, test ID:",
|
||||
"run_load_test_file_content_not_found": "Cannot run test, cannot get test file content, test ID:",
|
||||
"run_load_test_file_init_error": "Failed to run test, failed to initialize run environment, test ID:",
|
||||
"load_test_is_running": "Load test is running, please wait.",
|
||||
"node_deep_limit": "The node depth does not exceed 5 layers!",
|
||||
"no_nodes_message": "No node message",
|
||||
"duplicate_node_ip": "Duplicate IPs",
|
||||
"only_one_k8s": "Only one K8s can be added",
|
||||
"organization_id_is_null": "Organization ID cannot be null",
|
||||
"max_thread_insufficient": "The number of concurrent users exceeds"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
error_lang_invalid=Invalid language parameter
|
||||
load_test_already_exists=Duplicate load test name
|
||||
project_name_is_null=Project name cannot be null
|
||||
project_name_already_exists=The project name already exists
|
||||
workspace_name_is_null=Workspace name cannot be null
|
||||
workspace_name_already_exists=The workspace name already exists
|
||||
workspace_does_not_belong_to_user=The current workspace does not belong to the current user
|
||||
organization_does_not_belong_to_user=The current organization does not belong to the current user
|
||||
file_cannot_be_null=File cannot be empty!
|
||||
edit_load_test_not_found=Cannot edit test, test not found=
|
||||
run_load_test_not_found=Cannot run test, test not found=
|
||||
run_load_test_file_not_found=Unable to run test, unable to get test file meta information, test ID=
|
||||
run_load_test_file_content_not_found=Cannot run test, cannot get test file content, test ID=
|
||||
run_load_test_file_init_error=Failed to run test, failed to initialize run environment, test ID=
|
||||
load_test_is_running=Load test is running, please wait.
|
||||
node_deep_limit=The node depth does not exceed 5 layers!
|
||||
no_nodes_message=No node message
|
||||
duplicate_node_ip=Duplicate IPs
|
||||
only_one_k8s=Only one K8s can be added
|
||||
organization_id_is_null=Organization ID cannot be null
|
||||
max_thread_insufficient=The number of concurrent users exceeds
|
|
@ -0,0 +1,21 @@
|
|||
error_lang_invalid=\u8BED\u8A00\u53C2\u6570\u9519\u8BEF
|
||||
load_test_already_exists=\u6D4B\u8BD5\u540D\u79F0\u4E0D\u80FD\u91CD\u590D
|
||||
project_name_is_null=\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||
project_name_already_exists=\u9879\u76EE\u540D\u79F0\u5DF2\u5B58\u5728
|
||||
workspace_name_is_null=\u5DE5\u4F5C\u7A7A\u95F4\u540D\u4E0D\u80FD\u4E3A\u7A7A
|
||||
workspace_name_already_exists=\u5DE5\u4F5C\u7A7A\u95F4\u540D\u5DF2\u5B58\u5728
|
||||
workspace_does_not_belong_to_user=\u5F53\u524D\u5DE5\u4F5C\u7A7A\u95F4\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237
|
||||
organization_does_not_belong_to_user=\u5F53\u524D\u7EC4\u7EC7\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7528\u6237
|
||||
file_cannot_be_null=\u6587\u4EF6\u4E0D\u80FD\u4E3A\u7A7A\uFF01
|
||||
edit_load_test_not_found=\u65E0\u6CD5\u7F16\u8F91\u6D4B\u8BD5\uFF0C\u672A\u627E\u5230\u6D4B\u8BD5\uFF1A
|
||||
run_load_test_not_found=\u65E0\u6CD5\u8FD0\u884C\u6D4B\u8BD5\uFF0C\u672A\u627E\u5230\u6D4B\u8BD5\uFF1A
|
||||
run_load_test_file_not_found=\u65E0\u6CD5\u8FD0\u884C\u6D4B\u8BD5\uFF0C\u65E0\u6CD5\u83B7\u53D6\u6D4B\u8BD5\u6587\u4EF6\u5143\u4FE1\u606F\uFF0C\u6D4B\u8BD5ID\uFF1A
|
||||
run_load_test_file_content_not_found=\u65E0\u6CD5\u8FD0\u884C\u6D4B\u8BD5\uFF0C\u65E0\u6CD5\u83B7\u53D6\u6D4B\u8BD5\u6587\u4EF6\u5185\u5BB9\uFF0C\u6D4B\u8BD5ID\uFF1A
|
||||
run_load_test_file_init_error=\u65E0\u6CD5\u8FD0\u884C\u6D4B\u8BD5\uFF0C\u521D\u59CB\u5316\u8FD0\u884C\u73AF\u5883\u5931\u8D25\uFF0C\u6D4B\u8BD5ID\uFF1A
|
||||
load_test_is_running=\u6D4B\u8BD5\u6B63\u5728\u8FD0\u884C, \u8BF7\u7B49\u5F85
|
||||
node_deep_limit=\u8282\u70B9\u6DF1\u5EA6\u4E0D\u8D85\u8FC75\u5C42\uFF01
|
||||
no_nodes_message=\u6CA1\u6709\u8282\u70B9\u4FE1\u606F
|
||||
duplicate_node_ip=\u8282\u70B9 IP \u91CD\u590D
|
||||
only_one_k8s=\u53EA\u80FD\u6DFB\u52A0\u4E00\u4E2A K8s
|
||||
organization_id_is_null=\u7EC4\u7EC7 ID \u4E0D\u80FD\u4E3A\u7A7A
|
||||
max_thread_insufficient=\u5E76\u53D1\u7528\u6237\u6570\u8D85\u989D
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"error_lang_invalid": "语言参数错误",
|
||||
"load_test_already_exists": "测试名称不能重复",
|
||||
"project_name_is_null": "项目名称不能为空",
|
||||
"project_name_already_exists": "项目名称已存在",
|
||||
"workspace_name_is_null": "工作空间名不能为空",
|
||||
"workspace_name_already_exists": "工作空间名已存在",
|
||||
"workspace_does_not_belong_to_user": "当前工作空间不属于当前用户",
|
||||
"organization_does_not_belong_to_user": "当前组织不属于当前用户",
|
||||
"file_cannot_be_null": "文件不能为空!",
|
||||
"edit_load_test_not_found": "无法编辑测试,未找到测试:",
|
||||
"run_load_test_not_found": "无法运行测试,未找到测试:",
|
||||
"run_load_test_file_not_found": "无法运行测试,无法获取测试文件元信息,测试ID:",
|
||||
"run_load_test_file_content_not_found": "无法运行测试,无法获取测试文件内容,测试ID:",
|
||||
"run_load_test_file_init_error": "无法运行测试,初始化运行环境失败,测试ID:",
|
||||
"load_test_is_running": "测试正在运行, 请等待",
|
||||
"node_deep_limit": "节点深度不超过5层!",
|
||||
"no_nodes_message": "没有节点信息",
|
||||
"duplicate_node_ip": "节点 IP 重复",
|
||||
"only_one_k8s": "只能添加一个 K8s",
|
||||
"organization_id_is_null": "组织 ID 不能为空",
|
||||
"max_thread_insufficient": "并发用户数超额"
|
||||
}
|
|
@ -5,9 +5,13 @@ import io.metersphere.base.mapper.LoadTestReportMapper;
|
|||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.jmeter.report.core.Sample;
|
||||
import org.apache.jmeter.report.core.SampleMetadata;
|
||||
import org.apache.jmeter.report.dashboard.JsonizerVisitor;
|
||||
import org.apache.jmeter.report.processor.ListResultData;
|
||||
import org.apache.jmeter.report.processor.MapResultData;
|
||||
import org.apache.jmeter.report.processor.ResultData;
|
||||
import org.apache.jmeter.report.processor.SampleContext;
|
||||
import org.apache.jmeter.report.processor.graph.AbstractOverTimeGraphConsumer;
|
||||
import org.apache.jmeter.report.processor.graph.impl.LatencyOverTimeGraphConsumer;
|
||||
import org.apache.jmeter.report.processor.graph.impl.ActiveThreadsGraphConsumer;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -16,7 +20,7 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
|
@ -32,8 +36,8 @@ public class GraphConsumerTest {
|
|||
|
||||
@Before
|
||||
public void init() {
|
||||
timeGraphConsumer = new LatencyOverTimeGraphConsumer();
|
||||
timeGraphConsumer.setTitle("graph title");
|
||||
timeGraphConsumer = new ActiveThreadsGraphConsumer();
|
||||
// timeGraphConsumer.setTitle("graph title");
|
||||
timeGraphConsumer.setGranularity(60000);
|
||||
|
||||
JMeterUtils.loadJMeterProperties("jmeter.properties"); // 这个路径不存在
|
||||
|
@ -56,8 +60,9 @@ public class GraphConsumerTest {
|
|||
|
||||
@Test
|
||||
public void test2() {
|
||||
int row = 0;
|
||||
SampleContext sampleContext = new SampleContext();
|
||||
sampleContext.setWorkingDirectory(new File("/tmp/test_report/"));
|
||||
// sampleContext.setWorkingDirectory(new File("/tmp/test_report/"));
|
||||
timeGraphConsumer.setSampleContext(sampleContext);
|
||||
|
||||
timeGraphConsumer.initialize();
|
||||
|
@ -70,11 +75,33 @@ public class GraphConsumerTest {
|
|||
while (tokenizer.hasMoreTokens()) {
|
||||
String line = tokenizer.nextToken();
|
||||
String[] data = line.split(",", -1);
|
||||
Sample sample = new Sample(0, sampleMetaData, data);
|
||||
Sample sample = new Sample(row++, sampleMetaData, data);
|
||||
timeGraphConsumer.consume(sample, 0);
|
||||
}
|
||||
timeGraphConsumer.stopConsuming();
|
||||
System.out.println(sampleContext.getData());
|
||||
Map<String, Object> map = sampleContext.getData();
|
||||
for (String key : map.keySet()) {
|
||||
MapResultData mapResultData = (MapResultData) map.get(key);
|
||||
ResultData maxY = mapResultData.getResult("maxY");
|
||||
ListResultData series = (ListResultData) mapResultData.getResult("series");
|
||||
if (series.getSize() > 0) {
|
||||
MapResultData resultData = (MapResultData) series.get(0);
|
||||
ListResultData data = (ListResultData) resultData.getResult("data");
|
||||
if (data.getSize() > 0) {
|
||||
for (int i = 0; i < data.getSize(); i++) {
|
||||
ListResultData resultData1 = (ListResultData) data.get(i);
|
||||
String accept = resultData1.accept(new JsonizerVisitor());
|
||||
String[] split = accept.split(",");
|
||||
System.out.println(resultData1);
|
||||
System.out.println(accept);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.out.println("+++++++++" + sampleContext.getData());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<el-col v-if="auth">
|
||||
<el-row id="header-top" type="flex" justify="space-between" align="middle">
|
||||
<el-col :span="3">
|
||||
<el-col :span="2">
|
||||
<a class="logo"/>
|
||||
</el-col>
|
||||
<el-col :span="9">
|
||||
<el-col :span="10">
|
||||
<ms-top-menus/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="confirm">确 定</el-button>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsDialogFooter",
|
||||
methods: {
|
||||
cancel() {
|
||||
this.$emit("cancel");
|
||||
},
|
||||
confirm() {
|
||||
this.$emit("confirm");
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
|
||||
|
||||
<el-tooltip :disabled="disabled"
|
||||
:content="tip"
|
||||
placement="bottom"
|
||||
:effect="effect">
|
||||
|
||||
<el-button @click="exec()"
|
||||
circle
|
||||
:type="type"
|
||||
:icon="icon"
|
||||
:size="size"/>
|
||||
</el-tooltip>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsTipButton",
|
||||
props: {
|
||||
tip: String,
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'el-icon-question'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
effect: {
|
||||
type: String,
|
||||
default: 'dark'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'mini'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
exec() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-col :span="15" :offset="3">
|
||||
<el-col :span="21">
|
||||
<el-menu :unique-opened="true" mode="horizontal" router
|
||||
class="header-user-menu align-right"
|
||||
background-color="#2c2a48"
|
||||
|
|
|
@ -157,7 +157,8 @@
|
|||
this.$get("/performance/report/content/res_chart/" + this.id, res => {
|
||||
let data = res.data;
|
||||
let userList = data.filter(m => m.groupName === "users").map(m => m.yAxis);
|
||||
let responseTimeList = data.filter(m => m.groupName === "responseTime").map(m => m.yAxis);
|
||||
let responseTimeList = data.filter(m => m.groupName != "users").map(m => m.yAxis);
|
||||
let responseGroupNameList = data.filter(m => m.groupName != "users").map(m => m.groupName);
|
||||
let userMax = this._getChartMax(userList);
|
||||
let resMax = this._getChartMax(responseTimeList);
|
||||
let resOption = {
|
||||
|
@ -171,7 +172,24 @@
|
|||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis'
|
||||
trigger: 'axis',
|
||||
extraCssText: 'z-index: 999;',
|
||||
formatter: function (params, ticket, callback) {
|
||||
let result = "";
|
||||
let name = params[0].name;
|
||||
result += name + "<br/>";
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
let seriesName = params[i].seriesName;
|
||||
if (seriesName.length > 100) {
|
||||
seriesName = seriesName.substring(0, 100);
|
||||
}
|
||||
let value = params[i].value;
|
||||
let marker = params[i].marker;
|
||||
result += marker + seriesName + ": " + value[1] + "<br/>";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
legend: {},
|
||||
xAxis: {},
|
||||
|
@ -197,14 +215,12 @@
|
|||
{
|
||||
name: 'users',
|
||||
color: '#0CA74A',
|
||||
},
|
||||
{
|
||||
name: "responseTime",
|
||||
yAxisIndex: '1',
|
||||
color: '#99743C',
|
||||
}
|
||||
]
|
||||
}
|
||||
responseGroupNameList.forEach(item => {
|
||||
setting["series"].splice(0, 0, {name: item, yAxisIndex: '1'})
|
||||
})
|
||||
this.resOption = this.generateOption(resOption, data, setting);
|
||||
})
|
||||
},
|
||||
|
@ -229,17 +245,19 @@
|
|||
legend.push(name)
|
||||
series[name] = []
|
||||
}
|
||||
series[name].splice(xAxis.indexOf(item.xAxis), 0, item.yAxis.toFixed(2));
|
||||
series[name].splice(xAxis.indexOf(item.xAxis), 0, [item.xAxis, item.yAxis.toFixed(2)]);
|
||||
})
|
||||
this.$set(option.legend, "data", legend);
|
||||
this.$set(option.legend, "bottom", 10);
|
||||
this.$set(option.legend, "type", "scroll");
|
||||
this.$set(option.legend, "bottom", "10px");
|
||||
this.$set(option.xAxis, "data", xAxis);
|
||||
for (let name in series) {
|
||||
let data = series[name];
|
||||
let d = series[name];
|
||||
d.sort((a, b) => a[0].localeCompare(b[0]));
|
||||
let items = {
|
||||
name: name,
|
||||
type: 'line',
|
||||
data: data
|
||||
data: d
|
||||
};
|
||||
let seriesArrayNames = seriesArray.map(m => m.name);
|
||||
if (seriesArrayNames.includes(name)) {
|
||||
|
|
|
@ -89,23 +89,8 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1">
|
||||
<div class="table-page">
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[5, 10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||
:total="total"/>
|
||||
|
||||
</el-card>
|
||||
</el-main>
|
||||
|
@ -116,22 +101,21 @@
|
|||
import MsCreateBox from '../../../settings/CreateBox';
|
||||
import TestCaseImport from '../components/TestCaseImport';
|
||||
import TestCaseExport from '../components/TestCaseExport';
|
||||
import MsTablePagination from '../../../../components/common/pagination/TablePagination';
|
||||
|
||||
|
||||
export default {
|
||||
name: "TestCaseList",
|
||||
components: {MsCreateBox, TestCaseImport, TestCaseExport},
|
||||
components: {MsCreateBox, TestCaseImport, TestCaseExport, MsTablePagination},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
deletePath: "/test/case/delete",
|
||||
condition: "",
|
||||
tableData: [],
|
||||
multipleSelection: [],
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
loadingRequire: {project: true, testCase: true},
|
||||
testId: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
@ -157,7 +141,6 @@
|
|||
if (this.currentProject) {
|
||||
param.projectId = this.currentProject.id;
|
||||
this.result = this.$post(this.buildPagePath('/test/case/list'), param, response => {
|
||||
this.loadingRequire.testCase = false;
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
|
@ -173,17 +156,6 @@
|
|||
testCaseCreate() {
|
||||
this.$emit('openTestCaseEditDialog');
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size;
|
||||
this.initTableData();
|
||||
},
|
||||
handleCurrentChange(current) {
|
||||
this.currentPage = current;
|
||||
this.initTableData();
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val;
|
||||
},
|
||||
handleEdit(testCase) {
|
||||
this.$emit('testCaseEdit', testCase);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<el-row type="flex" justify="start" :gutter="20">
|
||||
<el-col>
|
||||
<el-button type="success" round
|
||||
:icon="status == 'Pass' ? 'el-icon-check' : ''"
|
||||
@click="setStatus('Pass')"> {{$t('test_track.pass')}}</el-button>
|
||||
</el-col>
|
||||
<el-col >
|
||||
<el-button type="danger" round
|
||||
:icon="status == 'Failure' ? 'el-icon-check' : ''"
|
||||
@click="setStatus('Failure')"> {{$t('test_track.failure')}}</el-button>
|
||||
</el-col>
|
||||
<el-col >
|
||||
<el-button type="warning" round
|
||||
:icon="status == 'Blocking' ? 'el-icon-check' : ''"
|
||||
@click="setStatus('Blocking')"> {{$t('test_track.blocking')}}</el-button>
|
||||
</el-col>
|
||||
<el-col >
|
||||
<el-button type="info" round
|
||||
:icon="status == 'Skip' ? 'el-icon-check' : ''"
|
||||
@click="setStatus('Skip')"> {{$t('test_track.skip')}}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TestPlanTestCaseStatusButton",
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setStatus(status) {
|
||||
this.$emit('statusChange', status);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-row {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
|
||||
<el-dialog title="更改执行人"
|
||||
:visible.sync="executorEditVisible"
|
||||
width="20%">
|
||||
<el-select v-model="executor" placeholder="请选择活动区域">
|
||||
<el-option v-for="item in executorOptions" :key="item.id"
|
||||
:label="item.name" :value="item.name"></el-option>
|
||||
</el-select>
|
||||
|
||||
<template v-slot:footer>
|
||||
<ms-dialog-footer
|
||||
@cancel="executorEditVisible = false"
|
||||
@confirm="saveExecutor"/>
|
||||
</template>
|
||||
|
||||
|
||||
</el-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {WORKSPACE_ID} from '../../../../../common/js/constants'
|
||||
import MsDialogFooter from '../../../common/components/MsDialogFooter'
|
||||
|
||||
export default {
|
||||
name: "executorEdit",
|
||||
components: {MsDialogFooter},
|
||||
data() {
|
||||
return {
|
||||
executorEditVisible: false,
|
||||
executor: '',
|
||||
executorOptions: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
selectIds: {
|
||||
type: Set
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setMaintainerOptions() {
|
||||
let workspaceId = localStorage.getItem(WORKSPACE_ID);
|
||||
this.$post('/user/ws/member/list/all', {workspaceId:workspaceId}, response => {
|
||||
this.executorOptions = response.data;
|
||||
});
|
||||
},
|
||||
saveExecutor() {
|
||||
let param = {};
|
||||
param.executor = this.executor;
|
||||
if (this.executor === '') {
|
||||
this.$message('请选择执行人!');
|
||||
return;
|
||||
}
|
||||
param.ids = [...this.selectIds];
|
||||
this.$post('/test/plan/case/batch/edit' , param, () => {
|
||||
this.executor = '';
|
||||
this.selectIds.clear();
|
||||
this.$message.success("保存成功");
|
||||
this.executorEditVisible = false;
|
||||
this.$emit('refresh');
|
||||
});
|
||||
},
|
||||
openExecutorEdit() {
|
||||
this.executorEditVisible = true;
|
||||
this.setMaintainerOptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
|
||||
<el-dialog title="更改执行结果"
|
||||
:visible.sync="statusEditVisible"
|
||||
width="30%">
|
||||
|
||||
<test-plan-test-case-status-button
|
||||
@statusChange="statusChange"
|
||||
:status="status"/>
|
||||
|
||||
<template v-slot:footer>
|
||||
<ms-dialog-footer
|
||||
@cancel="statusEditVisible = false"
|
||||
@confirm="saveStatus"/>
|
||||
</template>
|
||||
|
||||
</el-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TestPlanTestCaseStatusButton from '../common/TestPlanTestCaseStatusButton';
|
||||
import MsDialogFooter from '../../../common/components/MsDialogFooter'
|
||||
|
||||
export default {
|
||||
name: "statusEdit",
|
||||
components: {TestPlanTestCaseStatusButton, MsDialogFooter},
|
||||
data() {
|
||||
return {
|
||||
statusEditVisible: false,
|
||||
status: ''
|
||||
}
|
||||
},
|
||||
props: {
|
||||
selectIds: {
|
||||
type: Set
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
statusChange(status) {
|
||||
this.status = status;
|
||||
},
|
||||
saveStatus() {
|
||||
let param = {};
|
||||
if (this.status === '') {
|
||||
this.$message('请选择执行结果!');
|
||||
return;
|
||||
}
|
||||
param.status = this.status;
|
||||
param.ids = [...this.selectIds];
|
||||
this.$post('/test/plan/case/batch/edit' , param, () => {
|
||||
this.selectIds.clear();
|
||||
this.status = '';
|
||||
this.$message.success("保存成功");
|
||||
this.statusEditVisible = false;
|
||||
this.$emit('refresh');
|
||||
});
|
||||
},
|
||||
openStatusEdit() {
|
||||
this.statusEditVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -143,7 +143,6 @@
|
|||
}
|
||||
},
|
||||
close() {
|
||||
console.log("clear");
|
||||
this.selectIds.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<el-input type="text" size="small" :placeholder="$t('load_test.search_by_name')"
|
||||
prefix-icon="el-icon-search"
|
||||
maxlength="60"
|
||||
v-model="condition" @change="search" clearable/>
|
||||
v-model="condition" @change="initTableData" clearable/>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
@ -88,23 +88,25 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1">
|
||||
<div class="table-page">
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[5, 10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<!--<div>-->
|
||||
<!--<el-row>-->
|
||||
<!--<el-col :span="22" :offset="1">-->
|
||||
<!--<div class="table-page">-->
|
||||
<!--<el-pagination-->
|
||||
<!--@size-change="handleSizeChange"-->
|
||||
<!--@current-change="handleCurrentChange"-->
|
||||
<!--:current-page.sync="currentPage"-->
|
||||
<!--:page-sizes="[5, 10, 20, 50, 100]"-->
|
||||
<!--:page-size="pageSize"-->
|
||||
<!--layout="total, sizes, prev, pager, next, jumper"-->
|
||||
<!--:total="total">-->
|
||||
<!--</el-pagination>-->
|
||||
<!--</div>-->
|
||||
<!--</el-col>-->
|
||||
<!--</el-row>-->
|
||||
<!--</div>-->
|
||||
<ms-table-pagination :change="initTableData" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||
:total="total"/>
|
||||
|
||||
</el-card>
|
||||
</el-main>
|
||||
|
@ -113,22 +115,21 @@
|
|||
|
||||
<script>
|
||||
import MsCreateBox from '../../../settings/CreateBox';
|
||||
import MsTablePagination from '../../../../components/common/pagination/TablePagination';
|
||||
|
||||
export default {
|
||||
name: "TestPlanList",
|
||||
components: {MsCreateBox},
|
||||
components: {MsCreateBox, MsTablePagination},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
queryPath: "/test/plan/list",
|
||||
deletePath: "/test/plan/delete",
|
||||
condition: "",
|
||||
tableData: [],
|
||||
multipleSelection: [],
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
testId: null,
|
||||
tableData: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -146,26 +147,12 @@
|
|||
this.tableData = data.listObject;
|
||||
});
|
||||
},
|
||||
search() {
|
||||
this.initTableData();
|
||||
},
|
||||
buildPagePath(path) {
|
||||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
testPlanCreate() {
|
||||
this.$emit('openTestPlanEditDialog');
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size;
|
||||
this.initTableData();
|
||||
},
|
||||
handleCurrentChange(current) {
|
||||
this.currentPage = current;
|
||||
this.initTableData();
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val;
|
||||
},
|
||||
handleEdit(testPlan) {
|
||||
this.$emit('testPlanEdit', testPlan);
|
||||
},
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row >
|
||||
<el-row>
|
||||
<el-col :span="15" :offset="1">
|
||||
<div style="margin-bottom: 5px;">
|
||||
<span class="cast_label">{{$t('commons.remark')}}:</span>
|
||||
|
@ -98,28 +98,9 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :offset="1" :span="2">
|
||||
<el-button type="success" round
|
||||
:icon="testCase.status == 'Pass' ? 'el-icon-check' : ''"
|
||||
@click="setTestCaseStatus('Pass')"> {{$t('test_track.pass')}}</el-button>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-button type="danger" round
|
||||
:icon="testCase.status == 'Failure' ? 'el-icon-check' : ''"
|
||||
@click="setTestCaseStatus('Failure')"> {{$t('test_track.failure')}}</el-button>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-button type="warning" round
|
||||
:icon="testCase.status == 'Blocking' ? 'el-icon-check' : ''"
|
||||
@click="setTestCaseStatus('Blocking')"> {{$t('test_track.blocking')}}</el-button>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-button type="info" round
|
||||
:icon="testCase.status == 'Skip' ? 'el-icon-check' : ''"
|
||||
@click="setTestCaseStatus('Skip')"> {{$t('test_track.skip')}}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<test-plan-test-case-status-button class="status-button"
|
||||
@statusChange="statusChange"
|
||||
:status="testCase.status"/>
|
||||
|
||||
<el-row type="flex" justify="end">
|
||||
<el-col :span="5">
|
||||
|
@ -136,12 +117,15 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import TestPlanTestCaseStatusButton from '../common/TestPlanTestCaseStatusButton';
|
||||
|
||||
export default {
|
||||
name: "TestPlanTestCaseEdit",
|
||||
components: {TestPlanTestCaseStatusButton},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
testCase: {}
|
||||
testCase: {TestPlanTestCaseStatusButton}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -151,7 +135,7 @@
|
|||
cancel() {
|
||||
this.dialog = false;
|
||||
},
|
||||
setTestCaseStatus(status) {
|
||||
statusChange(status) {
|
||||
this.testCase.status = status;
|
||||
},
|
||||
saveCase() {
|
||||
|
@ -197,4 +181,8 @@
|
|||
color: dimgray;
|
||||
}
|
||||
|
||||
.status-button {
|
||||
padding-left: 4%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -4,22 +4,47 @@
|
|||
<el-card v-loading="result.loading">
|
||||
<template v-slot:header>
|
||||
<div>
|
||||
<el-row type="flex" justify="space-between" align="middle">
|
||||
<el-col :span="5">
|
||||
<span class="title">{{$t('test_track.test_case')}}</span>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-col>
|
||||
<span class="title">{{$t('test_track.test_case')}} </span>
|
||||
<ms-tip-button v-if="!showMyTestCase"
|
||||
:tip="'我的用例'"
|
||||
icon="el-icon-s-custom" @click="searchMyTestCase"/>
|
||||
<ms-tip-button v-if="showMyTestCase"
|
||||
:tip="'全部用例'"
|
||||
icon="el-icon-files" @click="searchMyTestCase"/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="2" :offset="8">
|
||||
<el-col :offset="8">
|
||||
<el-button icon="el-icon-connection" size="small" round
|
||||
@click="$emit('openTestCaseRelevanceDialog')" >{{$t('test_track.relevance_test_case')}}</el-button>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="5">
|
||||
<el-col>
|
||||
<el-button icon="el-icon-edit-outline" size="small" round
|
||||
@click="handleBatch('status')" >更改执行结果</el-button>
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<el-button icon="el-icon-user" size="small" round
|
||||
@click="handleBatch('executor')" >更改执行人</el-button>
|
||||
</el-col>
|
||||
|
||||
<executor-edit
|
||||
ref="executorEdit"
|
||||
:select-ids="selectIds"
|
||||
@refresh="initTableData"/>
|
||||
<status-edit
|
||||
ref="statusEdit"
|
||||
:select-ids="selectIds"
|
||||
@refresh="initTableData"/>
|
||||
|
||||
<el-col>
|
||||
<span class="search">
|
||||
<el-input type="text" size="small" :placeholder="$t('load_test.search_by_name')"
|
||||
prefix-icon="el-icon-search"
|
||||
maxlength="60"
|
||||
v-model="condition" @change="search" clearable/>
|
||||
v-model="condition.name" @change="search" clearable/>
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
@ -27,8 +52,14 @@
|
|||
</template>
|
||||
|
||||
<el-table
|
||||
@select-all="handleSelectAll"
|
||||
@select="handleSelectionChange"
|
||||
row-key="id"
|
||||
:data="tableData">
|
||||
|
||||
<el-table-column
|
||||
type="selection"></el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('commons.name')">
|
||||
|
@ -106,23 +137,8 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1">
|
||||
<div class="table-page">
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[5, 10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<ms-table-pagination :change="search" :current-page.sync="currentPage" :page-size.sync="pageSize"
|
||||
:total="total"/>
|
||||
|
||||
</el-card>
|
||||
</el-main>
|
||||
|
@ -130,22 +146,26 @@
|
|||
|
||||
<script>
|
||||
import PlanNodeTree from './PlanNodeTree';
|
||||
import ExecutorEdit from './ExecutorEdit';
|
||||
import StatusEdit from './StatusEdit';
|
||||
import MsTipButton from '../../../../components/common/components/MsTipButton';
|
||||
import MsTablePagination from '../../../../components/common/pagination/TablePagination';
|
||||
import {TokenKey} from '../../../../../common/js/constants';
|
||||
|
||||
export default {
|
||||
name: "TestPlanTestCaseList",
|
||||
components: {PlanNodeTree},
|
||||
components: {PlanNodeTree, StatusEdit, ExecutorEdit, MsTipButton, MsTablePagination},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
deletePath: "/test/case/delete",
|
||||
condition: "",
|
||||
condition: {},
|
||||
showMyTestCase: false,
|
||||
tableData: [],
|
||||
multipleSelection: [],
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
loadingRequire: {project: true, testCase: true},
|
||||
testId: null
|
||||
selectIds: new Set(),
|
||||
}
|
||||
},
|
||||
props:{
|
||||
|
@ -164,13 +184,11 @@
|
|||
methods: {
|
||||
initTableData(nodeIds) {
|
||||
if (this.planId) {
|
||||
let param = {
|
||||
name: this.condition,
|
||||
};
|
||||
let param = {};
|
||||
Object.assign(param, this.condition);
|
||||
param.nodeIds = nodeIds;
|
||||
param.planId = this.planId;
|
||||
this.result = this.$post(this.buildPagePath('/test/plan/case/list'), param, response => {
|
||||
this.loadingRequire.testCase = false;
|
||||
let data = response.data;
|
||||
this.total = data.itemCount;
|
||||
this.tableData = data.listObject;
|
||||
|
@ -183,17 +201,6 @@
|
|||
buildPagePath(path) {
|
||||
return path + "/" + this.currentPage + "/" + this.pageSize;
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size;
|
||||
this.initTableData();
|
||||
},
|
||||
handleCurrentChange(current) {
|
||||
this.currentPage = current;
|
||||
this.initTableData();
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val;
|
||||
},
|
||||
handleEdit(testCase) {
|
||||
this.$emit('editTestPlanTestCase', testCase);
|
||||
},
|
||||
|
@ -217,6 +224,43 @@
|
|||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
handleSelectAll(selection) {
|
||||
if(selection.length > 0) {
|
||||
this.tableData.forEach(item => {
|
||||
this.selectIds.add(item.id);
|
||||
});
|
||||
} else {
|
||||
this.selectIds.clear();
|
||||
}
|
||||
},
|
||||
handleSelectionChange(selection, row) {
|
||||
if(this.selectIds.has(row.id)){
|
||||
this.selectIds.delete(row.id);
|
||||
} else {
|
||||
this.selectIds.add(row.id);
|
||||
}
|
||||
},
|
||||
handleBatch(type){
|
||||
if (this.selectIds.size < 1) {
|
||||
this.$message.warning('请选择需要操作的数据');
|
||||
return;
|
||||
}
|
||||
if (type === 'executor'){
|
||||
this.$refs.executorEdit.openExecutorEdit();
|
||||
} else if (type === 'status'){
|
||||
this.$refs.statusEdit.openStatusEdit();
|
||||
}
|
||||
},
|
||||
searchMyTestCase() {
|
||||
this.showMyTestCase = !this.showMyTestCase;
|
||||
if (this.showMyTestCase) {
|
||||
let user = JSON.parse(localStorage.getItem(TokenKey));
|
||||
this.condition.executor = user.id;
|
||||
} else {
|
||||
this.condition.executor = null;
|
||||
}
|
||||
this.initTableData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,10 +275,4 @@
|
|||
}
|
||||
|
||||
|
||||
/*.main-content {*/
|
||||
/*margin: 0 auto;*/
|
||||
/*width: 100%;*/
|
||||
/*max-width: 1200px;*/
|
||||
/*}*/
|
||||
|
||||
</style>
|
||||
|
|
|
@ -9,24 +9,24 @@ import zh_CN from "./zh-CN";
|
|||
Vue.use(VueI18n);
|
||||
|
||||
const messages = {
|
||||
'en-US': {
|
||||
'en_US': {
|
||||
...enLocale
|
||||
},
|
||||
'zh-CN': {
|
||||
'zh_CN': {
|
||||
...zh_CN,
|
||||
...zh_CNLocale
|
||||
},
|
||||
'zh-TW': {
|
||||
'zh_TW': {
|
||||
...zh_TWLocale
|
||||
}
|
||||
};
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: 'zh-CN',
|
||||
locale: 'zh_CN',
|
||||
messages,
|
||||
});
|
||||
|
||||
const loadedLanguages = ['zh-CN'];
|
||||
const loadedLanguages = ['zh_CN'];
|
||||
|
||||
function setI18nLanguage(lang) {
|
||||
i18n.locale = lang;
|
||||
|
|
Loading…
Reference in New Issue