diff --git a/backend/pom.xml b/backend/pom.xml index c85ffa4cc2..7b96a55141 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -351,7 +351,17 @@ json 20171018 - + + + com.aliyun + alibaba-dingtalk-service-sdk + 1.0.1 + + + org.apache.httpcomponents + httpclient + 4.5.6 + diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 67173ef791..207ecf1f9c 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -5,11 +5,13 @@ import io.metersphere.api.service.APITestService; import io.metersphere.base.domain.ApiTestReport; import io.metersphere.commons.constants.APITestStatus; import io.metersphere.commons.constants.ApiRunMode; +import io.metersphere.commons.constants.TestPlanTestCaseStatus; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.LogUtil; import io.metersphere.notice.domain.NoticeDetail; import io.metersphere.notice.service.MailService; import io.metersphere.notice.service.NoticeService; +import io.metersphere.track.service.TestPlanTestCaseService; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleResult; @@ -117,6 +119,21 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl apiReportService.complete(testResult, report); queue.clear(); super.teardownTest(context); + + TestPlanTestCaseService testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class); + List ids = testPlanTestCaseService.getTestPlanTestCaseIds(testResult.getTestId()); + if (ids.size() > 0) { + try { + if (StringUtils.equals(APITestStatus.Success.name(), report.getStatus())) { + testPlanTestCaseService.updateTestCaseStates(ids, TestPlanTestCaseStatus.Pass.name()); + } else { + testPlanTestCaseService.updateTestCaseStates(ids, TestPlanTestCaseStatus.Failure.name()); + } + } catch (Exception e) { + LogUtil.error(e); + } + + } NoticeService noticeService = CommonBeanFactory.getBean(NoticeService.class); try { List noticeList = noticeService.queryNotice(testResult.getTestId()); diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java index 0cfb60a557..a42648ab81 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java @@ -26,4 +26,9 @@ public interface ExtTestPlanTestCaseMapper { List getPendingTestCases(@Param("request") QueryTestPlanCaseRequest request); List getStatusByPlanId(String planId); + + int updateTestCaseStates(@Param("ids") List ids, @Param("reportStatus") String reportStatus); + + List getTestPlanTestCaseIds(String testId); + } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml index 577f53ed1f..1c7b037ade 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml @@ -279,4 +279,24 @@ from test_plan_test_case where plan_id = #{planId} + + + update test_plan_test_case + + + status=#{reportStatus,jdbcType=VARCHAR} + + + where id in + + #{id} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/notice/controller/NoticeController.java b/backend/src/main/java/io/metersphere/notice/controller/NoticeController.java index f0b816d633..7a7a033fc2 100644 --- a/backend/src/main/java/io/metersphere/notice/controller/NoticeController.java +++ b/backend/src/main/java/io/metersphere/notice/controller/NoticeController.java @@ -24,4 +24,9 @@ public class NoticeController { return noticeService.queryNotice(testId); } + @PostMapping("save/message") + public void saveMessage() { + + } } + diff --git a/backend/src/main/java/io/metersphere/notice/domain/MessageDetail.java b/backend/src/main/java/io/metersphere/notice/domain/MessageDetail.java new file mode 100644 index 0000000000..6db39677e9 --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/domain/MessageDetail.java @@ -0,0 +1,4 @@ +package io.metersphere.notice.domain; + +public class MessageDetail { +} diff --git a/backend/src/main/java/io/metersphere/notice/message/LinkMessage.java b/backend/src/main/java/io/metersphere/notice/message/LinkMessage.java new file mode 100644 index 0000000000..7faf699dee --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/message/LinkMessage.java @@ -0,0 +1,46 @@ +package io.metersphere.notice.message; + +import com.alibaba.fastjson.JSON; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class LinkMessage implements Message { + + private String title; + private String text; + private String picUrl; + private String messageUrl; + + public String toJsonString() { + Map items = new HashMap(); + items.put("msgtype", "link"); + + Map linkContent = new HashMap(); + if (StringUtils.isBlank(title)) { + throw new IllegalArgumentException("title should not be blank"); + } + linkContent.put("title", title); + + if (StringUtils.isBlank(messageUrl)) { + throw new IllegalArgumentException("messageUrl should not be blank"); + } + linkContent.put("messageUrl", messageUrl); + + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("text should not be blank"); + } + linkContent.put("text", text); + + if (StringUtils.isNotBlank(picUrl)) { + linkContent.put("picUrl", picUrl); + } + + items.put("link", linkContent); + + return JSON.toJSONString(items); + } +} diff --git a/backend/src/main/java/io/metersphere/notice/message/Message.java b/backend/src/main/java/io/metersphere/notice/message/Message.java new file mode 100644 index 0000000000..c8bc65e0a2 --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/message/Message.java @@ -0,0 +1,5 @@ +package io.metersphere.notice.message; + +public interface Message { + String toJsonString(); +} diff --git a/backend/src/main/java/io/metersphere/notice/message/TextMessage.java b/backend/src/main/java/io/metersphere/notice/message/TextMessage.java new file mode 100644 index 0000000000..d112a25323 --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/message/TextMessage.java @@ -0,0 +1,65 @@ +package io.metersphere.notice.message; + +import com.alibaba.fastjson.JSON; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +public class TextMessage implements Message { + private String text; + private List mentionedMobileList; + private boolean isAtAll; + + public TextMessage(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public boolean isAtAll() { + return isAtAll; + } + + public void setIsAtAll(boolean isAtAll) { + this.isAtAll = isAtAll; + } + + public List getMentionedMobileList() { + return mentionedMobileList; + } + + public void setMentionedMobileList(List mentionedMobileList) { + this.mentionedMobileList = mentionedMobileList; + } + + public String toJsonString() { + Map items = new HashMap(); + items.put("msgtype", "text"); + + Map textContent = new HashMap(); + if (StringUtils.isBlank(text)) { + throw new IllegalArgumentException("text should not be blank"); + } + textContent.put("content", text); + if (isAtAll) { + if (mentionedMobileList == null) mentionedMobileList = new ArrayList(); + mentionedMobileList.add("@all"); + } + if (mentionedMobileList != null && !mentionedMobileList.isEmpty()) { + textContent.put("mentioned_mobile_list", mentionedMobileList); + } + items.put("text", textContent); + return JSON.toJSONString(items); + } +} diff --git a/backend/src/main/java/io/metersphere/notice/service/DingTaskService.java b/backend/src/main/java/io/metersphere/notice/service/DingTaskService.java new file mode 100644 index 0000000000..17ebf292f8 --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/service/DingTaskService.java @@ -0,0 +1,9 @@ +package io.metersphere.notice.service; + +import org.springframework.stereotype.Service; + +@Service +public class DingTaskService { + + +} 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 d1137264af..2438349fcd 100644 --- a/backend/src/main/java/io/metersphere/notice/service/MailService.java +++ b/backend/src/main/java/io/metersphere/notice/service/MailService.java @@ -95,6 +95,7 @@ public class MailService { try { javaMailSender.send(mimeMessage); } catch (MailException e) { + LogUtil.error(e); LogUtil.error("Failed to send mail"); } } diff --git a/backend/src/main/java/io/metersphere/notice/service/WxChatTaskService.java b/backend/src/main/java/io/metersphere/notice/service/WxChatTaskService.java new file mode 100644 index 0000000000..346a57398e --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/service/WxChatTaskService.java @@ -0,0 +1,7 @@ +package io.metersphere.notice.service; + +import org.springframework.stereotype.Service; + +@Service +public class WxChatTaskService { +} diff --git a/backend/src/main/java/io/metersphere/notice/util/SendResult.java b/backend/src/main/java/io/metersphere/notice/util/SendResult.java new file mode 100644 index 0000000000..7b3b861d49 --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/util/SendResult.java @@ -0,0 +1,47 @@ +package io.metersphere.notice.util; + +import com.alibaba.fastjson.JSON; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class SendResult { + private boolean isSuccess; + private Integer errorCode; + private String errorMsg; + + public boolean isSuccess() { + return isSuccess; + } + + public void setIsSuccess(boolean isSuccess) { + this.isSuccess = isSuccess; + } + + public Integer getErrorCode() { + return errorCode; + } + + public void setErrorCode(Integer errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + public String toString() { + Map items = new HashMap(); + items.put("errorCode", errorCode); + items.put("errorMsg", errorMsg); + items.put("isSuccess", isSuccess); + return JSON.toJSONString(items); + } +} diff --git a/backend/src/main/java/io/metersphere/notice/util/WxChatbotClient.java b/backend/src/main/java/io/metersphere/notice/util/WxChatbotClient.java new file mode 100644 index 0000000000..7125adadfe --- /dev/null +++ b/backend/src/main/java/io/metersphere/notice/util/WxChatbotClient.java @@ -0,0 +1,50 @@ +package io.metersphere.notice.util; + +import com.alibaba.fastjson.JSONObject; +import io.metersphere.notice.message.Message; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; + +/** + * + */ +public class WxChatbotClient { + + static HttpClient httpclient = HttpClients.createDefault(); + + public static SendResult send(String webhook, Message message) throws IOException { + + if (StringUtils.isBlank(webhook)) { + return new SendResult(); + } + HttpPost httppost = new HttpPost(webhook); + httppost.addHeader("Content-Type", "application/json; charset=utf-8"); + StringEntity se = new StringEntity(message.toJsonString(), "utf-8"); + httppost.setEntity(se); + + SendResult sendResult = new SendResult(); + HttpResponse response = httpclient.execute(httppost); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + String result = EntityUtils.toString(response.getEntity()); + JSONObject obj = JSONObject.parseObject(result); + + Integer errcode = obj.getInteger("errcode"); + sendResult.setErrorCode(errcode); + sendResult.setErrorMsg(obj.getString("errmsg")); + sendResult.setIsSuccess(errcode.equals(0)); + } + + return sendResult; + } + +} + + 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 4717468b20..214db09993 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseReviewService.java @@ -6,12 +6,10 @@ import io.metersphere.base.mapper.ext.ExtProjectMapper; import io.metersphere.base.mapper.ext.ExtTestCaseReviewMapper; import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper; import io.metersphere.commons.constants.TestCaseReviewStatus; -import io.metersphere.commons.constants.TestPlanTestCaseStatus; import io.metersphere.commons.constants.TestReviewCaseStatus; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.utils.LogUtil; -import io.metersphere.commons.utils.MathUtils; import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.controller.request.member.QueryMemberRequest; @@ -32,6 +30,7 @@ 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; @@ -94,6 +93,7 @@ public class TestCaseReviewService { reviewRequest.setCreator(SessionUtils.getUser().getId()); reviewRequest.setStatus(TestCaseReviewStatus.Prepare.name()); testCaseReviewMapper.insert(reviewRequest); + try { mailService.sendReviewerNotice(userIds, reviewRequest); } catch (Exception e) { diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java index 272c8f8c4c..04abe860b0 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java @@ -140,4 +140,12 @@ public class TestPlanTestCaseService { example.createCriteria().andIdIn(request.getIds()); testPlanTestCaseMapper.deleteByExample(example); } + + public List getTestPlanTestCaseIds(String testId) { + return extTestPlanTestCaseMapper.getTestPlanTestCaseIds(testId); + } + + public int updateTestCaseStates(List ids, String reportStatus) { + return extTestPlanTestCaseMapper.updateTestCaseStates(ids, reportStatus); + } } diff --git a/backend/src/main/resources/db/migration/V30__test_case_prerequisite.sql b/backend/src/main/resources/db/migration/V30__test_case_prerequisite.sql new file mode 100644 index 0000000000..8eced76731 --- /dev/null +++ b/backend/src/main/resources/db/migration/V30__test_case_prerequisite.sql @@ -0,0 +1 @@ +alter table test_case modify prerequisite varchar(500) null comment 'Test case prerequisite condition'; \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V31__message_task.sql b/backend/src/main/resources/db/migration/V31__message_task.sql new file mode 100644 index 0000000000..bc4e7a3bad --- /dev/null +++ b/backend/src/main/resources/db/migration/V31__message_task.sql @@ -0,0 +1,12 @@ +create table message_task +( + id varchar(255) not null, + type varchar(255) not null comment '消息类型', + event varchar(255) not null comment '通知事件类型', + userId varchar(500) not null comment '接收人id', + userName varchar(500) not null comment '接收人姓名', + taskType varchar(255) not null, + webhook varchar(255) not null comment 'webhook地址', + constraint message_manage_pk + primary key (id) +)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/frontend/src/business/components/settings/organization/IssuesManagement.vue b/frontend/src/business/components/settings/organization/IssuesManagement.vue index 80c08decba..6506b90f06 100644 --- a/frontend/src/business/components/settings/organization/IssuesManagement.vue +++ b/frontend/src/business/components/settings/organization/IssuesManagement.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/business/components/settings/organization/MessageSettings.vue b/frontend/src/business/components/settings/organization/MessageSettings.vue new file mode 100644 index 0000000000..550887ed0c --- /dev/null +++ b/frontend/src/business/components/settings/organization/MessageSettings.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/frontend/src/business/components/settings/organization/TaskNotification.vue b/frontend/src/business/components/settings/organization/TaskNotification.vue new file mode 100644 index 0000000000..23f0754298 --- /dev/null +++ b/frontend/src/business/components/settings/organization/TaskNotification.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/frontend/src/business/components/settings/router.js b/frontend/src/business/components/settings/router.js index 08c5d7842f..93da75f536 100644 --- a/frontend/src/business/components/settings/router.js +++ b/frontend/src/business/components/settings/router.js @@ -48,6 +48,11 @@ export default { component: () => import('@/business/components/settings/organization/ServiceIntegration'), meta: {organization: true, title: 'organization.service_integration'} }, + { + path: 'messagesettings', + component: () => import('@/business/components/settings/organization/MessageSettings'), + meta: {organization: true, title: 'organization.message_settings'} + }, { path: 'member', component: () => import('@/business/components/settings/workspace/WorkspaceMember'), diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 9986e5cf4a..bd3fa6abbc 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -213,6 +213,20 @@ export default { select: 'Select Organization', service_integration: 'Service integration', defect_manage: 'Defect management platform', + message_settings:'Message settings', + message:{ + jenkins_task_notification:'Jenkins task notification', + test_plan_task_notification:'Test plan task notification', + test_review_task_notice:'Test review task notice', + defect_task_notification:'Defect task notification', + create_new_notification:'Create a new notification', + select_events:'Select event', + select_receiving_method:'Select receiving method', + mail:'mail', + nail_robot:'Nail robot', + enterprise_wechat_robot:'Enterprise wechat robot', + + }, integration: { select_defect_platform: 'Please select the defect management platform to be integrated:', basic_auth_info: 'Basic Auth account information:', diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 3ca1f88a10..d07a2b7477 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -214,6 +214,22 @@ export default { delete_warning: '删除该组织将同步删除该组织下所有相关工作空间和相关工作空间下的所有项目,以及项目中的所有用例、接口测试、性能测试等,确定要删除吗?', service_integration: '服务集成', defect_manage: '缺陷管理平台', + message_settings:'消息设置', + message:{ + jenkins_task_notification:'Jenkins任务通知', + test_plan_task_notification:'测试计划任务通知', + test_review_task_notice:'测试评审任务通知', + create_new_notification:'创建新通知', + select_events:'选择事件', + defect_task_notification:'缺陷任务通知', + select_receiving_method:'选择接收方式', + mail:'邮件', + nail_robot:'钉钉机器人', + enterprise_wechat_robot:'企业微信机器人', + + + + }, integration: { select_defect_platform: '请选择要集成的缺陷管理平台:', basic_auth_info: 'Basic Auth 账号信息:', diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index f2ca497ec1..7fa0bf298b 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -214,6 +214,19 @@ export default { delete_warning: '刪除該組織將同步刪除該組織下所有相關工作空間和相關工作空間下的所有項目,以及項目中的所有用例、接口測試、性能測試等,確定要刪除嗎?', service_integration: '服務集成', defect_manage: '缺陷管理平臺', + message_settings:'消息設定', + message:{ + jenkins_task_notification:'Jenkins任務通知', + test_plan_task_notification:'測試計畫任務通知', + test_review_task_notice:'測試評審任務通知', + defect_task_notification:'缺陷任務通知', + create_new_notification:'創建新通知', + select_events:'選擇事件', + select_receiving_method:'選擇接收管道', + mail:'郵件', + nail_robot:'釘釘機器人', + enterprise_wechat_robot:'企業微信機器人', + }, integration: { select_defect_platform: '請選擇要集成的缺陷管理平臺:', basic_auth_info: 'Basic Auth 賬號信息:', @@ -236,7 +249,10 @@ export default { successful_operation: '操作成功', not_integrated: '未集成該平臺', choose_platform: '請選擇集成的平臺', - verified: '驗證通過' + verified: '驗證通過', + mail:'郵件', + nail_robot:'釘釘機器人', + enterprise_wechat_robot:'企業微信機器人', } }, project: {