summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2017-09-01 23:01:34 +0300
committerLauri Ojansivu <x@xet7.org>2017-09-01 23:01:34 +0300
commite041f55254e7a2ac48c25cbc70f6977dd2447270 (patch)
treee525719e1bb20fb16fa7cd142330a405fdb7197d
parent491a352792238abf58ba9d973b5009bff581f904 (diff)
parentba9f4fa7b7e5654bcc93251a0b75e958e1015e7f (diff)
downloadwekan-e041f55254e7a2ac48c25cbc70f6977dd2447270.tar.gz
wekan-e041f55254e7a2ac48c25cbc70f6977dd2447270.tar.bz2
wekan-e041f55254e7a2ac48c25cbc70f6977dd2447270.zip
Merge branch 'andresmanelli-devel' into devel
Add more than one Outgoing Webhook. Thanks to andresmanelli !
-rw-r--r--CHANGELOG.md8
-rw-r--r--client/components/boards/boardHeader.jade20
-rw-r--r--client/components/boards/boardHeader.js27
-rw-r--r--client/components/boards/boardHeader.styl3
-rw-r--r--i18n/en.i18n.json2
-rw-r--r--models/activities.js6
-rw-r--r--models/integrations.js146
-rw-r--r--server/notifications/outgoing.js23
8 files changed, 199 insertions, 36 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b63c0a13..46a07757 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,14 @@
# Upcoming Wekan release
-This release fixes the following bugs:
+This release adds the following new features:
+
+* [Add more than one Outgoing Webhook](https://github.com/wekan/wekan/pull/1199).
+
+and fixes the following bugs:
* [Fix errors caused by checklist items activities](https://github.com/wekan/wekan/pull/1200).
-Thanks to GitHub users GhassenRjab and nztqa for contributions.
+Thanks to GitHub users andresmanelli, GhassenRjab and nztqa for contributions.
# v0.34 2017-08-30 Wekan release
diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade
index 3d98322d..9539fc79 100644
--- a/client/components/boards/boardHeader.jade
+++ b/client/components/boards/boardHeader.jade
@@ -227,11 +227,21 @@ template(name="archiveBoardPopup")
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
template(name="outgoingWebhooksPopup")
- form
+ each integrations
+ form.integration-form
+ if title
+ h4 {{title}}
+ else
+ h4 {{_ 'no-name'}}
+ label
+ | URL
+ input.js-outgoing-webhooks-url(type="text" name="url" value=url)
+ input(type="hidden" value=_id name="id")
+ input.primary.wide(type="submit" value="{{_ 'save'}}")
+ form.integration-form
+ h4
+ | {{_ 'new-integration'}}
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.js-outgoing-webhooks-url(type="text" name="url" autofocus)
input.primary.wide(type="submit" value="{{_ 'save'}}")
diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js
index 2ee21905..b7807ca9 100644
--- a/client/components/boards/boardHeader.js
+++ b/client/components/boards/boardHeader.js
@@ -241,39 +241,44 @@ BlazeComponent.extendComponent({
}).register('boardChangeWatchPopup');
BlazeComponent.extendComponent({
- integration() {
+ integrations() {
const boardId = Session.get('currentBoard');
- return Integrations.findOne({ boardId: `${boardId}` });
+ return Integrations.find({ boardId: `${boardId}` }).fetch();
+ },
+
+ integration(id) {
+ const boardId = Session.get('currentBoard');
+ return Integrations.findOne({ _id: id, boardId: `${boardId}` });
},
events() {
return [{
'submit'(evt) {
evt.preventDefault();
- const url = this.find('.js-outgoing-webhooks-url').value.trim();
+ const url = evt.target.url.value;
const boardId = Session.get('currentBoard');
- const integration = this.integration();
- if (integration) {
+ let id = null;
+ let integration = null;
+ if (evt.target.id) {
+ id = evt.target.id.value;
+ integration = this.integration(id);
if (url) {
Integrations.update(integration._id, {
$set: {
- enabled: true,
url: `${url}`,
},
});
} else {
- Integrations.update(integration._id, {
- $set: {
- enabled: false,
- },
- });
+ Integrations.remove(integration._id);
}
} else if (url) {
Integrations.insert({
+ userId: Meteor.userId(),
enabled: true,
type: 'outgoing-webhooks',
url: `${url}`,
boardId: `${boardId}`,
+ activities: ['all'],
});
}
Popup.close();
diff --git a/client/components/boards/boardHeader.styl b/client/components/boards/boardHeader.styl
new file mode 100644
index 00000000..0abdb5bd
--- /dev/null
+++ b/client/components/boards/boardHeader.styl
@@ -0,0 +1,3 @@
+.integration-form
+ padding: 5px
+ border-bottom: 1px solid #ccc
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index b2ea9354..ebf4722b 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -368,6 +368,8 @@
"error-notAuthorized": "You are not authorized to view this page.",
"outgoing-webhooks": "Outgoing Webhooks",
"outgoingWebhooksPopup-title": "Outgoing Webhooks",
+ "new-integration": "New integration",
+ "no-name": "(Unknown)",
"Wekan_version": "Wekan version",
"Node_version": "Node version",
"OS_Arch": "OS Arch",
diff --git a/models/activities.js b/models/activities.js
index 208dd629..c732a940 100644
--- a/models/activities.js
+++ b/models/activities.js
@@ -140,9 +140,9 @@ if (Meteor.isServer) {
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);
+ const integrations = Integrations.find({ boardId: board._id, type: 'outgoing-webhooks', enabled: true, activities: { '$in': [description, 'all'] } }).fetch();
+ if (integrations.length > 0) {
+ Meteor.call('outgoingWebhooks', integrations, description, params);
}
});
}
diff --git a/models/integrations.js b/models/integrations.js
index b9bf248f..826873ce 100644
--- a/models/integrations.js
+++ b/models/integrations.js
@@ -11,6 +11,11 @@ Integrations.attachSchema(new SimpleSchema({
},
type: {
type: String,
+ defaultValue: 'outgoing-webhooks',
+ },
+ activities: {
+ type: [String],
+ defaultValue: ['all'],
},
url: { // URL validation regex (https://mathiasbynens.be/demo/url-regex)
type: String,
@@ -35,11 +40,6 @@ Integrations.attachSchema(new SimpleSchema({
},
userId: {
type: String,
- autoValue() { // eslint-disable-line consistent-return
- if (this.isInsert || this.isUpdate) {
- return this.userId;
- }
- },
},
}));
@@ -50,5 +50,141 @@ Integrations.allow({
update(userId, doc) {
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
+ remove(userId, doc) {
+ return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+ },
fetch: ['boardId'],
});
+
+//INTEGRATIONS REST API
+if (Meteor.isServer) {
+ // Get all integrations in board
+ JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function(req, res, next) {
+ const paramBoardId = req.params.boardId;
+ Authentication.checkBoardAccess(req.userId, paramBoardId);
+
+ const data = Integrations.find({ boardId: paramBoardId }, { fields: { token: 0 } }).map(function(doc) {
+ return doc;
+ });
+
+ JsonRoutes.sendResult(res, {code: 200, data});
+ });
+
+ // Get a single integration in board
+ JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function(req, res, next) {
+ const paramBoardId = req.params.boardId;
+ const paramIntId = req.params.intId;
+ Authentication.checkBoardAccess(req.userId, paramBoardId);
+
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: Integrations.findOne({ _id: paramIntId, boardId: paramBoardId }, { fields: { token: 0 } }),
+ });
+ });
+
+ // Create a new integration
+ JsonRoutes.add('POST', '/api/boards/:boardId/integrations', function(req, res, next) {
+ const paramBoardId = req.params.boardId;
+ Authentication.checkBoardAccess(req.userId, paramBoardId);
+
+ const id = Integrations.insert({
+ userId: req.userId,
+ boardId: paramBoardId,
+ url: req.body.url,
+ });
+
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: id,
+ },
+ });
+ });
+
+ // Edit integration data
+ JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', function (req, res, next) {
+ const paramBoardId = req.params.boardId;
+ const paramIntId = req.params.intId;
+ Authentication.checkBoardAccess(req.userId, paramBoardId);
+
+ if (req.body.hasOwnProperty('enabled')) {
+ const newEnabled = req.body.enabled;
+ Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
+ {$set: {enabled: newEnabled}});
+ }
+ if (req.body.hasOwnProperty('title')) {
+ const newTitle = req.body.title;
+ Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
+ {$set: {title: newTitle}});
+ }
+ if (req.body.hasOwnProperty('url')) {
+ const newUrl = req.body.url;
+ Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
+ {$set: {url: newUrl}});
+ }
+ if (req.body.hasOwnProperty('token')) {
+ const newToken = req.body.token;
+ Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
+ {$set: {token: newToken}});
+ }
+ if (req.body.hasOwnProperty('activities')) {
+ const newActivities = req.body.activities;
+ Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
+ {$set: {activities: newActivities}});
+ }
+
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: paramIntId,
+ },
+ });
+ });
+
+ // Delete subscribed activities
+ JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId/activities', function (req, res, next) {
+ const paramBoardId = req.params.boardId;
+ const paramIntId = req.params.intId;
+ const newActivities = req.body.activities;
+ Authentication.checkBoardAccess(req.userId, paramBoardId);
+
+ Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
+ {$pullAll: {activities: newActivities}});
+
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: Integrations.findOne({_id: paramIntId, boardId: paramBoardId}, { fields: {_id: 1, activities: 1}}),
+ });
+ });
+
+ // Add subscribed activities
+ JsonRoutes.add('POST', '/api/boards/:boardId/integrations/:intId/activities', function (req, res, next) {
+ const paramBoardId = req.params.boardId;
+ const paramIntId = req.params.intId;
+ const newActivities = req.body.activities;
+ Authentication.checkBoardAccess(req.userId, paramBoardId);
+
+ Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
+ {$addToSet: {activities: { $each: newActivities}}});
+
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: Integrations.findOne({_id: paramIntId, boardId: paramBoardId}, { fields: {_id: 1, activities: 1}}),
+ });
+ });
+
+ // Delete integration
+ JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', function (req, res, next) {
+ const paramBoardId = req.params.boardId;
+ const paramIntId = req.params.intId;
+ Authentication.checkBoardAccess(req.userId, paramBoardId);
+
+ Integrations.direct.remove({_id: paramIntId, boardId: paramBoardId});
+ JsonRoutes.sendResult(res, {
+ code: 200,
+ data: {
+ _id: paramIntId,
+ },
+ });
+ });
+}
diff --git a/server/notifications/outgoing.js b/server/notifications/outgoing.js
index c227366e..07a0a903 100644
--- a/server/notifications/outgoing.js
+++ b/server/notifications/outgoing.js
@@ -9,8 +9,8 @@ const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
});
Meteor.methods({
- outgoingWebhooks(integration, description, params) {
- check(integration, Object);
+ outgoingWebhooks(integrations, description, params) {
+ check(integrations, Array);
check(description, String);
check(params, Object);
@@ -19,7 +19,8 @@ Meteor.methods({
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
});
- const user = Users.findOne(integration.userId);
+ const userId = (params.userId)?params.userId:integrations[0].userId;
+ const user = Users.findOne(userId);
const text = `${params.user} ${TAPi18n.__(description, quoteParams, user.getLanguage())}\n${params.url}`;
if (text.length === 0) return;
@@ -31,7 +32,7 @@ Meteor.methods({
['cardId', 'listId', 'oldListId', 'boardId'].forEach((key) => {
if (params[key]) value[key] = params[key];
});
- value.$description = description;
+ value.description = description;
const options = {
headers: {
@@ -41,12 +42,14 @@ Meteor.methods({
data: value,
};
- const response = postCatchError(integration.url, options);
+ integrations.forEach((integration) => {
+ 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');
- }
+ if (response && response.statusCode && response.statusCode === 200) {
+ return true; // eslint-disable-line consistent-return
+ } else {
+ throw new Meteor.Error('error-invalid-webhook-response');
+ }
+ });
},
});