feat: Add an option to edit webhook URL (#2316)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
@@ -12,10 +12,25 @@
|
|||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "There are no webhooks configured for this account.",
|
"404": "There are no webhooks configured for this account.",
|
||||||
"TITLE": "Manage webhooks",
|
"TITLE": "Manage webhooks",
|
||||||
"TABLE_HEADER": [
|
"TABLE_HEADER": ["Webhook endpoint", "Actions"]
|
||||||
"Webhook endpoint",
|
},
|
||||||
"Actions"
|
"EDIT": {
|
||||||
]
|
"BUTTON_TEXT": "Edit",
|
||||||
|
"TITLE": "Edit webhook",
|
||||||
|
"CANCEL": "Cancel",
|
||||||
|
"DESC": "Webhook events provide you the realtime information about what's happening in your Chatwoot account. Please enter a valid URL to configure a callback.",
|
||||||
|
"FORM": {
|
||||||
|
"END_POINT": {
|
||||||
|
"LABEL": "Webhook URL",
|
||||||
|
"PLACEHOLDER": "Example: https://example/api/webhook",
|
||||||
|
"ERROR": "Please enter a valid URL"
|
||||||
|
},
|
||||||
|
"SUBMIT": "Edit webhook"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Webhook URL updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ADD": {
|
"ADD": {
|
||||||
"CANCEL": "Cancel",
|
"CANCEL": "Cancel",
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<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>
|
||||||
@@ -28,14 +28,15 @@
|
|||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="medium-12 columns">
|
<div class="medium-12 columns">
|
||||||
<woot-submit-button
|
<woot-button
|
||||||
:disabled="$v.endPoint.$invalid || addWebHook.showLoading"
|
:disabled="$v.endPoint.$invalid || addWebHook.showLoading"
|
||||||
:button-text="$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.FORM.SUBMIT')"
|
:is-loading="addWebHook.showLoading"
|
||||||
:loading="addWebHook.showLoading"
|
>
|
||||||
/>
|
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.FORM.SUBMIT') }}
|
||||||
<button class="button clear" @click.prevent="onClose">
|
</woot-button>
|
||||||
|
<woot-button class="button clear" @click.prevent="onClose">
|
||||||
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.CANCEL') }}
|
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.ADD.CANCEL') }}
|
||||||
</button>
|
</woot-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -45,15 +46,14 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { required, url, minLength } from 'vuelidate/lib/validators';
|
import { required, url, minLength } from 'vuelidate/lib/validators';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import WootSubmitButton from '../../../../components/buttons/FormSubmitButton';
|
|
||||||
import Modal from '../../../../components/Modal';
|
import Modal from '../../../../components/Modal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
WootSubmitButton,
|
|
||||||
Modal,
|
Modal,
|
||||||
},
|
},
|
||||||
|
mixins: [alertMixin],
|
||||||
props: {
|
props: {
|
||||||
onClose: {
|
onClose: {
|
||||||
type: Function,
|
type: Function,
|
||||||
@@ -66,7 +66,6 @@ export default {
|
|||||||
addWebHook: {
|
addWebHook: {
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
showLoading: false,
|
showLoading: false,
|
||||||
message: '',
|
|
||||||
},
|
},
|
||||||
show: true,
|
show: true,
|
||||||
};
|
};
|
||||||
@@ -79,9 +78,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showAlert() {
|
|
||||||
bus.$emit('newToastMessage', this.addWebHook.message);
|
|
||||||
},
|
|
||||||
resetForm() {
|
resetForm() {
|
||||||
this.endPoint = '';
|
this.endPoint = '';
|
||||||
this.$v.endPoint.$reset();
|
this.$v.endPoint.$reset();
|
||||||
@@ -94,18 +90,20 @@ export default {
|
|||||||
webhook: { url: this.endPoint },
|
webhook: { url: this.endPoint },
|
||||||
});
|
});
|
||||||
this.addWebHook.showLoading = false;
|
this.addWebHook.showLoading = false;
|
||||||
|
|
||||||
this.addWebHook.message = this.$t(
|
this.addWebHook.message = this.$t(
|
||||||
'INTEGRATION_SETTINGS.WEBHOOK.ADD.API.SUCCESS_MESSAGE'
|
'INTEGRATION_SETTINGS.WEBHOOK.ADD.API.SUCCESS_MESSAGE'
|
||||||
);
|
);
|
||||||
this.showAlert();
|
|
||||||
this.resetForm();
|
this.resetForm();
|
||||||
this.onClose();
|
this.onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.addWebHook.showLoading = false;
|
this.addWebHook.showLoading = false;
|
||||||
this.addWebHook.message =
|
this.addWebHook.message =
|
||||||
error.response.data.message ||
|
error.response.data.message ||
|
||||||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.API.ERROR_MESSAGE');
|
this.$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.ERROR_MESSAGE');
|
||||||
this.showAlert();
|
} finally {
|
||||||
|
this.addWebHook.showLoading = false;
|
||||||
|
this.showAlert(this.addWebHook.message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -42,16 +42,22 @@
|
|||||||
{{ webHookItem.url }}
|
{{ webHookItem.url }}
|
||||||
</td>
|
</td>
|
||||||
<td class="button-wrapper">
|
<td class="button-wrapper">
|
||||||
<div @click="openDeletePopup(webHookItem, index)">
|
<woot-button
|
||||||
<woot-button
|
variant="link"
|
||||||
:is-loading="loading[webHookItem.id]"
|
color-scheme="secondary"
|
||||||
icon="ion-close-circled"
|
icon="ion-edit"
|
||||||
variant="link"
|
@click="openEditPopup(webHookItem)"
|
||||||
color-scheme="secondary"
|
>
|
||||||
>
|
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.BUTTON_TEXT') }}
|
||||||
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT') }}
|
</woot-button>
|
||||||
</woot-button>
|
<woot-button
|
||||||
</div>
|
variant="link"
|
||||||
|
icon="ion-close-circled"
|
||||||
|
color-scheme="secondary"
|
||||||
|
@click="openDeletePopup(webHookItem, index)"
|
||||||
|
>
|
||||||
|
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT') }}
|
||||||
|
</woot-button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -74,6 +80,15 @@
|
|||||||
<new-webhook :on-close="hideAddPopup" />
|
<new-webhook :on-close="hideAddPopup" />
|
||||||
</woot-modal>
|
</woot-modal>
|
||||||
|
|
||||||
|
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
|
||||||
|
<edit-webhook
|
||||||
|
v-if="showEditPopup"
|
||||||
|
:id="selectedWebHook.id"
|
||||||
|
:url="selectedWebHook.url"
|
||||||
|
:on-close="hideEditPopup"
|
||||||
|
/>
|
||||||
|
</woot-modal>
|
||||||
|
|
||||||
<woot-delete-modal
|
<woot-delete-modal
|
||||||
:show.sync="showDeleteConfirmationPopup"
|
:show.sync="showDeleteConfirmationPopup"
|
||||||
:on-close="closeDeletePopup"
|
:on-close="closeDeletePopup"
|
||||||
@@ -87,19 +102,22 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import NewWebhook from './New';
|
import NewWebhook from './NewWebHook';
|
||||||
|
import EditWebhook from './EditWebHook';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
NewWebhook,
|
NewWebhook,
|
||||||
|
EditWebhook,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin, globalConfigMixin],
|
mixins: [alertMixin, globalConfigMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: {},
|
loading: {},
|
||||||
showAddPopup: false,
|
showAddPopup: false,
|
||||||
|
showEditPopup: false,
|
||||||
showDeleteConfirmationPopup: false,
|
showDeleteConfirmationPopup: false,
|
||||||
selectedWebHook: {},
|
selectedWebHook: {},
|
||||||
};
|
};
|
||||||
@@ -128,6 +146,13 @@ export default {
|
|||||||
closeDeletePopup() {
|
closeDeletePopup() {
|
||||||
this.showDeleteConfirmationPopup = false;
|
this.showDeleteConfirmationPopup = false;
|
||||||
},
|
},
|
||||||
|
openEditPopup(webhook) {
|
||||||
|
this.showEditPopup = true;
|
||||||
|
this.selectedWebHook = webhook;
|
||||||
|
},
|
||||||
|
hideEditPopup() {
|
||||||
|
this.showEditPopup = false;
|
||||||
|
},
|
||||||
confirmDeletion() {
|
confirmDeletion() {
|
||||||
this.loading[this.selectedWebHook.id] = true;
|
this.loading[this.selectedWebHook.id] = true;
|
||||||
this.closeDeletePopup();
|
this.closeDeletePopup();
|
||||||
@@ -152,4 +177,7 @@ export default {
|
|||||||
.webhook-link {
|
.webhook-link {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
.button-wrapper button:nth-child(2) {
|
||||||
|
margin-left: var(--space-normal);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -52,6 +52,30 @@ describe('#actions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#update', () => {
|
||||||
|
it('sends correct actions if API is success', async () => {
|
||||||
|
axios.patch.mockResolvedValue({
|
||||||
|
data: { payload: { webhook: webhooks[1] } },
|
||||||
|
});
|
||||||
|
await actions.update({ commit }, webhooks[1]);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.default.SET_WEBHOOK_UI_FLAG, { updatingItem: true }],
|
||||||
|
[types.default.UPDATE_WEBHOOK, webhooks[1]],
|
||||||
|
[types.default.SET_WEBHOOK_UI_FLAG, { updatingItem: false }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('sends correct actions if API is error', async () => {
|
||||||
|
axios.put.mockRejectedValue({ message: 'Incorrect header' });
|
||||||
|
await expect(actions.update({ commit }, webhooks[0])).rejects.toThrow(
|
||||||
|
Error
|
||||||
|
);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.default.SET_WEBHOOK_UI_FLAG, { updatingItem: true }],
|
||||||
|
[types.default.SET_WEBHOOK_UI_FLAG, { updatingItem: false }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#delete', () => {
|
describe('#delete', () => {
|
||||||
it('sends correct actions if API is success', async () => {
|
it('sends correct actions if API is success', async () => {
|
||||||
axios.delete.mockResolvedValue({ data: webhooks[0] });
|
axios.delete.mockResolvedValue({ data: webhooks[0] });
|
||||||
|
|||||||
@@ -30,4 +30,14 @@ describe('#mutations', () => {
|
|||||||
expect(state.records).toEqual([]);
|
expect(state.records).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#UPDATE_WEBHOOK', () => {
|
||||||
|
it('update webhook ', () => {
|
||||||
|
const state = {
|
||||||
|
records: [webhooks[0]],
|
||||||
|
};
|
||||||
|
mutations[types.default.UPDATE_WEBHOOK](state, webhooks[0]);
|
||||||
|
expect(state.records[0].url).toEqual('https://1.chatwoot.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const state = {
|
|||||||
fetchingList: false,
|
fetchingList: false,
|
||||||
creatingItem: false,
|
creatingItem: false,
|
||||||
deletingItem: false,
|
deletingItem: false,
|
||||||
|
updatingItem: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,7 +37,10 @@ export const actions = {
|
|||||||
commit(types.default.SET_WEBHOOK_UI_FLAG, { creatingItem: true });
|
commit(types.default.SET_WEBHOOK_UI_FLAG, { creatingItem: true });
|
||||||
try {
|
try {
|
||||||
const response = await webHookAPI.create(params);
|
const response = await webHookAPI.create(params);
|
||||||
commit(types.default.ADD_WEBHOOK, response.data.payload.webhook);
|
const {
|
||||||
|
payload: { webhook },
|
||||||
|
} = response.data;
|
||||||
|
commit(types.default.ADD_WEBHOOK, webhook);
|
||||||
commit(types.default.SET_WEBHOOK_UI_FLAG, { creatingItem: false });
|
commit(types.default.SET_WEBHOOK_UI_FLAG, { creatingItem: false });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
commit(types.default.SET_WEBHOOK_UI_FLAG, { creatingItem: false });
|
commit(types.default.SET_WEBHOOK_UI_FLAG, { creatingItem: false });
|
||||||
@@ -44,6 +48,18 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
update: async ({ commit }, { id, ...updateObj }) => {
|
||||||
|
commit(types.default.SET_WEBHOOK_UI_FLAG, { updatingItem: true });
|
||||||
|
try {
|
||||||
|
const response = await webHookAPI.update(id, updateObj);
|
||||||
|
commit(types.default.UPDATE_WEBHOOK, response.data.payload.webhook);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit(types.default.SET_WEBHOOK_UI_FLAG, { updatingItem: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async delete({ commit }, id) {
|
async delete({ commit }, id) {
|
||||||
commit(types.default.SET_WEBHOOK_UI_FLAG, { deletingItem: true });
|
commit(types.default.SET_WEBHOOK_UI_FLAG, { deletingItem: true });
|
||||||
try {
|
try {
|
||||||
@@ -64,10 +80,10 @@ export const mutations = {
|
|||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.default.SET_WEBHOOK]: MutationHelpers.set,
|
[types.default.SET_WEBHOOK]: MutationHelpers.set,
|
||||||
[types.default.ADD_WEBHOOK]: MutationHelpers.create,
|
[types.default.ADD_WEBHOOK]: MutationHelpers.create,
|
||||||
[types.default.DELETE_WEBHOOK]: MutationHelpers.destroy,
|
[types.default.DELETE_WEBHOOK]: MutationHelpers.destroy,
|
||||||
|
[types.default.UPDATE_WEBHOOK]: MutationHelpers.update,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export default {
|
|||||||
SET_WEBHOOK: 'SET_WEBHOOK',
|
SET_WEBHOOK: 'SET_WEBHOOK',
|
||||||
ADD_WEBHOOK: 'ADD_WEBHOOK',
|
ADD_WEBHOOK: 'ADD_WEBHOOK',
|
||||||
DELETE_WEBHOOK: 'DELETE_WEBHOOK',
|
DELETE_WEBHOOK: 'DELETE_WEBHOOK',
|
||||||
|
UPDATE_WEBHOOK: 'UPDATE_WEBHOOK',
|
||||||
|
|
||||||
// Contacts
|
// Contacts
|
||||||
SET_CONTACT_META: 'SET_CONTACT_META',
|
SET_CONTACT_META: 'SET_CONTACT_META',
|
||||||
|
|||||||
Reference in New Issue
Block a user