feat: 消息设置

This commit is contained in:
wenyann 2020-10-16 14:25:24 +08:00
parent 3f73ecd5da
commit 07dea85f7c
25 changed files with 744 additions and 189 deletions

View File

@ -351,7 +351,17 @@
<artifactId>json</artifactId>
<version>20171018</version>
</dependency>
<!--钉钉sdk-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
</dependencies>

View File

@ -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<String> 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<NoticeDetail> noticeList = noticeService.queryNotice(testResult.getTestId());

View File

@ -26,4 +26,9 @@ public interface ExtTestPlanTestCaseMapper {
List<TestPlanCaseDTO> getPendingTestCases(@Param("request") QueryTestPlanCaseRequest request);
List<String> getStatusByPlanId(String planId);
int updateTestCaseStates(@Param("ids") List<String> ids, @Param("reportStatus") String reportStatus);
List<String> getTestPlanTestCaseIds(String testId);
}

View File

@ -279,4 +279,24 @@
from test_plan_test_case
where plan_id = #{planId}
</select>
<select id="getTestPlanTestCaseIds" resultType="java.lang.String">
select c.id from test_case a left join test_plan_test_case c on a.id=c.case_id
<where>
<if test="testId!=null">
and a.test_id=#{testId}
</if>
</where>
</select>
<update id="updateTestCaseStates" parameterType="java.lang.String">
update test_plan_test_case
<set>
<if test="reportStatus!=null">
status=#{reportStatus,jdbcType=VARCHAR}
</if>
</set>
where id in
<foreach collection="ids" item="id" index="index" open="(" close=")" separator=",">
#{id}
</foreach>
</update>
</mapper>

View File

@ -24,4 +24,9 @@ public class NoticeController {
return noticeService.queryNotice(testId);
}
@PostMapping("save/message")
public void saveMessage() {
}
}

View File

@ -0,0 +1,4 @@
package io.metersphere.notice.domain;
public class MessageDetail {
}

View File

@ -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<String, Object> items = new HashMap<String, Object>();
items.put("msgtype", "link");
Map<String, String> linkContent = new HashMap<String, String>();
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);
}
}

View File

@ -0,0 +1,5 @@
package io.metersphere.notice.message;
public interface Message {
String toJsonString();
}

View File

@ -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<String> 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<String> getMentionedMobileList() {
return mentionedMobileList;
}
public void setMentionedMobileList(List<String> mentionedMobileList) {
this.mentionedMobileList = mentionedMobileList;
}
public String toJsonString() {
Map<String, Object> items = new HashMap<String, Object>();
items.put("msgtype", "text");
Map<String, Object> textContent = new HashMap<String, Object>();
if (StringUtils.isBlank(text)) {
throw new IllegalArgumentException("text should not be blank");
}
textContent.put("content", text);
if (isAtAll) {
if (mentionedMobileList == null) mentionedMobileList = new ArrayList<String>();
mentionedMobileList.add("@all");
}
if (mentionedMobileList != null && !mentionedMobileList.isEmpty()) {
textContent.put("mentioned_mobile_list", mentionedMobileList);
}
items.put("text", textContent);
return JSON.toJSONString(items);
}
}

View File

@ -0,0 +1,9 @@
package io.metersphere.notice.service;
import org.springframework.stereotype.Service;
@Service
public class DingTaskService {
}

View File

@ -95,6 +95,7 @@ public class MailService {
try {
javaMailSender.send(mimeMessage);
} catch (MailException e) {
LogUtil.error(e);
LogUtil.error("Failed to send mail");
}
}

View File

@ -0,0 +1,7 @@
package io.metersphere.notice.service;
import org.springframework.stereotype.Service;
@Service
public class WxChatTaskService {
}

View File

@ -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<String, Object> items = new HashMap<String, Object>();
items.put("errorCode", errorCode);
items.put("errorMsg", errorMsg);
items.put("isSuccess", isSuccess);
return JSON.toJSONString(items);
}
}

View File

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

View File

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

View File

@ -140,4 +140,12 @@ public class TestPlanTestCaseService {
example.createCriteria().andIdIn(request.getIds());
testPlanTestCaseMapper.deleteByExample(example);
}
public List<String> getTestPlanTestCaseIds(String testId) {
return extTestPlanTestCaseMapper.getTestPlanTestCaseIds(testId);
}
public int updateTestCaseStates(List<String> ids, String reportStatus) {
return extTestPlanTestCaseMapper.updateTestCaseStates(ids, reportStatus);
}
}

