feat: Add event subscription option to webhooks (#4540)

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Pranav Raj S
2022-04-25 17:44:42 +05:30
committed by GitHub
parent fa51fd1d73
commit 899176a793
25 changed files with 552 additions and 359 deletions

View File

@@ -1,108 +0,0 @@
<template>
<div class="column content-box">
<woot-modal-header
:header-title="$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.TITLE')"
/>
<form class="row" @submit.prevent="editWebhook">
<div class="medium-12 columns">
<label :class="{ error: $v.endPoint.$error }">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.FORM.END_POINT.LABEL') }}
<input
v-model.trim="endPoint"
type="text"
name="endPoint"
:placeholder="
$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.FORM.END_POINT.PLACEHOLDER')
"
@input="$v.endPoint.$touch"
/>
<span v-if="$v.endPoint.$error" class="message">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.FORM.END_POINT.ERROR') }}
</span>
</label>
</div>
<div class="modal-footer">
<div class="medium-12 columns">
<woot-button
:is-disabled="
$v.endPoint.$invalid || uiFlags.updatingItem || endPoint === url
"
:is-loading="uiFlags.updatingItem"
>
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.FORM.SUBMIT') }}
</woot-button>
<woot-button class="button clear" @click.prevent="onClose">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.CANCEL') }}
</woot-button>
</div>
</div>
</form>
</div>
</template>
<script>
import { required, url, minLength } from 'vuelidate/lib/validators';
import alertMixin from 'shared/mixins/alertMixin';
import { mapGetters } from 'vuex';
export default {
mixins: [alertMixin],
props: {
id: {
type: Number,
required: true,
},
url: {
type: String,
required: true,
},
onClose: {
type: Function,
required: true,
},
},
data() {
return {
alertMessage: '',
endPoint: this.url,
webhookId: this.id,
};
},
validations: {
endPoint: {
required,
minLength: minLength(7),
url,
},
},
computed: {
...mapGetters({ uiFlags: 'webhooks/getUIFlags' }),
},
methods: {
resetForm() {
this.endPoint = '';
this.$v.endPoint.$reset();
},
async editWebhook() {
try {
await this.$store.dispatch('webhooks/update', {
webhook: { url: this.endPoint },
id: this.webhookId,
});
this.alertMessage = this.$t(
'INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.SUCCESS_MESSAGE'
);
this.resetForm();
this.onClose();
} catch (error) {
this.alertMessage =
error.response.data.message ||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.ERROR_MESSAGE');
} finally {
this.showAlert(this.alertMessage);
}
},
},
};
</script>

View File

@@ -1,121 +0,0 @@
<template>
<modal :show.sync="show" :on-close="onClose" :close-on-backdrop-click="false">
<div class="column content-box">
<woot-modal-header
:header-title="$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.TITLE')"
:header-content="
useInstallationName(
$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.DESC'),
globalConfig.installationName
)
"
/>
<form class="row" @submit.prevent="addWebhook">
<div class="medium-12 columns">
<label :class="{ error: $v.endPoint.$error }">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.FORM.END_POINT.LABEL') }}
<input
v-model.trim="endPoint"
type="text"
name="endPoint"
:placeholder="
$t(
'INTEGRATION_SETTINGS.WEBHOOK.ADD.FORM.END_POINT.PLACEHOLDER'
)
"
@input="$v.endPoint.$touch"
/>
<span v-if="$v.endPoint.$error" class="message">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.FORM.END_POINT.ERROR') }}
</span>
</label>
</div>
<div class="modal-footer">
<div class="medium-12 columns">
<woot-button
:disabled="$v.endPoint.$invalid || addWebHook.showLoading"
:is-loading="addWebHook.showLoading"
>
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.FORM.SUBMIT') }}
</woot-button>
<woot-button class="button clear" @click.prevent="onClose">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.CANCEL') }}
</woot-button>
</div>
</div>
</form>
</div>
</modal>
</template>
<script>
import { required, url, minLength } from 'vuelidate/lib/validators';
import alertMixin from 'shared/mixins/alertMixin';
import Modal from '../../../../components/Modal';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import { mapGetters } from 'vuex';
export default {
components: {
Modal,
},
mixins: [alertMixin, globalConfigMixin],
props: {
onClose: {
type: Function,
required: true,
},
},
data() {
return {
endPoint: '',
addWebHook: {
showAlert: false,
showLoading: false,
},
show: true,
};
},
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
},
validations: {
endPoint: {
required,
minLength: minLength(7),
url,
},
},
methods: {
resetForm() {
this.endPoint = '';
this.$v.endPoint.$reset();
},
async addWebhook() {
this.addWebHook.showLoading = true;
try {
await this.$store.dispatch('webhooks/create', {
webhook: { url: this.endPoint },
});
this.addWebHook.showLoading = false;
this.addWebHook.message = this.$t(
'INTEGRATION_SETTINGS.WEBHOOK.ADD.API.SUCCESS_MESSAGE'
);
this.resetForm();
this.onClose();
} catch (error) {
this.addWebHook.showLoading = false;
this.addWebHook.message =
error.response.data.message ||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.ERROR_MESSAGE');
} finally {
this.addWebHook.showLoading = false;
this.showAlert(this.addWebHook.message);
}
},
},
};
</script>

