Add Feature: enable two-way webhooks - stage one

This commit is contained in:
Sam X. Chen 2019-08-17 19:17:57 -04:00
parent f3147af2f7
commit 3f0600fed7
9 changed files with 115 additions and 49 deletions

View file

@ -18,6 +18,8 @@ template(name="setting")
a.js-setting-menu(data-id="announcement-setting") {{_ 'admin-announcement'}} a.js-setting-menu(data-id="announcement-setting") {{_ 'admin-announcement'}}
li li
a.js-setting-menu(data-id="layout-setting") {{_ 'layout'}} a.js-setting-menu(data-id="layout-setting") {{_ 'layout'}}
li
a.js-setting-menu(data-id="webhook-setting") {{_ 'global-webhook'}}
.main-body .main-body
if loading.get if loading.get
+spinner +spinner
@ -31,6 +33,12 @@ template(name="setting")
+announcementSettings +announcementSettings
else if layoutSetting.get else if layoutSetting.get
+layoutSettings +layoutSettings
else if webhookSetting.get
+webhookSettings
template(name="webhookSettings")
span
+outgoingWebhooksPopup
template(name="general") template(name="general")
ul#registration-setting.setting-detail ul#registration-setting.setting-detail

View file

@ -7,11 +7,13 @@ BlazeComponent.extendComponent({
this.accountSetting = new ReactiveVar(false); this.accountSetting = new ReactiveVar(false);
this.announcementSetting = new ReactiveVar(false); this.announcementSetting = new ReactiveVar(false);
this.layoutSetting = new ReactiveVar(false); this.layoutSetting = new ReactiveVar(false);
this.webhookSetting = new ReactiveVar(false);
Meteor.subscribe('setting'); Meteor.subscribe('setting');
Meteor.subscribe('mailServer'); Meteor.subscribe('mailServer');
Meteor.subscribe('accountSettings'); Meteor.subscribe('accountSettings');
Meteor.subscribe('announcements'); Meteor.subscribe('announcements');
Meteor.subscribe('globalwebhooks');
}, },
setError(error) { setError(error) {
@ -83,6 +85,7 @@ BlazeComponent.extendComponent({
this.accountSetting.set('account-setting' === targetID); this.accountSetting.set('account-setting' === targetID);
this.announcementSetting.set('announcement-setting' === targetID); this.announcementSetting.set('announcement-setting' === targetID);
this.layoutSetting.set('layout-setting' === targetID); this.layoutSetting.set('layout-setting' === targetID);
this.webhookSetting.set('webhook-setting' === targetID);
} }
}, },

View file

@ -135,22 +135,30 @@ template(name="archiveBoardPopup")
template(name="outgoingWebhooksPopup") template(name="outgoingWebhooksPopup")
each integrations each integrations
form.integration-form form.integration-form
if title a.flex
h4 {{title}} span {{_ 'disable-webhook'}}
else b  
h4 {{_ 'no-name'}} .materialCheckBox(class="{{#unless enabled}}is-checked{{/unless}}")
label input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
| URL input.js-outgoing-webhooks-url(type="text" name="url" value=url autofocus)
input.js-outgoing-webhooks-url(type="text" name="url" value=url) input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" value=token name="token")
input(type="hidden" value=_id name="id") select.js-outgoing-webhooks-type(name="type")
each _type in types
if($eq _type this.type)
option(value=_type selected="selected") {{_ _type}}
else
option(value=_type) {{_ _type}}
input(type="hidden" value=this.type name="_type")
input(type="hidden" value=_id name="id")
input.primary.wide(type="submit" value="{{_ 'save'}}") input.primary.wide(type="submit" value="{{_ 'save'}}")
form.integration-form form.integration-form
h4 input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" autofocus)
| {{_ 'new-outgoing-webhook'}} input.js-outgoing-webhooks-url(placeholder="{{_ 'URL' }}" type="text" name="url")
label input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" name="token")
| URL select.js-outgoing-webhooks-type(name="type")
input.js-outgoing-webhooks-url(type="text" name="url" autofocus) each _type in types
input.primary.wide(type="submit" value="{{_ 'save'}}") option(value=_type) {{_ _type}}
input.primary.wide(type="submit" value="{{_ 'create'}}")
template(name="boardMenuPopup") template(name="boardMenuPopup")
ul.pop-over-list ul.pop-over-list

