mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 20:18:08 +00:00
feat: Ability to block contacts permanently (#8922)
Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
@@ -148,7 +148,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:name, :identifier, :email, :phone_number, :avatar, :avatar_url, additional_attributes: {}, custom_attributes: {})
|
||||
params.permit(:name, :identifier, :email, :phone_number, :avatar, :blocked, :avatar_url, additional_attributes: {}, custom_attributes: {})
|
||||
end
|
||||
|
||||
def contact_custom_attributes
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('MoveActions', () => {
|
||||
|
||||
expect(window.bus.$emit).toBeCalledWith(
|
||||
'newToastMessage',
|
||||
'This conversation is muted for 6 hours',
|
||||
'This contact is blocked successfully. You will not be notified of any future conversations.',
|
||||
undefined
|
||||
);
|
||||
});
|
||||
@@ -104,7 +104,7 @@ describe('MoveActions', () => {
|
||||
|
||||
expect(window.bus.$emit).toBeCalledWith(
|
||||
'newToastMessage',
|
||||
'This conversation is unmuted',
|
||||
'This contact is unblocked successfully.',
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
},
|
||||
"MERGE_CONTACT": "Merge contact",
|
||||
"CONTACT_ACTIONS": "Contact actions",
|
||||
"MUTE_CONTACT": "Mute Conversation",
|
||||
"UNMUTE_CONTACT": "Unmute Conversation",
|
||||
"MUTED_SUCCESS": "This conversation is muted for 6 hours",
|
||||
"UNMUTED_SUCCESS": "This conversation is unmuted",
|
||||
"MUTE_CONTACT": "Block Contact",
|
||||
"UNMUTE_CONTACT": "Unblock Contact",
|
||||
"MUTED_SUCCESS": "This contact is blocked successfully. You will not be notified of any future conversations.",
|
||||
"UNMUTED_SUCCESS": "This contact is unblocked successfully.",
|
||||
"SEND_TRANSCRIPT": "Send Transcript",
|
||||
"EDIT_LABEL": "Edit",
|
||||
"SIDEBAR_SECTIONS": {
|
||||
|
||||
@@ -3,26 +3,16 @@ module ConversationMuteHelpers
|
||||
|
||||
def mute!
|
||||
resolved!
|
||||
Redis::Alfred.setex(mute_key, 1, mute_period)
|
||||
contact.update(blocked: true)
|
||||
create_muted_message
|
||||
end
|
||||
|
||||
def unmute!
|
||||
Redis::Alfred.delete(mute_key)
|
||||
contact.update(blocked: false)
|
||||
create_unmuted_message
|
||||
end
|
||||
|
||||
def muted?
|
||||
Redis::Alfred.get(mute_key).present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mute_key
|
||||
format(Redis::RedisKeys::CONVERSATION_MUTE_KEY, id: id)
|
||||
end
|
||||
|
||||
def mute_period
|
||||
6.hours
|
||||
contact.blocked?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# additional_attributes :jsonb
|
||||
# blocked :boolean default(FALSE), not null
|
||||
# contact_type :integer default("visitor")
|
||||
# country_code :string default("")
|
||||
# custom_attributes :jsonb
|
||||
@@ -24,6 +25,7 @@
|
||||
# Indexes
|
||||
#
|
||||
# index_contacts_on_account_id (account_id)
|
||||
# index_contacts_on_blocked (blocked)
|
||||
# index_contacts_on_lower_email_account_id (lower((email)::text), account_id)
|
||||
# index_contacts_on_name_email_phone_number_identifier (name,email,phone_number,identifier) USING gin
|
||||
# index_contacts_on_nonempty_fields (account_id,email,phone_number,identifier) WHERE (((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))
|
||||
|
||||
@@ -61,6 +61,7 @@ class Conversation < ApplicationRecord
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
validates :contact_id, presence: true
|
||||
before_validation :validate_additional_attributes
|
||||
validates :additional_attributes, jsonb_attributes_length: true
|
||||
validates :custom_attributes, jsonb_attributes_length: true
|
||||
@@ -103,7 +104,7 @@ class Conversation < ApplicationRecord
|
||||
has_many :attachments, through: :messages
|
||||
|
||||
before_save :ensure_snooze_until_reset
|
||||
before_create :mark_conversation_pending_if_bot
|
||||
before_create :determine_conversation_status
|
||||
before_create :ensure_waiting_since
|
||||
|
||||
after_update_commit :execute_after_update_commit_callbacks
|
||||
@@ -226,7 +227,9 @@ class Conversation < ApplicationRecord
|
||||
self.additional_attributes = {} unless additional_attributes.is_a?(Hash)
|
||||
end
|
||||
|
||||
def mark_conversation_pending_if_bot
|
||||
def determine_conversation_status
|
||||
self.status = :resolved and return if contact.blocked?
|
||||
|
||||
# Message template hooks aren't executed for conversations from campaigns
|
||||
# So making these conversations open for agent visibility
|
||||
return if campaign.present?
|
||||
|
||||
6
db/migrate/20240213131252_add_blocked_to_contacts.rb
Normal file
6
db/migrate/20240213131252_add_blocked_to_contacts.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class AddBlockedToContacts < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :contacts, :blocked, :boolean, default: false, null: false
|
||||
add_index :contacts, :blocked
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_02_16_055809) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
@@ -423,10 +424,12 @@ ActiveRecord::Schema[7.0].define(version: 2024_02_16_055809) do
|
||||
t.string "last_name", default: ""
|
||||
t.string "location", default: ""
|
||||
t.string "country_code", default: ""
|
||||
t.boolean "blocked", default: false, null: false
|
||||
t.index "lower((email)::text), account_id", name: "index_contacts_on_lower_email_account_id"
|
||||
t.index ["account_id", "email", "phone_number", "identifier"], name: "index_contacts_on_nonempty_fields", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))"
|
||||
t.index ["account_id"], name: "index_contacts_on_account_id"
|
||||
t.index ["account_id"], name: "index_resolved_contact_account_id", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))"
|
||||
t.index ["blocked"], name: "index_contacts_on_blocked"
|
||||
t.index ["email", "account_id"], name: "uniq_email_per_account_contact", unique: true
|
||||
t.index ["identifier", "account_id"], name: "uniq_identifier_per_account_contact", unique: true
|
||||
t.index ["name", "email", "phone_number", "identifier"], name: "index_contacts_on_name_email_phone_number_identifier", opclass: :gin_trgm_ops, using: :gin
|
||||
|
||||
@@ -557,6 +557,27 @@ RSpec.describe 'Contacts API', type: :request do
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(Avatar::AvatarFromUrlJob).to have_been_enqueued.with(contact, 'http://example.com/avatar.png')
|
||||
end
|
||||
|
||||
it 'allows blocking of contact' do
|
||||
patch "/api/v1/accounts/#{account.id}/contacts/#{contact.id}",
|
||||
params: { blocked: true },
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(contact.reload.blocked).to be(true)
|
||||
end
|
||||
|
||||
it 'allows unblocking of contact' do
|
||||
contact.update(blocked: true)
|
||||
patch "/api/v1/accounts/#{account.id}/contacts/#{contact.id}",
|
||||
params: { blocked: false },
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(contact.reload.blocked).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -372,9 +372,9 @@ RSpec.describe Conversation do
|
||||
expect(conversation.reload.resolved?).to be(true)
|
||||
end
|
||||
|
||||
it 'marks conversation as muted in redis' do
|
||||
it 'blocks the contact' do
|
||||
mute!
|
||||
expect(Redis::Alfred.get(conversation.send(:mute_key))).not_to be_nil
|
||||
expect(conversation.reload.contact.blocked?).to be(true)
|
||||
end
|
||||
|
||||
it 'creates mute message' do
|
||||
@@ -400,10 +400,9 @@ RSpec.describe Conversation do
|
||||
expect { unmute! }.not_to(change { conversation.reload.status })
|
||||
end
|
||||
|
||||
it 'marks conversation as muted in redis' do
|
||||
expect { unmute! }
|
||||
.to change { Redis::Alfred.get(conversation.send(:mute_key)) }
|
||||
.to nil
|
||||
it 'unblocks the contact' do
|
||||
unmute!
|
||||
expect(conversation.reload.contact.blocked?).to be(false)
|
||||
end
|
||||
|
||||
it 'creates unmute message' do
|
||||
@@ -549,6 +548,17 @@ RSpec.describe Conversation do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when conversation is created by blocked contact' do
|
||||
let(:account) { create(:account) }
|
||||
let(:blocked_contact) { create(:contact, account: account, blocked: true) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
|
||||
it 'creates conversation in resolved state' do
|
||||
conversation = create(:conversation, account: account, contact: blocked_contact, inbox: inbox)
|
||||
expect(conversation.status).to eq('resolved')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#botinbox: when conversation created inside inbox with agent bot' do
|
||||
let!(:bot_inbox) { create(:agent_bot_inbox) }
|
||||
let(:conversation) { create(:conversation, inbox: bot_inbox.inbox) }
|
||||
|
||||
Reference in New Issue
Block a user