feat: Update the design for the webhook management page (#10050)

This commit is contained in:
Pranav
2024-08-29 20:29:27 +05:30
committed by GitHub
parent 3a47b7e3d1
commit f087461abc
12 changed files with 157 additions and 201 deletions

View File

@@ -1,38 +1,34 @@
<script> <script setup>
export default { import { computed, ref } from 'vue';
props: { import { useI18n } from 'dashboard/composables/useI18n';
text: {
type: String,
default: '',
},
limit: {
type: Number,
default: 120,
},
},
data() {
return {
showMore: false,
};
},
computed: {
textToBeDisplayed() {
if (this.showMore || this.text.length <= this.limit) {
return this.text;
}
return this.text.slice(0, this.limit) + '...'; const props = defineProps({
}, text: {
buttonLabel() { type: String,
const i18nKey = !this.showMore ? 'SHOW_MORE' : 'SHOW_LESS'; default: '',
return this.$t(`COMPONENTS.SHOW_MORE_BLOCK.${i18nKey}`);
},
}, },
methods: { limit: {
toggleShowMore() { type: Number,
this.showMore = !this.showMore; default: 120,
},
}, },
});
const { t } = useI18n();
const showMore = ref(false);
const textToBeDisplayed = computed(() => {
if (showMore.value || props.text.length <= props.limit) {
return props.text;
}
return props.text.slice(0, props.limit) + '...';
});
const buttonLabel = computed(() => {
const i18nKey = !showMore.value ? 'SHOW_MORE' : 'SHOW_LESS';
return t(`COMPONENTS.SHOW_MORE_BLOCK.${i18nKey}`);
});
const toggleShowMore = () => {
showMore.value = !showMore.value;
}; };
</script> </script>
@@ -41,16 +37,10 @@ export default {
{{ textToBeDisplayed }} {{ textToBeDisplayed }}
<button <button
v-if="text.length > limit" v-if="text.length > limit"
class="show-more--button" class="text-woot-500 !p-0 !border-0 align-top"
@click="toggleShowMore" @click="toggleShowMore"
> >
{{ buttonLabel }} {{ buttonLabel }}
</button> </button>
</span> </span>
</template> </template>
<style scoped>
.show-more--button {
color: var(--w-500);
}
</style>

View File

@@ -17,6 +17,7 @@ const FEATURE_HELP_URLS = {
reports: 'https://chwt.app/hc/reports', reports: 'https://chwt.app/hc/reports',
sla: 'https://chwt.app/hc/sla', sla: 'https://chwt.app/hc/sla',
team_management: 'https://chwt.app/hc/teams', team_management: 'https://chwt.app/hc/teams',
webhook: 'https://chwt.app/hc/webhooks',
}; };
export function getHelpUrlForFeature(featureName) { export function getHelpUrlForFeature(featureName) {

View File

@@ -12,6 +12,7 @@
}, },
"WEBHOOK": { "WEBHOOK": {
"SUBSCRIBED_EVENTS": "Subscribed Events", "SUBSCRIBED_EVENTS": "Subscribed Events",
"LEARN_MORE": "Learn more about webhooks",
"FORM": { "FORM": {
"CANCEL": "Cancel", "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.", "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.",

View File

@@ -2,7 +2,7 @@
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import EditAttribute from './EditAttribute.vue'; import EditAttribute from './EditAttribute.vue';
import { useStoreGetters, useStore } from 'dashboard/composables/store'; import { useStoreGetters, useStore } from 'dashboard/composables/store';
import { computed, onMounted, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n'; import { useI18n } from 'dashboard/composables/useI18n';
const props = defineProps({ const props = defineProps({
attributeModel: { attributeModel: {

View File

@@ -3,16 +3,18 @@ import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import NewWebhook from './NewWebHook.vue'; import NewWebhook from './NewWebHook.vue';
import EditWebhook from './EditWebHook.vue'; import EditWebhook from './EditWebHook.vue';
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
import WebhookRow from './WebhookRow.vue'; import WebhookRow from './WebhookRow.vue';
import BaseSettingsHeader from '../../components/BaseSettingsHeader.vue';
import SettingsLayout from '../../SettingsLayout.vue';
export default { export default {
components: { components: {
SettingsLayout,
BaseSettingsHeader,
NewWebhook, NewWebhook,
EditWebhook, EditWebhook,
WebhookRow, WebhookRow,
}, },
mixins: [globalConfigMixin],
data() { data() {
return { return {
loading: {}, loading: {},
@@ -26,8 +28,10 @@ export default {
...mapGetters({ ...mapGetters({
records: 'webhooks/getWebhooks', records: 'webhooks/getWebhooks',
uiFlags: 'webhooks/getUIFlags', uiFlags: 'webhooks/getUIFlags',
globalConfig: 'globalConfig/get',
}), }),
integration() {
return this.$store.getters['integrations/getIntegration']('webhook');
},
}, },
mounted() { mounted() {
this.$store.dispatch('webhooks/get'); this.$store.dispatch('webhooks/get');
@@ -75,69 +79,59 @@ export default {
</script> </script>
<template> <template>
<div class="flex-1 p-4 overflow-auto"> <SettingsLayout
<woot-button :is-loading="uiFlags.fetchingList"
color-scheme="success" :loading-message="$t('INTEGRATION_SETTINGS.WEBHOOK.LOADING')"
class-names="button--fixed-top" :no-records-message="$t('INTEGRATION_SETTINGS.WEBHOOK.LIST.404')"
icon="add-circle" :no-records-found="!records.length"
@click="openAddPopup" >
> <template #header>
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.HEADER_BTN_TXT') }} <BaseSettingsHeader
</woot-button> v-if="integration.name"
:title="integration.name"
<div class="flex flex-row gap-4"> :description="integration.description"
<div class="w-full lg:w-3/5"> :link-text="$t('INTEGRATION_SETTINGS.WEBHOOK.LEARN_MORE')"
<p feature-name="webhook"
v-if="!uiFlags.fetchingList && !records.length" :back-button-label="$t('INTEGRATION_SETTINGS.HEADER')"
class="flex flex-col items-center justify-center h-full" >
<template #actions>
<woot-button
class="button nice rounded-md"
icon="add-circle"
@click="openAddPopup"
>
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.HEADER_BTN_TXT') }}
</woot-button>
</template>
</BaseSettingsHeader>
</template>
<template #body>
<table class="min-w-full divide-y divide-slate-75 dark:divide-slate-700">
<thead>
<th
v-for="thHeader in $t(
'INTEGRATION_SETTINGS.WEBHOOK.LIST.TABLE_HEADER'
)"
:key="thHeader"
class="py-4 pr-4 text-left font-semibold text-slate-700 dark:text-slate-300 last:text-right last:pr-4"
>
{{ thHeader }}
</th>
</thead>
<tbody
class="divide-y divide-slate-25 dark:divide-slate-800 flex-1 text-slate-700 dark:text-slate-100"
> >
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.LIST.404') }} <WebhookRow
</p> v-for="(webHookItem, index) in records"
<woot-loading-state :key="webHookItem.id"
v-if="uiFlags.fetchingList" :index="index"
:message="$t('INTEGRATION_SETTINGS.WEBHOOK.LOADING')" :webhook="webHookItem"
/> @edit="openEditPopup"
@delete="openDeletePopup"
<table />
v-if="!uiFlags.fetchingList && records.length" </tbody>
class="woot-table" </table>
> </template>
<thead>
<th
v-for="thHeader in $t(
'INTEGRATION_SETTINGS.WEBHOOK.LIST.TABLE_HEADER'
)"
:key="thHeader"
class="last:text-right"
>
{{ thHeader }}
</th>
</thead>
<tbody>
<WebhookRow
v-for="(webHookItem, index) in records"
:key="webHookItem.id"
:index="index"
:webhook="webHookItem"
@edit="openEditPopup"
@delete="openDeletePopup"
/>
</tbody>
</table>
</div>
<div class="hidden w-1/3 lg:block">
<span
v-dompurify-html="
useInstallationName(
$t('INTEGRATION_SETTINGS.WEBHOOK.SIDEBAR_TXT'),
globalConfig.installationName
)
"
/>
</div>
</div>
<woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup"> <woot-modal :show.sync="showAddPopup" :on-close="hideAddPopup">
<NewWebhook v-if="showAddPopup" :on-close="hideAddPopup" /> <NewWebhook v-if="showAddPopup" :on-close="hideAddPopup" />
</woot-modal> </woot-modal>
@@ -163,5 +157,5 @@ export default {
:confirm-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.YES')" :confirm-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.YES')"
:reject-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.NO')" :reject-text="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.CONFIRM.NO')"
/> />
</div> </SettingsLayout>
</template> </template>

View File

@@ -1,8 +1,8 @@
<script> <script>
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { required, url, minLength } from '@vuelidate/validators'; import { required, url, minLength } from '@vuelidate/validators';
import webhookMixin from './webhookMixin';
import wootConstants from 'dashboard/constants/globals'; import wootConstants from 'dashboard/constants/globals';
import { getEventNamei18n } from './webhookHelper';
const { EXAMPLE_WEBHOOK_URL } = wootConstants; const { EXAMPLE_WEBHOOK_URL } = wootConstants;
@@ -18,7 +18,6 @@ const SUPPORTED_WEBHOOK_EVENTS = [
]; ];
export default { export default {
mixins: [webhookMixin],
props: { props: {
value: { value: {
type: Object, type: Object,
@@ -70,6 +69,7 @@ export default {
subscriptions: this.subscriptions, subscriptions: this.subscriptions,
}); });
}, },
getEventNamei18n,
}, },
}; };
</script> </script>
@@ -105,10 +105,10 @@ export default {
type="checkbox" type="checkbox"
:value="event" :value="event"
name="subscriptions" name="subscriptions"
class="checkbox" class="mr-2"
/> />
<label :for="event" class="text-sm"> <label :for="event" class="text-sm">
{{ `${getEventLabel(event)} (${event})` }} {{ `${$t(getEventNamei18n(event))} (${event})` }}
</label> </label>
</div> </div>
</div> </div>
@@ -129,9 +129,3 @@ export default {
</div> </div>
</form> </form>
</template> </template>
<style lang="scss" scoped>
.checkbox {
@apply mr-2;
}
</style>

View File

@@ -1,59 +1,58 @@
<script> <script setup>
import webhookMixin from './webhookMixin'; import { computed } from 'vue';
import { getEventNamei18n } from './webhookHelper';
import ShowMore from 'dashboard/components/widgets/ShowMore.vue'; import ShowMore from 'dashboard/components/widgets/ShowMore.vue';
import { useI18n } from 'dashboard/composables/useI18n';
export default { const props = defineProps({
components: { ShowMore }, webhook: {
mixins: [webhookMixin], type: Object,
props: { required: true,
webhook: {
type: Object,
required: true,
},
index: {
type: Number,
required: true,
},
}, },
computed: { index: {
subscribedEvents() { type: Number,
const { subscriptions } = this.webhook; required: true,
return subscriptions.map(event => this.getEventLabel(event)).join(', ');
},
}, },
}; });
const { t } = useI18n();
const subscribedEvents = computed(() => {
const { subscriptions } = props.webhook;
return subscriptions.map(event => t(getEventNamei18n(event))).join(', ');
});
</script> </script>
<template> <template>
<tr class="space-x-2"> <tr>
<td class="max-w-2xl"> <td class="py-4 ltr:pr-4 rtl:pl-4">
<div class="font-medium break-words text-slate-700 dark:text-slate-100"> <div class="font-medium break-words text-slate-700 dark:text-slate-100">
{{ webhook.url }} {{ webhook.url }}
</div> </div>
<span class="text-xs text-slate-500 dark:text-slate-400"> <div class="text-sm text-slate-500 dark:text-slate-400 block mt-1">
<span class="font-medium"> <span class="font-medium">
{{ $t('INTEGRATION_SETTINGS.WEBHOOK.SUBSCRIBED_EVENTS') }}: {{ $t('INTEGRATION_SETTINGS.WEBHOOK.SUBSCRIBED_EVENTS') }}:
</span> </span>
<ShowMore :text="subscribedEvents" :limit="60" /> <ShowMore :text="subscribedEvents" :limit="60" />
</span> </div>
</td> </td>
<td class="min-w-[7rem] flex gap-1 justify-end flex-shrink-0"> <td class="py-4 min-w-xs">
<woot-button <div class="flex gap-1 justify-end">
v-tooltip.top="$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.BUTTON_TEXT')" <woot-button
variant="smooth" v-tooltip.top="$t('INTEGRATION_SETTINGS.WEBHOOK.EDIT.BUTTON_TEXT')"
size="tiny" variant="smooth"
color-scheme="secondary" size="tiny"
icon="edit" color-scheme="secondary"
@click="$emit('edit', webhook)" icon="edit"
/> @click="$emit('edit', webhook)"
<woot-button />
v-tooltip.top="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT')" <woot-button
variant="smooth" v-tooltip.top="$t('INTEGRATION_SETTINGS.WEBHOOK.DELETE.BUTTON_TEXT')"
color-scheme="alert" variant="smooth"
size="tiny" color-scheme="alert"
icon="dismiss-circle" size="tiny"
@click="$emit('delete', webhook, index)" icon="dismiss-circle"
/> @click="$emit('delete', webhook, index)"
/>
</div>
</td> </td>
</tr> </tr>
</template> </template>

View File

@@ -0,0 +1,9 @@
import { getEventNamei18n } from '../webhookHelper';
describe('#getEventNamei18n', () => {
it('returns correct i18n translation text', () => {
expect(getEventNamei18n('message_created')).toEqual(
`INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS.MESSAGE_CREATED`
);
});
});

View File

@@ -1,26 +0,0 @@
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,4 @@
export const getEventNamei18n = event => {
const eventName = event.toUpperCase();
return `INTEGRATION_SETTINGS.WEBHOOK.FORM.SUBSCRIPTIONS.EVENTS.${eventName}`;
};

View File

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

View File

@@ -30,6 +30,14 @@ export default {
permissions: ['administrator'], permissions: ['administrator'],
}, },
}, },
{
path: 'webhook',
component: Webhook,
name: 'settings_integrations_webhook',
meta: {
permissions: ['administrator'],
},
},
], ],
}, },
{ {
@@ -49,14 +57,6 @@ export default {
}; };
}, },
children: [ children: [
{
path: 'webhook',
component: Webhook,
name: 'settings_integrations_webhook',
meta: {
permissions: ['administrator'],
},
},
{ {
path: 'slack', path: 'slack',
name: 'settings_integrations_slack', name: 'settings_integrations_slack',