Browse Source

Merge branch 'andresmanelli-devel' into devel

Add more than one Outgoing Webhook. Thanks to andresmanelli !
Lauri Ojansivu 7 years ago
parent
commit
e041f55254

+ 6 - 2
CHANGELOG.md

@@ -1,10 +1,14 @@
 # Upcoming Wekan release
 # 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).
 * [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
 # v0.34 2017-08-30 Wekan release
 
 

+ 15 - 5
client/components/boards/boardHeader.jade

@@ -227,11 +227,21 @@ template(name="archiveBoardPopup")
   button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
   button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
 
 
 template(name="outgoingWebhooksPopup")
 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
     label
       | URL
       | 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'}}")
     input.primary.wide(type="submit" value="{{_ 'save'}}")

+ 16 - 11
client/components/boards/boardHeader.js

@@ -241,39 +241,44 @@ BlazeComponent.extendComponent({
 }).register('boardChangeWatchPopup');
 }).register('boardChangeWatchPopup');
 
 
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
-  integration() {
+  integrations() {
     const boardId = Session.get('currentBoard');
     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() {
   events() {
     return [{
     return [{
       'submit'(evt) {
       'submit'(evt) {
         evt.preventDefault();
         evt.preventDefault();
-        const url = this.find('.js-outgoing-webhooks-url').value.trim();
+        const url = evt.target.url.value;
         const boardId = Session.get('currentBoard');
         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) {
           if (url) {
             Integrations.update(integration._id, {
             Integrations.update(integration._id, {
               $set: {
               $set: {
-                enabled: true,
                 url: `${url}`,
                 url: `${url}`,
               },
               },
             });
             });
           } else {
           } else {
-            Integrations.update(integration._id, {
-              $set: {
-                enabled: false,
-              },
-            });
+            Integrations.remove(integration._id);
           }
           }
         } else if (url) {
         } else if (url) {
           Integrations.insert({
           Integrations.insert({
+            userId: Meteor.userId(),
             enabled: true,
             enabled: true,
             type: 'outgoing-webhooks',
             type: 'outgoing-webhooks',
             url: `${url}`,
             url: `${url}`,
             boardId: `${boardId}`,
             boardId: `${boardId}`,
+            activities: ['all'],
           });
           });
         }
         }
         Popup.close();
         Popup.close();

+ 3 - 0
client/components/boards/boardHeader.styl

@@ -0,0 +1,3 @@
+.integration-form
+  padding: 5px
+  border-bottom: 1px solid #ccc

+ 2 - 0
i18n/en.i18n.json

@@ -368,6 +368,8 @@
     "error-notAuthorized": "You are not authorized to view this page.",
     "error-notAuthorized": "You are not authorized to view this page.",
     "outgoing-webhooks": "Outgoing Webhooks",
     "outgoing-webhooks": "Outgoing Webhooks",
     "outgoingWebhooksPopup-title": "Outgoing Webhooks",
     "outgoingWebhooksPopup-title": "Outgoing Webhooks",
+    "new-integration": "New integration",
+    "no-name": "(Unknown)",
     "Wekan_version": "Wekan version",
     "Wekan_version": "Wekan version",
     "Node_version": "Node version",
     "Node_version": "Node version",
     "OS_Arch": "OS Arch",
     "OS_Arch": "OS Arch",

+ 3 - 3
models/activities.js

@@ -140,9 +140,9 @@ if (Meteor.isServer) {
       Notifications.notify(user, title, description, params);
       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);
     }
     }
   });
   });
 }
 }

+ 141 - 5
models/integrations.js

@@ -11,6 +11,11 @@ Integrations.attachSchema(new SimpleSchema({
   },
   },
   type: {
   type: {
     type: String,
     type: String,
+    defaultValue: 'outgoing-webhooks',
+  },
+  activities: {
+    type: [String],
+    defaultValue: ['all'],
   },
   },
   url: { // URL validation regex (https://mathiasbynens.be/demo/url-regex)
   url: { // URL validation regex (https://mathiasbynens.be/demo/url-regex)
     type: String,
     type: String,
@@ -35,11 +40,6 @@ Integrations.attachSchema(new SimpleSchema({
   },
   },
   userId: {
   userId: {
     type: String,
     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) {
   update(userId, doc) {
     return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
     return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
   },
   },
+  remove(userId, doc) {
+    return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+  },
   fetch: ['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,
+      },
+    });
+  });
+}

+ 13 - 10
server/notifications/outgoing.js

@@ -9,8 +9,8 @@ const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
 });
 });
 
 
 Meteor.methods({
 Meteor.methods({
-  outgoingWebhooks(integration, description, params) {
-    check(integration, Object);
+  outgoingWebhooks(integrations, description, params) {
+    check(integrations, Array);
     check(description, String);
     check(description, String);
     check(params, Object);
     check(params, Object);
 
 
@@ -19,7 +19,8 @@ Meteor.methods({
       if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
       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}`;
     const text = `${params.user} ${TAPi18n.__(description, quoteParams, user.getLanguage())}\n${params.url}`;
 
 
     if (text.length === 0) return;
     if (text.length === 0) return;
@@ -31,7 +32,7 @@ Meteor.methods({
     ['cardId', 'listId', 'oldListId', 'boardId'].forEach((key) => {
     ['cardId', 'listId', 'oldListId', 'boardId'].forEach((key) => {
       if (params[key]) value[key] = params[key];
       if (params[key]) value[key] = params[key];
     });
     });
-    value.$description = description;
+    value.description = description;
 
 
     const options = {
     const options = {
       headers: {
       headers: {
@@ -41,12 +42,14 @@ Meteor.methods({
       data: value,
       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');
+      }
+    });
   },
   },
 });
 });