feat: Add an option to edit webhook URL (#2316)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Muhsin Keloth 2021-06-06 16:59:47 +05:30 committed by GitHub
parent 14b51e108a
commit 94a83ea995
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 233 additions and 33 deletions

View file

@ -12,10 +12,25 @@
"LIST": {
"404": "There are no webhooks configured for this account.",
"TITLE": "Manage webhooks",
"TABLE_HEADER": [
"Webhook endpoint",
"Actions"
]
"TABLE_HEADER": ["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": {
"CANCEL": "Cancel",

View file

@ -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>

View file

@ -28,14 +28,15 @@
<div class="modal-footer">
<div class="medium-12 columns">
<woot-submit-button
<woot-button
:disabled="$v.endPoint.$invalid || addWebHook.showLoading"
:button-text="$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.FORM.SUBMIT')"
:loading="addWebHook.showLoading"
/>
<button class="button clear" @click.prevent="onClose">
: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') }}
</button>
</woot-button>
</div>
</div>
</form>
@ -45,15 +46,14 @@
<script>
import { required, url, minLength } from 'vuelidate/lib/validators';
import WootSubmitButton from '../../../../components/buttons/FormSubmitButton';
import alertMixin from 'shared/mixins/alertMixin';
import Modal from '../../../../components/Modal';
export default {
components: {
WootSubmitButton,
Modal,
},
mixins: [alertMixin],
props: {
onClose: {
type: Function,
@ -66,7 +66,6 @@ export default {
addWebHook: {
showAlert: false,
showLoading: false,
message: '',
},
show: true,
};
@ -79,9 +78,6 @@ export default {
},
},
methods: {
showAlert() {
bus.$emit('newToastMessage', this.addWebHook.message);
},
resetForm() {
this.endPoint = '';
this.$v.endPoint.$reset();
@ -94,18 +90,20 @@ export default {
webhook: { url: this.endPoint },
});
this.addWebHook.showLoading = false;
this.addWebHook.message = this.$t(
'INTEGRATION_SETTINGS.WEBHOOK.ADD.API.SUCCESS_MESSAGE'
);
this.showAlert();
this.resetForm();
this.onClose();
} catch (error) {
this.addWebHook.showLoading = false;
this.addWebHook.message =
error.response.data.message ||
this.$t('INTEGRATION_SETTINGS.WEBHOOK.ADD.API.ERROR_MESSAGE');
this.showAlert();
this.$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.API.ERROR_MESSAGE');
} finally {
this.addWebHook.showLoading = false;
this.showAlert(this.addWebHook.message);
}
},
},

View file

@ -42,16 +42,22 @@
{{ webHookItem.url }}
</td>
<td class="button-wrapper">
<div @click="openDeletePopup(webHookItem, index)">
<woot-button
:is-loading="loading[webHookItem.id]"
icon="ion-close-circled"
variant="link"
color-scheme="secondary"
icon="ion-edit"
@click="openEditPopup(webHookItem)"
>
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.BUTTON_TEXT') }}
</woot-button>
<woot-button
variant="link"
icon="ion-close-circled"
color-scheme="secondary"
@click="openDeletePopup(webHookItem, index)"
>
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT') }}
</woot-button>
</div>
</td>
</tr>
</tbody>
@ -74,6 +80,15 @@
<new-webhook :on-close="hideAddPopup" />
</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
:show.sync="showDeleteConfirmationPopup"
:on-close="closeDeletePopup"
@ -87,19 +102,22 @@
</template>
<script>
import { mapGetters } from 'vuex';
import NewWebhook from './New';
import NewWebhook from './NewWebHook';
import EditWebhook from './EditWebHook';
import alertMixin from 'shared/mixins/alertMixin';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
export default {
components: {
NewWebhook,
EditWebhook,
},
mixins: [alertMixin, globalConfigMixin],
data() {
return {
loading: {},
showAddPopup: false,
showEditPopup: false,
showDeleteConfirmationPopup: false,
selectedWebHook: {},
};
@ -128,6 +146,13 @@ export default {
closeDeletePopup() {
this.showDeleteConfirmationPopup = false;
},
openEditPopup(webhook) {
this.showEditPopup = true;
this.selectedWebHook = webhook;
},
hideEditPopup() {
this.showEditPopup = false;
},
confirmDeletion() {
this.loading[this.selectedWebHook.id] = true;
this.closeDeletePopup();
@ -152,4 +177,7 @@ export default {
.webhook-link {
word-break: break-word;
}
.button-wrapper button:nth-child(2) {
margin-left: var(--space-normal);
}
</style>

View file

@ -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', () => {
it('sends correct actions if API is success', async () => {
axios.delete.mockResolvedValue({ data: webhooks[0] });

View file

@ -30,4 +30,14 @@ describe('#mutations', () => {
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');
});
});
});

View file

@ -8,6 +8,7 @@ const state = {
fetchingList: false,
creatingItem: false,
deletingItem: false,
updatingItem: false,
},
};
@ -36,7 +37,10 @@ export const actions = {
commit(types.default.SET_WEBHOOK_UI_FLAG, { creatingItem: true });
try {
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 });
} catch (error) {
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) {
commit(types.default.SET_WEBHOOK_UI_FLAG, { deletingItem: true });
try {
@ -64,10 +80,10 @@ export const mutations = {
...data,
};
},
[types.default.SET_WEBHOOK]: MutationHelpers.set,
[types.default.ADD_WEBHOOK]: MutationHelpers.create,
[types.default.DELETE_WEBHOOK]: MutationHelpers.destroy,
[types.default.UPDATE_WEBHOOK]: MutationHelpers.update,
};
export default {

View file

@ -91,6 +91,7 @@ export default {
SET_WEBHOOK: 'SET_WEBHOOK',
ADD_WEBHOOK: 'ADD_WEBHOOK',
DELETE_WEBHOOK: 'DELETE_WEBHOOK',
UPDATE_WEBHOOK: 'UPDATE_WEBHOOK',
// Contacts
SET_CONTACT_META: 'SET_CONTACT_META',