Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
q4speed 2020-04-17 18:38:57 +08:00
commit d2817334b7
35 changed files with 765 additions and 577 deletions

View File

@ -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"]

View File

@ -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);
}

View File

@ -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>

View File

@ -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() {

View File

@ -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());
}
}

View File

@ -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("");
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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");

View File

@ -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);
}
}

View File

@ -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

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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": "并发用户数超额"
}

View File

@ -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());
}

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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)) {

View File

@ -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);
},

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -143,7 +143,6 @@
}
},
close() {
console.log("clear");
this.selectIds.clear();
}
}

View File

@ -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);
},

View File

@ -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>

View File

@ -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>

View File

@ -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;