feat(inboxes): add inbox disable toggle
This commit is contained in:
@@ -3,6 +3,8 @@ class Campaigns::CampaignConversationBuilder
|
||||
|
||||
def perform
|
||||
@contact_inbox = ContactInbox.find(@contact_inbox_id)
|
||||
return unless @contact_inbox.inbox.active?
|
||||
|
||||
@campaign = @contact_inbox.inbox.campaigns.find_by!(display_id: campaign_display_id)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
|
||||
@@ -2,6 +2,8 @@ class ConversationBuilder
|
||||
pattr_initialize [:params!, :contact_inbox!]
|
||||
|
||||
def perform
|
||||
raise CustomExceptions::Inbox::Disabled unless @contact_inbox.inbox.active?
|
||||
|
||||
look_up_exising_conversation || create_new_conversation
|
||||
end
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||
def perform
|
||||
# This channel might require reauthorization, may be owner might have changed the fb password
|
||||
return if @inbox.channel.reauthorization_required?
|
||||
return unless @inbox.active?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact_inbox
|
||||
|
||||
@@ -10,6 +10,7 @@ class Messages::Instagram::BaseMessageBuilder < Messages::Messenger::MessageBuil
|
||||
|
||||
def perform
|
||||
return if @inbox.channel.reauthorization_required?
|
||||
return unless @inbox.active?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
build_message
|
||||
|
||||
@@ -22,14 +22,15 @@ class Messages::MessageBuilder
|
||||
end
|
||||
|
||||
def perform
|
||||
raise CustomExceptions::Inbox::Disabled unless @conversation.inbox.active?
|
||||
|
||||
@message = @conversation.messages.build(message_params)
|
||||
process_attachments
|
||||
process_emails
|
||||
# When the message has no quoted content, it will just be rendered as a regular message
|
||||
# The frontend is equipped to handle this case
|
||||
process_email_content
|
||||
@message.save!
|
||||
@message
|
||||
@message.tap(&:save!)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController
|
||||
before_action :ensure_api_inbox, only: :update
|
||||
before_action :ensure_inbox_active, only: [:create, :retry]
|
||||
|
||||
def index
|
||||
@messages = message_finder.perform
|
||||
@@ -9,6 +10,8 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
||||
user = Current.user || @resource
|
||||
mb = Messages::MessageBuilder.new(user, @conversation, params)
|
||||
@message = mb.perform
|
||||
rescue CustomExceptions::Inbox::Disabled
|
||||
render_inbox_disabled_error
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
@@ -32,6 +35,8 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
||||
service.perform
|
||||
message.update!(content_attributes: {})
|
||||
::SendReplyJob.perform_later(message.id)
|
||||
rescue CustomExceptions::Inbox::Disabled
|
||||
render_inbox_disabled_error
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
@@ -77,4 +82,8 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
||||
# Only API inboxes can update messages
|
||||
render json: { error: 'Message status update is only allowed for API inboxes' }, status: :forbidden unless @conversation.inbox.api?
|
||||
end
|
||||
|
||||
def ensure_inbox_active
|
||||
render_inbox_disabled_error unless @conversation.inbox.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,6 +197,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
|
||||
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
authorize @inbox, :show?
|
||||
render_inbox_disabled_error unless @inbox.active?
|
||||
end
|
||||
|
||||
def contact
|
||||
@@ -213,6 +214,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
# and deprecate the support of passing only source_id as the param
|
||||
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
|
||||
authorize @contact_inbox.inbox, :show?
|
||||
render_inbox_disabled_error unless @contact_inbox.inbox.active?
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
render json: { error: 'source_id should be unique' }, status: :unprocessable_entity
|
||||
end
|
||||
@@ -220,12 +222,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
def build_contact_inbox
|
||||
return if @inbox.blank? || @contact.blank?
|
||||
|
||||
ContactInboxBuilder.new(
|
||||
contact: @contact,
|
||||
inbox: @inbox,
|
||||
source_id: params[:source_id],
|
||||
hmac_verified: hmac_verified?
|
||||
).perform
|
||||
ContactInboxBuilder.new(contact: @contact, inbox: @inbox, source_id: params[:source_id], hmac_verified: hmac_verified?).perform
|
||||
end
|
||||
|
||||
def conversation_finder
|
||||
|
||||
@@ -157,7 +157,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
end
|
||||
|
||||
def inbox_attributes
|
||||
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
|
||||
[:name, :avatar, :active, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
|
||||
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
|
||||
:lock_to_single_conversation, :portal_id, :sender_name_type, :business_name,
|
||||
{ csat_config: [:display_type, :message, :button_text, :language,
|
||||
|
||||
@@ -3,6 +3,7 @@ class Api::V1::Widget::BaseController < ApplicationController
|
||||
include WebsiteTokenHelper
|
||||
|
||||
before_action :set_web_widget
|
||||
before_action :ensure_inbox_active
|
||||
before_action :set_contact
|
||||
|
||||
private
|
||||
@@ -86,4 +87,8 @@ class Api::V1::Widget::BaseController < ApplicationController
|
||||
message_type: :incoming
|
||||
}
|
||||
end
|
||||
|
||||
def ensure_inbox_active
|
||||
render_inbox_disabled_error unless @web_widget.inbox.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
include Events::Types
|
||||
|
||||
DISABLED_INBOX_ACTIONS = [:create, :toggle_typing, :toggle_status, :update_last_seen, :set_custom_attributes, :destroy_custom_attributes].freeze
|
||||
|
||||
before_action :render_not_found_if_empty, only: [:toggle_typing, :toggle_status, :set_custom_attributes, :destroy_custom_attributes]
|
||||
before_action :ensure_inbox_active, only: DISABLED_INBOX_ACTIONS
|
||||
|
||||
def index
|
||||
@conversation = conversation
|
||||
|
||||
@@ -3,6 +3,7 @@ module RequestExceptionHandler
|
||||
|
||||
included do
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
|
||||
rescue_from CustomExceptions::Inbox::Disabled, with: :render_inbox_disabled_error
|
||||
end
|
||||
|
||||
private
|
||||
@@ -35,6 +36,13 @@ module RequestExceptionHandler
|
||||
render json: { error: message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def render_inbox_disabled_error(_exception = nil)
|
||||
render json: {
|
||||
error: 'inbox_disabled',
|
||||
message: 'This inbox is currently disabled'
|
||||
}, status: :forbidden
|
||||
end
|
||||
|
||||
def render_payment_required(message)
|
||||
render json: { error: message }, status: :payment_required
|
||||
end
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
class Platform::Api::V1::InboxesController < PlatformController
|
||||
before_action :set_account
|
||||
before_action :validate_account_permissible
|
||||
before_action :set_inbox
|
||||
|
||||
def disable
|
||||
update_active_state(false)
|
||||
end
|
||||
|
||||
def enable
|
||||
update_active_state(true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def validate_account_permissible
|
||||
return if @platform_app.platform_app_permissibles.find_by(permissible: @account)
|
||||
|
||||
render json: { error: 'Non permissible resource' }, status: :unauthorized
|
||||
end
|
||||
|
||||
def set_inbox
|
||||
@inbox = @account.inboxes.find(params[:id])
|
||||
end
|
||||
|
||||
def update_active_state(active)
|
||||
@inbox.update!(active: active)
|
||||
render json: { success: true, inbox_id: @inbox.id, active: @inbox.active }
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,6 @@
|
||||
class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesController
|
||||
before_action :contact_inbox, except: [:create]
|
||||
before_action :ensure_inbox_active, only: [:create, :update]
|
||||
before_action :process_hmac
|
||||
|
||||
def show; end
|
||||
@@ -45,4 +46,8 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
|
||||
def permitted_params
|
||||
params.permit(:identifier, :identifier_hash, :email, :name, :avatar_url, :phone_number, custom_attributes: {})
|
||||
end
|
||||
|
||||
def ensure_inbox_active
|
||||
render_inbox_disabled_error unless @inbox_channel.inbox.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::InboxesController
|
||||
include Events::Types
|
||||
before_action :set_conversation, only: [:toggle_typing, :update_last_seen, :show, :toggle_status]
|
||||
before_action :ensure_inbox_active, only: [:create, :toggle_typing, :toggle_status, :update_last_seen]
|
||||
|
||||
def index
|
||||
@conversations = @contact_inbox.hmac_verified? ? @contact_inbox.contact.conversations : @contact_inbox.conversations
|
||||
@@ -64,4 +65,8 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
|
||||
def conversation_params
|
||||
params.permit(custom_attributes: {})
|
||||
end
|
||||
|
||||
def ensure_inbox_active
|
||||
render_inbox_disabled_error unless @contact_inbox.inbox.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesController
|
||||
before_action :set_message, only: [:update]
|
||||
before_action :ensure_inbox_active, only: [:create, :update]
|
||||
|
||||
def index
|
||||
@messages = @conversation.nil? ? [] : message_finder.perform
|
||||
@@ -70,4 +71,8 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
|
||||
def check_csat_locked
|
||||
(Time.zone.now.to_date - @message.created_at.to_date).to_i > 14 and @message.content_type == 'input_csat'
|
||||
end
|
||||
|
||||
def ensure_inbox_active
|
||||
render_inbox_disabled_error unless @conversation.inbox.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,7 @@ class WidgetsController < ActionController::Base
|
||||
before_action :set_global_config
|
||||
before_action :set_web_widget
|
||||
before_action :ensure_account_is_active
|
||||
before_action :ensure_inbox_active
|
||||
before_action :ensure_location_is_supported
|
||||
before_action :set_token
|
||||
before_action :set_contact
|
||||
@@ -62,6 +63,10 @@ class WidgetsController < ActionController::Base
|
||||
render json: { error: 'Account is suspended' }, status: :unauthorized unless @web_widget.inbox.account.active?
|
||||
end
|
||||
|
||||
def ensure_inbox_active
|
||||
head :not_found unless @web_widget.inbox.active?
|
||||
end
|
||||
|
||||
def ensure_location_is_supported; end
|
||||
|
||||
def additional_attributes
|
||||
|
||||
@@ -10,10 +10,16 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :title="inbox.name" class="flex items-center gap-0.5 min-w-0">
|
||||
<div :title="inbox.name" class="flex items-center gap-1 min-w-0">
|
||||
<ChannelIcon :inbox="inbox" class="size-4 flex-shrink-0 text-n-slate-11" />
|
||||
<span class="truncate text-body-main text-n-slate-11">
|
||||
{{ inbox.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="inbox.active === false"
|
||||
class="shrink-0 rounded-md bg-n-ruby-3 px-1 py-0.5 text-xs font-medium text-n-ruby-11"
|
||||
>
|
||||
{{ $t('INBOX_MGMT.DISABLED') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -27,6 +27,10 @@ const props = defineProps({
|
||||
const reauthorizationRequired = computed(() => {
|
||||
return props.inbox.reauthorization_required;
|
||||
});
|
||||
|
||||
const inboxDisabled = computed(() => {
|
||||
return props.inbox.active === false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -34,6 +38,13 @@ const reauthorizationRequired = computed(() => {
|
||||
<ChannelIcon :inbox="inbox" class="size-4" />
|
||||
</span>
|
||||
<div class="flex-1 truncate min-w-0">{{ label }}</div>
|
||||
<div
|
||||
v-if="inboxDisabled"
|
||||
v-tooltip.top-end="$t('INBOX_MGMT.DISABLED')"
|
||||
class="grid place-content-center size-5 bg-n-ruby-3 rounded-full"
|
||||
>
|
||||
<Icon icon="i-woot-alert" class="size-3 text-n-ruby-9" />
|
||||
</div>
|
||||
<SidebarUnreadBadge :count="badgeCount" />
|
||||
<div
|
||||
v-if="reauthorizationRequired"
|
||||
|
||||
@@ -10,10 +10,16 @@ defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :title="inbox.name" class="flex items-center gap-0.5 min-w-0">
|
||||
<div :title="inbox.name" class="flex items-center gap-1 min-w-0">
|
||||
<ChannelIcon :inbox="inbox" class="size-4 flex-shrink-0 text-n-slate-11" />
|
||||
<span class="truncate text-label-small text-n-slate-11">
|
||||
{{ inbox.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="inbox.active === false"
|
||||
class="shrink-0 rounded-md bg-n-ruby-3 px-1 py-0.5 text-xs font-medium text-n-ruby-11"
|
||||
>
|
||||
{{ $t('INBOX_MGMT.DISABLED') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -170,8 +170,14 @@ export default {
|
||||
instagramInbox
|
||||
);
|
||||
},
|
||||
isInboxDisabled() {
|
||||
return this.inbox.active === false;
|
||||
},
|
||||
|
||||
replyWindowBannerMessage() {
|
||||
if (this.isInboxDisabled) {
|
||||
return this.$t('CONVERSATION.INBOX_DISABLED');
|
||||
}
|
||||
if (this.isAWhatsAppChannel) {
|
||||
return this.$t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY');
|
||||
}
|
||||
@@ -194,6 +200,9 @@ export default {
|
||||
return this.$t('CONVERSATION.CANNOT_REPLY');
|
||||
},
|
||||
replyWindowLink() {
|
||||
if (this.isInboxDisabled) {
|
||||
return '';
|
||||
}
|
||||
if (this.isAFacebookInbox || this.isAnInstagramChannel) {
|
||||
return REPLY_POLICY.FACEBOOK;
|
||||
}
|
||||
@@ -209,6 +218,9 @@ export default {
|
||||
return '';
|
||||
},
|
||||
replyWindowLinkText() {
|
||||
if (this.isInboxDisabled) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
this.isAWhatsAppChannel ||
|
||||
this.isAFacebookInbox ||
|
||||
@@ -455,7 +467,7 @@ export default {
|
||||
>
|
||||
<div ref="topBannerRef">
|
||||
<Banner
|
||||
v-if="!currentChat.can_reply"
|
||||
v-if="isInboxDisabled || !currentChat.can_reply"
|
||||
color-scheme="alert"
|
||||
class="mx-2 mt-2 overflow-hidden rounded-lg"
|
||||
:banner-message="replyWindowBannerMessage"
|
||||
|
||||
@@ -208,6 +208,9 @@ export default {
|
||||
},
|
||||
messagePlaceHolder() {
|
||||
if (this.isEditorDisabled) {
|
||||
if (this.inbox.active === false) {
|
||||
return this.$t('CONVERSATION.FOOTER.MESSAGING_RESTRICTED_INBOX');
|
||||
}
|
||||
if (this.isAWhatsAppChannel) {
|
||||
return this.$t('CONVERSATION.FOOTER.MESSAGING_RESTRICTED_WHATSAPP');
|
||||
}
|
||||
@@ -438,6 +441,10 @@ export default {
|
||||
return !this.showAudioRecorderEditor && !this.copilot.isActive.value;
|
||||
},
|
||||
isEditorDisabled() {
|
||||
if (this.inbox.active === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
(this.isAWhatsAppChannel || this.isAPIInbox) &&
|
||||
!this.isOnPrivateNote &&
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"LOADING_INBOXES": "Loading inboxes",
|
||||
"LOADING_CONVERSATIONS": "Loading Conversations",
|
||||
"CANNOT_REPLY": "You cannot reply due to",
|
||||
"INBOX_DISABLED": "This inbox is disabled. You cannot send messages.",
|
||||
"24_HOURS_WINDOW": "24 hour message window restriction",
|
||||
"48_HOURS_WINDOW": "48 hour message window restriction",
|
||||
"API_HOURS_WINDOW": "You can only reply to this conversation within {hours} hours",
|
||||
@@ -211,6 +212,7 @@
|
||||
"MSG_INPUT": "Shift + enter for new line. Start with '/' to select a Canned Response.",
|
||||
"PRIVATE_MSG_INPUT": "Shift + enter for new line. This will be visible only to Agents",
|
||||
"MESSAGING_RESTRICTED": "You cannot reply to this conversation",
|
||||
"MESSAGING_RESTRICTED_INBOX": "This inbox is disabled. You cannot send messages.",
|
||||
"MESSAGING_RESTRICTED_WHATSAPP": "You can only reply using a template message due to 24-hour message window restriction",
|
||||
"MESSAGING_RESTRICTED_API": "You can only reply using a template message due to message window restriction",
|
||||
"MESSAGE_SIGNATURE_NOT_CONFIGURED": "Message signature is not configured, please configure it in profile settings.",
|
||||
|
||||
@@ -743,6 +743,7 @@
|
||||
}
|
||||
},
|
||||
"SETTINGS": "Settings",
|
||||
"DISABLED": "Disabled",
|
||||
"FEATURES": {
|
||||
"LABEL": "Features",
|
||||
"DISPLAY_FILE_PICKER": "Display file picker on the widget",
|
||||
@@ -772,6 +773,10 @@
|
||||
},
|
||||
"INBOX_AGENTS": "Agents",
|
||||
"INBOX_AGENTS_SUB_TEXT": "Add or remove agents from this inbox",
|
||||
"INBOX_ACTIVE": {
|
||||
"TITLE": "Enable inbox",
|
||||
"DESCRIPTION": "When disabled, this inbox stays visible but stops new conversations, incoming messages, replies, private notes, automations, and bot processing."
|
||||
},
|
||||
"AGENT_ASSIGNMENT": "Conversation Assignment",
|
||||
"AGENT_ASSIGNMENT_SUB_TEXT": "Update conversation assignment settings",
|
||||
"UPDATE": "Update",
|
||||
|
||||
@@ -139,9 +139,19 @@ const openDelete = inbox => {
|
||||
<ChannelIcon class="size-6 text-n-slate-10" :inbox="inbox" />
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<span class="block text-heading-3 text-n-slate-12 capitalize">
|
||||
{{ inbox.name }}
|
||||
</span>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<span
|
||||
class="block text-heading-3 text-n-slate-12 capitalize truncate"
|
||||
>
|
||||
{{ inbox.name }}
|
||||
</span>
|
||||
<span
|
||||
v-if="inbox.active === false"
|
||||
class="shrink-0 rounded-md bg-n-ruby-3 px-1.5 py-0.5 text-xs font-medium text-n-ruby-11"
|
||||
>
|
||||
{{ $t('INBOX_MGMT.DISABLED') }}
|
||||
</span>
|
||||
</div>
|
||||
<ChannelName
|
||||
:channel-type="inbox.channel_type"
|
||||
:medium="inbox.medium"
|
||||
|
||||
@@ -91,6 +91,7 @@ export default {
|
||||
senderNameType: 'friendly',
|
||||
businessName: '',
|
||||
locktoSingleConversation: false,
|
||||
inboxActive: true,
|
||||
allowMessagesAfterResolved: true,
|
||||
continuityViaEmail: true,
|
||||
selectedInboxName: '',
|
||||
@@ -470,6 +471,7 @@ export default {
|
||||
this.businessName = this.inbox.business_name;
|
||||
this.allowMessagesAfterResolved =
|
||||
this.inbox.allow_messages_after_resolved;
|
||||
this.inboxActive = this.inbox.active !== false;
|
||||
this.continuityViaEmail = this.inbox.continuity_via_email;
|
||||
this.channelWebsiteUrl = this.inbox.website_url;
|
||||
this.channelWelcomeTitle = this.inbox.welcome_title;
|
||||
@@ -581,6 +583,7 @@ export default {
|
||||
const payload = {
|
||||
id: this.currentInboxId,
|
||||
name: this.selectedInboxName?.trim(),
|
||||
active: this.inboxActive,
|
||||
enable_email_collect: this.emailCollectEnabled,
|
||||
allow_messages_after_resolved: this.allowMessagesAfterResolved,
|
||||
greeting_enabled: this.greetingEnabled,
|
||||
@@ -819,6 +822,15 @@ export default {
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsToggleSection
|
||||
v-model="inboxActive"
|
||||
:header="$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_ACTIVE.TITLE')"
|
||||
:description="
|
||||
$t('INBOX_MGMT.SETTINGS_POPUP.INBOX_ACTIVE.DESCRIPTION')
|
||||
"
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<SettingsFieldSection
|
||||
v-if="isAWebWidgetInbox"
|
||||
:label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_DOMAIN.LABEL')"
|
||||
|
||||
@@ -13,6 +13,7 @@ class Inboxes::FetchImapEmailInboxesJob < ApplicationJob
|
||||
|
||||
def should_fetch_emails?(inbox)
|
||||
return false if inbox.account.suspended?
|
||||
return false unless inbox.active?
|
||||
return false unless inbox.channel.imap_enabled
|
||||
return false if inbox.channel.reauthorization_required?
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class Inboxes::FetchImapEmailsJob < MutexApplicationJob
|
||||
private
|
||||
|
||||
def should_fetch_email?(channel)
|
||||
channel.imap_enabled? && !channel.reauthorization_required?
|
||||
channel.inbox.active? && channel.imap_enabled? && !channel.reauthorization_required?
|
||||
end
|
||||
|
||||
def process_email_for_channel(channel, interval)
|
||||
|
||||
@@ -17,6 +17,8 @@ class SendReplyJob < ApplicationJob
|
||||
|
||||
def perform(message_id)
|
||||
message = Message.find(message_id)
|
||||
return unless message.inbox.active?
|
||||
|
||||
channel_name = message.conversation.inbox.channel.class.to_s
|
||||
|
||||
return send_on_facebook_page(message) if channel_name == 'Channel::FacebookPage'
|
||||
|
||||
@@ -56,6 +56,7 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob
|
||||
channel = find_channel(instagram_id)
|
||||
|
||||
next if channel.blank?
|
||||
next unless channel.inbox.active?
|
||||
|
||||
if (event_name = event_name(messaging))
|
||||
send(event_name, messaging, channel)
|
||||
|
||||
@@ -4,6 +4,7 @@ class Webhooks::LineEventsJob < ApplicationJob
|
||||
def perform(params: {}, signature: '', post_body: '')
|
||||
@params = params
|
||||
return unless valid_event_payload?
|
||||
return unless @channel.inbox.active?
|
||||
return unless valid_post_body?(post_body, signature)
|
||||
|
||||
Line::IncomingMessageService.new(inbox: @channel.inbox, params: @params['line'].with_indifferent_access).perform
|
||||
|
||||
@@ -8,6 +8,7 @@ class Webhooks::SmsEventsJob < ApplicationJob
|
||||
|
||||
channel = Channel::Sms.find_by(phone_number: params[:to])
|
||||
return unless channel
|
||||
return unless channel.inbox.active?
|
||||
|
||||
process_event_params(channel, params)
|
||||
end
|
||||
|
||||
@@ -19,6 +19,7 @@ class Webhooks::TelegramEventsJob < ApplicationJob
|
||||
def channel_is_inactive?(channel)
|
||||
return true if channel.blank?
|
||||
return true unless channel.account.active?
|
||||
return true unless channel.inbox.active?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
@@ -21,6 +21,7 @@ class Webhooks::TiktokEventsJob < MutexApplicationJob
|
||||
def channel_is_inactive?
|
||||
return true if channel.blank?
|
||||
return true unless channel.account.active?
|
||||
return true unless channel.inbox.active?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
@@ -129,6 +129,7 @@ class Webhooks::WhatsappEventsJob < MutexApplicationJob
|
||||
# Only skip for embedded signup when reauth is required; manual flow uses API keys and should still receive webhooks
|
||||
return true if channel.reauthorization_required? && embedded_signup_channel?(channel)
|
||||
return true unless channel.account.active?
|
||||
return true unless channel.inbox.active?
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
@@ -63,6 +63,8 @@ class AgentBotListener < BaseListener
|
||||
private
|
||||
|
||||
def agent_bots_for(inbox, conversation = nil)
|
||||
return [] unless inbox.active?
|
||||
|
||||
bots = []
|
||||
bots << conversation.assignee_agent_bot if conversation&.assignee_agent_bot.present?
|
||||
inbox_bot = active_inbox_agent_bot(inbox)
|
||||
|
||||
@@ -18,6 +18,7 @@ class AutomationRuleListener < BaseListener
|
||||
def message_created(event)
|
||||
message = event.data[:message]
|
||||
|
||||
return unless message.inbox.active?
|
||||
return if ignore_message_created_event?(event)
|
||||
|
||||
account = message.try(:account)
|
||||
@@ -39,10 +40,11 @@ class AutomationRuleListener < BaseListener
|
||||
def process_conversation_event(event, event_name)
|
||||
return if performed_by_automation?(event)
|
||||
|
||||
auto_reply_skip_events = %w[conversation_created conversation_opened]
|
||||
return if auto_reply_skip_events.include?(event_name) && ignore_auto_reply_event?(event)
|
||||
return if auto_reply_event?(event, event_name)
|
||||
|
||||
conversation = event.data[:conversation]
|
||||
return unless conversation.inbox.active?
|
||||
|
||||
account = conversation.account
|
||||
changed_attributes = event.data[:changed_attributes]
|
||||
|
||||
@@ -74,6 +76,10 @@ class AutomationRuleListener < BaseListener
|
||||
event.data[:performed_by].present? && event.data[:performed_by].instance_of?(AutomationRule)
|
||||
end
|
||||
|
||||
def auto_reply_event?(event, event_name)
|
||||
%w[conversation_created conversation_opened].include?(event_name) && ignore_auto_reply_event?(event)
|
||||
end
|
||||
|
||||
def ignore_auto_reply_event?(event)
|
||||
conversation = event.data[:conversation]
|
||||
conversation.additional_attributes['auto_reply'].present?
|
||||
|
||||
@@ -7,6 +7,8 @@ class Line::IncomingMessageService
|
||||
LINE_STICKER_IMAGE_URL = 'https://stickershop.line-scdn.net/stickershop/v1/sticker/%s/android/sticker.png'.freeze
|
||||
|
||||
def perform
|
||||
return unless @inbox.active?
|
||||
|
||||
# probably test events
|
||||
return if params[:events].blank?
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ class Sms::IncomingMessageService
|
||||
pattr_initialize [:inbox!, :params!]
|
||||
|
||||
def perform
|
||||
return unless @inbox.active?
|
||||
|
||||
set_contact
|
||||
set_conversation
|
||||
@message = @conversation.messages.create!(
|
||||
|
||||
@@ -7,6 +7,8 @@ class Telegram::IncomingMessageService
|
||||
pattr_initialize [:inbox!, :params!]
|
||||
|
||||
def perform
|
||||
return unless @inbox.active?
|
||||
|
||||
# chatwoot doesn't support group conversations at the moment
|
||||
transform_business_message!
|
||||
return unless private_message?
|
||||
|
||||
@@ -4,6 +4,8 @@ class Tiktok::MessageService
|
||||
pattr_initialize [:channel!, :content!, :outgoing_echo]
|
||||
|
||||
def perform
|
||||
return unless channel.inbox.active?
|
||||
|
||||
if outgoing_message?
|
||||
message = find_message(tt_conversation_id, tt_message_id)
|
||||
return if message.present?
|
||||
|
||||
@@ -6,6 +6,7 @@ class Twilio::IncomingMessageService
|
||||
|
||||
def perform
|
||||
return if twilio_channel.blank?
|
||||
return unless inbox.active?
|
||||
|
||||
set_contact
|
||||
set_conversation
|
||||
|
||||
@@ -5,6 +5,8 @@ class Twitter::DirectMessageParserService < Twitter::WebhooksBaseService
|
||||
return if source_app_id == parent_app_id
|
||||
|
||||
set_inbox
|
||||
return unless @inbox.active?
|
||||
|
||||
ensure_contacts
|
||||
set_conversation
|
||||
@message = @conversation.messages.create!(
|
||||
|
||||
@@ -4,6 +4,7 @@ class Twitter::TweetParserService < Twitter::WebhooksBaseService
|
||||
def perform
|
||||
set_inbox
|
||||
|
||||
return unless @inbox.active?
|
||||
return if !tweets_enabled? || message_already_exist? || user_has_blocked?
|
||||
|
||||
create_message
|
||||
|
||||
@@ -8,6 +8,8 @@ class Whatsapp::IncomingMessageBaseService
|
||||
pattr_initialize [:inbox!, :params!, :outgoing_echo]
|
||||
|
||||
def perform
|
||||
return unless @inbox.active?
|
||||
|
||||
processed_params
|
||||
|
||||
if processed_params.try(:[], :statuses).present?
|
||||
|
||||
@@ -2,6 +2,7 @@ json.id resource.id
|
||||
json.avatar_url resource.try(:avatar_url)
|
||||
json.channel_id resource.channel_id
|
||||
json.name resource.name
|
||||
json.active resource.active
|
||||
json.channel_type resource.channel_type
|
||||
json.greeting_enabled resource.greeting_enabled
|
||||
json.greeting_message resource.greeting_message
|
||||
|
||||
@@ -556,6 +556,12 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
resources :email_channel_migrations, only: [:create]
|
||||
resources :inboxes, only: [] do
|
||||
member do
|
||||
post :disable
|
||||
post :enable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddActiveToInboxes < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :inboxes, :active, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
||||
+2
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_05_25_093000) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_09_090000) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -898,6 +898,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_05_25_093000) do
|
||||
t.boolean "enable_email_collect", default: true
|
||||
t.boolean "csat_survey_enabled", default: false
|
||||
t.boolean "allow_messages_after_resolved", default: true
|
||||
t.boolean "active", default: true, null: false
|
||||
t.jsonb "auto_assignment_config", default: {}
|
||||
t.boolean "lock_to_single_conversation", default: false, null: false
|
||||
t.bigint "portal_id"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CustomExceptions::Inbox::Disabled < CustomExceptions::Base
|
||||
def initialize(data = {})
|
||||
super
|
||||
end
|
||||
|
||||
def message
|
||||
'This inbox is currently disabled'
|
||||
end
|
||||
|
||||
def error_code
|
||||
'inbox_disabled'
|
||||
end
|
||||
|
||||
def http_status
|
||||
:forbidden
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user