Merge branch 'release/1.20.0' into develop

This commit is contained in:
Sojan 2021-09-15 20:19:09 +05:30
commit 928766f07b
31 changed files with 241 additions and 85 deletions

View File

@ -148,6 +148,14 @@ class Messages::Facebook::MessageBuilder
} }
end end
def process_contact_params_result(result)
{
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
account_id: @inbox.account_id,
remote_avatar_url: result['profile_pic'] || ''
}
end
def contact_params def contact_params
begin begin
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook? k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
@ -155,14 +163,15 @@ class Messages::Facebook::MessageBuilder
rescue Koala::Facebook::AuthenticationError rescue Koala::Facebook::AuthenticationError
@inbox.channel.authorization_error! @inbox.channel.authorization_error!
raise raise
rescue Koala::Facebook::ClientError => e
result = {}
# OAuthException, code: 100, error_subcode: 2018218, message: (#100) No profile available for this user
# We don't need to capture this error as we don't care about contact params in case of echo messages
Sentry.capture_exception(e) unless outgoing_echo?
rescue StandardError => e rescue StandardError => e
result = {} result = {}
Sentry.capture_exception(e) Sentry.capture_exception(e)
end end
{ process_contact_params_result(result)
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
account_id: @inbox.account_id,
remote_avatar_url: result['profile_pic'] || ''
}
end end
end end

View File

@ -100,6 +100,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
end end
def update_channel_feature_flags def update_channel_feature_flags
return unless @inbox.web_widget?
return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
@inbox.channel.selected_feature_flags = permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel][:selected_feature_flags] @inbox.channel.selected_feature_flags = permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel][:selected_feature_flags]

View File

@ -3,10 +3,9 @@ class SuperAdmin::DashboardController < SuperAdmin::ApplicationController
def index def index
@data = Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a @data = Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a
@accounts_count = number_with_delimiter(Account.all.length) @accounts_count = number_with_delimiter(Account.count)
@users_count = number_with_delimiter(User.all.length) @users_count = number_with_delimiter(User.count)
@inboxes_count = number_with_delimiter(Inbox.all.length) @inboxes_count = number_with_delimiter(Inbox.count)
@conversations_count = number_with_delimiter(Conversation.all.length) @conversations_count = number_with_delimiter(Conversation.count)
@messages_count = number_with_delimiter(Message.all.length)
end end
end end

View File

