fix: Remove validation for WhatsApp templates with no params (#4820)
This commit is contained in:
parent
6385e42d9b
commit
c5c89449dd
3 changed files with 375 additions and 40 deletions
|
@ -6,30 +6,28 @@
|
||||||
readonly
|
readonly
|
||||||
class="template-input"
|
class="template-input"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div>
|
<div v-if="variables" class="template__variables-container">
|
||||||
<div class="template__variables-container">
|
<p class="variables-label">
|
||||||
<p class="variables-label">
|
{{ $t('WHATSAPP_TEMPLATES.PARSER.VARIABLES_LABEL') }}
|
||||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.VARIABLES_LABEL') }}
|
</p>
|
||||||
</p>
|
<div
|
||||||
<div
|
v-for="(variable, key) in processedParams"
|
||||||
v-for="(variable, key) in processedParams"
|
:key="key"
|
||||||
:key="key"
|
class="template__variable-item"
|
||||||
class="template__variable-item"
|
>
|
||||||
>
|
<span class="variable-label">
|
||||||
<span class="variable-label">
|
{{ key }}
|
||||||
{{ key }}
|
</span>
|
||||||
</span>
|
<woot-input
|
||||||
<woot-input
|
v-model="processedParams[key]"
|
||||||
v-model="processedParams[key]"
|
type="text"
|
||||||
type="text"
|
class="variable-input"
|
||||||
class="variable-input"
|
:styles="{ marginBottom: 0 }"
|
||||||
:styles="{ marginBottom: 0 }"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p v-if="showRequiredMessage" class="error">
|
|
||||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.FORM_ERROR_MESSAGE') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="$v.$dirty && $v.$invalid" class="error">
|
||||||
|
{{ $t('WHATSAPP_TEMPLATES.PARSER.FORM_ERROR_MESSAGE') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<woot-button variant="smooth" @click="$emit('resetTemplate')">
|
<woot-button variant="smooth" @click="$emit('resetTemplate')">
|
||||||
|
@ -43,12 +41,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { required } from 'vuelidate/lib/validators';
|
|
||||||
|
|
||||||
const allKeysRequired = value => {
|
const allKeysRequired = value => {
|
||||||
const keys = Object.keys(value);
|
const keys = Object.keys(value);
|
||||||
return keys.every(key => value[key]);
|
return keys.every(key => value[key]);
|
||||||
};
|
};
|
||||||
|
import { requiredIf } from 'vuelidate/lib/validators';
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
template: {
|
template: {
|
||||||
|
@ -58,15 +55,13 @@ export default {
|
||||||
},
|
},
|
||||||
validations: {
|
validations: {
|
||||||
processedParams: {
|
processedParams: {
|
||||||
required,
|
requiredIfKeysPresent: requiredIf('variables'),
|
||||||
allKeysRequired,
|
allKeysRequired,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
message: this.template.message,
|
|
||||||
processedParams: {},
|
processedParams: {},
|
||||||
showRequiredMessage: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -92,11 +87,8 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
sendMessage() {
|
sendMessage() {
|
||||||
this.$v.$touch();
|
this.$v.$touch();
|
||||||
if (this.$v.$invalid) {
|
if (this.$v.$invalid) return;
|
||||||
this.showRequiredMessage = true;
|
const payload = {
|
||||||
return;
|
|
||||||
}
|
|
||||||
const message = {
|
|
||||||
message: this.processedString,
|
message: this.processedString,
|
||||||
templateParams: {
|
templateParams: {
|
||||||
name: this.template.name,
|
name: this.template.name,
|
||||||
|
@ -106,18 +98,16 @@ export default {
|
||||||
processed_params: this.processedParams,
|
processed_params: this.processedParams,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
this.$emit('sendMessage', message);
|
this.$emit('sendMessage', payload);
|
||||||
},
|
},
|
||||||
processVariable(str) {
|
processVariable(str) {
|
||||||
return str.replace(/{{|}}/g, '');
|
return str.replace(/{{|}}/g, '');
|
||||||
},
|
},
|
||||||
generateVariables() {
|
generateVariables() {
|
||||||
const templateString = this.template.components.find(
|
const matchedVariables = this.templateString.match(/{{([^}]+)}}/g);
|
||||||
component => component.type === 'BODY'
|
if (!matchedVariables) return;
|
||||||
).text;
|
|
||||||
const variables = templateString.match(/{{([^}]+)}}/g).map(variable => {
|
const variables = matchedVariables.map(i => this.processVariable(i));
|
||||||
return this.processVariable(variable);
|
|
||||||
});
|
|
||||||
this.processedParams = variables.reduce((acc, variable) => {
|
this.processedParams = variables.reduce((acc, variable) => {
|
||||||
acc[variable] = '';
|
acc[variable] = '';
|
||||||
return acc;
|
return acc;
|
||||||
|
|
268
app/javascript/shared/mixins/specs/whatsappTemplates/fixtures.js
Normal file
268
app/javascript/shared/mixins/specs/whatsappTemplates/fixtures.js
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
export const templates = [
|
||||||
|
{
|
||||||
|
name: 'sample_flight_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'TICKET_UPDATE',
|
||||||
|
language: 'pt_BR',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{ type: 'HEADER', format: 'DOCUMENT' },
|
||||||
|
{
|
||||||
|
text: 'Esta é a sua confirmação de voo para {{1}}-{{2}} em {{3}}.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Esta mensagem é de uma empresa não verificada.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_issue_resolution',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'ISSUE_RESOLUTION',
|
||||||
|
language: 'pt_BR',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text:
|
||||||
|
'Oi, {{1}}. Nós conseguimos resolver o problema que você estava enfrentando?',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Esta mensagem é de uma empresa não verificada.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{ text: 'Sim', type: 'QUICK_REPLY' },
|
||||||
|
{ text: 'Não', type: 'QUICK_REPLY' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_issue_resolution',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'ISSUE_RESOLUTION',
|
||||||
|
language: 'es',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Hola, {{1}}. ¿Pudiste solucionar el problema que tenías?',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Este mensaje proviene de un negocio no verificado.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{ text: 'Sí', type: 'QUICK_REPLY' },
|
||||||
|
{ text: 'No', type: 'QUICK_REPLY' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_issue_resolution',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'ISSUE_RESOLUTION',
|
||||||
|
language: 'id',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text:
|
||||||
|
'Halo {{1}}, apakah kami bisa mengatasi masalah yang sedang Anda hadapi?',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Pesan ini berasal dari bisnis yang tidak terverifikasi.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{ text: 'Ya', type: 'QUICK_REPLY' },
|
||||||
|
{ text: 'Tidak', type: 'QUICK_REPLY' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_shipping_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'SHIPPING_UPDATE',
|
||||||
|
language: 'pt_BR',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Seu pacote foi enviado. Ele será entregue em {{1}} dias úteis.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Esta mensagem é de uma empresa não verificada.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_shipping_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'SHIPPING_UPDATE',
|
||||||
|
language: 'id',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text:
|
||||||
|
'Paket Anda sudah dikirim. Paket akan sampai dalam {{1}} hari kerja.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Pesan ini berasal dari bisnis yang tidak terverifikasi.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_shipping_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'SHIPPING_UPDATE',
|
||||||
|
language: 'es',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'ó tu paquete. La entrega se realizará en {{1}} dí.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Este mensaje proviene de un negocio no verificado.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_flight_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'TICKET_UPDATE',
|
||||||
|
language: 'id',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{ type: 'HEADER', format: 'DOCUMENT' },
|
||||||
|
{
|
||||||
|
text:
|
||||||
|
'Ini merupakan konfirmasi penerbangan Anda untuk {{1}}-{{2}} di {{3}}.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Pesan ini berasal dari bisnis yang tidak terverifikasi.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_issue_resolution',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'ISSUE_RESOLUTION',
|
||||||
|
language: 'en_US',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text: 'Hi {{1}}, were we able to solve the issue that you were facing?',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{ text: 'This message is from an unverified business.', type: 'FOOTER' },
|
||||||
|
{
|
||||||
|
type: 'BUTTONS',
|
||||||
|
buttons: [
|
||||||
|
{ text: 'Yes', type: 'QUICK_REPLY' },
|
||||||
|
{ text: 'No', type: 'QUICK_REPLY' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_flight_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'TICKET_UPDATE',
|
||||||
|
language: 'es',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{ type: 'HEADER', format: 'DOCUMENT' },
|
||||||
|
{
|
||||||
|
text: 'Confirmamos tu vuelo a {{1}}-{{2}} para el {{3}}.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Este mensaje proviene de un negocio no verificado.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_flight_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'TICKET_UPDATE',
|
||||||
|
language: 'en_US',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{ type: 'HEADER', format: 'DOCUMENT' },
|
||||||
|
{
|
||||||
|
text: 'This is your flight confirmation for {{1}}-{{2}} on {{3}}.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{ text: 'This message is from an unverified business.', type: 'FOOTER' },
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sample_shipping_confirmation',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'SHIPPING_UPDATE',
|
||||||
|
language: 'en_US',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
text:
|
||||||
|
'Your package has been shipped. It will be delivered in {{1}} business days.',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{ text: 'This message is from an unverified business.', type: 'FOOTER' },
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'no_variable_template',
|
||||||
|
status: 'approved',
|
||||||
|
category: 'TICKET_UPDATE',
|
||||||
|
language: 'pt_BR',
|
||||||
|
namespace: 'ed41a221_133a_4558_a1d6_192960e3aee9',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'DOCUMENT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'This is a test whatsapp template',
|
||||||
|
type: 'BODY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Esta mensagem é de uma empresa não verificada.',
|
||||||
|
type: 'FOOTER',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rejected_reason: 'NONE',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,77 @@
|
||||||
|
import TemplateParser from '../../../../dashboard/components/widgets/conversation/WhatsappTemplates/TemplateParser.vue';
|
||||||
|
import { mount, createLocalVue } from '@vue/test-utils';
|
||||||
|
import { templates } from './fixtures';
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
import VueI18n from 'vue-i18n';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import Vuelidate from 'vuelidate';
|
||||||
|
Vue.use(Vuelidate);
|
||||||
|
|
||||||
|
import i18n from 'dashboard/i18n';
|
||||||
|
|
||||||
|
localVue.use(VueI18n);
|
||||||
|
|
||||||
|
const i18nConfig = new VueI18n({
|
||||||
|
locale: 'en',
|
||||||
|
messages: i18n,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#WhatsAppTemplates', () => {
|
||||||
|
it('returns all variables from a template string', () => {
|
||||||
|
const wrapper = mount(TemplateParser, {
|
||||||
|
localVue,
|
||||||
|
propsData: {
|
||||||
|
template: templates[0],
|
||||||
|
},
|
||||||
|
i18n: i18nConfig,
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.variables).toEqual(['{{1}}', '{{2}}', '{{3}}']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns no variables from a template string if it does not contain variables', () => {
|
||||||
|
const wrapper = mount(TemplateParser, {
|
||||||
|
localVue,
|
||||||
|
propsData: {
|
||||||
|
template: templates[12],
|
||||||
|
},
|
||||||
|
i18n: i18nConfig,
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.variables).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the body of a template', () => {
|
||||||
|
const wrapper = mount(TemplateParser, {
|
||||||
|
localVue,
|
||||||
|
propsData: {
|
||||||
|
template: templates[1],
|
||||||
|
},
|
||||||
|
i18n: i18nConfig,
|
||||||
|
});
|
||||||
|
const expectedOutput = templates[1].components.find(i => i.type === 'BODY')
|
||||||
|
.text;
|
||||||
|
expect(wrapper.vm.templateString).toEqual(expectedOutput);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates the templates from variable input', async () => {
|
||||||
|
const wrapper = mount(TemplateParser, {
|
||||||
|
localVue,
|
||||||
|
propsData: {
|
||||||
|
template: templates[0],
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
processedParams: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
i18n: i18nConfig,
|
||||||
|
});
|
||||||
|
await wrapper.setData({
|
||||||
|
processedParams: { '1': 'abc', '2': 'xyz', '3': 'qwerty' },
|
||||||
|
});
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
const expectedOutput =
|
||||||
|
'Esta é a sua confirmação de voo para abc-xyz em qwerty.';
|
||||||
|
expect(wrapper.vm.processedString).toEqual(expectedOutput);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue