mirror of
https://github.com/wekan/wekan.git
synced 2025-04-24 14:08:31 -04:00
Merge branch 'improve-notify' of https://github.com/nztqa/wekan into nztqa-improve-notify
This commit is contained in:
commit
510708d0e1
9 changed files with 168 additions and 2 deletions
|
@ -127,6 +127,8 @@
|
|||
"InvitationCodes": true,
|
||||
"Winston":true,
|
||||
"JsonRoutes": true,
|
||||
"Authentication": true
|
||||
"Authentication": true,
|
||||
"Integrations": true,
|
||||
"HTTP": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ mquandalle:moment
|
|||
ongoworks:speakingurl
|
||||
raix:handlebar-helpers
|
||||
tap:i18n
|
||||
http
|
||||
|
||||
# UI components
|
||||
blaze
|
||||
|
|
|
@ -112,6 +112,7 @@ template(name="boardMenuPopup")
|
|||
ul.pop-over-list
|
||||
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
|
||||
li: a.js-archive-board {{_ 'archive-board'}}
|
||||
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
|
||||
|
||||
template(name="boardVisibilityList")
|
||||
ul.pop-over-list
|
||||
|
@ -213,3 +214,13 @@ template(name="boardChangeTitlePopup")
|
|||
template(name="archiveBoardPopup")
|
||||
p {{_ 'close-board-pop'}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
|
||||
|
||||
template(name="outgoingWebhooksPopup")
|
||||
form
|
||||
label
|
||||
| URL
|
||||
if integration.enabled
|
||||
input.js-outgoing-webhooks-url(type="text" value=integration.url autofocus)
|
||||
else
|
||||
input.js-outgoing-webhooks-url(type="text" autofocus)
|
||||
input.primary.wide(type="submit" value="{{_ 'save'}}")
|
||||
|
|
|
@ -13,6 +13,7 @@ Template.boardMenuPopup.events({
|
|||
// confirm that the board was successfully archived.
|
||||
FlowRouter.go('home');
|
||||
}),
|
||||
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
|
||||
});
|
||||
|
||||
Template.boardMenuPopup.helpers({
|
||||
|
@ -234,3 +235,45 @@ BlazeComponent.extendComponent({
|
|||
}];
|
||||
},
|
||||
}).register('boardChangeWatchPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
integration() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
return Integrations.findOne({ boardId: `${boardId}` });
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
'submit'(evt) {
|
||||
evt.preventDefault();
|
||||
const url = this.find('.js-outgoing-webhooks-url').value.trim();
|
||||
const boardId = Session.get('currentBoard');
|
||||
const integration = this.integration();
|
||||
if (integration) {
|
||||
if (url) {
|
||||
Integrations.update(integration._id, {
|
||||
$set: {
|
||||
enabled: true,
|
||||
url: `${url}`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
Integrations.update(integration._id, {
|
||||
$set: {
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (url) {
|
||||
Integrations.insert({
|
||||
enabled: true,
|
||||
type: 'outgoing-webhooks',
|
||||
url: `${url}`,
|
||||
boardId: `${boardId}`,
|
||||
});
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
}];
|
||||
},
|
||||
}).register('outgoingWebhooksPopup');
|
||||
|
|
|
@ -360,5 +360,7 @@
|
|||
"email-invite-register-subject": "__inviter__ sent you an invitation",
|
||||
"email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to Wekan for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.",
|
||||
"error-invitation-code-not-exist": "Invitation code doesn't exist",
|
||||
"error-notAuthorized": "You are not authorized to view this page."
|
||||
"error-notAuthorized": "You are not authorized to view this page.",
|
||||
"outgoing-webhooks": "Outgoing Webhooks",
|
||||
"outgoingWebhooksPopup-title": "Outgoing Webhooks"
|
||||
}
|
||||
|
|
|
@ -131,5 +131,10 @@ if (Meteor.isServer) {
|
|||
Notifications.getUsers(participants, watchers).forEach((user) => {
|
||||
Notifications.notify(user, title, description, params);
|
||||
});
|
||||
|
||||
const integration = Integrations.findOne({ boardId: board._id, type: 'outgoing-webhooks', enabled: true });
|
||||
if (integration) {
|
||||
Meteor.call('outgoingWebhooks', integration, description, params);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
54
models/integrations.js
Normal file
54
models/integrations.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
Integrations = new Mongo.Collection('integrations');
|
||||
|
||||
Integrations.attachSchema(new SimpleSchema({
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
},
|
||||
url: { // URL validation regex (https://mathiasbynens.be/demo/url-regex)
|
||||
type: String,
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
boardId: {
|
||||
type: String,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
denyUpdate: false,
|
||||
autoValue() { // eslint-disable-line consistent-return
|
||||
if (this.isInsert) {
|
||||
return new Date();
|
||||
} else {
|
||||
this.unset();
|
||||
}
|
||||
},
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
autoValue() { // eslint-disable-line consistent-return
|
||||
if (this.isInsert || this.isUpdate) {
|
||||
return this.userId;
|
||||
}
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
Integrations.allow({
|
||||
insert(userId, doc) {
|
||||
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
|
||||
},
|
||||
update(userId, doc) {
|
||||
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
|
||||
},
|
||||
fetch: ['boardId'],
|
||||
});
|
47
server/notifications/outgoing.js
Normal file
47
server/notifications/outgoing.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
|
||||
HTTP.post(url, options, (err, res) => {
|
||||
if (err) {
|
||||
resolve(null, err.response);
|
||||
} else {
|
||||
resolve(null, res);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
outgoingWebhooks(integration, description, params) {
|
||||
check(integration, Object);
|
||||
check(description, String);
|
||||
check(params, Object);
|
||||
|
||||
const quoteParams = _.clone(params);
|
||||
['card', 'list', 'oldList', 'board', 'comment'].forEach((key) => {
|
||||
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
|
||||
});
|
||||
|
||||
const user = Users.findOne(integration.userId);
|
||||
const text = `${params.user} ${TAPi18n.__(description, quoteParams, user.getLanguage())}\n${params.url}`;
|
||||
|
||||
if (text.length === 0) return;
|
||||
|
||||
const value = {
|
||||
text: `${text}`,
|
||||
};
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// 'X-Wekan-Activities-Token': 'Random.Id()',
|
||||
},
|
||||
data: value,
|
||||
};
|
||||
|
||||
const response = postCatchError(integration.url, options);
|
||||
|
||||
if (response && response.statusCode && response.statusCode === 200) {
|
||||
return true; // eslint-disable-line consistent-return
|
||||
} else {
|
||||
throw new Meteor.Error('error-invalid-webhook-response');
|
||||
}
|
||||
},
|
||||
});
|
|
@ -73,6 +73,7 @@ Meteor.publishRelations('board', function(boardId) {
|
|||
],
|
||||
}, { limit: 1 }), function(boardId, board) {
|
||||
this.cursor(Lists.find({ boardId }));
|
||||
this.cursor(Integrations.find({ boardId }));
|
||||
|
||||
// Cards and cards comments
|
||||
// XXX Originally we were publishing the card documents as a child of the
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue