From 1ccd29140dc67f8b2d52d8479d5929d4af47ee5c Mon Sep 17 00:00:00 2001 From: Tejaswini Chile Date: Tue, 19 Apr 2022 12:21:20 +0530 Subject: [PATCH 01/57] Feat: send fb message outside of standard messaging window (#4439) --- app/models/channel/facebook_page.rb | 2 +- app/models/conversation.rb | 12 +++++++++++- app/services/facebook/send_on_facebook_service.rb | 8 ++++++-- spec/jobs/webhooks/instagram_events_job_spec.rb | 1 - spec/models/conversation_spec.rb | 4 ++-- .../facebook/send_on_facebook_service_spec.rb | 8 ++++++-- 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/models/channel/facebook_page.rb b/app/models/channel/facebook_page.rb index bed88e575..6b36a3a54 100644 --- a/app/models/channel/facebook_page.rb +++ b/app/models/channel/facebook_page.rb @@ -33,7 +33,7 @@ class Channel::FacebookPage < ApplicationRecord end def has_24_hour_messaging_window? - true + false end def create_contact_inbox(instagram_id, name) diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 409efab6d..cf3a4c256 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -90,10 +90,20 @@ class Conversation < ApplicationRecord delegate :auto_resolve_duration, to: :account def can_reply? + return last_message_less_than_24_hrs? if additional_attributes['type'] == 'instagram_direct_message' + return true unless inbox&.channel&.has_24_hour_messaging_window? - last_incoming_message = messages.incoming.last + return false if last_incoming_message.nil? + last_message_less_than_24_hrs? + end + + def last_incoming_message + messages&.incoming&.last + end + + def last_message_less_than_24_hrs? return false if last_incoming_message.nil? Time.current < last_incoming_message.created_at + 24.hours diff --git a/app/services/facebook/send_on_facebook_service.rb b/app/services/facebook/send_on_facebook_service.rb index 12e416ef7..52c259edb 100644 --- a/app/services/facebook/send_on_facebook_service.rb +++ b/app/services/facebook/send_on_facebook_service.rb @@ -22,7 +22,9 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService def fb_text_message_params { recipient: { id: contact.get_source_id(inbox.id) }, - message: { text: message.content } + message: { text: message.content }, + messaging_type: 'MESSAGE_TAG', + tag: 'ACCOUNT_UPDATE' } end @@ -37,7 +39,9 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService url: attachment.file_url } } - } + }, + messaging_type: 'MESSAGE_TAG', + tag: 'ACCOUNT_UPDATE' } end diff --git a/spec/jobs/webhooks/instagram_events_job_spec.rb b/spec/jobs/webhooks/instagram_events_job_spec.rb index bf3b43e14..69952920a 100644 --- a/spec/jobs/webhooks/instagram_events_job_spec.rb +++ b/spec/jobs/webhooks/instagram_events_job_spec.rb @@ -57,7 +57,6 @@ describe Webhooks::InstagramEventsJob do instagram_webhook.perform_now(test_params[:entry]) instagram_inbox.reload - expect(instagram_inbox.messages.count).to be 1 expect(instagram_inbox.messages.last.content).to eq('This is a test message from facebook.') end diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index d5295e340..8c278a81b 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -489,7 +489,7 @@ RSpec.describe Conversation, type: :model do let!(:conversation) { create(:conversation, inbox: facebook_inbox, account: facebook_channel.account) } it 'returns false if there are no incoming messages' do - expect(conversation.can_reply?).to eq false + expect(conversation.can_reply?).to eq true end it 'return false if last incoming message is outside of 24 hour window' do @@ -500,7 +500,7 @@ RSpec.describe Conversation, type: :model do conversation: conversation, created_at: Time.now - 25.hours ) - expect(conversation.can_reply?).to eq false + expect(conversation.can_reply?).to eq true end it 'return true if last incoming message is inside 24 hour window' do diff --git a/spec/services/facebook/send_on_facebook_service_spec.rb b/spec/services/facebook/send_on_facebook_service_spec.rb index 8ff842591..e0a39ba75 100644 --- a/spec/services/facebook/send_on_facebook_service_spec.rb +++ b/spec/services/facebook/send_on_facebook_service_spec.rb @@ -60,7 +60,9 @@ describe Facebook::SendOnFacebookService do ::Facebook::SendOnFacebookService.new(message: message).perform expect(bot).to have_received(:deliver).with({ recipient: { id: contact_inbox.source_id }, - message: { text: message.content } + message: { text: message.content }, + messaging_type: 'MESSAGE_TAG', + tag: 'ACCOUNT_UPDATE' }, { page_id: facebook_channel.page_id }) expect(bot).to have_received(:deliver).with({ recipient: { id: contact_inbox.source_id }, @@ -71,7 +73,9 @@ describe Facebook::SendOnFacebookService do url: attachment.file_url } } - } + }, + messaging_type: 'MESSAGE_TAG', + tag: 'ACCOUNT_UPDATE' }, { page_id: facebook_channel.page_id }) end end From 26f23a6e214c74b5b6abaa7b3d3a5282bfd03b97 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 19 Apr 2022 12:47:29 +0530 Subject: [PATCH 02/57] feat: Custom fields in pre-chat form (#4189) --- .../dashboard/components/ui/Switch.vue | 8 +- app/javascript/dashboard/helper/preChat.js | 100 ++++++ .../dashboard/helper/specs/inboxFixture.js | 47 +++ .../dashboard/helper/specs/preChat.spec.js | 76 +++++ .../dashboard/i18n/locale/en/inboxMgmt.json | 16 +- .../inbox/PreChatForm/PreChatFields.vue | 91 ++++++ .../settings/inbox/PreChatForm/Settings.vue | 130 +++++--- .../dashboard/store/modules/attributes.js | 3 + .../modules/specs/attributes/getters.spec.js | 20 ++ app/javascript/packs/widget.js | 13 +- .../FluentIcon/dashboard-icons.json | 3 +- app/javascript/widget/App.vue | 5 +- .../widget/components/PreChat/Form.vue | 308 +++++++++++++----- app/javascript/widget/i18n/locale/en.json | 14 +- app/javascript/widget/mixins/configMixin.js | 12 +- .../widget/mixins/specs/configMixin.spec.js | 30 +- app/javascript/widget/views/Home.vue | 8 +- app/javascript/widget/views/PreChatForm.vue | 20 +- app/models/channel/web_widget.rb | 25 +- app/models/custom_attribute_definition.rb | 2 +- .../api/v1/widget/contacts/show.json.jbuilder | 1 + .../v1/widget/contacts/update.json.jbuilder | 1 + ...4933_add_custom_fields_to_pre_chat_form.rb | 29 ++ spec/models/channel/web_widget_spec.rb | 15 + spec/models/channel/widget_test.rb | 7 - 25 files changed, 824 insertions(+), 160 deletions(-) create mode 100644 app/javascript/dashboard/helper/preChat.js create mode 100644 app/javascript/dashboard/helper/specs/inboxFixture.js create mode 100644 app/javascript/dashboard/helper/specs/preChat.spec.js create mode 100644 app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/PreChatFields.vue create mode 100644 db/migrate/20220316054933_add_custom_fields_to_pre_chat_form.rb create mode 100644 spec/models/channel/web_widget_spec.rb delete mode 100644 spec/models/channel/widget_test.rb diff --git a/app/javascript/dashboard/components/ui/Switch.vue b/app/javascript/dashboard/components/ui/Switch.vue index b0bc32881..7dc4bcbfb 100644 --- a/app/javascript/dashboard/components/ui/Switch.vue +++ b/app/javascript/dashboard/components/ui/Switch.vue @@ -17,8 +17,12 @@ export default { value: { type: Boolean, default: false }, }, methods: { - onClick() { - this.$emit('input', !this.value); + onClick(event) { + if (event.pointerId === -1) { + event.preventDefault(); + } else { + this.$emit('input', !this.value); + } }, }, }; diff --git a/app/javascript/dashboard/helper/preChat.js b/app/javascript/dashboard/helper/preChat.js new file mode 100644 index 000000000..bc5502e5b --- /dev/null +++ b/app/javascript/dashboard/helper/preChat.js @@ -0,0 +1,100 @@ +import i18n from 'widget/i18n/index'; +const defaultTranslations = Object.fromEntries( + Object.entries(i18n).filter(([key]) => key.includes('en')) +).en; + +export const standardFieldKeys = { + emailAddress: { + key: 'EMAIL_ADDRESS', + label: 'Email Id', + placeholder: 'Please enter your email address', + }, + fullName: { + key: 'FULL_NAME', + label: 'Full Name', + placeholder: 'Please enter your full name', + }, + phoneNumber: { + key: 'PHONE_NUMBER', + label: 'Phone Number', + placeholder: 'Please enter your phone number', + }, +}; + +export const getLabel = ({ key, label }) => { + return defaultTranslations.PRE_CHAT_FORM.FIELDS[key] + ? defaultTranslations.PRE_CHAT_FORM.FIELDS[key].LABEL + : label; +}; +export const getPlaceHolder = ({ key, placeholder }) => { + return defaultTranslations.PRE_CHAT_FORM.FIELDS[key] + ? defaultTranslations.PRE_CHAT_FORM.FIELDS[key].PLACEHOLDER + : placeholder; +}; + +export const getCustomFields = ({ standardFields, customAttributes }) => { + let customFields = []; + const { pre_chat_fields: preChatFields } = standardFields; + customAttributes.forEach(attribute => { + const itemExist = preChatFields.find( + item => item.name === attribute.attribute_key + ); + if (!itemExist) { + customFields.push({ + label: attribute.attribute_display_name, + placeholder: attribute.attribute_display_name, + name: attribute.attribute_key, + type: attribute.attribute_display_type, + values: attribute.attribute_values, + field_type: attribute.attribute_model, + required: false, + enabled: false, + }); + } + }); + return customFields; +}; + +export const getFormattedPreChatFields = ({ preChatFields }) => { + return preChatFields.map(item => { + return { + ...item, + label: getLabel({ + key: standardFieldKeys[item.name] + ? standardFieldKeys[item.name].key + : item.name, + label: item.label ? item.label : item.name, + }), + placeholder: getPlaceHolder({ + key: standardFieldKeys[item.name] + ? standardFieldKeys[item.name].key + : item.name, + placeholder: item.placeholder ? item.placeholder : item.name, + }), + }; + }); +}; + +export const getPreChatFields = ({ + preChatFormOptions = {}, + customAttributes = [], +}) => { + const { pre_chat_message, pre_chat_fields } = preChatFormOptions; + let customFields = {}; + let preChatFields = {}; + + const formattedPreChatFields = getFormattedPreChatFields({ + preChatFields: pre_chat_fields, + }); + + customFields = getCustomFields({ + standardFields: { pre_chat_fields: formattedPreChatFields }, + customAttributes, + }); + preChatFields = [...formattedPreChatFields, ...customFields]; + + return { + pre_chat_message, + pre_chat_fields: preChatFields, + }; +}; diff --git a/app/javascript/dashboard/helper/specs/inboxFixture.js b/app/javascript/dashboard/helper/specs/inboxFixture.js new file mode 100644 index 000000000..6622a6de2 --- /dev/null +++ b/app/javascript/dashboard/helper/specs/inboxFixture.js @@ -0,0 +1,47 @@ +export default { + customFields: { + pre_chat_message: 'Share your queries or comments here.', + pre_chat_fields: [ + { + label: 'Email Address', + name: 'emailAddress', + type: 'email', + field_type: 'standard', + required: false, + enabled: false, + + placeholder: 'Please enter your email address', + }, + { + label: 'Full Name', + name: 'fullName', + type: 'text', + field_type: 'standard', + required: false, + enabled: false, + placeholder: 'Please enter your full name', + }, + { + label: 'Phone Number', + name: 'phoneNumber', + type: 'text', + field_type: 'standard', + required: false, + enabled: false, + placeholder: 'Please enter your phone number', + }, + ], + }, + customAttributes: [ + { + id: 101, + attribute_description: 'Order Identifier', + attribute_display_name: 'Order Id', + attribute_display_type: 'number', + attribute_key: 'order_id', + attribute_model: 'conversation_attribute', + attribute_values: Array(0), + created_at: '2021-11-29T10:20:04.563Z', + }, + ], +}; diff --git a/app/javascript/dashboard/helper/specs/preChat.spec.js b/app/javascript/dashboard/helper/specs/preChat.spec.js new file mode 100644 index 000000000..74f3e72f5 --- /dev/null +++ b/app/javascript/dashboard/helper/specs/preChat.spec.js @@ -0,0 +1,76 @@ +import { + getPreChatFields, + getFormattedPreChatFields, + getCustomFields, +} from '../preChat'; +import inboxFixture from './inboxFixture'; + +const { customFields, customAttributes } = inboxFixture; +describe('#Pre chat Helpers', () => { + describe('getPreChatFields', () => { + it('should return correct pre-chat fields form options passed', () => { + expect(getPreChatFields({ preChatFormOptions: customFields })).toEqual( + customFields + ); + }); + }); + describe('getFormattedPreChatFields', () => { + it('should return correct custom fields', () => { + expect( + getFormattedPreChatFields({ + preChatFields: customFields.pre_chat_fields, + }) + ).toEqual([ + { + label: 'Email Address', + name: 'emailAddress', + placeholder: 'Please enter your email address', + type: 'email', + field_type: 'standard', + + required: false, + enabled: false, + }, + { + label: 'Full Name', + name: 'fullName', + placeholder: 'Please enter your full name', + type: 'text', + field_type: 'standard', + required: false, + enabled: false, + }, + { + label: 'Phone Number', + name: 'phoneNumber', + placeholder: 'Please enter your phone number', + type: 'text', + field_type: 'standard', + required: false, + enabled: false, + }, + ]); + }); + }); + describe('getCustomFields', () => { + it('should return correct custom fields', () => { + expect( + getCustomFields({ + standardFields: { pre_chat_fields: customFields.pre_chat_fields }, + customAttributes, + }) + ).toEqual([ + { + enabled: false, + label: 'Order Id', + placeholder: 'Order Id', + name: 'order_id', + required: false, + field_type: 'conversation_attribute', + type: 'number', + values: [], + }, + ]); + }); + }); +}); diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index 365fd92f9..218d1cf95 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -187,7 +187,7 @@ } } }, - "WHATSAPP": { + "WHATSAPP": { "TITLE": "WhatsApp Channel", "DESC": "Start supporting your customers via WhatsApp.", "PROVIDERS": { @@ -211,7 +211,6 @@ "PLACEHOLDER": "API key", "APPLY_FOR_ACCESS": "Don't have any API key? Apply for access here", "ERROR": "Please enter a valid value." - }, "SUBMIT_BUTTON": "Create WhatsApp Channel", "API": { @@ -433,6 +432,15 @@ }, "PRE_CHAT_FORM": { "DESCRIPTION": "Pre chat forms enable you to capture user information before they start conversation with you.", + "SET_FIELDS": "Pre chat form fields", + "SET_FIELDS_HEADER": { + "FIELDS": "Fields", + "LABEL": "Label", + "PLACE_HOLDER":"Placeholder", + "KEY": "Key", + "TYPE": "Type", + "REQUIRED": "Required" + }, "ENABLE": { "LABEL": "Enable pre chat form", "OPTIONS": { @@ -441,7 +449,7 @@ } }, "PRE_CHAT_MESSAGE": { - "LABEL": "Pre Chat Message", + "LABEL": "Pre chat message", "PLACEHOLDER": "This message would be visible to the users along with the form" }, "REQUIRE_EMAIL": { @@ -465,7 +473,7 @@ "VALIDATION_ERROR": "Starting time should be before closing time.", "CHOOSE": "Choose" }, - "ALL_DAY":"All-Day" + "ALL_DAY": "All-Day" }, "IMAP": { "TITLE": "IMAP", diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/PreChatFields.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/PreChatFields.vue new file mode 100644 index 000000000..9d2691036 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/PreChatFields.vue @@ -0,0 +1,91 @@ + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue index 6c01bc3dd..184e16e0f 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/PreChatForm/Settings.vue @@ -1,10 +1,10 @@