View File

@ -0,0 +1 @@
alter table test_case modify prerequisite varchar(500) null comment 'Test case prerequisite condition';

View File

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

View File

@ -1,7 +1,7 @@
<template>
<div class="header-title" v-loading="result.loading">
<div>
<div>{{$t('organization.integration.select_defect_platform')}}</div>
<div>{{ $t('organization.integration.select_defect_platform') }}</div>
<el-radio-group v-model="platform" style="margin-top: 10px" @change="change">
<el-radio label="Tapd">
<img class="platform" src="../../../../assets/tapd.png" alt="Tapd"/>
@ -13,7 +13,7 @@
</div>
<div style="width: 500px">
<div style="margin-top: 20px;margin-bottom: 10px">{{$t('organization.integration.basic_auth_info')}}</div>
<div style="margin-top: 20px;margin-bottom: 10px">{{ $t('organization.integration.basic_auth_info') }}</div>
<el-form :model="form" ref="form" label-width="120px" size="small" :disabled="show" :rules="rules">
<el-form-item :label="$t('organization.integration.api_account')" prop="account">
<el-input v-model="form.account" :placeholder="$t('organization.integration.input_api_account')"/>
@ -25,36 +25,37 @@
<el-form-item :label="$t('organization.integration.jira_url')" prop="url" v-if="platform === 'Jira'">
<el-input v-model="form.url" :placeholder="$t('organization.integration.input_jira_url')"/>
</el-form-item>
<el-form-item :label="$t('organization.integration.jira_issuetype')" prop="issuetype" v-if="platform === 'Jira'">
<el-form-item :label="$t('organization.integration.jira_issuetype')" prop="issuetype"
v-if="platform === 'Jira'">
<el-input v-model="form.issuetype" :placeholder="$t('organization.integration.input_jira_issuetype')"/>
</el-form-item>
</el-form>
</div>
<div style="margin-left: 120px">
<el-button type="primary" size="mini" :disabled="!show" @click="testConnection">{{$t('ldap.test_connect')}}
<el-button type="primary" size="mini" :disabled="!show" @click="testConnection">{{ $t('ldap.test_connect') }}
</el-button>
<el-button v-if="showEdit" size="mini" @click="edit">{{$t('commons.edit')}}</el-button>
<el-button type="primary" v-if="showSave" size="mini" @click="save('form')">{{$t('commons.save')}}</el-button>
<el-button v-if="showCancel" size="mini" @click="cancelEdit">{{$t('organization.integration.cancel_edit')}}
<el-button v-if="showEdit" size="mini" @click="edit">{{ $t('commons.edit') }}</el-button>
<el-button type="primary" v-if="showSave" size="mini" @click="save('form')">{{ $t('commons.save') }}</el-button>
<el-button v-if="showCancel" size="mini" @click="cancelEdit">{{ $t('organization.integration.cancel_edit') }}
</el-button>
<el-button type="info" size="mini" @click="cancelIntegration('form')" :disabled="!show">
{{$t('organization.integration.cancel_integration')}}
{{ $t('organization.integration.cancel_integration') }}
</el-button>
</div>
<div class="defect-tip">
<div>{{$t('organization.integration.use_tip')}}</div>
<div>{{ $t('organization.integration.use_tip') }}</div>
<div>
1. {{$t('organization.integration.use_tip_tapd')}}
1. {{ $t('organization.integration.use_tip_tapd') }}
</div>
<div>
2. {{$t('organization.integration.use_tip_jira')}}
2. {{ $t('organization.integration.use_tip_jira') }}
</div>
<div>
3. {{$t('organization.integration.use_tip_two')}}
3. {{ $t('organization.integration.use_tip_two') }}
<router-link to="/track/project/all" style="margin-left: 5px">
{{$t('organization.integration.link_the_project_now')}}
{{ $t('organization.integration.link_the_project_now') }}
</router-link>
</div>
</div>
@ -62,9 +63,9 @@
</template>
<script>
import {getCurrentUser} from "../../../../common/js/utils";
import {getCurrentUser} from "../../../../common/js/utils";
export default {
export default {
name: "IssuesManagement",
data() {
return {
@ -235,24 +236,24 @@
}
}
}
}
}
</script>
<style scoped>
.header-title {
.header-title {
padding: 10px 30px;
}
}
.defect-tip {
.defect-tip {
background: #EDEDED;
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
}
.platform {
.platform {
height: 90px;
vertical-align: middle
}
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<div>
<el-card>
<el-tabs class="system-setting" v-model="activeName">
<el-tab-pane :label="$t('organization.message_settings')" name="message">
<message-notification/>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script>
import MessageNotification from "./TaskNotification"
export default {
name: "MessageSettings",
components: {
MessageNotification
},
data(){
return{
activeName: 'message'
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,160 @@
<template>
<div style="margin-left: 40px">
<el-row class="row">
<el-col :span="20">
<div class="grid-content bg-purple-dark">
<el-row>
<el-col :span="6">
<span style="font-weight:bold;">{{$t('organization.message.jenkins_task_notification')}}</span>
</el-col>
<el-col :span="14">
<el-button type="text" icon="el-icon-plus" size="mini" @click="handleAddStep('jenkinsTask')">
{{$t('organization.message.create_new_notification')}}
</el-button>
</el-col>
</el-row>
</div>
<el-table
:data="jenkinsTask"
class="tb-edit"
border
size="mini"
:header-cell-style="{background:'#EDEDED'}"
>
<el-table-column :label="$t('schedule.event')" min-width="20%" prop="event">
<template slot-scope="scope">
<el-select v-model="scope.row.event" multiple :placeholder="$t('organization.message.select_events')" prop="event">
<el-option
v-for="item in jenkinsEventOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('schedule.receiver')" prop="receiver" min-width="20%">
<template v-slot:default="{row}">
<el-select v-model="row.userIds" filterable multiple
:placeholder="$t('commons.please_select')"
@click.native="userList()" style="width: 100%;">
<el-option
v-for="item in jenkinsReceiverOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('schedule.receiving_mode')" min-width="20%" prop="type">
<template slot-scope="scope">
<el-select v-model="scope.row.type" :placeholder="$t('organization.message.select_receiving_method')">
<el-option
v-for="item in receiveOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="webhook" min-width="20%" prop="webhook">
<template v-slot:default="scope">
<el-input v-model="scope.row.webhook" placeholder="webhook地址"></el-input>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" min-width="20%" prop="result">
<template v-slot:default="scope">
<el-button
type="primary"
size="mini"
v-show="!scope.row.showAdd"
@click="handleAddTask(scope.$index,scope.row)"
>{{$t('commons.add')}}
</el-button>
<el-button
size="mini"
v-show="!scope.row.showCancel"
@click="removeRow(scope.$index,scope.row)"
>{{$t('commons.cancel')}}
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
v-show="scope.row.showDelete"
@click="removeRow(scope.$index,scope.row)"
></el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: "TaskNotification",
data() {
return {
jenkinsTask: [{}],
planCaseTask: [{}],
jenkinsEventOptions: [
{value: 'EXECUTE_SUCCESSFUL', label: this.$t('schedule.event_success')},
{value: 'EXECUTE_FAILED', label: this.$t('schedule.event_failed')}
],
jenkinsReceiverOptions: [],
receiveOptions: [
{value: 'email', label: this.$t('organization.message.mail')},
{value: 'nailRobot', label: this.$t('organization.message.nail_robot')},
{value: 'wechatRobot', label: this.$t('organization.message.enterprise_wechat_robot')}
],
webhook: "",
showAdd: true,
showDelete: false,
showCancel: true,
}
},
methods: {
userList() {
this.result = this.$get('user/list', response => {
this.jenkinsReceiverOptions = response.data
})
},
handleAddStep(index, data) {
this.showAdd = true;
this.showCancel = true;
this.showDelete = false;
let jenkinsTask = {};
this.jenkinsTask.unshift(jenkinsTask)
},
handleAddTask(index,row) {
this.result = this.$post('/notice/save', this.jenkinsTask, () => {
})
},
removeRow(index, rows) { //
this.jenkinsTask.splice(index, 1)
}
}
}
</script>
<style scoped>
/deep/ .el-select__tags {
flex-wrap: unset;
overflow: auto;
}
.row {
margin-bottom: 30px;
}
</style>

View File

@ -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'),

View File

@ -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:',

View File

@ -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 账号信息:',

View File

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