fix: templates in whatsapp (#9862)

This commit is contained in:
Shivam Mishra
2024-07-31 15:33:31 +05:30
committed by GitHub
parent f7102d7f8b
commit 68482db3a2
3 changed files with 96 additions and 73 deletions

View File

@@ -30,7 +30,7 @@
</p> </p>
</div> </div>
<footer> <footer>
<woot-button variant="smooth" @click="$emit('resetTemplate')"> <woot-button variant="smooth" @click="resetTemplate">
{{ $t('WHATSAPP_TEMPLATES.PARSER.GO_BACK_LABEL') }} {{ $t('WHATSAPP_TEMPLATES.PARSER.GO_BACK_LABEL') }}
</woot-button> </woot-button>
<woot-button type="button" @click="sendMessage"> <woot-button type="button" @click="sendMessage">
@@ -41,82 +41,108 @@
</template> </template>
<script> <script>
/**
* This component handles parsing and sending WhatsApp message templates.
* It works as follows:
* 1. Displays the template text with variable placeholders.
* 2. Generates input fields for each variable in the template.
* 3. Validates that all variables are filled before sending.
* 4. Replaces placeholders with user-provided values.
* 5. Emits events to send the processed message or reset the template.
*/
import { ref, computed, onMounted } from 'vue';
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { requiredIf } from '@vuelidate/validators'; import { requiredIf } from '@vuelidate/validators';
const allKeysRequired = value => {
const keys = Object.keys(value);
return keys.every(key => value[key]);
};
export default { export default {
props: { props: {
template: { template: {
type: Object, type: Object,
default: () => {}, default: () => ({}),
}, },
}, },
setup() { setup(props, { emit }) {
return { v$: useVuelidate() }; const processVariable = str => {
}, return str.replace(/{{|}}/g, '');
validations: {
processedParams: {
requiredIfKeysPresent: requiredIf('variables'),
allKeysRequired,
},
},
data() {
return {
processedParams: {},
}; };
},
computed: { const allKeysRequired = value => {
variables() { const keys = Object.keys(value);
const variables = this.templateString.match(/{{([^}]+)}}/g); return keys.every(key => value[key]);
return variables; };
},
templateString() { const processedParams = ref({});
return this.template.components.find(
const templateString = computed(() => {
return props.template.components.find(
component => component.type === 'BODY' component => component.type === 'BODY'
).text; ).text;
}, });
processedString() {
return this.templateString.replace(/{{([^}]+)}}/g, (match, variable) => { const variables = computed(() => {
const variableKey = this.processVariable(variable); return templateString.value.match(/{{([^}]+)}}/g);
return this.processedParams[variableKey] || `{{${variable}}}`; });
const processedString = computed(() => {
return templateString.value.replace(/{{([^}]+)}}/g, (match, variable) => {
const variableKey = processVariable(variable);
return processedParams.value[variableKey] || `{{${variable}}}`;
}); });
}, });
},
mounted() { const v$ = useVuelidate(
this.generateVariables(); {
}, processedParams: {
methods: { requiredIfKeysPresent: requiredIf(variables),
sendMessage() { allKeysRequired,
this.v$.$touch();
if (this.v$.$invalid) return;
const payload = {
message: this.processedString,
templateParams: {
name: this.template.name,
category: this.template.category,
language: this.template.language,
namespace: this.template.namespace,
processed_params: this.processedParams,
}, },
}; },
this.$emit('sendMessage', payload); { processedParams }
}, );
processVariable(str) {
return str.replace(/{{|}}/g, ''); const generateVariables = () => {
}, const matchedVariables = templateString.value.match(/{{([^}]+)}}/g);
generateVariables() {
const matchedVariables = this.templateString.match(/{{([^}]+)}}/g);
if (!matchedVariables) return; if (!matchedVariables) return;
const variables = matchedVariables.map(i => this.processVariable(i)); const finalVars = matchedVariables.map(i => processVariable(i));
this.processedParams = variables.reduce((acc, variable) => { processedParams.value = finalVars.reduce((acc, variable) => {
acc[variable] = ''; acc[variable] = '';
return acc; return acc;
}, {}); }, {});
}, };
const resetTemplate = () => {
emit('resetTemplate');
};
const sendMessage = () => {
v$.value.$touch();
if (v$.value.$invalid) return;
const payload = {
message: processedString.value,
templateParams: {
name: props.template.name,
category: props.template.category,
language: props.template.language,
namespace: props.template.namespace,
processed_params: processedParams.value,
},
};
emit('sendMessage', payload);
};
onMounted(generateVariables);
return {
processedParams,
variables,
templateString,
processedString,
v$,
resetTemplate,
sendMessage,
};
}, },
}; };
</script> </script>