View File

@@ -0,0 +1,61 @@
<template>
<div class="column content-box">
<woot-modal-header
:header-title="$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.TITLE')"
/>
<webhook-form
:value="value"
:is-submitting="uiFlags.updatingItem"
:submit-label="$t('INTEGRATION_SETTINGS.WEBHOOK.FORM.EDIT_SUBMIT')"
@submit="onSubmit"
@cancel="onClose"
/>
</div>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import { mapGetters } from 'vuex';
import WebhookForm from './WebhookForm.vue';
export default {
components: { WebhookForm },
mixins: [alertMixin],
props: {
value: {
type: Object,
required: true,
},
id: {
type: [Number, String],
required: true,
},
onClose: {
type: Function,
required: true,
},
},
computed: {
...mapGetters({ uiFlags: 'webhooks/getUIFlags' }),
},
methods: {
async onSubmit(webhook) {
try {
await this.$store.dispatch('webhooks/update', {
webhook,
id: this.id,
});
this.showAlert(
this.$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.SUCCESS_MESSAGE')
);
this.onClose();
} catch (error) {
const alertMessage =
error.response.data.message ||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.ERROR_MESSAGE');
this.showAlert(alertMessage);
}
},
},
};
</script>

View File

@@ -4,7 +4,7 @@
color-scheme="success"
class-names="button--fixed-right-top"
icon="add-circle"
@click="openAddPopup()"
@click="openAddPopup"
>
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.HEADER_BTN_TXT') }}
</woot-button>
@@ -37,35 +37,14 @@
</th>
</thead>
<tbody>
<tr v-for="(webHookItem, index) in records" :key="webHookItem.id">
<td class="webhook-link">
{{ webHookItem.url }}
</td>
<td class="button-wrapper">
<woot-button
v-tooltip.top="
$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.BUTTON_TEXT')
"
variant="smooth"
size="tiny"
color-scheme="secondary"
icon="edit"
@click="openEditPopup(webHookItem)"
>
</woot-button>
<woot-button
v-tooltip.top="
$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT')
"
variant="smooth"
color-scheme="alert"
size="tiny"
icon="dismiss-circle"
@click="openDeletePopup(webHookItem, index)"
>
</woot-button>
</td>
</tr>
<webhook-row
v-for="(webHookItem, index) in records"
:key="webHookItem.id"
:index="index"
:webhook="webHookItem"
@edit="openEditPopup"
@delete="openDeletePopup"
/>
</tbody>
</table>
</div>
@@ -83,24 +62,27 @@
</div>
<woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup">
<new-webhook :on-close="hideAddPopup" />
<new-webhook v-if="showAddPopup" :on-close="hideAddPopup" />
</woot-modal>
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
<edit-webhook
v-if="showEditPopup"
:id="selectedWebHook.id"
:url="selectedWebHook.url"
:value="selectedWebHook"
:on-close="hideEditPopup"
/>
</woot-modal>
<woot-delete-modal
:show.sync="showDeleteConfirmationPopup"
:on-close="closeDeletePopup"
:on-confirm="confirmDeletion"
:title="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.TITLE')"
:message="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.MESSAGE')"
:message="
$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.MESSAGE', {
webhookURL: selectedWebHook.url,
})
"
:confirm-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.YES')"
:reject-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.NO')"
/>
@@ -112,11 +94,13 @@ import NewWebhook from './NewWebHook';
import EditWebhook from './EditWebHook';
import alertMixin from 'shared/mixins/alertMixin';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import WebhookRow from './WebhookRow';
export default {
components: {
NewWebhook,
EditWebhook,
WebhookRow,
},
mixins: [alertMixin, globalConfigMixin],
data() {
@@ -179,11 +163,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.webhook-link {
word-break: break-word;
}
.button-wrapper button:nth-child(2) {
margin-left: var(--space-normal);
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<div class="column content-box">
<woot-modal-header
:header-title="$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.TITLE')"
:header-content="
useInstallationName(
$t('INTEGRATION_SETTINGS.WEBHOOK.FORM.DESC'),
globalConfig.installationName
)
"
/>
<webhook-form
:is-submitting="uiFlags.creatingItem"
:submit-label="$t('INTEGRATION_SETTINGS.WEBHOOK.FORM.ADD_SUBMIT')"
@submit="onSubmit"
@cancel="onClose"
/>
</div>
</template>
<script>
import alertMixin from 'shared/mixins/alertMixin';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import { mapGetters } from 'vuex';
import WebhookForm from './WebhookForm.vue';
export default {
components: { WebhookForm },
mixins: [alertMixin, globalConfigMixin],
props: {
onClose: {
type: Function,
required: true,
},
},
computed: {
...mapGetters({
globalConfig: 'globalConfig/get',
uiFlags: 'webhooks/getUIFlags',
}),
},
methods: {
async onSubmit(webhook) {
try {
await this.$store.dispatch('webhooks/create', { webhook });
this.showAlert(
this.$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.API.SUCCESS_MESSAGE')
);
this.onClose();
} catch (error) {
const message =
error.response.data.message ||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.ERROR_MESSAGE');
this.showAlert(message);
}
},
},
};
</script>

View File

@@ -0,0 +1,108 @@
<template>
<form class="row" @submit.prevent="onSubmit">
<div class="medium-12 columns">
<label :class="{ error: $v.url.$error }">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.FORM.END_POINT.LABEL') }}
<input
v-model.trim="url"
type="text"
name="url"
:placeholder="
$t('INTEGRATION_SETTINGS.WEBHOOK.FORM.END_POINT.PLACEHOLDER')
"
@input="$v.url.$touch"
/>
<span v-if="$v.url.$error" class="message">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.FORM.END_POINT.ERROR') }}
</span>
</label>
<label :class="{ error: $v.url.$error }" class="margin-bottom-small">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.LABEL') }}
</label>
<div v-for="event in supportedWebhookEvents" :key="event">
<input
:id="event"
v-model="subscriptions"
type="checkbox"
:value="event"
name="subscriptions"
class="margin-right-small"
/>
<span class="fs-small">
{{ `${getEventLabel(event)} (${event})` }}
</span>
</div>
</div>
<div class="modal-footer">
<div class="medium-12 columns">
<woot-button
:disabled="$v.$invalid || isSubmitting"
:is-loading="isSubmitting"
>
{{ submitLabel }}
</woot-button>
<woot-button class="button clear" @click.prevent="$emit('cancel')">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.FORM.CANCEL') }}
</woot-button>
</div>
</div>
</form>
</template>
<script>
import { required, url, minLength } from 'vuelidate/lib/validators';
import webhookMixin from './webhookMixin';
const SUPPORTED_WEBHOOK_EVENTS = [
'conversation_created',
'conversation_status_changed',
'conversation_updated',
'message_created',
'message_updated',
'webwidget_triggered',
];
export default {
mixins: [webhookMixin],
props: {
value: {
type: Object,
default: () => ({}),
},
isSubmitting: {
type: Boolean,
default: false,
},
submitLabel: {
type: String,
required: true,
},
},
validations: {
url: {
required,
minLength: minLength(7),
url,
},
subscriptions: {
required,
},
},
data() {
return {
url: this.value.url || '',
subscriptions: this.value.subscriptions || [],
supportedWebhookEvents: SUPPORTED_WEBHOOK_EVENTS,
};
},
methods: {
onSubmit() {
this.$emit('submit', {
url: this.url,
subscriptions: this.subscriptions,
});
},
},
};
</script>

View File

@@ -0,0 +1,83 @@
<template>
<tr>
<td>
<div class="webhook--link">{{ webhook.url }}</div>
<span class="webhook--subscribed-events">
<span class="webhook--subscribed-label">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.SUBSCRIBED_EVENTS') }}:
</span>
<show-more :text="subscribedEvents" :limit="60" />
</span>
</td>
<td class="button-wrapper">
<woot-button
v-tooltip.top="$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.BUTTON_TEXT')"
variant="smooth"
size="tiny"
color-scheme="secondary"
icon="edit"
@click="$emit('edit', webhook)"
>
</woot-button>
<woot-button
v-tooltip.top="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT')"
variant="smooth"
color-scheme="alert"
size="tiny"
icon="dismiss-circle"
@click="$emit('delete', webhook, index)"
>
</woot-button>
</td>
</tr>
</template>
<script>
import webhookMixin from './webhookMixin';
import ShowMore from 'dashboard/components/widgets/ShowMore';
export default {
components: { ShowMore },
mixins: [webhookMixin],
props: {
webhook: {
type: Object,
required: true,
},
index: {
type: Number,
required: true,
},
},
computed: {
subscribedEvents() {
const { subscriptions } = this.webhook;
return subscriptions.map(event => this.getEventLabel(event)).join(', ');
},
},
};
</script>
<style scoped lang="scss">
.webhook--link {
color: var(--s-700);
font-weight: var(--font-weight-medium);
word-break: break-word;
}
.webhook--subscribed-events {
color: var(--s-500);
font-size: var(--font-size-mini);
}
.webhook--subscribed-label {
font-weight: var(--font-weight-medium);
}
.button-wrapper {
max-width: var(--space-mega);
min-width: auto;
button:nth-child(2) {
margin-left: var(--space-normal);
}
}
</style>

View File

@@ -0,0 +1,26 @@
import { createWrapper } from '@vue/test-utils';
import webhookMixin from '../webhookMixin';
import Vue from 'vue';
describe('webhookMixin', () => {
describe('#getEventLabel', () => {
it('returns correct i18n translation:', () => {
const Component = {
render() {},
title: 'WebhookComponent',
mixins: [webhookMixin],
methods: {
$t(text) {
return text;
},
},
};
const Constructor = Vue.extend(Component);
const vm = new Constructor().$mount();
const wrapper = createWrapper(vm);
expect(wrapper.vm.getEventLabel('message_created')).toEqual(
`INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS.MESSAGE_CREATED`
);
});
});
});

View File

@@ -0,0 +1,10 @@
export default {
methods: {
getEventLabel(event) {
const eventName = event.toUpperCase();
return this.$t(
`INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS.${eventName}`
);
},
},
};

View File

@@ -1,6 +1,6 @@
import Index from './Index';
import SettingsContent from '../Wrapper';
import Webhook from './Webhook';
import Webhook from './Webhooks/Index';
import ShowIntegration from './ShowIntegration';
import { frontendURL } from '../../../../helper/URLHelper';