diff --git a/backend/src/main/java/io/metersphere/api/controller/APITestController.java b/backend/src/main/java/io/metersphere/api/controller/APITestController.java index 4ddbf28531..6ac534febb 100644 --- a/backend/src/main/java/io/metersphere/api/controller/APITestController.java +++ b/backend/src/main/java/io/metersphere/api/controller/APITestController.java @@ -12,6 +12,7 @@ import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.controller.request.QueryScheduleRequest; +import io.metersphere.dto.LicenseDTO; import io.metersphere.dto.ScheduleDao; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; @@ -19,7 +20,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; - import java.util.List; @RestController @@ -127,4 +127,10 @@ public class APITestController { public List listSchedule(@RequestBody QueryScheduleRequest request) { return apiTestService.listSchedule(request); } + + @GetMapping("/license/valid") + public LicenseDTO valid() { + return apiTestService.validateLicense(); + } + } diff --git a/backend/src/main/java/io/metersphere/api/service/APITestService.java b/backend/src/main/java/io/metersphere/api/service/APITestService.java index ab2d8e2302..83b33c5b37 100644 --- a/backend/src/main/java/io/metersphere/api/service/APITestService.java +++ b/backend/src/main/java/io/metersphere/api/service/APITestService.java @@ -12,7 +12,6 @@ import io.metersphere.api.parse.JmeterDocumentParser; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.ApiTestFileMapper; import io.metersphere.base.mapper.ApiTestMapper; -import io.metersphere.base.mapper.UserMapper; import io.metersphere.base.mapper.ext.ExtApiTestMapper; import io.metersphere.commons.constants.APITestStatus; import io.metersphere.commons.constants.FileType; @@ -21,15 +20,13 @@ import io.metersphere.commons.constants.ScheduleType; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.*; import io.metersphere.controller.request.QueryScheduleRequest; +import io.metersphere.dto.LicenseDTO; import io.metersphere.dto.ScheduleDao; import io.metersphere.i18n.Translator; import io.metersphere.job.sechedule.ApiTestJob; import io.metersphere.notice.service.MailService; import io.metersphere.notice.service.NoticeService; -import io.metersphere.service.FileService; -import io.metersphere.service.QuotaService; -import io.metersphere.service.ScheduleService; -import io.metersphere.service.UserService; +import io.metersphere.service.*; import io.metersphere.track.service.TestCaseService; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; @@ -40,7 +37,6 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; - import java.io.*; import java.util.*; import java.util.stream.Collectors; @@ -441,4 +437,13 @@ public class APITestService { quotaService.checkAPITestQuota(); } } + + public LicenseDTO validateLicense() { + LicenseService licenseService = CommonBeanFactory.getBean(LicenseService.class); + if (licenseService != null) { + return licenseService.valid(); + } + return null; + } + } diff --git a/backend/src/main/java/io/metersphere/base/domain/Issues.java b/backend/src/main/java/io/metersphere/base/domain/Issues.java index fafaa92c08..bc9814bfcd 100644 --- a/backend/src/main/java/io/metersphere/base/domain/Issues.java +++ b/backend/src/main/java/io/metersphere/base/domain/Issues.java @@ -28,5 +28,7 @@ public class Issues implements Serializable { private String projectName; + private String currentOwner; + private static final long serialVersionUID = 1L; -} \ No newline at end of file +} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.xml index 96a18c6767..0dfdc87839 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseReviewMapper.xml @@ -2,12 +2,16 @@ - + select distinct test_case_review.id, test_case_review.name, user.name as creator, test_case_review.status, + test_case_review.create_time, test_case_review.update_time, test_case_review.end_time, + test_case_review.description + from test_case_review, project, test_case_review_project, user test_case_review.id = test_case_review_project.review_id and test_case_review_project.project_id = project.id + and user.id = test_case_review.creator and test_case_review.name like CONCAT('%', #{request.name},'%') @@ -21,7 +25,8 @@ - select distinct test_case_review.* from test_case_review, project, test_case_review_project where test_case_review.id = test_case_review_project.review_id diff --git a/backend/src/main/java/io/metersphere/dto/LicenseDTO.java b/backend/src/main/java/io/metersphere/dto/LicenseDTO.java new file mode 100644 index 0000000000..245f875979 --- /dev/null +++ b/backend/src/main/java/io/metersphere/dto/LicenseDTO.java @@ -0,0 +1,14 @@ +package io.metersphere.dto; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class LicenseDTO implements Serializable { + + private String status; + + private LicenseInfoDTO license; + +} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/dto/LicenseInfoDTO.java b/backend/src/main/java/io/metersphere/dto/LicenseInfoDTO.java new file mode 100644 index 0000000000..682ebe564e --- /dev/null +++ b/backend/src/main/java/io/metersphere/dto/LicenseInfoDTO.java @@ -0,0 +1,21 @@ +package io.metersphere.dto; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class LicenseInfoDTO implements Serializable { + // 客户名称 + private String corporation; + // 授权截止时间 + private String expired; + //产品名称 + private String product; + //产品版本 + private String edition; + //icense版本 + private String licenseVersion; + //授权数量 + private int licenseCount; +} diff --git a/backend/src/main/java/io/metersphere/notice/service/MailService.java b/backend/src/main/java/io/metersphere/notice/service/MailService.java index c423e8e83b..4390e52190 100644 --- a/backend/src/main/java/io/metersphere/notice/service/MailService.java +++ b/backend/src/main/java/io/metersphere/notice/service/MailService.java @@ -76,7 +76,7 @@ public class MailService { "\n" + "
\n" + "

" + type + "定时任务结果通知

\n" + - "

尊敬的用户:您好,您所执行的" + testName + "运行失败,请点击报告链接查看

\n" + + "

尊敬的用户:您好,您所执行的" + testName + "运行失败

\n" + "
\n" + "\n" + ""; @@ -89,7 +89,7 @@ public class MailService { "\n" + "
\n" + "

" + type + "定时任务结果通知

\n" + - "

尊敬的用户:您好," + testName + "运行成功,请点击报告链接查看

\n" + + "

尊敬的用户:您好," + testName + "运行成功

\n" + "
\n" + "\n" + ""; diff --git a/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java b/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java index 2c580d4b17..9c7e07647c 100644 --- a/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java +++ b/backend/src/main/java/io/metersphere/performance/service/PerformanceTestService.java @@ -238,8 +238,17 @@ public class PerformanceTestService { startEngine(loadTest, engine, request.getTriggerMode()); if (request.getTriggerMode().equals("SCHEDULE")) { - List notice = noticeService.queryNotice(request.getId()); - mailService.sendHtml(engine.getReportId(), notice, "status", "performance"); + List notice = null; + try { + notice = noticeService.queryNotice(request.getId()); + } catch (Exception e) { + e.printStackTrace(); + } + try { + mailService.sendHtml(engine.getReportId(), notice, "status", "performance"); + } catch (Exception e) { + e.printStackTrace(); + } } return engine.getReportId(); } diff --git a/backend/src/main/java/io/metersphere/service/LicenseService.java b/backend/src/main/java/io/metersphere/service/LicenseService.java new file mode 100644 index 0000000000..25e46b3d18 --- /dev/null +++ b/backend/src/main/java/io/metersphere/service/LicenseService.java @@ -0,0 +1,10 @@ +package io.metersphere.service; + +import io.metersphere.dto.LicenseDTO; + +public interface LicenseService { + + public LicenseDTO valid(); + + public LicenseDTO addValidLicense(String reqLicenseCode); +} diff --git a/backend/src/main/java/io/metersphere/track/controller/TestCaseReviewController.java b/backend/src/main/java/io/metersphere/track/controller/TestCaseReviewController.java index 664612f156..dd0921a569 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestCaseReviewController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestCaseReviewController.java @@ -67,7 +67,7 @@ public class TestCaseReviewController { @PostMapping("/edit") @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) - public void editCaseReview(@RequestBody TestCaseReview testCaseReview) { + public void editCaseReview(@RequestBody SaveTestCaseReviewRequest testCaseReview) { testCaseReviewService.editCaseReview(testCaseReview); } diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java index 7a4079c695..5abe6b7e34 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java @@ -26,6 +26,8 @@ import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + import javax.annotation.Resource; import java.util.*; import java.util.stream.Collectors; @@ -54,6 +56,10 @@ public class TestCaseReviewService { ExtProjectMapper extProjectMapper; @Resource UserService userService; + @Resource + TestCaseMapper testCaseMapper; + @Resource + TestCaseReviewTestCaseMapper testCaseReviewTestCaseMapper; public void saveTestCaseReview(SaveTestCaseReviewRequest reviewRequest) { checkCaseReviewExist(reviewRequest); @@ -119,20 +125,87 @@ public class TestCaseReviewService { .collect(Collectors.toList()); UserExample userExample = new UserExample(); - userExample.createCriteria().andIdIn(userIds); - return userMapper.selectByExample(userExample); + UserExample.Criteria criteria = userExample.createCriteria(); + if (!CollectionUtils.isEmpty(userIds)) { + criteria.andIdIn(userIds); + return userMapper.selectByExample(userExample); + } + return new ArrayList<>(); } public List recent(String currentWorkspaceId) { return extTestCaseReviewMapper.listByWorkspaceId(currentWorkspaceId); } - public void editCaseReview(TestCaseReview testCaseReview) { + public void editCaseReview(SaveTestCaseReviewRequest testCaseReview) { + editCaseReviewer(testCaseReview); + editCaseReviewProject(testCaseReview); testCaseReview.setUpdateTime(System.currentTimeMillis()); checkCaseReviewExist(testCaseReview); testCaseReviewMapper.updateByPrimaryKeySelective(testCaseReview); } + private void editCaseReviewer(SaveTestCaseReviewRequest testCaseReview) { + // 要更新的reviewerIds + List reviewerIds = testCaseReview.getUserIds(); + + String id = testCaseReview.getId(); + TestCaseReviewUsersExample testCaseReviewUsersExample = new TestCaseReviewUsersExample(); + testCaseReviewUsersExample.createCriteria().andReviewIdEqualTo(id); + List testCaseReviewUsers = testCaseReviewUsersMapper.selectByExample(testCaseReviewUsersExample); + List dbReviewIds = testCaseReviewUsers.stream().map(TestCaseReviewUsers::getUserId).collect(Collectors.toList()); + + reviewerIds.forEach(reviewerId -> { + if (!dbReviewIds.contains(reviewerId)) { + TestCaseReviewUsers caseReviewUser = new TestCaseReviewUsers(); + caseReviewUser.setUserId(reviewerId); + caseReviewUser.setReviewId(id); + testCaseReviewUsersMapper.insertSelective(caseReviewUser); + } + }); + + TestCaseReviewUsersExample example = new TestCaseReviewUsersExample(); + example.createCriteria().andReviewIdEqualTo(id).andUserIdNotIn(reviewerIds); + testCaseReviewUsersMapper.deleteByExample(example); + } + + private void editCaseReviewProject(SaveTestCaseReviewRequest testCaseReview) { + List projectIds = testCaseReview.getProjectIds(); + String id = testCaseReview.getId(); + if (!CollectionUtils.isEmpty(projectIds)) { + TestCaseReviewProjectExample testCaseReviewProjectExample = new TestCaseReviewProjectExample(); + testCaseReviewProjectExample.createCriteria().andReviewIdEqualTo(id); + List testCaseReviewProjects = testCaseReviewProjectMapper.selectByExample(testCaseReviewProjectExample); + List dbProjectIds = testCaseReviewProjects.stream().map(TestCaseReviewProject::getProjectId).collect(Collectors.toList()); + projectIds.forEach(projectId -> { + if (!dbProjectIds.contains(projectId)) { + TestCaseReviewProject testCaseReviewProject = new TestCaseReviewProject(); + testCaseReviewProject.setReviewId(id); + testCaseReviewProject.setProjectId(projectId); + testCaseReviewProjectMapper.insert(testCaseReviewProject); + } + }); + + TestCaseReviewProjectExample example = new TestCaseReviewProjectExample(); + example.createCriteria().andReviewIdEqualTo(id).andProjectIdNotIn(projectIds); + testCaseReviewProjectMapper.deleteByExample(example); + + + // 关联的项目下的用例idList + TestCaseExample testCaseExample = new TestCaseExample(); + testCaseExample.createCriteria().andProjectIdIn(projectIds); + List caseList = testCaseMapper.selectByExample(testCaseExample); + List caseIds = caseList.stream().map(TestCase::getId).collect(Collectors.toList()); + + TestCaseReviewTestCaseExample testCaseReviewTestCaseExample = new TestCaseReviewTestCaseExample(); + TestCaseReviewTestCaseExample.Criteria criteria = testCaseReviewTestCaseExample.createCriteria().andReviewIdEqualTo(id); + if (!CollectionUtils.isEmpty(caseIds)) { + criteria.andCaseIdNotIn(caseIds); + } + testCaseReviewTestCaseMapper.deleteByExample(testCaseReviewTestCaseExample); + } + } + private void checkCaseReviewExist(TestCaseReview testCaseReview) { if (testCaseReview.getName() != null) { TestCaseReviewExample example = new TestCaseReviewExample(); @@ -153,6 +226,7 @@ public class TestCaseReviewService { public void deleteCaseReview(String reviewId) { deleteCaseReviewProject(reviewId); deleteCaseReviewUsers(reviewId); + deleteCaseReviewTestCase(reviewId); testCaseReviewMapper.deleteByPrimaryKey(reviewId); } @@ -168,6 +242,12 @@ public class TestCaseReviewService { testCaseReviewUsersMapper.deleteByExample(testCaseReviewUsersExample); } + private void deleteCaseReviewTestCase(String reviewId) { + TestCaseReviewTestCaseExample testCaseReviewTestCaseExample = new TestCaseReviewTestCaseExample(); + testCaseReviewTestCaseExample.createCriteria().andReviewIdEqualTo(reviewId); + testCaseReviewTestCaseMapper.deleteByExample(testCaseReviewTestCaseExample); + } + public List listCaseReviewAll(String currentWorkspaceId) { ProjectExample projectExample = new ProjectExample(); projectExample.createCriteria().andWorkspaceIdEqualTo(currentWorkspaceId); @@ -311,7 +391,7 @@ public class TestCaseReviewService { } return name; } - + public List listTestCaseByProjectIds(List projectIds) { QueryCaseReviewRequest request = new QueryCaseReviewRequest(); request.setProjectIds(projectIds); diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java index 2239515a26..07bb336bad 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseService.java @@ -27,7 +27,7 @@ import io.metersphere.i18n.Translator; import io.metersphere.track.dto.TestCaseDTO; import io.metersphere.track.request.testcase.QueryTestCaseRequest; import io.metersphere.track.request.testcase.TestCaseBatchRequest; -import io.metersphere.xmind.XmindToTestCaseParser; +import io.metersphere.xmind.XmindCaseParser; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; @@ -273,13 +273,18 @@ public class TestCaseService { if (multipartFile.getOriginalFilename().endsWith(".xmind")) { try { errList = new ArrayList<>(); - String processLog = new XmindToTestCaseParser(this, userId, projectId, testCaseNames).importXmind(multipartFile); + XmindCaseParser xmindParser = new XmindCaseParser(this, userId, projectId, testCaseNames); + String processLog = xmindParser.parse(multipartFile); if (!StringUtils.isEmpty(processLog)) { excelResponse.setSuccess(false); - ExcelErrData excelErrData = new ExcelErrData(null, 1, Translator.get("upload_fail")+":"+ processLog); + ExcelErrData excelErrData = new ExcelErrData(null, 1, Translator.get("upload_fail") + ":" + processLog); errList.add(excelErrData); excelResponse.setErrList(errList); } else { + if (!xmindParser.getTestCase().isEmpty()) { + this.saveImportData(xmindParser.getTestCase(), projectId); + xmindParser.clear(); + } excelResponse.setSuccess(true); } } catch (Exception e) { @@ -345,7 +350,7 @@ public class TestCaseService { // 发送给客户端的数据 byte[] buff = new byte[1024]; try (OutputStream outputStream = res.getOutputStream(); - BufferedInputStream bis = new BufferedInputStream(TestCaseService.class.getResourceAsStream("/io/metersphere/xmind/template/testcase.xml"));) { + BufferedInputStream bis = new BufferedInputStream(TestCaseService.class.getResourceAsStream("/io/metersphere/xmind/template/xmind.xml"));) { int i = bis.read(buff); while (i != -1) { outputStream.write(buff, 0, buff.length); diff --git a/backend/src/main/java/io/metersphere/xmind/XmindToTestCaseParser.java b/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java similarity index 67% rename from backend/src/main/java/io/metersphere/xmind/XmindToTestCaseParser.java rename to backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java index 699948c904..99441d8e72 100644 --- a/backend/src/main/java/io/metersphere/xmind/XmindToTestCaseParser.java +++ b/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java @@ -1,6 +1,5 @@ package io.metersphere.xmind; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import io.metersphere.base.domain.TestCaseWithBLOBs; @@ -11,51 +10,60 @@ import io.metersphere.excel.domain.TestCaseExcelData; import io.metersphere.i18n.Translator; import io.metersphere.track.service.TestCaseService; import io.metersphere.xmind.parser.XmindParser; -import io.metersphere.xmind.parser.domain.Attached; -import io.metersphere.xmind.parser.domain.JsonRootBean; -import org.springframework.util.StringUtils; +import io.metersphere.xmind.parser.pojo.Attached; +import io.metersphere.xmind.parser.pojo.JsonRootBean; +import org.apache.commons.lang3.StringUtils; import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 数据转换 - * 1 解析Xmind文件 XmindParser.parseJson - * 2 解析后的JSON 转成测试用例 + * 1 解析Xmind文件 XmindParser.parseObject + * 2 解析后的JSON this.parse 转成测试用例 */ -public class XmindToTestCaseParser { +public class XmindCaseParser { private TestCaseService testCaseService; private String maintainer; private String projectId; private StringBuffer process; // 过程校验记录 + // 已存在用例名称 private Set testCaseNames; - public XmindToTestCaseParser(TestCaseService testCaseService, String userId, String projectId, Set testCaseNames) { + // 转换后的案例信息 + private List testCases; + + // 案例详情重写了hashCode方法去重用 + private List compartDatas; + + public XmindCaseParser(TestCaseService testCaseService, String userId, String projectId, Set testCaseNames) { this.testCaseService = testCaseService; this.maintainer = userId; this.projectId = projectId; this.testCaseNames = testCaseNames; - testCaseWithBLOBs = new LinkedList<>(); - xmindDataList = new ArrayList<>(); + testCases = new LinkedList<>(); + compartDatas = new ArrayList<>(); process = new StringBuffer(); } - // 案例详情 - private List testCaseWithBLOBs; - // 用于重复对比 - protected List xmindDataList; + // 这里清理是为了 加快jvm 回收 + public void clear() { + compartDatas.clear(); + testCases.clear(); + testCaseNames.clear(); + } + + public List getTestCase() { + return this.testCases; + } // 递归处理案例数据 - private void makeXmind(StringBuffer processBuffer, Attached parent, int level, String nodePath, List attacheds) { + private void recursion(StringBuffer processBuffer, Attached parent, int level, String nodePath, List attacheds) { for (Attached item : attacheds) { - if (isBlack(item.getTitle(), "(?:tc:|tc:|tc)")) { // 用例 + if (isAvailable(item.getTitle(), "(?:tc:|tc:|tc)")) { // 用例 item.setParent(parent); this.newTestCase(item.getTitle(), parent.getPath(), item.getChildren() != null ? item.getChildren().getAttached() : null); } else { @@ -63,14 +71,13 @@ public class XmindToTestCaseParser { item.setPath(nodePath); if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) { item.setParent(parent); - makeXmind(processBuffer, item, level + 1, nodePath, item.getChildren().getAttached()); + recursion(processBuffer, item, level + 1, nodePath, item.getChildren().getAttached()); } } } } - private boolean isBlack(String str, String regex) { - // regex = "(?:tc:|tc:)" + private boolean isAvailable(String str, String regex) { if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex)) return false; Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); @@ -88,7 +95,7 @@ public class XmindToTestCaseParser { } // 获取步骤数据 - public String getSteps(List attacheds) { + private String getSteps(List attacheds) { JSONArray jsonArray = new JSONArray(); for (int i = 0; i < attacheds.size(); i++) { // 保持插入顺序,判断用例是否有相同的steps @@ -148,9 +155,9 @@ public class XmindToTestCaseParser { List steps = new LinkedList<>(); if (attacheds != null && !attacheds.isEmpty()) { attacheds.forEach(item -> { - if (isBlack(item.getTitle(), "(?:pc:|pc:)")) { + if (isAvailable(item.getTitle(), "(?:pc:|pc:)")) { testCase.setPrerequisite(replace(item.getTitle(), "(?:pc:|pc:)")); - } else if (isBlack(item.getTitle(), "(?:rc:|rc:)")) { + } else if (isAvailable(item.getTitle(), "(?:rc:|rc:)")) { testCase.setRemark(replace(item.getTitle(), "(?:rc:|rc:)")); } else { steps.add(item); @@ -171,74 +178,42 @@ public class XmindToTestCaseParser { } TestCaseExcelData compartData = new TestCaseExcelData(); BeanUtils.copyBean(compartData, testCase); - if (xmindDataList.contains(compartData)) { + if (compartDatas.contains(compartData)) { process.append(Translator.get("test_case_already_exists_excel") + ":" + testCase.getName() + "; "); } else if (validate(testCase)) { testCase.setId(UUID.randomUUID().toString()); testCase.setCreateTime(System.currentTimeMillis()); testCase.setUpdateTime(System.currentTimeMillis()); - testCaseWithBLOBs.add(testCase); + testCases.add(testCase); } - xmindDataList.add(compartData); + compartDatas.add(compartData); } - //获取流文件 - private static void inputStreamToFile(InputStream ins, File file) { - try (OutputStream os = new FileOutputStream(file);) { - int bytesRead = 0; - byte[] buffer = new byte[8192]; - while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { - os.write(buffer, 0, bytesRead); - } - } catch (Exception e) { - LogUtil.error(e.getMessage()); - } - } - - /** - * MultipartFile 转 File - * - * @param file - * @throws Exception - */ - private File multipartFileToFile(MultipartFile file) throws Exception { - if (file != null && file.getSize() > 0) { - try (InputStream ins = file.getInputStream();) { - File toFile = new File(file.getOriginalFilename()); - inputStreamToFile(ins, toFile); - return toFile; - } - } - return null; - } - - - public boolean validate(TestCaseWithBLOBs data) { + // 验证合法性 + private boolean validate(TestCaseWithBLOBs data) { String nodePath = data.getNodePath(); StringBuilder stringBuilder = new StringBuilder(); - if (nodePath != null) { + if (!StringUtils.isEmpty(nodePath)) { String[] nodes = nodePath.split("/"); if (nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) { stringBuilder.append(Translator.get("test_case_node_level_tip") + TestCaseConstants.MAX_NODE_DEPTH + Translator.get("test_case_node_level") + "; "); } for (int i = 0; i < nodes.length; i++) { - if (i != 0 && org.apache.commons.lang3.StringUtils.equals(nodes[i].trim(), "")) { + if (i != 0 && StringUtils.equals(nodes[i].trim(), "")) { stringBuilder.append(Translator.get("module_not_null") + "; "); break; } } } - if (org.apache.commons.lang3.StringUtils.equals(data.getType(), TestCaseConstants.Type.Functional.getValue()) && org.apache.commons.lang3.StringUtils.equals(data.getMethod(), TestCaseConstants.Method.Auto.getValue())) { + if (StringUtils.equals(data.getType(), TestCaseConstants.Type.Functional.getValue()) && StringUtils.equals(data.getMethod(), TestCaseConstants.Method.Auto.getValue())) { stringBuilder.append(Translator.get("functional_method_tip") + "; "); } if (testCaseNames.contains(data.getName())) { boolean dbExist = testCaseService.exist(data); - boolean excelExist = false; - if (dbExist) { // db exist stringBuilder.append(Translator.get("test_case_already_exists_excel") + ":" + data.getName() + "; "); @@ -255,49 +230,29 @@ public class XmindToTestCaseParser { } // 导入思维导图处理 - public String importXmind(MultipartFile multipartFile) { + public String parse(MultipartFile multipartFile) { StringBuffer processBuffer = new StringBuffer(); - File file = null; try { - file = multipartFileToFile(multipartFile); - if (file == null || !file.exists()) - return Translator.get("incorrect_format"); - // 获取思维导图内容 - String content = XmindParser.parseJson(file); - if (StringUtils.isEmpty(content) || content.split("(?:tc:|tc:|TC:|TC:|tc|TC)").length == 1) { - return Translator.get("import_xmind_not_found"); - } - if (!StringUtils.isEmpty(content) && content.split("(?:tc:|tc:|TC:|TC:|tc|TC)").length > 500) { - return Translator.get("import_xmind_count_error"); - } - JsonRootBean root = JSON.parseObject(content, JsonRootBean.class); - + JsonRootBean root = XmindParser.parseObject(multipartFile); if (root != null && root.getRootTopic() != null && root.getRootTopic().getChildren() != null) { // 判断是模块还是用例 for (Attached item : root.getRootTopic().getChildren().getAttached()) { - if (isBlack(item.getTitle(), "(?:tc:|tc:|tc)")) { // 用例 + if (isAvailable(item.getTitle(), "(?:tc:|tc:|tc)")) { // 用例 return replace(item.getTitle(), "(?:tc:|tc:|tc)") + ":" + Translator.get("test_case_create_module_fail"); } else { item.setPath(item.getTitle()); if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) { item.setPath(item.getTitle()); - makeXmind(processBuffer, item, 1, item.getPath(), item.getChildren().getAttached()); + recursion(processBuffer, item, 1, item.getPath(), item.getChildren().getAttached()); } } } } - if (StringUtils.isEmpty(process.toString()) && !testCaseWithBLOBs.isEmpty()) { - testCaseService.saveImportData(testCaseWithBLOBs, projectId); - } } catch (Exception ex) { processBuffer.append(Translator.get("incorrect_format")); LogUtil.error(ex.getMessage()); - ex.printStackTrace(); - } finally { - if (file != null) - file.delete(); - testCaseWithBLOBs.clear(); + return ex.getMessage(); } return process.toString(); } diff --git a/backend/src/main/java/io/metersphere/xmind/parser/XmindParser.java b/backend/src/main/java/io/metersphere/xmind/parser/XmindParser.java index 44e1cd5055..d61a28a9d7 100644 --- a/backend/src/main/java/io/metersphere/xmind/parser/XmindParser.java +++ b/backend/src/main/java/io/metersphere/xmind/parser/XmindParser.java @@ -1,9 +1,14 @@ package io.metersphere.xmind.parser; import com.alibaba.fastjson.JSON; -import io.metersphere.xmind.parser.domain.JsonRootBean; +import io.metersphere.commons.exception.MSException; +import io.metersphere.i18n.Translator; +import io.metersphere.xmind.parser.pojo.JsonRootBean; +import io.metersphere.xmind.utils.FileUtil; import org.apache.commons.compress.archivers.ArchiveException; import org.dom4j.DocumentException; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; @@ -16,101 +21,98 @@ import java.util.Objects; * @Description 解析主体 */ public class XmindParser { - public static final String xmindZenJson = "content.json"; - public static final String xmindLegacyContent = "content.xml"; - public static final String xmindLegacyComments = "comments.xml"; + public static final String xmindZenJson = "content.json"; + public static final String xmindLegacyContent = "content.xml"; + public static final String xmindLegacyComments = "comments.xml"; - /** - * 解析脑图文件,返回content整合后的内容 - * - * @param file - * @return - * @throws IOException - * @throws ArchiveException - * @throws DocumentException - */ - public static String parseJson(File file) throws IOException, ArchiveException, DocumentException { - String res = ZipUtils.extract(file); + /** + * 解析脑图文件,返回content整合后的内容 + * + * @param multipartFile + * @return + * @throws IOException + * @throws ArchiveException + * @throws DocumentException + */ + public static String parseJson(MultipartFile multipartFile) throws IOException, ArchiveException, DocumentException { - String content = null; - if (isXmindZen(res, file)) { - content = getXmindZenContent(file, res); - } else { - content = getXmindLegacyContent(file, res); - } + File file = FileUtil.multipartFileToFile(multipartFile); + if (file == null || !file.exists()) + MSException.throwException(Translator.get("incorrect_format")); - // 删除生成的文件夹 - File dir = new File(res); - boolean flag = deleteDir(dir); - if (flag) { - // do something - } - JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class); - return (JSON.toJSONString(jsonRootBean, false)); - } + String res = ZipUtils.extract(file); + String content = null; + if (isXmindZen(res, file)) { + content = getXmindZenContent(file, res); + } else { + content = getXmindLegacyContent(file, res); + } - public static JsonRootBean parseObject(File file) throws DocumentException, ArchiveException, IOException { - String content = parseJson(file); - JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class); - return jsonRootBean; - } + // 删除生成的文件夹 + File dir = new File(res); + FileUtil.deleteDir(dir); + JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class); + // 删除零时文件 + if (file != null) + file.delete(); + String json = (JSON.toJSONString(jsonRootBean, false)); - public static boolean deleteDir(File dir) { - if (dir.isDirectory()) { - String[] children = dir.list(); - // 递归删除目录中的子目录下 - for (int i = 0; i < children.length; i++) { - boolean success = deleteDir(new File(dir, children[i])); - if (!success) { - return false; - } - } - } - // 目录此时为空,可以删除 - return dir.delete(); - } + if (StringUtils.isEmpty(content) || content.split("(?:tc:|tc:|TC:|TC:|tc|TC)").length == 1) { + MSException.throwException(Translator.get("import_xmind_not_found")); + } + if (!StringUtils.isEmpty(content) && content.split("(?:tc:|tc:|TC:|TC:|tc|TC)").length > 500) { + MSException.throwException(Translator.get("import_xmind_count_error")); + } + return json; + } - /** - * @return - */ - public static String getXmindZenContent(File file, String extractFileDir) - throws IOException, ArchiveException { - List keys = new ArrayList<>(); - keys.add(xmindZenJson); - Map map = ZipUtils.getContents(keys, file, extractFileDir); - String content = map.get(xmindZenJson); - content = XmindZen.getContent(content); - return content; - } + public static JsonRootBean parseObject(MultipartFile multipartFile) throws DocumentException, ArchiveException, IOException { + String content = parseJson(multipartFile); + JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class); + return jsonRootBean; + } - /** - * @return - */ - public static String getXmindLegacyContent(File file, String extractFileDir) - throws IOException, ArchiveException, DocumentException { - List keys = new ArrayList<>(); - keys.add(xmindLegacyContent); - keys.add(xmindLegacyComments); - Map map = ZipUtils.getContents(keys, file, extractFileDir); + /** + * @return + */ + public static String getXmindZenContent(File file, String extractFileDir) + throws IOException, ArchiveException { + List keys = new ArrayList<>(); + keys.add(xmindZenJson); + Map map = ZipUtils.getContents(keys, file, extractFileDir); + String content = map.get(xmindZenJson); + content = XmindZen.getContent(content); + return content; + } - String contentXml = map.get(xmindLegacyContent); - String commentsXml = map.get(xmindLegacyComments); - String xmlContent = XmindLegacy.getContent(contentXml, commentsXml); + /** + * @return + */ + public static String getXmindLegacyContent(File file, String extractFileDir) + throws IOException, ArchiveException, DocumentException { + List keys = new ArrayList<>(); + keys.add(xmindLegacyContent); + keys.add(xmindLegacyComments); + Map map = ZipUtils.getContents(keys, file, extractFileDir); - return xmlContent; - } + String contentXml = map.get(xmindLegacyContent); + String commentsXml = map.get(xmindLegacyComments); + String xmlContent = XmindLegacy.getContent(contentXml, commentsXml); - private static boolean isXmindZen(String res, File file) throws IOException, ArchiveException { - // 解压 - File parent = new File(res); - if (parent.isDirectory()) { - String[] files = parent.list(new ZipUtils.FileFilter()); - for (int i = 0; i < Objects.requireNonNull(files).length; i++) { - if (files[i].equals(xmindZenJson)) { - return true; - } - } - } - return false; - } + return xmlContent; + } + + private static boolean isXmindZen(String res, File file) throws IOException, ArchiveException { + // 解压 + File parent = new File(res); + if (parent.isDirectory()) { + String[] files = parent.list(new ZipUtils.FileFilter()); + for (int i = 0; i < Objects.requireNonNull(files).length; i++) { + if (files[i].equals(xmindZenJson)) { + return true; + } + } + } + return false; + } } diff --git a/backend/src/main/java/io/metersphere/xmind/parser/domain/Attached.java b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Attached.java similarity index 84% rename from backend/src/main/java/io/metersphere/xmind/parser/domain/Attached.java rename to backend/src/main/java/io/metersphere/xmind/parser/pojo/Attached.java index df7932a26d..5781da5500 100755 --- a/backend/src/main/java/io/metersphere/xmind/parser/domain/Attached.java +++ b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Attached.java @@ -1,5 +1,5 @@ -package io.metersphere.xmind.parser.domain; +package io.metersphere.xmind.parser.pojo; import lombok.Data; diff --git a/backend/src/main/java/io/metersphere/xmind/parser/domain/Children.java b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Children.java similarity index 71% rename from backend/src/main/java/io/metersphere/xmind/parser/domain/Children.java rename to backend/src/main/java/io/metersphere/xmind/parser/pojo/Children.java index 761a5fd9ce..c24b4cdd3d 100755 --- a/backend/src/main/java/io/metersphere/xmind/parser/domain/Children.java +++ b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Children.java @@ -1,4 +1,4 @@ -package io.metersphere.xmind.parser.domain; +package io.metersphere.xmind.parser.pojo; import lombok.Data; diff --git a/backend/src/main/java/io/metersphere/xmind/parser/domain/Comments.java b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Comments.java similarity index 74% rename from backend/src/main/java/io/metersphere/xmind/parser/domain/Comments.java rename to backend/src/main/java/io/metersphere/xmind/parser/pojo/Comments.java index e96dda1065..a5204e1f97 100755 --- a/backend/src/main/java/io/metersphere/xmind/parser/domain/Comments.java +++ b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Comments.java @@ -1,4 +1,4 @@ -package io.metersphere.xmind.parser.domain; +package io.metersphere.xmind.parser.pojo; import lombok.Data; diff --git a/backend/src/main/java/io/metersphere/xmind/parser/domain/JsonRootBean.java b/backend/src/main/java/io/metersphere/xmind/parser/pojo/JsonRootBean.java similarity index 74% rename from backend/src/main/java/io/metersphere/xmind/parser/domain/JsonRootBean.java rename to backend/src/main/java/io/metersphere/xmind/parser/pojo/JsonRootBean.java index f69dc6d9d4..93d3bf0008 100755 --- a/backend/src/main/java/io/metersphere/xmind/parser/domain/JsonRootBean.java +++ b/backend/src/main/java/io/metersphere/xmind/parser/pojo/JsonRootBean.java @@ -1,4 +1,4 @@ -package io.metersphere.xmind.parser.domain; +package io.metersphere.xmind.parser.pojo; import lombok.Data; diff --git a/backend/src/main/java/io/metersphere/xmind/parser/domain/Notes.java b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Notes.java similarity index 62% rename from backend/src/main/java/io/metersphere/xmind/parser/domain/Notes.java rename to backend/src/main/java/io/metersphere/xmind/parser/pojo/Notes.java index 882ee5682d..d3bfe93e24 100755 --- a/backend/src/main/java/io/metersphere/xmind/parser/domain/Notes.java +++ b/backend/src/main/java/io/metersphere/xmind/parser/pojo/Notes.java @@ -1,4 +1,4 @@ -package io.metersphere.xmind.parser.domain; +package io.metersphere.xmind.parser.pojo; import lombok.Data; diff --git a/backend/src/main/java/io/metersphere/xmind/parser/domain/RootTopic.java b/backend/src/main/java/io/metersphere/xmind/parser/pojo/RootTopic.java similarity index 82% rename from backend/src/main/java/io/metersphere/xmind/parser/domain/RootTopic.java rename to backend/src/main/java/io/metersphere/xmind/parser/pojo/RootTopic.java index 74deb34695..eebaa0b43b 100755 --- a/backend/src/main/java/io/metersphere/xmind/parser/domain/RootTopic.java +++ b/backend/src/main/java/io/metersphere/xmind/parser/pojo/RootTopic.java @@ -1,4 +1,4 @@ -package io.metersphere.xmind.parser.domain; +package io.metersphere.xmind.parser.pojo; import lombok.Data; diff --git a/backend/src/main/java/io/metersphere/xmind/template/testcase.xml b/backend/src/main/java/io/metersphere/xmind/template/xmind.xml similarity index 100% rename from backend/src/main/java/io/metersphere/xmind/template/testcase.xml rename to backend/src/main/java/io/metersphere/xmind/template/xmind.xml diff --git a/backend/src/main/java/io/metersphere/xmind/utils/FileUtil.java b/backend/src/main/java/io/metersphere/xmind/utils/FileUtil.java new file mode 100644 index 0000000000..60ad9cf53f --- /dev/null +++ b/backend/src/main/java/io/metersphere/xmind/utils/FileUtil.java @@ -0,0 +1,61 @@ +package io.metersphere.xmind.utils; + +import io.metersphere.commons.utils.LogUtil; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +public class FileUtil { + + //获取流文件 + private static void inputStreamToFile(InputStream ins, File file) { + try (OutputStream os = new FileOutputStream(file);) { + int bytesRead = 0; + byte[] buffer = new byte[8192]; + while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) { + os.write(buffer, 0, bytesRead); + } + } catch (Exception e) { + LogUtil.error(e.getMessage()); + } + } + + /** + * MultipartFile 转 File + * + * @param file + * @throws Exception + */ + public static File multipartFileToFile(MultipartFile file) { + if (file != null && file.getSize() > 0) { + try (InputStream ins = file.getInputStream();) { + File toFile = new File(file.getOriginalFilename()); + inputStreamToFile(ins, toFile); + return toFile; + } catch (Exception e) { + LogUtil.error(e.getMessage()); + } + } + return null; + } + + public static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + String[] children = dir.list(); + // 递归删除目录中的子目录下 + for (int i = 0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + return false; + } + } + } + // 目录此时为空,可以删除 + return dir.delete(); + } + + +} diff --git a/frontend/package.json b/frontend/package.json index cf5672e833..fa0ad64992 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,9 @@ "md5": "^2.3.0", "sha.js": "^2.4.11", "js-base64": "^3.4.4", - "json-bigint": "^1.0.0" + "json-bigint": "^1.0.0", + "html2canvas": "^1.0.0-rc.7", + "jspdf": "^2.1.1" }, "devDependencies": { "@vue/cli-plugin-babel": "^4.1.0", diff --git a/frontend/src/business/App.vue b/frontend/src/business/App.vue index 1fb604f01a..266b73605e 100644 --- a/frontend/src/business/App.vue +++ b/frontend/src/business/App.vue @@ -2,7 +2,7 @@ -
License has expired since +
License has expired since {{(validData!= undefined && validData.license!= undefined) ? validData.license.expired:''}},please update license.
@@ -40,15 +40,7 @@ export default { name: 'app', data() { - let xpack = false; - Setting.children.forEach(child => { - if (child.path === "license") { - xpack = true; - return; - } - }) return { - valid: xpack, validData: {}, auth: false } @@ -67,13 +59,14 @@ }); }, beforeMount() { - if (this.valid === true) { - // 验证license - this.result = this.$get("/license/valid", response => { + // 验证license + this.result = this.$get("/api/license/valid", response => { + let data = response.data; + if (data != undefined && data != null) { this.validData = response.data; saveLicense(response.data); - }); - } + } + }); }, components: {MsLanguageSwitch, MsUser, MsView, MsTopMenus, MsHeaderOrgWs}, methods: {} diff --git a/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue b/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue index 86bd3aa750..bd70cc938e 100644 --- a/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue +++ b/frontend/src/business/components/api/test/components/ApiEnvironmentConfig.vue @@ -20,7 +20,7 @@ import MsMainContainer from "../../../common/components/MsMainContainer"; import MsAsideItem from "../../../common/components/MsAsideItem"; import EnvironmentEdit from "./environment/EnvironmentEdit"; - import {listenGoBack, removeGoBackListener} from "../../../../../common/js/utils"; + import {deepClone, listenGoBack, removeGoBackListener} from "../../../../../common/js/utils"; import {Environment, parseEnvironment} from "../model/EnvironmentModel"; export default { @@ -68,12 +68,13 @@ } }, copyEnvironment(environment) { + this.currentEnvironment = environment; if (!environment.id) { this.$warning(this.$t('commons.please_save')); return; } let newEnvironment = {}; - Object.assign(newEnvironment, environment); + newEnvironment = new Environment(environment); newEnvironment.id = null; newEnvironment.name = this.getNoRepeatName(newEnvironment.name); if (!this.validateEnvironment(newEnvironment)) { @@ -84,11 +85,7 @@ this.$refs.environmentItems.itemSelected(this.environments.length - 1, newEnvironment); }, validateEnvironment(environment) { - if (!environment.name || !!environment.name && environment.name.length > 64) { - this.$error(this.$t('commons.input_limit', [1, 64])); - return false; - } - if (!this.$refs.environmentEdit.validateSocket(environment.socket)) { + if (!this.$refs.environmentEdit.validate()) { this.$error(this.$t('commons.formatErr')); return false; } diff --git a/frontend/src/business/components/api/test/components/environment/EnvironmentEdit.vue b/frontend/src/business/components/api/test/components/environment/EnvironmentEdit.vue index 6f8ac770a0..ccf5c9ed79 100644 --- a/frontend/src/business/components/api/test/components/environment/EnvironmentEdit.vue +++ b/frontend/src/business/components/api/test/components/environment/EnvironmentEdit.vue @@ -79,6 +79,17 @@ } }); }, + validate() { + let isValidate = false; + this.$refs['environment'].validate((valid) => { + if (valid && this.$refs.commonConfig.validate() && this.$refs.httpConfig.validate()) { + isValidate = true; + } else { + isValidate = false; + } + }); + return isValidate; + }, _save(environment) { let param = this.buildParam(environment); let url = '/api/environment/add'; diff --git a/frontend/src/business/components/api/test/model/EnvironmentModel.js b/frontend/src/business/components/api/test/model/EnvironmentModel.js index aa4207d246..0f26c89ba5 100644 --- a/frontend/src/business/components/api/test/model/EnvironmentModel.js +++ b/frontend/src/business/components/api/test/model/EnvironmentModel.js @@ -8,13 +8,14 @@ export class Environment extends BaseConfig { this.projectId = undefined; this.name = undefined; this.id = undefined; - this.config = options.config || new Config(); + this.config = undefined; this.set(options); this.sets({}, options); } initOptions(options = {}) { + this.config = new Config(options.config); return options; } } @@ -22,14 +23,16 @@ export class Environment extends BaseConfig { export class Config extends BaseConfig { constructor(options = {}) { super(); - this.commonConfig = options.commonConfig || new CommonConfig(); - this.httpConfig = options.httpConfig || new HttpConfig(); + this.commonConfig = undefined; + this.httpConfig = undefined; this.databaseConfigs = []; this.set(options); this.sets({databaseConfigs: DatabaseConfig}, options); } initOptions(options = {}) { + this.commonConfig = new CommonConfig(options.commonConfig); + this.httpConfig = new HttpConfig(options.httpConfig); options.databaseConfigs = options.databaseConfigs || []; return options; } diff --git a/frontend/src/business/components/common/components/MsScheduleEdit.vue b/frontend/src/business/components/common/components/MsScheduleEdit.vue index cb6d6bd411..50e866fb49 100644 --- a/frontend/src/business/components/common/components/MsScheduleEdit.vue +++ b/frontend/src/business/components/common/components/MsScheduleEdit.vue @@ -187,6 +187,7 @@ this.form.cronValue = this.schedule.value; listenGoBack(this.close); this.handleClick() + this.activeName = 'first' }, crontabFill(value, resultList) { //确定后回传的值 diff --git a/frontend/src/business/components/track/head/TrackHeaderMenus.vue b/frontend/src/business/components/track/head/TrackHeaderMenus.vue index a49833f7d2..341bb490bd 100644 --- a/frontend/src/business/components/track/head/TrackHeaderMenus.vue +++ b/frontend/src/business/components/track/head/TrackHeaderMenus.vue @@ -32,12 +32,12 @@ - + - + @@ -94,7 +94,7 @@ export default { } }, reviewRecent: { - title: "最近的评审", + title: this.$t('test_track.recent_review'), url: "/test/case/review/recent/5", index: function (item) { return '/track/review/view/' + item.id; diff --git a/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/DefectListComponent.vue b/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/DefectListComponent.vue index 3a141ed057..9e19cb6c0c 100644 --- a/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/DefectListComponent.vue +++ b/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/DefectListComponent.vue @@ -48,7 +48,7 @@ show-overflow-tooltip> diff --git a/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue b/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue index d2b48f293a..47da205f32 100644 --- a/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue +++ b/frontend/src/business/components/track/plan/view/comonents/report/TemplateComponent/TemplateComponent.vue @@ -13,12 +13,12 @@
- - - - - - + + + + + +
@@ -31,6 +31,8 @@ import RichTextComponent from "./RichTextComponent"; import FailureResultComponent from "./FailureResultComponent"; import DefectListComponent from "./DefectListComponent"; + import html2canvas from 'html2canvas'; + export default { name: "TemplateComponent", components: { @@ -51,6 +53,41 @@ type: Boolean, default: true }, + index: { + type: Number, + default: 0 + }, + }, + methods: { + getCanvas(canvasList) { + let index = this.index; + let componentId = this.getComponentId(); + return new Promise(function(resolve, reject) { + html2canvas(document.getElementById(componentId), { + scale: 2 + }).then(function(canvas) { + //排序 + canvasList.splice(index, 0, canvas); + resolve('success'); + }); + }); + }, + getComponentId() { + switch (this.preview.id) { + case 1: + return "baseInfoComponent"; + case 2: + return "testResultComponent"; + case 3: + return "resultChartComponent"; + case 4: + return "failureResultComponent"; + case 5: + return "defectListComponent"; + default: + return "richTextComponent"; + } + }, } } diff --git a/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue b/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue index 7277e73c68..ce08b348c4 100644 --- a/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue +++ b/frontend/src/business/components/track/plan/view/comonents/report/TestCaseReportView.vue @@ -25,38 +25,33 @@ {{$t('test_track.plan_view.edit_component')}} - +
-
- +
+
-
diff --git a/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue b/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue index 3ea4a3dfbd..9c411e16e0 100644 --- a/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue +++ b/frontend/src/business/components/track/review/components/TestCaseReviewEdit.vue @@ -3,7 +3,7 @@
@@ -23,11 +23,10 @@ - + - + - + @@ -86,7 +84,7 @@ - + @@ -132,6 +130,7 @@ export default { description: '', endTime: '' }, + dbProjectIds: [], rules: { name: [ {required: true, message: this.$t('test_track.plan.input_plan_name'), trigger: 'blur'}, @@ -161,6 +160,7 @@ export default { let tmp = {}; Object.assign(tmp, caseReview); Object.assign(this.form, tmp); + this.dbProjectIds = JSON.parse(JSON.stringify(this.form.projectIds)); } listenGoBack(this.close); this.dialogFormVisible = true; @@ -178,16 +178,44 @@ export default { if (this.operationType === 'save') { this.compareTime(new Date().getTime(), this.form.endTime); } - this.$post('/test/case/review/' + this.operationType, param, () => { - this.$success(this.$t('commons.save_success')); - this.dialogFormVisible = false; - this.$emit("refresh"); - }); + + if (this.operationType === 'edit') { + const nowIds = param.projectIds; + let sign = true; + this.dbProjectIds.forEach(dbId => { + if (nowIds.indexOf(dbId) === -1 && sign) { + sign = false; + this.$confirm('取消项目关联会同时取消该项目下已关联的测试用例', '提示', { + confirmButtonText: this.$t('commons.confirm'), + cancelButtonText: this.$t('commons.cancel'), + type: 'warning' + }).then(() => { + this.editTestReview(param); + }).catch(() => { + this.$info(this.$t('commons.cancel')) + }); + } + }); + if (sign) { + this.editTestReview(param); + } + } else { + this.editTestReview(param); + } + + } else { return false; } }); }, + editTestReview(param) { + this.$post('/test/case/review/' + this.operationType, param, () => { + this.$success(this.$t('commons.save_success')); + this.dialogFormVisible = false; + this.$emit("refresh"); + }); + }, getProjects() { this.result = this.$get("/project/listAll", (response) => { if (response.success) { @@ -204,7 +232,8 @@ export default { }); }, statusChange(status) { - + this.form.status = status; + this.$forceUpdate(); }, close() { removeGoBackListener(this.close); diff --git a/frontend/src/business/components/track/review/components/TestCaseReviewList.vue b/frontend/src/business/components/track/review/components/TestCaseReviewList.vue index b708fa6886..87651c0f24 100644 --- a/frontend/src/business/components/track/review/components/TestCaseReviewList.vue +++ b/frontend/src/business/components/track/review/components/TestCaseReviewList.vue @@ -3,8 +3,8 @@ - - - - - - - - - - @@ -217,9 +189,6 @@ export default { _sort(column, this.condition); this.initTableData(); }, - reCreate() { - - } } } diff --git a/frontend/src/business/components/track/review/view/TestCaseReviewView.vue b/frontend/src/business/components/track/review/view/TestCaseReviewView.vue index 47534baf22..3bca0da02c 100644 --- a/frontend/src/business/components/track/review/view/TestCaseReviewView.vue +++ b/frontend/src/business/components/track/review/view/TestCaseReviewView.vue @@ -5,7 +5,7 @@ - @@ -91,44 +91,13 @@ - - - - - @@ -137,22 +106,10 @@ prop="status" :filters="statusFilters" column-key="status" - :label="$t('test_track.plan_view.execute_result')"> + :label="$t('test_track.review_view.execute_result')"> @@ -320,18 +277,6 @@ export default { let data = response.data; this.total = data.itemCount; this.tableData = data.listObject; - for (let i = 0; i < this.tableData.length; i++) { - if (this.tableData[i]) { - this.$set(this.tableData[i], "issuesSize", 0); - this.$get("/issues/get/" + this.tableData[i].caseId, response => { - let issues = response.data; - if (this.tableData[i]) { - this.$set(this.tableData[i], "issuesSize", issues.length); - this.$set(this.tableData[i], "issuesContent", issues); - } - }) - } - } this.selectRows.clear(); }); } @@ -403,41 +348,10 @@ export default { }); }, handleSelectAll(selection) { - if (selection.length > 0) { - if (selection.length === 1) { - this.selectRows.add(selection[0]); - } else { - this.tableData.forEach(item => { - this.$set(item, "showMore", true); - this.selectRows.add(item); - }); - } - } else { - this.selectRows.clear(); - this.tableData.forEach(row => { - this.$set(row, "showMore", false); - }) - } + }, handleSelectionChange(selection, row) { - if (this.selectRows.has(row)) { - this.$set(row, "showMore", false); - this.selectRows.delete(row); - } else { - this.$set(row, "showMore", true); - this.selectRows.add(row); - } - let arr = Array.from(this.selectRows); - - // 选中1个以上的用例时显示更多操作 - if (this.selectRows.size === 1) { - this.$set(arr[0], "showMore", false); - } else if (this.selectRows.size === 2) { - arr.forEach(row => { - this.$set(row, "showMore", true); - }) - } }, handleBatch(type) { if (this.selectRows.size < 1) { @@ -502,6 +416,7 @@ export default { }, startReview() { if (this.tableData.length !== 0) { + this.isReadOnly = false; this.$refs.testReviewTestCaseEdit.openTestCaseEdit(this.tableData[0]); } else { this.$warning("没有关联的评审!"); diff --git a/frontend/src/common/js/utils.js b/frontend/src/common/js/utils.js index 10a28eb25b..6a344e4a0c 100644 --- a/frontend/src/common/js/utils.js +++ b/frontend/src/common/js/utils.js @@ -9,6 +9,7 @@ import { LicenseKey } from "./constants"; import axios from "axios"; +import {jsPDF} from "jspdf"; export function hasRole(role) { let user = getCurrentUser(); @@ -203,3 +204,57 @@ export function getUUID() { } +export function exportPdf(canvasList) { + + let pdf = new jsPDF('', 'pt', 'a4'); + + // 当前页面的当前高度 + let currentHeight = 0; + for (let canvas of canvasList) { + if (canvas) { + let contentWidth = canvas.width; + let contentHeight = canvas.height; + + //a4纸的尺寸[595.28,841.89] + let a4Width = 592.28; + let a4Height = 841.89; + + // html页面生成的canvas在pdf中图片的宽高 + let imgWidth = a4Width; + let imgHeight = a4Width/contentWidth * contentHeight; + + let pageData = canvas.toDataURL('image/jpeg', 1.0); + + // 当前图片的剩余高度 + let leftHeight = imgHeight; + + // 当前页面的剩余高度 + let blankHeight = a4Height - currentHeight; + + if (leftHeight > blankHeight) { + //页面偏移 + let position = 0; + while(leftHeight > 0) { + // 本次添加占用的高度 + let occupation = a4Height - currentHeight; + pdf.addImage(pageData, 'JPEG', 0, position + currentHeight, imgWidth, imgHeight); + currentHeight = leftHeight; + leftHeight -= occupation; + position -= occupation; + //避免添加空白页 + if(leftHeight > 0) { + pdf.addPage(); + currentHeight = 0; + } + } + } else { + pdf.addImage(pageData, 'JPEG', 0, currentHeight, imgWidth, imgHeight); + currentHeight += imgHeight; + } + } + } + + pdf.save('stone.pdf'); + +} + diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 12766b7e45..ae4389e145 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -617,6 +617,7 @@ export default { length_less_than: "The length less than", recent_plan: "Recent plan", recent_case: "Recent case", + recent_review: "Recent review", pass_rate: "Pass rate", execution_result: ": Please select the execution result", actual_result: ": The actual result is empty", @@ -722,6 +723,28 @@ export default { plan_delete_confirm: "All use cases under this plan will be deleted,confirm delete test plan: ", plan_delete: "Delete test plan", }, + review: { + test_review: "Test Review", + create_review: "Create Review", + edit_review: "Edit Review", + review_name: "Name", + reviewer: "Reviewer", + review_project: "Project", + review_creator: "Creator", + review_status: "Status", + end_time: "EndTime", + delete: "Delete", + input_review_name: "Please enter the name of the review", + input_review_project: "Please select the project", + input_reviewer: "Please select reviewer", + }, + review_view: { + review: "Review", + all_review: "All Review", + start_review: "Start Review", + relevance_case: "Relevance Case", + execute_result: "Result", + }, module: { search: "Search module", rename: "Rename", @@ -936,7 +959,7 @@ export default { performance: "Number of performance tests", resource_pool: "Available test resource pool", max_threads: "Maximum Concurrency", - duration: "Stress test duration", + duration: "Stress test duration(minutes)", use_default: "Use default quota", yes: "Yes", no: "No", diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index e0d3cf4bc4..408b29a6d0 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -619,6 +619,7 @@ export default { length_less_than: "长度必须小于", recent_plan: "最近的计划", recent_case: "最近的用例", + recent_review: "最近的评审", pass_rate: "通过率", execution_result: ": 请选择执行结果", actual_result: ": 实际结果为空", @@ -725,6 +726,28 @@ export default { plan_delete_confirm: "将删除该测试计划下所有用例,确认删除测试计划: ", plan_delete: "删除计划", }, + review: { + test_review: "用例评审", + create_review: "创建用例评审", + edit_review: "编辑用例评审", + review_name: "评审名称", + reviewer: "评审人", + review_project: "所属项目", + review_creator: "发起人", + review_status: "当前状态", + end_time: "截止时间", + delete: "删除评审", + input_review_name: "请输入评审名称", + input_review_project: "请选择所属项目", + input_reviewer: "请选择评审人", + }, + review_view: { + review: "评审", + all_review: "全部评审", + start_review: "开始评审", + relevance_case: "关联用例", + execute_result: "执行结果", + }, module: { search: "搜索模块", rename: "重命名", @@ -938,7 +961,7 @@ export default { performance: "性能测试数量", resource_pool: "可用测试资源池", max_threads: "最大并发数", - duration: "压测时长", + duration: "压测时长(分钟)", use_default: "使用默认配额", yes: "是", no: "否", diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 495b9cb10b..4515205925 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -619,6 +619,7 @@ export default { length_less_than: "長度必須小於", recent_plan: "最近的計劃", recent_case: "最近的用例", + recent_review: "最近的評審", pass_rate: "通過率", execution_result: ": 請選擇執行結果", actual_result: ": 實際結果為空", @@ -725,6 +726,28 @@ export default { plan_delete_confirm: "將刪除該測試計劃下所有用例,確認刪除測試計劃: ", plan_delete: "刪除計劃", }, + review: { + test_review: "用例評審", + create_review: "創建用例評審", + edit_review: "編輯用例評審", + review_name: "評審名稱", + reviewer: "評審人", + review_project: "所屬項目", + review_creator: "發起人", + review_status: "當前狀態", + end_time: "截止時間", + delete: "刪除評審", + input_review_name: "請輸入評審名稱", + input_review_project: "請選擇所屬項目", + input_reviewer: "請選擇評審人", + }, + review_view: { + review: "評審", + all_review: "全部評審", + start_review: "開始評審", + relevance_case: "關聯用例", + execute_result: "執行結果", + }, module: { search: "搜索模塊", rename: "重命名", @@ -938,7 +961,7 @@ export default { performance: "性能測試數量", resource_pool: "可用測試資源池", max_threads: "最大並發數", - duration: "壓測時長", + duration: "壓測時長(分鐘)", use_default: "使用默認配額", yes: "是", no: "否",