View File

@@ -285,10 +285,6 @@ export default {
type: Function, type: Function,
default: () => {}, default: () => {},
}, },
channelType: {
type: String,
default: '',
},
}, },
setup() { setup() {
const { fetchSignatureFlagFromUISettings, setSignatureFlagForInbox } = const { fetchSignatureFlagFromUISettings, setSignatureFlagForInbox } =

View File

@@ -4,6 +4,7 @@ import { templates } from './fixtures';
const localVue = createLocalVue(); const localVue = createLocalVue();
import VueI18n from 'vue-i18n'; import VueI18n from 'vue-i18n';
import i18n from 'dashboard/i18n'; import i18n from 'dashboard/i18n';
import { nextTick } from 'vue';
localVue.use(VueI18n); localVue.use(VueI18n);
@@ -18,30 +19,32 @@ const config = {
}; };
describe('#WhatsAppTemplates', () => { describe('#WhatsAppTemplates', () => {
it('returns all variables from a template string', () => { it('returns all variables from a template string', async () => {
const wrapper = shallowMount(TemplateParser, { const wrapper = shallowMount(TemplateParser, {
...config, ...config,
propsData: { template: templates[0] }, propsData: { template: templates[0] },
}); });
await nextTick();
expect(wrapper.vm.variables).toEqual(['{{1}}', '{{2}}', '{{3}}']); expect(wrapper.vm.variables).toEqual(['{{1}}', '{{2}}', '{{3}}']);
}); });
it('returns no variables from a template string if it does not contain variables', () => { it('returns no variables from a template string if it does not contain variables', async () => {
const wrapper = shallowMount(TemplateParser, { const wrapper = shallowMount(TemplateParser, {
...config, ...config,
propsData: { template: templates[12] }, propsData: { template: templates[12] },
}); });
await nextTick();
expect(wrapper.vm.variables).toBeNull(); expect(wrapper.vm.variables).toBeNull();
}); });
it('returns the body of a template', () => { it('returns the body of a template', async () => {
const wrapper = shallowMount(TemplateParser, { const wrapper = shallowMount(TemplateParser, {
...config, ...config,
propsData: { template: templates[1] }, propsData: { template: templates[1] },
}); });
const expectedOutput = templates[1].components.find( await nextTick();
i => i.type === 'BODY' const expectedOutput =
).text; templates[1].components.find(i => i.type === 'BODY')?.text || '';
expect(wrapper.vm.templateString).toEqual(expectedOutput); expect(wrapper.vm.templateString).toEqual(expectedOutput);
}); });
@@ -49,14 +52,12 @@ describe('#WhatsAppTemplates', () => {
const wrapper = shallowMount(TemplateParser, { const wrapper = shallowMount(TemplateParser, {
...config, ...config,
propsData: { template: templates[0] }, propsData: { template: templates[0] },
data: () => {
return { processedParams: {} };
},
}); });
await nextTick();
await wrapper.setData({ await wrapper.setData({
processedParams: { 1: 'abc', 2: 'xyz', 3: 'qwerty' }, processedParams: { 1: 'abc', 2: 'xyz', 3: 'qwerty' },
}); });
await wrapper.vm.$nextTick(); await nextTick();
const expectedOutput = const expectedOutput =
'Esta é a sua confirmação de voo para abc-xyz em qwerty.'; 'Esta é a sua confirmação de voo para abc-xyz em qwerty.';
expect(wrapper.vm.processedString).toEqual(expectedOutput); expect(wrapper.vm.processedString).toEqual(expectedOutput);