View file

@ -1,6 +1,8 @@
Sidebar = null; Sidebar = null;
const defaultView = 'home'; const defaultView = 'home';
const MCB = '.materialCheckBox';
const CKCLS = 'is-checked';
const viewTitles = { const viewTitles = {
filter: 'filter-cards', filter: 'filter-cards',
@ -280,44 +282,71 @@ Template.membersWidget.events({
}); });
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
boardId() {
return Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
},
integrations() { integrations() {
const boardId = Session.get('currentBoard'); const boardId = this.boardId();
return Integrations.find({ boardId: `${boardId}` }).fetch(); return Integrations.find({ boardId: `${boardId}` }).fetch();
}, },
types() {
integration(id) { return Integrations.Const.WEBHOOK_TYPES;
const boardId = Session.get('currentBoard'); },
return Integrations.findOne({ _id: id, boardId: `${boardId}` }); integration(cond) {
const boardId = this.boardId();
const condition = { boardId, ...cond };
for (const k in condition) {
if (!condition[k]) delete condition[k];
}
return Integrations.findOne(condition);
},
onCreated() {
this.disabled = new ReactiveVar(false);
}, },
events() { events() {
return [ return [
{ {
'click a.flex'(evt) {
this.disabled.set(!this.disabled.get());
$(evt.target).toggleClass(CKCLS, this.disabled.get());
},
submit(evt) { submit(evt) {
evt.preventDefault(); evt.preventDefault();
const url = evt.target.url.value; const url = evt.target.url.value;
const boardId = Session.get('currentBoard'); const boardId = this.boardId();
let id = null; let id = null;
let integration = null; let integration = null;
const title = evt.target.title.value;
const token = evt.target.token.value;
const type = evt.target.type.value;
const enabled = !this.disabled.get();
let remove = false;
const values = {
url,
type,
token,
title,
enabled,
};
if (evt.target.id) { if (evt.target.id) {
id = evt.target.id.value; id = evt.target.id.value;
integration = this.integration(id); integration = this.integration({ _id: id });
if (url) { remove = !url;
Integrations.update(integration._id, { } else if (url) {
$set: { integration = this.integration({ url, token });
url: `${url}`, }
}, if (remove) {
}); Integrations.remove(integration._id);
} else { } else if (integration && integration._id) {
Integrations.remove(integration._id); Integrations.update(integration._id, {
} $set: values,
});
} else if (url) { } else if (url) {
Integrations.insert({ Integrations.insert({
...values,
userId: Meteor.userId(), userId: Meteor.userId(),
enabled: true, enabled: true,
type: 'outgoing-webhooks', boardId,
url: `${url}`,
boardId: `${boardId}`,
activities: ['all'], activities: ['all'],
}); });
} }
@ -474,12 +503,12 @@ BlazeComponent.extendComponent({
evt.preventDefault(); evt.preventDefault();
this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks; this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks); this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
$('.js-field-has-subtasks .materialCheckBox').toggleClass( $(`.js-field-has-subtasks ${MCB}`).toggleClass(
'is-checked', CKCLS,
this.currentBoard.allowsSubtasks, this.currentBoard.allowsSubtasks,
); );
$('.js-field-has-subtasks').toggleClass( $('.js-field-has-subtasks').toggleClass(
'is-checked', CKCLS,
this.currentBoard.allowsSubtasks, this.currentBoard.allowsSubtasks,
); );
$('.js-field-deposit-board').prop( $('.js-field-deposit-board').prop(
@ -515,15 +544,12 @@ BlazeComponent.extendComponent({
]; ];
options.forEach(function(element) { options.forEach(function(element) {
if (element !== value) { if (element !== value) {
$(`#${element} .materialCheckBox`).toggleClass( $(`#${element} ${MCB}`).toggleClass(CKCLS, false);
'is-checked', $(`#${element}`).toggleClass(CKCLS, false);
false,
);
$(`#${element}`).toggleClass('is-checked', false);
} }
}); });
$(`#${value} .materialCheckBox`).toggleClass('is-checked', true); $(`#${value} ${MCB}`).toggleClass(CKCLS, true);
$(`#${value}`).toggleClass('is-checked', true); $(`#${value}`).toggleClass(CKCLS, true);
this.currentBoard.setPresentParentTask(value); this.currentBoard.setPresentParentTask(value);
evt.preventDefault(); evt.preventDefault();
}, },

View file

@ -23,7 +23,6 @@ Utils = {
}) })
); );
}, },
MAX_IMAGE_PIXEL: Meteor.settings.public.MAX_IMAGE_PIXEL, MAX_IMAGE_PIXEL: Meteor.settings.public.MAX_IMAGE_PIXEL,
COMPRESS_RATIO: Meteor.settings.public.IMAGE_COMPRESS_RATIO, COMPRESS_RATIO: Meteor.settings.public.IMAGE_COMPRESS_RATIO,
processUploadedAttachment(card, fileObj, callback) { processUploadedAttachment(card, fileObj, callback) {

View file

@ -508,9 +508,14 @@
"email-smtp-test-text": "You have successfully sent an email", "email-smtp-test-text": "You have successfully sent an email",
"error-invitation-code-not-exist": "Invitation code doesn't exist", "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.",
"webhook-title": "Webhook Name",
"webhook-token": "Token (Optional for Authentication)",
"outgoing-webhooks": "Outgoing Webhooks", "outgoing-webhooks": "Outgoing Webhooks",
"bidirectional-webhooks": "Two-Way Webhooks",
"outgoingWebhooksPopup-title": "Outgoing Webhooks", "outgoingWebhooksPopup-title": "Outgoing Webhooks",
"boardCardTitlePopup-title": "Card Title Filter", "boardCardTitlePopup-title": "Card Title Filter",
"disable-webhook": "Disable This Webhook",
"global-webhook": "Global Webhooks",
"new-outgoing-webhook": "New Outgoing Webhook", "new-outgoing-webhook": "New Outgoing Webhook",
"no-name": "(Unknown)", "no-name": "(Unknown)",
"Node_version": "Node version", "Node_version": "Node version",

View file

@ -88,16 +88,26 @@ Integrations.attachSchema(
}, },
}), }),
); );
Integrations.Const = {
GLOBAL_WEBHOOK_ID: '_global',
WEBHOOK_TYPES: ['outgoing-webhooks', 'bidirectional-webhooks'],
};
const permissionHelper = {
allow(userId, doc) {
const user = Users.findOne(userId);
const isAdmin = user && Meteor.user().isAdmin;
return isAdmin || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
};
Integrations.allow({ Integrations.allow({
insert(userId, doc) { insert(userId, doc) {
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); return permissionHelper.allow(userId, doc);
}, },
update(userId, doc) { update(userId, doc) {
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); return permissionHelper.allow(userId, doc);
}, },
remove(userId, doc) { remove(userId, doc) {
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); return permissionHelper.allow(userId, doc);
}, },
fetch: ['boardId'], fetch: ['boardId'],
}); });

View file

@ -147,6 +147,7 @@ if (Meteor.isServer) {
}:${doc.mailServer.port}/`; }:${doc.mailServer.port}/`;
} }
Accounts.emailTemplates.from = doc.mailServer.from; Accounts.emailTemplates.from = doc.mailServer.from;
console.log('Settings saved:', Accounts.emailTemplates);
} }
}); });

View file

@ -1,3 +1,9 @@
Meteor.publish('globalwebhooks', () => {
const boardId = Integrations.Const.GLOBAL_WEBHOOK_ID;
return Integrations.find({
boardId,
});
});
Meteor.publish('setting', () => { Meteor.publish('setting', () => {
return Settings.find( return Settings.find(
{}, {},