Merge branch 'master' of github.com:metersphere/metersphere

This commit is contained in:
chenjianxing 2021-03-15 16:21:14 +08:00
commit bbacb89b02
34 changed files with 1402 additions and 129 deletions

View File

@ -541,6 +541,11 @@
<include name="*.html"/>
</fileset>
</move>
<copy todir="src/main/resources/static/css">
<fileset dir="../frontend/src/assets/theme">
<include name="index.css"/>
</fileset>
</copy>
</target>
</configuration>
<goals>

View File

@ -63,7 +63,7 @@ public class MsAssertions extends MsTestElement {
private ResponseAssertion responseAssertion(MsAssertionRegex assertionRegex) {
ResponseAssertion assertion = new ResponseAssertion();
assertion.setEnabled(this.isEnable());
assertion.setName(assertionRegex.getDescription());
assertion.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : assertionRegex.getDescription());
assertion.setProperty(TestElement.TEST_CLASS, ResponseAssertion.class.getName());
assertion.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("AssertionGui"));
assertion.setAssumeSuccess(assertionRegex.isAssumeSuccess());
@ -88,7 +88,7 @@ public class MsAssertions extends MsTestElement {
private JSONPathAssertion jsonPathAssertion(MsAssertionJsonPath assertionJsonPath) {
JSONPathAssertion assertion = new JSONPathAssertion();
assertion.setEnabled(this.isEnable());
assertion.setName(StringUtils.isEmpty(assertionJsonPath.getDescription()) ? "JSONPathAssertion" : assertionJsonPath.getDescription());
assertion.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "JSONPathAssertion");
assertion.setProperty(TestElement.TEST_CLASS, JSONPathAssertion.class.getName());
assertion.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("JSONPathAssertionGui"));
assertion.setJsonPath(assertionJsonPath.getExpression());
@ -96,7 +96,7 @@ public class MsAssertions extends MsTestElement {
assertion.setJsonValidationBool(true);
assertion.setExpectNull(false);
assertion.setInvert(false);
assertion.setProperty("ASS_OPTION",assertionJsonPath.getOption());
assertion.setProperty("ASS_OPTION", assertionJsonPath.getOption());
if (StringUtils.isEmpty(assertionJsonPath.getOption()) || "REGEX".equals(assertionJsonPath.getOption())) {
assertion.setIsRegex(true);
} else {
@ -108,7 +108,7 @@ public class MsAssertions extends MsTestElement {
private XPath2Assertion xPath2Assertion(MsAssertionXPath2 assertionXPath2) {
XPath2Assertion assertion = new XPath2Assertion();
assertion.setEnabled(this.isEnable());
assertion.setName(StringUtils.isEmpty(assertionXPath2.getExpression()) ? "XPath2Assertion" : assertionXPath2.getExpression());
assertion.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "XPath2Assertion");
assertion.setProperty(TestElement.TEST_CLASS, XPath2Assertion.class.getName());
assertion.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("XPath2AssertionGui"));
assertion.setXPathString(assertionXPath2.getExpression());
@ -119,7 +119,7 @@ public class MsAssertions extends MsTestElement {
private DurationAssertion durationAssertion(MsAssertionDuration assertionDuration) {
DurationAssertion assertion = new DurationAssertion();
assertion.setEnabled(this.isEnable());
assertion.setName("Response In Time: " + assertionDuration.getValue());
assertion.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "Response In Time: " + assertionDuration.getValue());
assertion.setProperty(TestElement.TEST_CLASS, DurationAssertion.class.getName());
assertion.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("DurationAssertionGui"));
assertion.setAllowedDuration(assertionDuration.getValue());
@ -129,7 +129,7 @@ public class MsAssertions extends MsTestElement {
private JSR223Assertion jsr223Assertion(MsAssertionJSR223 assertionJSR223) {
JSR223Assertion assertion = new JSR223Assertion();
assertion.setEnabled(this.isEnable());
assertion.setName(StringUtils.isEmpty(assertionJSR223.getDesc()) ? "JSR223Assertion" : assertionJSR223.getDesc());
assertion.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : "JSR223Assertion");
assertion.setProperty(TestElement.TEST_CLASS, JSR223Assertion.class.getName());
assertion.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
assertion.setProperty("cacheKey", "true");

View File

@ -66,7 +66,7 @@ public class MsExtract extends MsTestElement {
RegexExtractor extractor = new RegexExtractor();
extractor.setEnabled(this.isEnable());
extractor.setName(extractRegex.getVariable() + " RegexExtractor");
extractor.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : " RegexExtractor");
extractor.setProperty(TestElement.TEST_CLASS, RegexExtractor.class.getName());
extractor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("RegexExtractorGui"));
extractor.setRefName(extractRegex.getVariable());
@ -84,7 +84,7 @@ public class MsExtract extends MsTestElement {
private XPath2Extractor xPath2Extractor(MsExtractXPath extractXPath, StringJoiner extract) {
XPath2Extractor extractor = new XPath2Extractor();
extractor.setEnabled(this.isEnable());
extractor.setName(extractXPath.getVariable() + " XPath2Extractor");
extractor.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : " XPath2Extractor");
extractor.setProperty(TestElement.TEST_CLASS, XPath2Extractor.class.getName());
extractor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("XPath2ExtractorGui"));
extractor.setRefName(extractXPath.getVariable());
@ -99,7 +99,7 @@ public class MsExtract extends MsTestElement {
private JSONPostProcessor jsonPostProcessor(MsExtractJSONPath extractJSONPath, StringJoiner extract) {
JSONPostProcessor extractor = new JSONPostProcessor();
extractor.setEnabled(this.isEnable());
extractor.setName(extractJSONPath.getVariable() + " JSONExtractor");
extractor.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() : " JSONExtractor");
extractor.setProperty(TestElement.TEST_CLASS, JSONPostProcessor.class.getName());
extractor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("JSONPostProcessorGui"));
extractor.setRefNames(extractJSONPath.getVariable());

View File

@ -198,7 +198,7 @@ public class ApiDefinitionService {
.andProtocolEqualTo(request.getProtocol()).andPathEqualTo(request.getPath())
.andProjectIdEqualTo(request.getProjectId()).andIdNotEqualTo(request.getId());
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
if (apiDefinitionMapper.countByExample(example) > 0 && (project.getRepeatable() == null || !project.getRepeatable())) {
if (apiDefinitionMapper.countByExample(example) > 0 && (project == null || project.getRepeatable() == null || !project.getRepeatable())) {
MSException.throwException(Translator.get("api_definition_url_not_repeating"));
}
} else {

View File

@ -5,6 +5,7 @@ import io.metersphere.controller.request.BaseQueryRequest;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.request.testcase.QueryTestCaseRequest;
import io.metersphere.track.request.testcase.TestCaseBatchRequest;
import io.metersphere.track.response.TrackCountResult;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -48,4 +49,27 @@ public interface ExtTestCaseMapper {
int checkIsHave(@Param("caseId") String caseId, @Param("workspaceIds") Set<String> workspaceIds);
List<String> selectIds(@Param("request") BaseQueryRequest condition);
/**
* 按照用例等级统计
* @param projectId 项目ID
* @return 统计结果
*/
List<TrackCountResult> countPriority(@Param("projectId") String projectId);
long countCreatedThisWeek(@Param("projectId") String projectId, @Param("firstDayTimestamp") long firstDayTimestamp, @Param("lastDayTimestamp") long lastDayTimestamp);
List<TrackCountResult> countStatus(@Param("projectId") String projectId);
List<TrackCountResult> countRelevance(@Param("projectId") String projectId);
long countRelevanceCreatedThisWeek(@Param("projectId") String projectId,@Param("firstDayTimestamp") long firstDayTimestamp, @Param("lastDayTimestamp") long lastDayTimestamp);
List<TrackCountResult> countCoverage(@Param("projectId") String projectId);
List<TrackCountResult> countFuncMaintainer(@Param("projectId") String projectId);
List<TrackCountResult> countRelevanceMaintainer(@Param("projectId") String projectId);
}

View File

@ -319,4 +319,42 @@
</where>
</sql>
<select id="countPriority" resultType="io.metersphere.track.response.TrackCountResult">
SELECT test_case.priority as groupField,count(id) AS countNumber FROM test_case WHERE project_id = #{projectId} GROUP BY test_case.priority
</select>
<!-- todo 排除删除的用例统计-->
<select id="countCreatedThisWeek" resultType="java.lang.Long">
SELECT count(id) AS countNumber FROM test_case WHERE test_case.project_id = #{projectId}
AND create_time BETWEEN #{firstDayTimestamp} AND #{lastDayTimestamp}
</select>
<!-- todo 排除删除的用例统计-->
<select id="countStatus" resultType="io.metersphere.track.response.TrackCountResult">
SELECT review_status AS groupField,count(id) AS countNumber FROM test_case WHERE project_id = #{projectId} GROUP BY test_case.review_status
</select>
<select id="countRelevance" resultType="io.metersphere.track.response.TrackCountResult">
SELECT type AS groupField, count(id) AS countNumber FROM test_case WHERE test_case.project_id = #{projectId} GROUP BY test_case.type
</select>
<select id="countRelevanceCreatedThisWeek" resultType="java.lang.Long">
SELECT count(id) AS countNumber FROM test_case WHERE test_case.project_id = #{projectId}
AND create_time BETWEEN #{firstDayTimestamp} AND #{lastDayTimestamp}
</select>
<select id="countCoverage" resultType="io.metersphere.track.response.TrackCountResult">
SELECT count(test_case.id) AS countNumber, if(test_case.test_id is null,"uncoverage","coverage") AS groupField
FROM test_case WHERE test_case.project_id=#{projectId} GROUP BY groupField
</select>
<select id="countFuncMaintainer" resultType="io.metersphere.track.response.TrackCountResult">
select count(tc.id) as countNumber, user.name as groupField from test_case tc right join user on tc.maintainer = user.id
where tc.project_id = #{projectId}
group by tc.maintainer
</select>
<select id="countRelevanceMaintainer" resultType="io.metersphere.track.response.TrackCountResult">
select count(tc.id) as countNumber, user.name as groupField from test_case tc right join user on tc.maintainer = user.id
where tc.project_id = #{projectId} and tc.test_id is not null
group by tc.maintainer
</select>
</mapper>

View File

@ -42,6 +42,8 @@ public class ShiroUtils {
//api-对外文档页面提供的查询接口
filterChainDefinitionMap.put("/api/document/**", "anon");
// filterChainDefinitionMap.put("/document/**", "anon");
filterChainDefinitionMap.put("/system/theme", "anon");
}
public static void ignoreCsrfFilter(Map<String, String> filterChainDefinitionMap) {

View File

@ -0,0 +1,93 @@
package io.metersphere.track.controller;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.performance.base.ChartsData;
import io.metersphere.track.response.BugStatustics;
import io.metersphere.track.response.TrackCountResult;
import io.metersphere.track.response.TrackStatisticsDTO;
import io.metersphere.track.service.TrackService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.text.DecimalFormat;
import java.util.List;
@RestController
@RequestMapping("/track")
@RequiresRoles(value = {RoleConstants.ADMIN, RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER, RoleConstants.ORG_ADMIN}, logical = Logical.OR)
public class TrackController {
@Resource
private TrackService trackService;
@GetMapping("/count/{projectId}")
public TrackStatisticsDTO getTrackCount(@PathVariable String projectId) {
TrackStatisticsDTO statistics = new TrackStatisticsDTO();
List<TrackCountResult> priorityResults = trackService.countPriority(projectId);
statistics.countPriority(priorityResults);
long size = trackService.countCreatedThisWeek(projectId);
statistics.setThisWeekAddedCount(size);
List<TrackCountResult> statusResults = trackService.countStatus(projectId);
statistics.countStatus(statusResults);
long total = statistics.getPrepareCount() + statistics.getPassCount() + statistics.getUnPassCount();
if (total != 0) {
float reviewed = (float) (statistics.getPassCount() + statistics.getUnPassCount()) * 100 / total;
DecimalFormat df = new DecimalFormat("0.0");
statistics.setReviewRage(df.format(reviewed) + "%");
}
statistics.setP0CountStr("P0&nbsp;&nbsp;<br/><br/>" + statistics.getP0CaseCountNumber());
statistics.setP1CountStr("P1&nbsp;&nbsp;<br/><br/>" + statistics.getP1CaseCountNumber());
statistics.setP2CountStr("P2&nbsp;&nbsp;<br/><br/>" + statistics.getP2CaseCountNumber());
statistics.setP3CountStr("P3&nbsp;&nbsp;<br/><br/>" + statistics.getP3CaseCountNumber());
return statistics;
}
@GetMapping("/relevance/count/{projectId}")
public TrackStatisticsDTO getRelevanceCount(@PathVariable String projectId) {
TrackStatisticsDTO statistics = new TrackStatisticsDTO();
List<TrackCountResult> relevanceResults = trackService.countRelevance(projectId);
statistics.countRelevance(relevanceResults);
long size = trackService.countRelevanceCreatedThisWeek(projectId);
statistics.setThisWeekAddedCount(size);
List<TrackCountResult> coverageResults = trackService.countCoverage(projectId);
statistics.countCoverage(coverageResults);
long total = statistics.getUncoverageCount() + statistics.getCoverageCount();
if (total != 0) {
float coverageRageNumber = (float) statistics.getCoverageCount() * 100 / total;
DecimalFormat df = new DecimalFormat("0.0");
statistics.setCoverageRage(df.format(coverageRageNumber) + "%");
}
statistics.setApiCaseCountStr("接口用例&nbsp;&nbsp;<br/><br/>" + statistics.getApiCaseCount());
statistics.setPerformanceCaseCountStr("性能用例&nbsp;&nbsp;<br/><br/>" + statistics.getPerformanceCaseCount());
statistics.setScenarioCaseStr("场景用例&nbsp;&nbsp;<br/><br/>" + statistics.getScenarioCaseCount());
return statistics;
}
@GetMapping("/case/bar/{projectId}")
public List<ChartsData> getCaseMaintenanceBar(@PathVariable String projectId) {
return trackService.getCaseMaintenanceBar(projectId);
}
@GetMapping("/bug/count/{projectId}")
public BugStatustics getBugStatustics(@PathVariable String projectId) {
return trackService.getBugStatustics(projectId);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.track.request.testcase;
public class TrackCount {
public static final String P0 = "P0";
public static final String P1 = "P1";
public static final String P2 = "P2";
public static final String P3 = "P3";
public static final String API = "api";
public static final String PERFORMANCE = "performance";
public static final String AUTOMATION = "automation";
}

View File

@ -0,0 +1,16 @@
package io.metersphere.track.response;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class BugStatustics {
private long bugTotalSize;
private String rage;
private List<TestPlanBugCount> list = new ArrayList<>();
}

View File

@ -0,0 +1,16 @@
package io.metersphere.track.response;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TestPlanBugCount {
private int index;
private String planName;
private long creatTime;
private String status;
private int caseSize;
private int bugSize;
private String passRage;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.track.response;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TrackCountResult {
/**
* 分组统计字段
*/
private String groupField;
/**
* 数据统计
*/
private long countNumber;
}

View File

@ -0,0 +1,173 @@
package io.metersphere.track.response;
import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.commons.constants.TestReviewCaseStatus;
import io.metersphere.track.request.testcase.TrackCount;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* 用例数量统计数据
*/
@Getter
@Setter
public class TrackStatisticsDTO {
/**
* 用例总计
*/
private long allCaseCountNumber = 0;
/**
* P0登记用例总计
*/
private long p0CaseCountNumber = 0;
/**
* P1登记用例总计
*/
private long p1CaseCountNumber = 0;
/**
* P2登记用例总计
*/
private long p2CaseCountNumber = 0;
/**
* P3登记用例总计
*/
private long p3CaseCountNumber = 0;
private String p0CountStr = "";
private String p1CountStr = "";
private String p2CountStr = "";
private String p3CountStr = "";
/**
* 关联用例数量统计
*/
private long allRelevanceCaseCount = 0;
/**
* 接口用例数量统计
*/
private long apiCaseCount = 0;
/**
* 场景用例数量统计
*/
private long scenarioCaseCount = 0;
/**
* 性能用例数量统计
*/
private long performanceCaseCount = 0;
private String apiCaseCountStr = "";
private String scenarioCaseStr = "";
private String performanceCaseCountStr = "";
/**
* 本周新增数量
*/
private long thisWeekAddedCount = 0;
/**
* 未覆盖
*/
private long uncoverageCount = 0;
/**
* 已覆盖
*/
private long coverageCount = 0;
/**
* 覆盖率
*/
private String coverageRage = " 0%";
/**
* 评审率
*/
private String reviewRage = " 0%";
/**
* 未评审
*/
private long prepareCount = 0;
/**
* 已通过
*/
private long passCount = 0;
/**
* 未通过
*/
private long unPassCount = 0;
/**
* 按照 Priority 统计
* @param trackCountResults 统计结果
*/
public void countPriority(List<TrackCountResult> trackCountResults) {
for (TrackCountResult countResult : trackCountResults) {
switch (countResult.getGroupField().toUpperCase()){
case TrackCount.P0:
this.p0CaseCountNumber += countResult.getCountNumber();
break;
case TrackCount.P1:
this.p1CaseCountNumber += countResult.getCountNumber();
break;
case TrackCount.P2:
this.p2CaseCountNumber += countResult.getCountNumber();
break;
case TrackCount.P3:
this.p3CaseCountNumber += countResult.getCountNumber();
break;
default:
break;
}
this.allCaseCountNumber += countResult.getCountNumber();
}
}
public void countStatus(List<TrackCountResult> statusResults) {
for (TrackCountResult countResult : statusResults) {
if(TestReviewCaseStatus.Prepare.name().equals(countResult.getGroupField())){
this.prepareCount += countResult.getCountNumber();
}else if(TestReviewCaseStatus.Pass.name().equals(countResult.getGroupField())){
this.passCount += countResult.getCountNumber();
}else if(TestReviewCaseStatus.UnPass.name().equals(countResult.getGroupField())){
this.unPassCount += countResult.getCountNumber();
}
}
}
public void countRelevance(List<TrackCountResult> relevanceResults) {
for (TrackCountResult countResult : relevanceResults) {
switch (countResult.getGroupField().toUpperCase()){
case TrackCount.API:
this.apiCaseCount += countResult.getCountNumber();
break;
case TrackCount.PERFORMANCE:
this.performanceCaseCount += countResult.getCountNumber();
break;
case TrackCount.AUTOMATION:
this.scenarioCaseCount += countResult.getCountNumber();
break;
default:
break;
}
this.allRelevanceCaseCount += countResult.getCountNumber();
}
}
public void countCoverage(List<TrackCountResult> coverageResults) {
for (TrackCountResult countResult : coverageResults) {
if("coverage".equals(countResult.getGroupField())){
this.coverageCount+= countResult.getCountNumber();
}else if("uncoverage".equals(countResult.getGroupField())){
this.uncoverageCount+= countResult.getCountNumber();
}
}
}
}

View File

@ -0,0 +1,168 @@
package io.metersphere.track.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.performance.base.ChartsData;
import io.metersphere.track.response.BugStatustics;
import io.metersphere.track.response.TestPlanBugCount;
import io.metersphere.track.response.TrackCountResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class TrackService {
@Resource
private ExtTestCaseMapper extTestCaseMapper;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanTestCaseMapper testPlanTestCaseMapper;
@Resource
private TestPlanLoadCaseMapper testPlanLoadCaseMapper;
@Resource
private TestPlanApiCaseMapper testPlanApiCaseMapper;
@Resource
private TestPlanApiScenarioMapper testPlanApiScenarioMapper;
public List<TrackCountResult> countPriority(String projectId) {
return extTestCaseMapper.countPriority(projectId);
}
public long countCreatedThisWeek(String projectId) {
Map<String, Date> startAndEndDateInWeek = DateUtils.getWeedFirstTimeAndLastTime(new Date());
Date firstTime = startAndEndDateInWeek.get("firstTime");
Date lastTime = startAndEndDateInWeek.get("lastTime");
if (firstTime == null || lastTime == null) {
return 0;
} else {
return extTestCaseMapper.countCreatedThisWeek(projectId, firstTime.getTime(), lastTime.getTime());
}
}
public List<TrackCountResult> countStatus(String projectId) {
return extTestCaseMapper.countStatus(projectId);
}
public List<TrackCountResult> countRelevance(String projectId) {
return extTestCaseMapper.countRelevance(projectId);
}
public long countRelevanceCreatedThisWeek(String projectId) {
Map<String, Date> startAndEndDateInWeek = DateUtils.getWeedFirstTimeAndLastTime(new Date());
Date firstTime = startAndEndDateInWeek.get("firstTime");
Date lastTime = startAndEndDateInWeek.get("lastTime");
if (firstTime == null || lastTime == null) {
return 0;
} else {
return extTestCaseMapper.countRelevanceCreatedThisWeek(projectId, firstTime.getTime(), lastTime.getTime());
}
}
public List<TrackCountResult> countCoverage(String projectId) {
return extTestCaseMapper.countCoverage(projectId);
}
public List<ChartsData> getCaseMaintenanceBar(String projectId) {
List<TrackCountResult> funcMaintainer = extTestCaseMapper.countFuncMaintainer(projectId);
List<TrackCountResult> relevanceMaintainer = extTestCaseMapper.countRelevanceMaintainer(projectId);
List<String> list = relevanceMaintainer.stream().map(TrackCountResult::getGroupField).collect(Collectors.toList());
List<ChartsData> charts = new ArrayList<>();
for (TrackCountResult result : funcMaintainer) {
String groupField = result.getGroupField();
if (!list.contains(groupField)) {
// 创建了功能用例但是未关联测试
TrackCountResult trackCount = new TrackCountResult();
trackCount.setCountNumber(0);
trackCount.setGroupField(groupField);
relevanceMaintainer.add(trackCount);
}
ChartsData chartsData = new ChartsData();
chartsData.setxAxis(groupField);
chartsData.setyAxis(BigDecimal.valueOf(result.getCountNumber()));
chartsData.setGroupName("FUNCTIONCASE");
charts.add(chartsData);
}
for (TrackCountResult result : relevanceMaintainer) {
ChartsData chartsData = new ChartsData();
chartsData.setxAxis(result.getGroupField());
chartsData.setyAxis(BigDecimal.valueOf(result.getCountNumber()));
chartsData.setGroupName("RELEVANCECASE");
charts.add(chartsData);
}
return charts;
}
public BugStatustics getBugStatustics(String projectId) {
TestPlanExample example = new TestPlanExample();
example.createCriteria().andProjectIdEqualTo(projectId);
List<TestPlan> plans = testPlanMapper.selectByExample(example);
List<TestPlanBugCount> list = new ArrayList<>();
BugStatustics bugStatustics = new BugStatustics();
int index = 1;
for (TestPlan plan : plans) {
TestPlanBugCount testPlanBug = new TestPlanBugCount();
testPlanBug.setIndex(index++);
testPlanBug.setPlanName(plan.getName());
testPlanBug.setCreatTime(plan.getCreateTime());
testPlanBug.setStatus(plan.getStatus());
testPlanBug.setCaseSize(getPlanCaseSize(plan.getId()));
testPlanBug.setBugSize(getPlanBugSize(plan.getId()));
testPlanBug.setPassRage(getPlanPassRage(plan.getId()));
list.add(testPlanBug);
}
// todo
bugStatustics.setList(list);
bugStatustics.setRage("1");
bugStatustics.setBugTotalSize(2);
return bugStatustics;
}
private int getPlanCaseSize(String planId) {
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(planId);
List<TestPlanTestCase> testPlanTestCases = testPlanTestCaseMapper.selectByExample(testPlanTestCaseExample);
TestPlanApiCaseExample testPlanApiCaseExample = new TestPlanApiCaseExample();
testPlanApiCaseExample.createCriteria().andTestPlanIdEqualTo(planId);
List<TestPlanApiCase> testPlanApiCases = testPlanApiCaseMapper.selectByExample(testPlanApiCaseExample);
TestPlanLoadCaseExample example = new TestPlanLoadCaseExample();
example.createCriteria().andTestPlanIdEqualTo(planId);
List<TestPlanLoadCase> testPlanLoadCases = testPlanLoadCaseMapper.selectByExample(example);
TestPlanApiScenarioExample testPlanApiScenarioExample = new TestPlanApiScenarioExample();
testPlanApiCaseExample.createCriteria().andTestPlanIdEqualTo(planId);
List<TestPlanApiScenario> testPlanApiScenarios = testPlanApiScenarioMapper.selectByExample(testPlanApiScenarioExample);
return testPlanTestCases.size() + testPlanApiCases.size() + testPlanLoadCases.size() + testPlanApiScenarios.size();
}
private int getPlanBugSize(String planId) {
return 1;
}
private String getPlanPassRage(String planId) {
return "10%";
}
}

View File

@ -31,7 +31,7 @@ import MsView from "./components/common/router/View";
import MsUser from "./components/common/head/HeaderUser";
import MsHeaderOrgWs from "./components/common/head/HeaderOrgWs";
import MsLanguageSwitch from "./components/common/head/LanguageSwitch";
import {hasLicense, saveLocalStorage, setColor, setOriginColor} from "@/common/js/utils";
import {hasLicense, saveLocalStorage, setColor, setDefaultTheme} from "@/common/js/utils";
import {registerRequestHeaders} from "@/common/js/ajax";
import {ORIGIN_COLOR} from "@/common/js/constants";
@ -55,13 +55,14 @@ export default {
created() {
registerRequestHeaders();
if (!hasLicense()) {
setOriginColor()
setDefaultTheme();
this.color = ORIGIN_COLOR;
} else {
//
this.$get('/system/theme', res => {
this.color = res.data ? res.data : ORIGIN_COLOR;
setColor(this.color, this.color, this.color, this.color);
setColor(this.color, this.color, this.color, this.color, this.color);
this.$store.commit('setTheme', res.data);
})
}
if (localStorage.getItem("store")) {

View File

@ -12,10 +12,10 @@
:title="displayTitle">
<template v-slot:behindHeaderLeft>
<el-tag size="mini" style="margin-left: 20px" v-if="request.referenced==='Deleted'" type="danger">{{$t('api_test.automation.reference_deleted')}}</el-tag>
<el-tag size="mini" style="margin-left: 20px" v-if="request.referenced==='Copy'">{{ $t('commons.copy') }}</el-tag>
<el-tag size="mini" style="margin-left: 20px" v-if="request.referenced ==='REF'">{{ $t('api_test.scenario.reference') }}</el-tag>
<span style="margin-left: 20px;">{{getProjectName(request.projectId)}}</span>
<el-tag size="mini" class="ms-tag" v-if="request.referenced==='Deleted'" type="danger">{{$t('api_test.automation.reference_deleted')}}</el-tag>
<el-tag size="mini" class="ms-tag" v-if="request.referenced==='Copy'">{{ $t('commons.copy') }}</el-tag>
<el-tag size="mini" class="ms-tag" v-if="request.referenced ==='REF'">{{ $t('api_test.scenario.reference') }}</el-tag>
<span class="ms-tag">{{getProjectName(request.projectId)}}</span>
<ms-run :debug="true" :reportId="reportId" :run-data="runData" :env-map="envMap"
@runRefresh="runRefresh" ref="runTest"/>
@ -23,7 +23,7 @@
<template v-slot:button>
<el-tooltip :content="$t('api_test.run')" placement="top">
<el-button @click="run" icon="el-icon-video-play" style="background-color: #409EFF;color: white;" size="mini" circle/>
<el-button @click="run" icon="el-icon-video-play" class="ms-btn" size="mini" circle/>
</el-tooltip>
</template>
@ -52,7 +52,7 @@
<api-response-component :currentProtocol="request.protocol" :result="request.requestResult" v-else/>
<!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)"
<el-button type="primary" size="small" class="ms-btn-flot" @click="saveTestCase(item)"
v-if="!request.referenced">
{{ $t('commons.save') }}
</el-button>
@ -67,7 +67,7 @@
import MsApiRequestForm from "../../../definition/components/request/http/ApiHttpRequestForm";
import MsRequestResultTail from "../../../definition/components/response/RequestResultTail";
import MsRun from "../../../definition/components/Run";
import {getUUID,getCurrentProjectID} from "@/common/js/utils";
import {getUUID, getCurrentProjectID} from "@/common/js/utils";
import ApiBaseComponent from "../common/ApiBaseComponent";
import ApiResponseComponent from "./ApiResponseComponent";
import CustomizeReqInfo from "@/business/components/api/automation/scenario/common/CustomizeReqInfo";
@ -333,4 +333,18 @@
content: "";
}
.ms-btn {
background-color: #409EFF;
color: white;
}
.ms-btn-flot {
margin: 20px;
float: right;
}
.ms-tag {
margin-left: 20px;
}
</style>

View File

@ -13,11 +13,11 @@
:title="$t('api_test.automation.scenario_import')">
<template v-slot:behindHeaderLeft>
<el-tag size="mini" style="margin-left: 20px" v-if="scenario.referenced==='Deleted'" type="danger">{{$t('api_test.automation.reference_deleted')}}</el-tag>
<el-tag size="mini" style="margin-left: 20px" v-if="scenario.referenced==='Copy'">{{ $t('commons.copy') }}</el-tag>
<el-tag size="mini" style="margin-left: 20px" v-if="scenario.referenced==='REF'">{{ $t('api_test.scenario.reference') }}</el-tag>
<el-tag size="mini" class="ms-tag" v-if="scenario.referenced==='Deleted'" type="danger">{{$t('api_test.automation.reference_deleted')}}</el-tag>
<el-tag size="mini" class="ms-tag" v-if="scenario.referenced==='Copy'">{{ $t('commons.copy') }}</el-tag>
<el-tag size="mini" class="ms-tag" v-if="scenario.referenced==='REF'">{{ $t('api_test.scenario.reference') }}</el-tag>
<span style="margin-left: 20px;">{{getProjectName(scenario.projectId)}}</span>
<span class="ms-tag">{{getProjectName(scenario.projectId)}}</span>
</template>
</api-base-component>
@ -128,7 +128,7 @@
}
},
getProjectName(id) {
const project = this.projectList.find(p => p.id === id) ;
const project = this.projectList.find(p => p.id === id);
return project ? project.name : "";
}
}
@ -145,4 +145,7 @@
transform: rotate(90deg);
}
.ms-tag {
margin-left: 20px;
}
</style>

View File

@ -14,11 +14,11 @@
<el-input draggable size="small" v-model="controller.variable" style="width: 20%" :placeholder="$t('api_test.request.condition_variable')"/>
<el-select v-model="controller.operator" :placeholder="$t('commons.please_select')" size="small"
@change="change" style="width: 10%;margin-left: 10px">
@change="change" class="ms-select">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
</el-select>
<el-input draggable size="small" v-model="controller.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<el-input draggable size="small" v-model="controller.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" class="ms-btn"/>
</template>
</api-base-component>
@ -26,6 +26,7 @@
<script>
import ApiBaseComponent from "../common/ApiBaseComponent";
export default {
name: "MsIfController",
components: {ApiBaseComponent},
@ -98,4 +99,13 @@
</script>
<style scoped>
.ms-btn {
width: 20%;
margin-left: 20px;
}
.ms-select {
width: 10%;
margin-left: 10px;
}
</style>

View File

@ -69,7 +69,6 @@
</div>
<div v-else draggable>
<el-input size="small" v-model="controller.whileController.variable" style="width: 20%" :placeholder="$t('api_test.request.condition_variable')"/>
<el-select v-model="controller.whileController.operator" :placeholder="$t('commons.please_select')" size="small"
@change="change" style="width: 10%;margin-left: 10px">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
@ -79,18 +78,6 @@
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="3000" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</div>
<!--<p class="tip">{{$t('api_test.definition.request.res_param')}} </p>-->
<!--<div>-->
<!--<el-tabs v-model="activeName" closable class="ms-tabs">-->
<!--<el-tab-pane :label="item.name" :name="item.name" v-for="(item,index) in requestResult.scenarios" :key="index">-->
<!--<div v-for="(result,i) in item.requestResults" :key="i" style="margin-bottom: 5px">-->
<!--<api-response-component :result="result"/>-->
<!--</div>-->
<!--</el-tab-pane>-->
<!--</el-tabs>-->
<!--</div>-->
</api-base-component>
</div>
@ -344,14 +331,6 @@ export default {
font-weight: normal;
}
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
margin: 20px 0;
}
.icon.is-active {
transform: rotate(90deg);
}

View File

@ -2,7 +2,7 @@
<div>
<template>
<span>{{ $t('commons.operating') }}
<i class='el-icon-setting' style="color:#7834c1; margin-left:10px" @click="customHeader"> </i>
<i class='el-icon-setting operator-color' @click="customHeader"> </i>
</span>
</template>
</div>
@ -21,5 +21,8 @@ export default {
</script>
<style scoped>
.operator-color {
color: var(--count_number);
margin-left:10px;
}
</style>

View File

@ -364,7 +364,7 @@ export default {
if (j === 0) {
seriesData.data.push([0, 0]);
}
if (j > tg.rampUpTime) {
if (j >= tg.rampUpTime) {
xAxis.push(tg.duration);
seriesData.data.push([j, tg.threadNumber]);
@ -475,7 +475,7 @@ export default {
if (i === 0) {
handler.options.series[0].data.push([0, 0]);
}
if (i > handler.rampUpTime) {
if (i >= handler.rampUpTime) {
handler.options.xAxis.data.push(handler.duration);
handler.options.series[0].data.push([i, handler.threadNumber]);

View File

@ -92,6 +92,7 @@
:disabled="isReadOnly"
:min="1"
v-model="threadGroup.rampUpTime"
@change="calculateChart(threadGroup)"
size="mini"/>
</el-form-item>
<el-form-item :label="$t('load_test.ramp_up_time_seconds')"/>
@ -402,7 +403,7 @@ export default {
if (j === 0) {
seriesData.data.push([0, 0]);
}
if (j > tg.rampUpTime) {
if (j >= tg.rampUpTime) {
xAxis.push(tg.duration);
seriesData.data.push([j, tg.threadNumber]);
@ -519,7 +520,7 @@ export default {
if (i === 0) {
handler.options.series[0].data.push([0, 0]);
}
if (i > handler.rampUpTime) {
if (i >= handler.rampUpTime) {
handler.options.xAxis.data.push(handler.duration);
handler.options.series[0].data.push([i, handler.threadNumber]);

View File

@ -1,57 +1,214 @@
<template>
<ms-container>
<ms-main-container>
<el-row :gutter="20">
<el-col :span="15">
<el-row>
<related-test-plan-list ref="relatedTestPlanList"/>
</el-row>
<el-row>
<review-list :title="$t('test_track.review.my_review')" ref="caseReviewList"/>
</el-row>
<el-header height="0">
<div style="float: right">
<div v-if="tipsType==='1'">
🤔 天凉了保温杯买了吗
</div>
<div v-else-if="tipsType==='2'">
😔 觉得MeterSphere不好用就来
<el-link href="https://github.com/metersphere/metersphere/issues" target="_blank" style="color: black"
type="primary">https://github.com/metersphere/metersphere/issues
</el-link>
吐个槽吧
</div>
<div v-else-if="tipsType==='3'">
😄 觉得MeterSphere好用就来
<el-link href="https://github.com/metersphere/metersphere" target="_blank" style="color: black"
type="primary">https://github.com/metersphere/metersphere
</el-link>
点个star吧
</div>
<div v-else>
😊 MeterSphere温馨提醒 多喝热水哟
</div>
</div>
</el-header>
<ms-main-container v-loading="result.loading">
<el-row :gutter="0"></el-row>
<el-row :gutter="10">
<el-col :span="6">
<div class="square">
<case-count-card :track-count-data="trackCountData" class="track-card"/>
</div>
</el-col>
<el-col :span="9">
<test-case-side-list :title="$t('test_track.home.recent_test')" ref="testCaseRecentList"/>
<el-col :span="6">
<div class="square">
<relevance-case-card :relevance-count-data="relevanceCountData" class="track-card"/>
</div>
</el-col>
<el-col :span="12">
<div class="square">
<case-maintenance :case-option="caseOption" class="track-card"/>
</div>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<bug-count-card class="track-card"/>
</el-col>
<el-col :span="12">
<ms-failure-test-case-list class="track-card"/>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<review-list class="track-card"/>
</el-col>
<el-col :span="12">
<ms-running-task-list class="track-card"/>
</el-col>
</el-row>
</ms-main-container>
</ms-container>
</template>
<script>
import RelatedTestPlanList from "./components/RelatedTestPlanList";
import TestCaseSideList from "./components/TestCaseSideList";
import MsContainer from "../../common/components/MsContainer";
import MsMainContainer from "../../common/components/MsMainContainer";
import ReviewList from "./components/ReviewList";
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import MsContainer from "@/business/components/common/components/MsContainer";
import CaseCountCard from "@/business/components/track/home/components/CaseCountCard";
import RelevanceCaseCard from "@/business/components/track/home/components/RelevanceCaseCard";
import {getCurrentProjectID} from "@/common/js/utils";
import CaseMaintenance from "@/business/components/track/home/components/CaseMaintenance";
import {COUNT_NUMBER, COUNT_NUMBER_SHALLOW} from "@/common/js/constants";
import BugCountCard from "@/business/components/track/home/components/BugCountCard";
import ReviewList from "@/business/components/track/home/components/ReviewList";
import MsRunningTaskList from "@/business/components/api/homepage/components/RunningTaskList";
import MsFailureTestCaseList from "@/business/components/api/homepage/components/FailureTestCaseList";
require('echarts/lib/component/legend');
export default {
name: "TrackHome",
components: {MsMainContainer, MsContainer, TestCaseSideList, RelatedTestPlanList, ReviewList},
watch: {
'$route'(to, from) {
if (to.path.indexOf('/track/home') > -1) {
this.innitData();
components: {
ReviewList,
BugCountCard,
RelevanceCaseCard,
CaseCountCard,
MsMainContainer,
MsContainer,
CaseMaintenance,
MsRunningTaskList,
MsFailureTestCaseList
},
data() {
return {
tipsType: "1",
result: {},
trackCountData: {},
relevanceCountData: {},
caseOption: {}
}
},
activated() {
this.checkTipsType();
this.init();
},
methods: {
checkTipsType() {
let random = Math.floor(Math.random() * (4 - 1 + 1)) + 1;
this.tipsType = random + "";
},
init() {
let selectProjectId = getCurrentProjectID();
this.$get("/track/count/" + selectProjectId, response => {
this.trackCountData = response.data;
});
this.$get("/track/relevance/count/" + selectProjectId, response => {
this.relevanceCountData = response.data;
});
this.$get("/track/case/bar/" + selectProjectId, response => {
let data = response.data;
this.setBarOption(data);
})
},
setBarOption(data) {
let xAxis = [];
data.map(d => {
if (!xAxis.includes(d.xAxis)) {
xAxis.push(d.xAxis);
}
});
let yAxis1 = data.filter(d => d.groupName === 'FUNCTIONCASE').map(d => d.yAxis);
let yAxis2 = data.filter(d => d.groupName === 'RELEVANCECASE').map(d => d.yAxis);
let option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
data: xAxis
},
yAxis: {
type: 'value',
axisLine: {
show: false
},
axisTick: {
show: false
}
},
legend: {
data: ["功能用例数", "关联用例数"],
orient: 'vertical',
right: '80',
},
series: [{
name: "功能用例数",
data: yAxis1,
type: 'bar',
itemStyle: {
normal: {
color: this.$store.state.theme.theme ? this.$store.state.theme.theme : COUNT_NUMBER
}
}
},
methods: {
innitData() {
this.$refs.relatedTestPlanList.initTableData();
this.$refs.testCaseRecentList.initTableData();
this.$refs.caseReviewList.initTableData();
{
name: "关联用例数",
data: yAxis2,
type: 'bar',
itemStyle: {
normal: {
color: this.$store.state.theme.theme ? this.$store.state.theme.theme : COUNT_NUMBER_SHALLOW
}
}
}]
};
this.caseOption = option;
}
}
}
</script>
<style scoped>
.square {
width: 100%;
height: 400px;
}
.ms-main-container >>> .el-table {
cursor: pointer;
.rectangle {
width: 100%;
height: 400px;
}
.el-row {
padding-bottom: 20px;
margin-bottom: 20px;
margin-left: 20px;
margin-right: 20px;
}
.track-card {
height: 100%;
}
</style>

View File

@ -0,0 +1,122 @@
<template>
<el-card class="table-card" v-loading="result.loading" body-style="padding:10px;">
<div slot="header">
<span class="title">
遗留缺陷统计
</span>
</div>
<el-container>
<el-aside width="150px">
<div class="main-number-show">
<span class="count-number">
{{ bugTotalSize }}
</span>
<span style="color: #6C317C;">
{{ $t('api_test.home_page.unit_of_measurement') }}
</span>
<div>
占比:
<el-link type="info" @click="redirectPage('thisWeekCount')" target="_blank" style="color: #000000">{{ rage }}
</el-link>
</div>
</div>
</el-aside>
<el-table border :data="tableData" class="adjust-table table-content" height="300">
<el-table-column prop="index" label="序号"
width="100" show-overflow-tooltip/>
<el-table-column prop="planName" label="测试计划名称"
width="130" show-overflow-tooltip/>
<el-table-column prop="createTime" :label="$t('commons.create_time')" width="130">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column
prop="status"
column-key="status"
:label="$t('test_track.plan.plan_status')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span @click.stop="clickt = 'stop'">
<plan-status-table-item :value="scope.row.status"/>
</span>
</template>
</el-table-column>
<el-table-column prop="caseSize" label="用例数"
width="80" show-overflow-tooltip/>
<el-table-column prop="bugSize" label="缺陷数"
width="80" show-overflow-tooltip/>
<el-table-column prop="passRage" label="通过率"
width="80" show-overflow-tooltip/>
</el-table>
</el-container>
</el-card>
</template>
<script>
import {getCurrentProjectID} from "@/common/js/utils";
import PlanStatusTableItem from "@/business/components/track/common/tableItems/plan/PlanStatusTableItem";
export default {
name: "BugCountCard",
components: {
PlanStatusTableItem
},
data() {
return {
tableData: [],
result: {},
bugTotalSize: 0,
rage: '0%'
}
},
methods: {
init() {
this.result = this.$get("/track/bug/count/" + getCurrentProjectID(), res => {
let data = res.data;
this.tableData = data.list;
this.bugTotalSize = data.bugTotalSize;
this.rage = data.rage;
})
}
},
created() {
this.init()
},
activated() {
this.init()
}
}
</script>
<style scoped>
.el-card /deep/ .el-card__header {
border-bottom: 0px solid #EBEEF5;
}
.el-aside {
line-height: 100px;
text-align: center;
}
.count-number {
font-family: 'ArialMT', 'Arial', sans-serif;
font-size: 33px;
color: var(--count_number);
}
.main-number-show {
width: 100px;
height: 100px;
border-style: solid;
border-width: 7px;
border-color: var(--count_number_shallow);
border-radius: 50%;
}
.count-number-show {
margin: 20px auto;
}
</style>

View File

@ -0,0 +1,191 @@
<template>
<el-card class="table-card" v-loading="result.loading" body-style="padding:10px;">
<div slot="header" >
<span class="title">
用例数量统计
</span>
</div>
<el-container>
<el-aside width="120px">
<div class="main-number-show">
<span class="count-number">
{{trackCountData.allCaseCountNumber}}
</span>
<span style="color: #6C317C;">
{{$t('api_test.home_page.unit_of_measurement')}}
</span>
</div>
</el-aside>
<el-main style="padding-left: 0px;padding-right: 0px;">
<div style="width: 200px;margin:0 auto">
<el-row align="center">
<el-col :span="6" style="padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">
<div class="count-info-div" v-html="trackCountData.p0CountStr"></div>
</el-col>
<el-col :span="6" style="padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">
<div class="count-info-div" v-html="trackCountData.p1CountStr"></div>
</el-col>
<el-col :span="6" style="padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">
<div class="count-info-div" v-html="trackCountData.p2CountStr"></div>
</el-col>
<el-col :span="6" style="padding: 5px;">
<div class="count-info-div" v-html="trackCountData.p3CountStr"></div>
</el-col>
</el-row>
<!-- <el-row align="right" style="margin-left: 20px" class="hidden-xl-only">-->
<!-- <el-col :span="6" style="padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">-->
<!-- <div class="count-info-div" v-html="trackCountData.p0CountStr"></div>-->
<!-- </el-col>-->
<!-- <el-col :span="6" style="padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">-->
<!-- <div class="count-info-div" v-html="trackCountData.p1CountStr"></div>-->
<!-- </el-col>-->
<!-- <el-col :span="6" style="padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">-->
<!-- <div class="count-info-div" v-html="trackCountData.p2CountStr"></div>-->
<!-- </el-col>-->
<!-- <el-col :span="6" style="padding: 5px;">-->
<!-- <div class="count-info-div" v-html="trackCountData.p3CountStr"></div>-->
<!-- </el-col>-->
<!-- </el-row>-->
</div>
</el-main>
</el-container>
<el-container class="detail-container">
<el-header style="height:20px;padding: 0px;margin-bottom: 10px;">
<el-row>
<el-col>
{{$t('api_test.home_page.api_details_card.this_week_add')}}
<el-link type="info" @click="redirectPage('thisWeekCount')" target="_blank" style="color: #000000">{{trackCountData.thisWeekAddedCount}}
</el-link>
{{$t('api_test.home_page.unit_of_measurement')}}
</el-col>
</el-row>
</el-header>
<el-main style="padding: 5px;margin-top: 10px">
<el-container>
<el-aside width="60%" class="count-number-show" style="margin-bottom: 0px;margin-top: 0px">
<el-container>
<el-aside width="30%">
评审率:
</el-aside>
<el-main style="padding: 0px 0px 0px 0px; line-height: 100px; text-align: center;">
<span class="count-number">
{{trackCountData.reviewRage}}
</span>
</el-main>
</el-container>
</el-aside>
<el-main style="padding: 5px">
<el-card class="no-shadow-card" body-style="padding-left:5px;padding-right:5px">
<main>
<el-row>
<el-col>
<span class="default-property">
{{$t('api_test.home_page.detail_card.running')}}
{{"\xa0\xa0"}}
<el-link type="info" @click="redirectPage('Underway')" target="_blank" style="color: #000000">
{{trackCountData.prepareCount}}
</el-link>
</span>
</el-col>
<el-col style="margin-top: 5px;">
<span class="default-property">
{{$t('api_test.home_page.detail_card.not_started')}}
{{"\xa0\xa0"}}
<el-link type="info" @click="redirectPage('Prepare')" target="_blank" style="color: #000000">
{{trackCountData.passCount}}
</el-link>
</span>
</el-col>
<el-col style="margin-top: 5px;">
<span class="main-property">
{{$t('api_test.home_page.detail_card.finished')}}
{{"\xa0\xa0"}}
<el-link type="info" @click="redirectPage('Completed')" target="_blank" style="color: #000000">
{{trackCountData.unPassCount}}
</el-link>
</span>
</el-col>
</el-row>
</main>
</el-card>
</el-main>
</el-container>
</el-main>
</el-container>
</el-card>
</template>
<script>
export default {
name: "CaseCountCard",
props:{
trackCountData: {},
},
data() {
return {
result: {
}
}
},
methods: {
redirectPage(clickType){
this.$emit("redirectPage","api","api",clickType);
}
}
}
</script>
<style scoped>
.el-aside {
line-height: 100px;
text-align: center;
}
.count-number{
font-family:'ArialMT', 'Arial', sans-serif;
font-size:33px;
color: var(--count_number);
}
.main-number-show {
width: 100px;
height: 100px;
border-style: solid;
border-width: 7px;
border-color: var(--count_number_shallow);
border-radius:50%;
}
.count-number-show{
margin:20px auto;
}
.detail-container{
margin-top: 30px
}
.no-shadow-card{
-webkit-box-shadow: 0 0px 0px 0 rgba(0,0,0,.1);
box-shadow: 0 0px 0px 0 rgba(0,0,0,.1);
}
.default-property{
font-size: 12px
}
.main-property{
color: #F39021;
font-size: 12px
}
.el-card /deep/ .el-card__header {
border-bottom: 0px solid #EBEEF5;
}
.count-info-div{
margin: 3px;
}
.count-info-div >>>p{
font-size: 10px;
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<el-card class="table-card" v-loading="result.loading" body-style="padding:10px;">
<div slot="header">
<span class="title">
用例维护人分布
</span>
</div>
<el-container>
<ms-chart ref="chart1" :options="caseOption" :autoresize="true" style="width: 100%;height: 340px"></ms-chart>
</el-container>
</el-card>
</template>
<script>
import MsChart from "@/business/components/common/chart/MsChart";
export default {
name: "CaseMaintenance",
components: {MsChart},
props: {
caseOption: {}
},
data() {
return {
result: {}
}
}
}
</script>
<style scoped>
.no-shadow-card{
-webkit-box-shadow: 0 0px 0px 0 rgba(0,0,0,.1);
box-shadow: 0 0px 0px 0 rgba(0,0,0,.1);
}
.el-card /deep/ .el-card__header {
border-bottom: 0px solid #EBEEF5;
padding-bottom: 0px;
}
</style>

View File

@ -0,0 +1,165 @@
<template>
<el-card class="table-card" v-loading="result.loading" body-style="padding:10px;">
<div slot="header" >
<span class="title">
关联用例数量统计
</span>
</div>
<el-container>
<el-aside width="120px">
<div class="main-number-show">
<span class="count-number">
{{relevanceCountData.allRelevanceCaseCount}}
</span>
<span style="color: #6C317C;">
{{$t('api_test.home_page.unit_of_measurement')}}
</span>
</div>
</el-aside>
<el-main style="padding-left: 0px;padding-right: 0px;">
<div style="width: 300px;margin:0 auto">
<el-row align="center">
<el-col :span="6" style="width:90px;padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">
<div class="count-info-div" v-html="relevanceCountData.apiCaseCountStr"></div>
</el-col>
<el-col :span="6" style="width:90px;padding: 5px;border-right-style: solid;border-right-width: 1px;border-right-color: #ECEEF4;">
<div class="count-info-div" v-html="relevanceCountData.scenarioCaseStr"></div>
</el-col>
<el-col :span="6" style="width:90px;padding: 5px;">
<div class="count-info-div" v-html="relevanceCountData.performanceCaseCountStr"></div>
</el-col>
</el-row>
</div>
</el-main>
</el-container>
<el-container class="detail-container">
<el-header style="height:20px;padding: 0px;margin-bottom: 10px;">
<el-row>
<el-col>
{{$t('api_test.home_page.api_details_card.this_week_add')}}
<el-link type="info" @click="redirectPage('thisWeekCount')" target="_blank" style="color: #000000">{{relevanceCountData.thisWeekAddedCount}}
</el-link>
{{$t('api_test.home_page.unit_of_measurement')}}
</el-col>
</el-row>
</el-header>
<el-main style="padding: 5px;margin-top: 10px">
<el-container>
<el-aside width="60%" class="count-number-show" style="margin-bottom: 0px;margin-top: 0px">
<el-container>
<el-aside width="30%">
覆盖率:
</el-aside>
<el-main style="padding: 0px 0px 0px 0px; line-height: 100px; text-align: center;">
<span class="count-number">
{{relevanceCountData.coverageRage}}
</span>
</el-main>
</el-container>
</el-aside>
<el-main style="padding: 5px">
<el-card class="no-shadow-card" body-style="padding-left:5px;padding-right:5px">
<main>
<el-row>
<el-col>
<span class="default-property">
未覆盖
{{"\xa0\xa0"}}
<el-link type="info" @click="redirectPage('Underway')" target="_blank" style="color: #000000">
{{relevanceCountData.uncoverageCount}}
</el-link>
</span>
</el-col>
<el-col style="margin-top: 5px;">
<span class="main-property">
已覆盖
{{"\xa0\xa0"}}
<el-link type="info" @click="redirectPage('Prepare')" target="_blank" style="color: #000000">
{{relevanceCountData.coverageCount}}
</el-link>
</span>
</el-col>
</el-row>
</main>
</el-card>
</el-main>
</el-container>
</el-main>
</el-container>
</el-card>
</template>
<script>
export default {
name: "RelevanceCaseCard",
props:{
relevanceCountData:{},
},
data() {
return {
result: {
}
}
},
methods: {
redirectPage(clickType){
this.$emit("redirectPage","api","api",clickType);
}
}
}
</script>
<style scoped>
.el-aside {
line-height: 100px;
text-align: center;
}
.count-number{
font-family:'ArialMT', 'Arial', sans-serif;
font-size:33px;
color: var(--count_number);
}
.main-number-show {
width: 100px;
height: 100px;
border-style: solid;
border-width: 7px;
border-color: var(--count_number_shallow);
border-radius:50%;
}
.count-number-show{
margin:20px auto;
}
.detail-container{
margin-top: 30px
}
.no-shadow-card{
-webkit-box-shadow: 0 0px 0px 0 rgba(0,0,0,.1);
box-shadow: 0 0px 0px 0 rgba(0,0,0,.1);
}
.default-property{
font-size: 12px
}
.main-property{
color: #F39021;
font-size: 12px
}
.el-card /deep/ .el-card__header {
border-bottom: 0px solid #EBEEF5;
}
.count-info-div{
margin: 3px;
}
.count-info-div >>>p{
font-size: 10px;
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<home-base-component :title="$t('test_track.review.my_review')" v-loading>
<template slot="header-area">
<div style="float: right">
<el-card class="table-card" v-loading="result.loading" body-style="padding:10px;">
<div slot="header">
<span class="title">
遗留缺陷统计
</span>
<ms-table-button :is-tester-permission="true" v-if="!showMyCreator" icon="el-icon-view"
:content="$t('test_track.review.my_create')" @click="searchMyCreator"/>
:content="$t('test_track.review.my_create')" @click="searchMyCreator" style="float: right"/>
<ms-table-button :is-tester-permission="true" v-if="showMyCreator" icon="el-icon-view"
:content="$t('test_track.review.reviewed_by_me')" @click="searchMyCreator"/>
:content="$t('test_track.review.reviewed_by_me')" @click="searchMyCreator" style="float: right"/>
</div>
</template>
<el-table
class="adjust-table"
border
@ -60,7 +60,7 @@
</el-table>
</home-base-component>
</el-card>
</template>
<script>
@ -120,5 +120,7 @@ export default {
</script>
<style scoped>
.el-card /deep/ .el-card__header {
border-bottom: 0px solid #EBEEF5;
}
</style>

@ -1 +1 @@
Subproject commit 4c33b9c3b12a83da6d9bd2740262c6c8baaab819
Subproject commit b2571e06e8b211821409115cc2c4a7c52cbac1db

View File

@ -50,11 +50,23 @@ const IsReadOnly = {
}
}
const Theme = {
state: {
theme: undefined
},
mutations: {
setTheme(state, value) {
state.theme = value;
}
}
}
export default new Vuex.Store({
modules: {
api: API,
common: Common,
switch: Switch,
isReadOnly: IsReadOnly,
theme: Theme
}
})

View File

@ -17,6 +17,7 @@ body {
/*--color: #2c2a48;*/
/*--color_shallow: #595591;*/
--color: '';
--primary_color: '';
--color_shallow: '';
--count_number: '';
--count_number_shallow: '';

View File

@ -173,3 +173,4 @@ export const ORIGIN_COLOR = '#2c2a48';
export const ORIGIN_COLOR_SHALLOW = '#595591';
export const COUNT_NUMBER = '#6C317C';
export const COUNT_NUMBER_SHALLOW = '#CDB9D2';
export const PRIMARY_COLOR = '#783887';

View File

@ -3,7 +3,7 @@ import {
COUNT_NUMBER_SHALLOW,
LicenseKey,
ORIGIN_COLOR,
ORIGIN_COLOR_SHALLOW,
ORIGIN_COLOR_SHALLOW, PRIMARY_COLOR,
PROJECT_ID,
REFRESH_SESSION_USER_URL,
ROLE_ADMIN,
@ -354,19 +354,19 @@ export function objToStrMap(obj) {
return strMap;
}
export function getColor() {
return localStorage.getItem('color');
}
export function setColor(a, b, c, d) {
export function setColor(a, b, c, d, e) {
// 顶部菜单背景色
document.body.style.setProperty('--color', a);
document.body.style.setProperty('--color_shallow', b);
// 首页颜色
document.body.style.setProperty('--count_number', c);
document.body.style.setProperty('--count_number_shallow', d);
// 主颜色
document.body.style.setProperty('--primary_color', e);
}
export function setOriginColor() {
setColor(ORIGIN_COLOR, ORIGIN_COLOR_SHALLOW, COUNT_NUMBER, COUNT_NUMBER_SHALLOW);
export function setDefaultTheme() {
setColor(ORIGIN_COLOR, ORIGIN_COLOR_SHALLOW, COUNT_NUMBER, COUNT_NUMBER_SHALLOW, PRIMARY_COLOR);
}
export function publicKeyEncrypt(input, publicKey) {

View File

@ -56,7 +56,7 @@
<script>
import {publicKeyEncrypt, saveLocalStorage} from '@/common/js/utils';
import {DEFAULT_LANGUAGE} from "@/common/js/constants";
import {DEFAULT_LANGUAGE, PRIMARY_COLOR} from "@/common/js/constants";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {};
@ -91,6 +91,10 @@ export default {
}
},
beforeCreate() {
this.$get('/system/theme', res => {
this.color = res.data ? res.data : PRIMARY_COLOR;
document.body.style.setProperty('--primary_color', this.color);
})
this.result = this.$get("/isLogin").then(response => {
if (display.default !== undefined) {
@ -230,7 +234,7 @@ export default {
margin-top: 12px;
margin-bottom: 75px;
font-size: 14px;
color: #843697;
color: var(--primary_color);
line-height: 14px;
text-align: center;
}
@ -243,18 +247,18 @@ export default {
.btn > .submit {
border-radius: 70px;
border-color: #8B479B;
background-color: #8B479B;
border-color: var(--primary_color);
background-color: var(--primary_color);
}
.btn > .submit:hover {
border-color: rgba(139, 71, 155, 0.9);
background-color: rgba(139, 71, 155, 0.9);
border-color: var(--primary_color);
background-color: var(--primary_color);
}
.btn > .submit:active {
border-color: rgba(139, 71, 155, 0.8);
background-color: rgba(139, 71, 155, 0.8);
border-color: var(--primary_color);
background-color: var(--primary_color);
}
.el-form-item:first-child {
@ -262,13 +266,13 @@ export default {
}
/deep/ .el-radio__input.is-checked .el-radio__inner {
background-color: #783887;
background: #783887;
border-color: #783887;
background-color: var(--primary_color);
background: var(--primary_color);
border-color: var(--primary_color);
}
/deep/ .el-radio__input.is-checked + .el-radio__label {
color: #783887;
color: var(--primary_color);
}
/deep/ .el-input__inner {
@ -284,7 +288,7 @@ export default {
}
/deep/ .el-input__inner:focus {
border: 1px solid #783887 !important;
border: 1px solid var(--primary_color) !important;
}
.divider {