@ -194,7 +194,7 @@ export default {
}); });
}, },
methods: { methods: {
handleKeyEvents(e) { getKeyboardListenerParams() {
const allConversations = this.$refs.activeConversation.querySelectorAll( const allConversations = this.$refs.activeConversation.querySelectorAll(
'div.conversations-list div.conversation' 'div.conversations-list div.conversation'
); );
@ -205,7 +205,19 @@ export default {
activeConversation activeConversation
); );
const lastConversationIndex = allConversations.length - 1; const lastConversationIndex = allConversations.length - 1;
return {
allConversations,
activeConversation,
activeConversationIndex,
lastConversationIndex,
};
},
handleKeyEvents(e) {
if (hasPressedAltAndJKey(e)) { if (hasPressedAltAndJKey(e)) {
const {
allConversations,
activeConversationIndex,
} = this.getKeyboardListenerParams();
if (activeConversationIndex === -1) { if (activeConversationIndex === -1) {
allConversations[0].click(); allConversations[0].click();
} }
@ -214,6 +226,11 @@ export default {
} }
} }
if (hasPressedAltAndKKey(e)) { if (hasPressedAltAndKKey(e)) {
const {
allConversations,
activeConversationIndex,
lastConversationIndex,
} = this.getKeyboardListenerParams();
if (activeConversationIndex === -1) { if (activeConversationIndex === -1) {
allConversations[lastConversationIndex].click(); allConversations[lastConversationIndex].click();
} else if (activeConversationIndex < lastConversationIndex) { } else if (activeConversationIndex < lastConversationIndex) {

View File

@ -176,7 +176,9 @@ export default {
'.conversations-list .conversation' '.conversations-list .conversation'
); );
if (hasPressedAltAndMKey(e)) { if (hasPressedAltAndMKey(e)) {
this.$refs.arrowDownButton.$el.click(); if (this.$refs.arrowDownButton) {
this.$refs.arrowDownButton.$el.click();
}
} }
if (hasPressedAltAndEKey(e)) { if (hasPressedAltAndEKey(e)) {
const activeConversation = document.querySelector( const activeConversation = document.querySelector(

View File

@ -95,10 +95,15 @@ import AddAccountModal from './sidebarComponents/AddAccountModal.vue';
import AddLabelModal from '../../routes/dashboard/settings/labels/AddLabel'; import AddLabelModal from '../../routes/dashboard/settings/labels/AddLabel';
import WootKeyShortcutModal from 'components/widgets/modal/WootKeyShortcutModal'; import WootKeyShortcutModal from 'components/widgets/modal/WootKeyShortcutModal';
import { import {
hasPressedAltAndCKey,
hasPressedAltAndRKey,
hasPressedAltAndSKey,
hasPressedAltAndVKey,
hasPressedCommandAndForwardSlash, hasPressedCommandAndForwardSlash,
isEscape, isEscape,
} from 'shared/helpers/KeyboardHelpers'; } from 'shared/helpers/KeyboardHelpers';
import eventListenerMixins from 'shared/mixins/eventListenerMixins'; import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import router from '../../routes';
export default { export default {
components: { components: {
@ -276,6 +281,27 @@ export default {
if (isEscape(e)) { if (isEscape(e)) {
this.closeKeyShortcutModal(); this.closeKeyShortcutModal();
} }
if (hasPressedAltAndCKey(e)) {
if (!this.isCurrentRouteSameAsNavigation('home')) {
router.push({ name: 'home' });
}
} else if (hasPressedAltAndVKey(e)) {
if (!this.isCurrentRouteSameAsNavigation('contacts_dashboard')) {
router.push({ name: 'contacts_dashboard' });
}
} else if (hasPressedAltAndRKey(e)) {
if (!this.isCurrentRouteSameAsNavigation('settings_account_reports')) {
router.push({ name: 'settings_account_reports' });
}
} else if (hasPressedAltAndSKey(e)) {
if (!this.isCurrentRouteSameAsNavigation('agent_list')) {
router.push({ name: 'agent_list' });
}
}
},
isCurrentRouteSameAsNavigation(routeName) {
return router.currentRoute && router.currentRoute.name === routeName;
}, },
toggleSupportChatWindow() { toggleSupportChatWindow() {
window.$chatwoot.toggle(); window.$chatwoot.toggle();

View File

@ -33,7 +33,7 @@
<a href="#" :class="computedChildClass(child)"> <a href="#" :class="computedChildClass(child)">
<div class="wrap"> <div class="wrap">
<i <i
v-if="computedInboxClass(child)" v-if="menuItem.key === 'inbox'"
class="inbox-icon" class="inbox-icon"
:class="computedInboxClass(child)" :class="computedInboxClass(child)"
/> />
@ -59,17 +59,10 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import router from '../../routes'; import router from '../../routes';
import {
hasPressedAltAndCKey,
hasPressedAltAndVKey,
hasPressedAltAndRKey,
hasPressedAltAndSKey,
} from 'shared/helpers/KeyboardHelpers';
import adminMixin from '../../mixins/isAdmin'; import adminMixin from '../../mixins/isAdmin';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { getInboxClassByType } from 'dashboard/helper/inbox'; import { getInboxClassByType } from 'dashboard/helper/inbox';
export default { export default {
mixins: [adminMixin, eventListenerMixins], mixins: [adminMixin],
props: { props: {
menuItem: { menuItem: {
type: Object, type: Object,
@ -124,20 +117,6 @@ export default {
} }
} }
}, },
handleKeyEvents(e) {
if (hasPressedAltAndCKey(e)) {
router.push({ name: 'home' });
}
if (hasPressedAltAndVKey(e)) {
router.push({ name: 'contacts_dashboard' });
}
if (hasPressedAltAndRKey(e)) {
router.push({ name: 'settings_account_reports' });
}
if (hasPressedAltAndSKey(e)) {
router.push({ name: 'settings_home' });
}
},
showItem(item) { showItem(item) {
return this.isAdmin && item.newLink !== undefined; return this.isAdmin && item.newLink !== undefined;
}, },

View File

@ -76,7 +76,6 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway'; import { mixin as clickaway } from 'vue-clickaway';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import EmojiInput from 'shared/components/emoji/EmojiInput'; import EmojiInput from 'shared/components/emoji/EmojiInput';
import CannedResponse from './CannedResponse'; import CannedResponse from './CannedResponse';
@ -108,13 +107,7 @@ export default {
ReplyBottomPanel, ReplyBottomPanel,
WootMessageEditor, WootMessageEditor,
}, },
mixins: [ mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
clickaway,
inboxMixin,
uiSettingsMixin,
alertMixin,
eventListenerMixins,
],
props: { props: {
selectedTweet: { selectedTweet: {
type: [Object, String], type: [Object, String],
@ -304,6 +297,15 @@ export default {
} }
}, },
}, },
mounted() {
// Donot use the keyboard listener mixin here as the events here are supposed to be
// working even if input/textarea is focussed.
document.addEventListener('keydown', this.handleKeyEvents);
},
destroyed() {
document.removeEventListener('keydown', this.handleKeyEvents);
},
methods: { methods: {
toggleUserMention(currentMentionState) { toggleUserMention(currentMentionState) {
this.hasUserMention = currentMentionState; this.hasUserMention = currentMentionState;

View File

@ -22,7 +22,10 @@ export const getInboxClassByType = (type, phoneNumber) => {
case INBOX_TYPES.EMAIL: case INBOX_TYPES.EMAIL:
return 'ion-ios-email'; return 'ion-ios-email';
case INBOX_TYPES.TELEGRAM:
return 'ion-ios-navigate';
default: default:
return ''; return 'ion-ios-chatbubble';
} }
}; };

View File

@ -195,6 +195,10 @@
"SUBMIT_BUTTON": "Create LINE Channel", "SUBMIT_BUTTON": "Create LINE Channel",
"API": { "API": {
"ERROR_MESSAGE": "We were not able to save the LINE channel" "ERROR_MESSAGE": "We were not able to save the LINE channel"
},
"API_CALLBACK": {
"TITLE": "Callback URL",
"SUBTITLE": "You have to configure the webhook URL in LINE application with the URL mentioned here."
} }
}, },
"TELEGRAM_CHANNEL": { "TELEGRAM_CHANNEL": {

View File

@ -13,18 +13,18 @@
"STATUS_TABS": [ "STATUS_TABS": [
{ {
"NAME": "Apri", "NAME": "Apri",
"KEY": "contaaperture" "KEY": "openCount"
}, },
{ {
"NAME": "Risolti", "NAME": "Risolti",
"KEY": "Conteggio" "KEY": "allConvCount"
} }
], ],
"ASSIGNEE_TYPE_TABS": [ "ASSIGNEE_TYPE_TABS": [
{ {
"NAME": "Miniera", "NAME": "Miniera",
"KEY": "Io", "KEY": "Io",
"COUNT_KEY": "contaMinore" "COUNT_KEY": "mineCount"
}, },
{ {
"NAME": "Non assegnato", "NAME": "Non assegnato",
@ -40,11 +40,11 @@
"CHAT_STATUS_ITEMS": [ "CHAT_STATUS_ITEMS": [
{ {
"TEXT": "Apri", "TEXT": "Apri",
"VALUE": "Aperto" "VALUE": "open"
}, },
{ {
"TEXT": "Risolti", "TEXT": "Risolti",
"VALUE": "risolto" "VALUE": "resolved"
}, },
{ {
"TEXT": "Pending", "TEXT": "Pending",

View File

@ -88,7 +88,7 @@ export default {
const selectedAgents = this.selectedAgents.map(x => x.id); const selectedAgents = this.selectedAgents.map(x => x.id);
try { try {
await InboxMembersAPI.create({ inboxId, agentList: selectedAgents }); await InboxMembersAPI.update({ inboxId, agentList: selectedAgents });
router.replace({ router.replace({
name: 'settings_inbox_finish', name: 'settings_inbox_finish',
params: { params: {

View File

@ -17,7 +17,7 @@
<woot-code <woot-code
v-if="isATwilioInbox" v-if="isATwilioInbox"
lang="html" lang="html"
:script="currentInbox.webhook_url" :script="currentInbox.callback_webhook_url"
> >
</woot-code> </woot-code>
</div> </div>
@ -25,7 +25,7 @@
<woot-code <woot-code
v-if="isALineInbox" v-if="isALineInbox"
lang="html" lang="html"
:script="currentInbox.webhook_url" :script="currentInbox.callback_webhook_url"
> >
</woot-code> </woot-code>
</div> </div>
@ -93,6 +93,12 @@ export default {
)}`; )}`;
} }
if (this.isALineInbox) {
return `${this.$t('INBOX_MGMT.FINISH.MESSAGE')}. ${this.$t(
'INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.SUBTITLE'
)}`;
}
if (this.isAEmailInbox) { if (this.isAEmailInbox) {
return this.$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.FINISH_MESSAGE'); return this.$t('INBOX_MGMT.ADD.EMAIL_CHANNEL.FINISH_MESSAGE');
} }

View File

@ -51,6 +51,9 @@
<span v-if="item.channel_type === 'Channel::Telegram'"> <span v-if="item.channel_type === 'Channel::Telegram'">
Telegram Telegram
</span> </span>
<span v-if="item.channel_type === 'Channel::Line'">
Line
</span>
<span v-if="item.channel_type === 'Channel::Api'"> <span v-if="item.channel_type === 'Channel::Api'">
{{ globalConfig.apiChannelName || 'API' }} {{ globalConfig.apiChannelName || 'API' }}
</span> </span>

View File

@ -259,7 +259,21 @@
:title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')" :title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.TITLE')"
:sub-title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.SUBTITLE')" :sub-title="$t('INBOX_MGMT.ADD.TWILIO.API_CALLBACK.SUBTITLE')"
> >
<woot-code :script="twilioCallbackURL" lang="html"></woot-code> <woot-code
:script="inbox.callback_webhook_url"
lang="html"
></woot-code>
</settings-section>
</div>
<div v-else-if="isALineChannel" class="settings--content">
<settings-section
:title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.TITLE')"
:sub-title="$t('INBOX_MGMT.ADD.LINE_CHANNEL.API_CALLBACK.SUBTITLE')"
>
<woot-code
:script="inbox.callback_webhook_url"
lang="html"
></woot-code>
</settings-section> </settings-section>
</div> </div>
<div v-else-if="isAWebWidgetInbox"> <div v-else-if="isAWebWidgetInbox">
@ -398,7 +412,12 @@ export default {
]; ];
} }
if (this.isATwilioChannel || this.isAPIInbox || this.isAnEmailChannel) { if (
this.isATwilioChannel ||
this.isALineChannel ||
this.isAPIInbox ||
this.isAnEmailChannel
) {
return [ return [
...visibleToAllChannelTabs, ...visibleToAllChannelTabs,
{ {

View File

@ -99,6 +99,7 @@ export const SDK_CSS = `.woot-widget-holder {
.woot--close::before, .woot--close::after { .woot--close::before, .woot--close::after {
background-color: #fff; background-color: #fff;
content: ' '; content: ' ';
display: inline;
height: 24px; height: 24px;
left: 32px; left: 32px;
position: absolute; position: absolute;
@ -149,7 +150,7 @@ export const SDK_CSS = `.woot-widget-holder {
max-height: 100vh; max-height: 100vh;
padding: 0 8px; padding: 0 8px;
} }
.woot-widget-holder.has-unread-view iframe { .woot-widget-holder.has-unread-view iframe {
min-height: unset !important; min-height: unset !important;
} }
@ -157,7 +158,7 @@ export const SDK_CSS = `.woot-widget-holder {
.woot-widget-holder.has-unread-view.woot-elements--left { .woot-widget-holder.has-unread-view.woot-elements--left {
left: 0; left: 0;
} }
.woot-widget-bubble.woot--close { .woot-widget-bubble.woot--close {
bottom: 60px; bottom: 60px;
opacity: 0; opacity: 0;

View File

@ -1,8 +1,28 @@
import { isEscape } from '../helpers/KeyboardHelpers';
export default { export default {
mounted() { mounted() {
document.addEventListener('keydown', this.handleKeyEvents); document.addEventListener('keydown', this.onKeyDownHandler);
}, },
destroyed() { beforeDestroy() {
document.removeEventListener('keydown', this.handleKeyEvents); document.removeEventListener('keydown', this.onKeyDownHandler);
},
methods: {
onKeyDownHandler(e) {
const isEventFromAnInputBox =
e.target?.tagName === 'INPUT' || e.target?.tagName === 'TEXTAREA';
const isEventFromProseMirror = e.target?.className?.includes(
'ProseMirror'
);
if (isEventFromAnInputBox || isEventFromProseMirror) {
if (isEscape(e)) {
e.target.blur();
}
return;
}
this.handleKeyEvents(e);
},
}, },
}; };

View File

@ -5,6 +5,8 @@ export const INBOX_TYPES = {
TWILIO: 'Channel::TwilioSms', TWILIO: 'Channel::TwilioSms',
API: 'Channel::Api', API: 'Channel::Api',
EMAIL: 'Channel::Email', EMAIL: 'Channel::Email',
TELEGRAM: 'Channel::Telegram',
LINE: 'Channel::Line',
}; };
export default { export default {
@ -27,6 +29,9 @@ export default {
isATwilioChannel() { isATwilioChannel() {
return this.channelType === INBOX_TYPES.TWILIO; return this.channelType === INBOX_TYPES.TWILIO;
}, },
isALineChannel() {
return this.channelType === INBOX_TYPES.LINE;
},
isAnEmailChannel() { isAnEmailChannel() {
return this.channelType === INBOX_TYPES.EMAIL; return this.channelType === INBOX_TYPES.EMAIL;
}, },

View File

@ -27,6 +27,7 @@ class ContactIpLookupJob < ApplicationJob
geocoder_result = Geocoder.search(ip).first geocoder_result = Geocoder.search(ip).first
return unless geocoder_result return unless geocoder_result
contact.additional_attributes ||= {}
contact.additional_attributes['city'] = geocoder_result.city contact.additional_attributes['city'] = geocoder_result.city
contact.additional_attributes['country'] = geocoder_result.country contact.additional_attributes['country'] = geocoder_result.country
contact.additional_attributes['country_code'] = geocoder_result.country_code contact.additional_attributes['country_code'] = geocoder_result.country_code
@ -34,7 +35,7 @@ class ContactIpLookupJob < ApplicationJob
end end
def get_contact_ip(contact) def get_contact_ip(contact)
contact.additional_attributes['updated_at_ip'] || contact.additional_attributes['created_at_ip'] contact.additional_attributes&.dig('updated_at_ip') || contact.additional_attributes&.dig('created_at_ip')
end end
def ensure_look_up_db def ensure_look_up_db

View File

@ -31,6 +31,7 @@ class Inbox < ApplicationRecord
include Avatarable include Avatarable
include OutOfOffisable include OutOfOffisable
validates :name, presence: true
validates :account_id, presence: true validates :account_id, presence: true
validates :timezone, inclusion: { in: TZInfo::Timezone.all_identifiers } validates :timezone, inclusion: { in: TZInfo::Timezone.all_identifiers }
@ -93,9 +94,9 @@ class Inbox < ApplicationRecord
} }
end end
def webhook_url def callback_webhook_url
case channel_type case channel_type
when 'Channel::TwilioSMS' when 'Channel::TwilioSms'
"#{ENV['FRONTEND_URL']}/twilio/callback" "#{ENV['FRONTEND_URL']}/twilio/callback"
when 'Channel::Line' when 'Channel::Line'
"#{ENV['FRONTEND_URL']}/webhooks/line/#{channel.line_channel_id}" "#{ENV['FRONTEND_URL']}/webhooks/line/#{channel.line_channel_id}"

View File

@ -1,9 +1,17 @@
# ref : https://developers.line.biz/en/docs/messaging-api/receiving-messages/#webhook-event-types
# https://developers.line.biz/en/reference/messaging-api/#message-event
class Line::IncomingMessageService class Line::IncomingMessageService
include ::FileTypeHelper include ::FileTypeHelper
pattr_initialize [:inbox!, :params!] pattr_initialize [:inbox!, :params!]
def perform def perform
# probably test events
return if params[:events].blank?
line_contact_info line_contact_info
return if line_contact_info['userId'].blank?
set_contact set_contact
set_conversation set_conversation
# TODO: iterate over the events and handle the attachments in future # TODO: iterate over the events and handle the attachments in future

View File

@ -1,8 +1,14 @@
# Find the various telegram payload samples here: https://core.telegram.org/bots/webhooks#testing-your-bot-with-updates
# https://core.telegram.org/bots/api#available-types
class Telegram::IncomingMessageService class Telegram::IncomingMessageService
include ::FileTypeHelper include ::FileTypeHelper
pattr_initialize [:inbox!, :params!] pattr_initialize [:inbox!, :params!]
def perform def perform
# chatwoot doesn't support group conversations at the moment
return unless private_message?
set_contact set_contact
update_contact_avatar update_contact_avatar
set_conversation set_conversation
@ -20,6 +26,10 @@ class Telegram::IncomingMessageService
private private
def private_message?
params.dig(:message, :chat, :type) == 'private'
end
def account def account
@account ||= inbox.account @account ||= inbox.account
end end

View File

@ -1,4 +1,5 @@
json.id resource.id json.id resource.id
json.avatar_url resource.try(:avatar_url)
json.channel_id resource.channel_id json.channel_id resource.channel_id
json.name resource.name json.name resource.name
json.channel_type resource.channel_type json.channel_type resource.channel_type
@ -6,30 +7,42 @@ json.greeting_enabled resource.greeting_enabled
json.greeting_message resource.greeting_message json.greeting_message resource.greeting_message
json.working_hours_enabled resource.working_hours_enabled json.working_hours_enabled resource.working_hours_enabled
json.enable_email_collect resource.enable_email_collect json.enable_email_collect resource.enable_email_collect
json.out_of_office_message resource.out_of_office_message
json.csat_survey_enabled resource.csat_survey_enabled json.csat_survey_enabled resource.csat_survey_enabled
json.enable_auto_assignment resource.enable_auto_assignment
json.out_of_office_message resource.out_of_office_message
json.working_hours resource.weekly_schedule json.working_hours resource.weekly_schedule
json.timezone resource.timezone json.timezone resource.timezone
json.webhook_url resource.webhook_url json.callback_webhook_url resource.callback_webhook_url
json.avatar_url resource.try(:avatar_url)
json.page_id resource.channel.try(:page_id) ## Channel specific settings
## TODO : Clean up and move the attributes into channel sub section
## WebWidget Attributes
json.widget_color resource.channel.try(:widget_color) json.widget_color resource.channel.try(:widget_color)
json.website_url resource.channel.try(:website_url) json.website_url resource.channel.try(:website_url)
json.welcome_title resource.channel.try(:welcome_title) json.welcome_title resource.channel.try(:welcome_title)
json.welcome_tagline resource.channel.try(:welcome_tagline) json.welcome_tagline resource.channel.try(:welcome_tagline)
json.enable_auto_assignment resource.enable_auto_assignment
json.web_widget_script resource.channel.try(:web_widget_script) json.web_widget_script resource.channel.try(:web_widget_script)
json.website_token resource.channel.try(:website_token) json.website_token resource.channel.try(:website_token)
json.forward_to_email resource.channel.try(:forward_to_email)
json.phone_number resource.channel.try(:phone_number)
json.selected_feature_flags resource.channel.try(:selected_feature_flags) json.selected_feature_flags resource.channel.try(:selected_feature_flags)
json.reply_time resource.channel.try(:reply_time) json.reply_time resource.channel.try(:reply_time)
json.reauthorization_required resource.channel.try(:reauthorization_required?) if resource.facebook?
if resource.web_widget? if resource.web_widget?
json.hmac_token resource.channel.try(:hmac_token) json.hmac_token resource.channel.try(:hmac_token)
json.pre_chat_form_enabled resource.channel.try(:pre_chat_form_enabled) json.pre_chat_form_enabled resource.channel.try(:pre_chat_form_enabled)
json.pre_chat_form_options resource.channel.try(:pre_chat_form_options) json.pre_chat_form_options resource.channel.try(:pre_chat_form_options)
end end
## Facebook Attributes
json.page_id resource.channel.try(:page_id)
json.reauthorization_required resource.channel.try(:reauthorization_required?) if resource.facebook?
## Twilio Attributes
json.phone_number resource.channel.try(:phone_number)
## Email Channel Attributes
json.forward_to_email resource.channel.try(:forward_to_email)
json.email resource.channel.try(:email) if resource.email? json.email resource.channel.try(:email) if resource.email?
## API Channel Attributes
json.webhook_url resource.channel.try(:webhook_url) if resource.api? json.webhook_url resource.channel.try(:webhook_url) if resource.api?
json.inbox_identifier resource.channel.try(:identifier) if resource.api? json.inbox_identifier resource.channel.try(:identifier) if resource.api?

View File

@ -53,10 +53,6 @@ It renders the `_table` partial to display details about the resources.
<div class="metric"><%= @conversations_count %></div> <div class="metric"><%= @conversations_count %></div>
<div>Conversations</div> <div>Conversations</div>
</div> </div>
<div class="report-card">
<div class="metric"><%= @messages_count %></div>
<div>Messages</div>
</div>
</div> </div>
<div class="chart--container" style="height:500px"> <div class="chart--container" style="height:500px">

View File

@ -54,13 +54,19 @@ class Rack::Attack
# ref: https://github.com/rack/rack-attack/issues/399 # ref: https://github.com/rack/rack-attack/issues/399
throttle('login/email', limit: 20, period: 5.minutes) do |req| throttle('login/email', limit: 20, period: 5.minutes) do |req|
email = req.params['email'].presence || ActionDispatch::Request.new(req.env).params['email'].presence if req.path == '/auth/sign_in' && req.post?
email.to_s.downcase.gsub(/\s+/, '') if req.path == '/auth/sign_in' && req.post? # NOTE: This line used to throw ArgumentError /rails/action_mailbox/sendgrid/inbound_emails : invalid byte sequence in UTF-8
# Hence placed in the if block
email = req.params['email'].presence || ActionDispatch::Request.new(req.env).params['email'].presence
email.to_s.downcase.gsub(/\s+/, '')
end
end end
throttle('reset_password/email', limit: 5, period: 1.hour) do |req| throttle('reset_password/email', limit: 5, period: 1.hour) do |req|
email = req.params['email'].presence || ActionDispatch::Request.new(req.env).params['email'].presence if req.path == '/auth/password' && req.post?
email.to_s.downcase.gsub(/\s+/, '') if req.path == '/auth/password' && req.post? email = req.params['email'].presence || ActionDispatch::Request.new(req.env).params['email'].presence
email.to_s.downcase.gsub(/\s+/, '')
end
end end
end end

View File

@ -63,7 +63,7 @@ sed -i -e '/POSTGRES_HOST/ s/=.*/=localhost/' .env
sed -i -e '/POSTGRES_USERNAME/ s/=.*/=chatwoot/' .env sed -i -e '/POSTGRES_USERNAME/ s/=.*/=chatwoot/' .env
sed -i -e "/POSTGRES_PASSWORD/ s/=.*/=$pg_pass/" .env sed -i -e "/POSTGRES_PASSWORD/ s/=.*/=$pg_pass/" .env
sed -i -e '/RAILS_ENV/ s/=.*/=$RAILS_ENV/' .env sed -i -e '/RAILS_ENV/ s/=.*/=$RAILS_ENV/' .env
echo -en "\nINSTALLATION_ENV=LINUX_SCRIPT" >> ".env" echo -en "\nINSTALLATION_ENV=linux_script" >> ".env"
RAILS_ENV=production bundle exec rake db:create RAILS_ENV=production bundle exec rake db:create
RAILS_ENV=production bundle exec rake db:reset RAILS_ENV=production bundle exec rake db:reset

View File

@ -70,7 +70,7 @@ sed -i -e '/POSTGRES_HOST/ s/=.*/=localhost/' .env
sed -i -e '/POSTGRES_USERNAME/ s/=.*/=chatwoot/' .env sed -i -e '/POSTGRES_USERNAME/ s/=.*/=chatwoot/' .env
sed -i -e "/POSTGRES_PASSWORD/ s/=.*/=$pg_pass/" .env sed -i -e "/POSTGRES_PASSWORD/ s/=.*/=$pg_pass/" .env
sed -i -e '/RAILS_ENV/ s/=.*/=$RAILS_ENV/' .env sed -i -e '/RAILS_ENV/ s/=.*/=$RAILS_ENV/' .env
echo -en "\nINSTALLATION_ENV=LINUX_SCRIPT" >> ".env" echo -en "\nINSTALLATION_ENV=linux_script" >> ".env"
RAILS_ENV=production bundle exec rake db:create RAILS_ENV=production bundle exec rake db:create
RAILS_ENV=production bundle exec rake db:reset RAILS_ENV=production bundle exec rake db:reset

View File

@ -7,14 +7,18 @@ class Integrations::Dialogflow::ProcessorService
return unless processable_message?(message) return unless processable_message?(message)
return unless message.conversation.pending? return unless message.conversation.pending?
response = get_dialogflow_response(message.conversation.contact_inbox.source_id, message_content(message)) content = message_content(message)
process_response(message, response) response = get_dialogflow_response(message.conversation.contact_inbox.source_id, content) if content.present?
process_response(message, response) if response.present?
end end
private private
def message_content(message) def message_content(message)
return message.content_attributes['submitted_values'].first['value'] if event_name == 'message.updated' # TODO: might needs to change this to a way that we fetch the updated value from event data instead
# cause the message.updated event could be that that the message was deleted
return message.content_attributes['submitted_values']&.dig 'value' if event_name == 'message.updated'
message.content message.content
end end

View File

@ -45,6 +45,8 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService
end end
def send_message def send_message
return if message_content.blank?
@slack_message = slack_client.chat_postMessage( @slack_message = slack_client.chat_postMessage(
channel: hook.reference_id, channel: hook.reference_id,
text: message_content, text: message_content,

View File

@ -306,6 +306,7 @@ RSpec.describe 'Inboxes API', type: :request do
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)
expect(response.body).to include('Line Inbox') expect(response.body).to include('Line Inbox')
expect(response.body).to include('callback_webhook_url')
end end
end end
end end
@ -352,7 +353,7 @@ RSpec.describe 'Inboxes API', type: :request do
patch "/api/v1/accounts/#{account.id}/inboxes/#{api_inbox.id}", patch "/api/v1/accounts/#{account.id}/inboxes/#{api_inbox.id}",
headers: admin.create_new_auth_token, headers: admin.create_new_auth_token,
params: { enable_auto_assignment: false, channel: { webhook_url: 'webhook.test' } }, params: { enable_auto_assignment: false, channel: { webhook_url: 'webhook.test', selected_feature_flags: [] } },
as: :json as: :json
expect(response).to have_http_status(:success) expect(response).to have_http_status(:success)

View File

@ -18,10 +18,28 @@ describe Telegram::IncomingMessageService do
} }
}.with_indifferent_access }.with_indifferent_access
described_class.new(inbox: telegram_channel.inbox, params: params).perform described_class.new(inbox: telegram_channel.inbox, params: params).perform
expect(telegram_channel.inbox.conversations).not_to eq(0) expect(telegram_channel.inbox.conversations.count).not_to eq(0)
expect(Contact.all.first.name).to eq('Sojan Jose') expect(Contact.all.first.name).to eq('Sojan Jose')
expect(telegram_channel.inbox.messages.first.content).to eq('test') expect(telegram_channel.inbox.messages.first.content).to eq('test')
end end
end end
context 'when group messages' do
it 'doesnot create conversations, message and contacts' do
params = {
'update_id' => 2_342_342_343_242,
'message' => {
'message_id' => 1,
'from' => {
'id' => 23, 'is_bot' => false, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'language_code' => 'en'
},
'chat' => { 'id' => 23, 'first_name' => 'Sojan', 'last_name' => 'Jose', 'username' => 'sojan', 'type' => 'group' },
'date' => 1_631_132_077, 'text' => 'test'
}
}.with_indifferent_access
described_class.new(inbox: telegram_channel.inbox, params: params).perform
expect(telegram_channel.inbox.conversations.count).to eq(0)
end
end
end end
end end