From ae4c8d818fe038aff5e1dac5fc6ea457c48f4851 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 22 Feb 2024 03:48:42 +0530 Subject: [PATCH] feat: Ability to block contacts permanently (#8922) Co-authored-by: Pranav --- .../api/v1/accounts/contacts_controller.rb | 2 +- .../conversation/specs/MoreActions.spec.js | 4 ++-- .../dashboard/i18n/locale/en/contact.json | 8 +++---- .../concerns/conversation_mute_helpers.rb | 16 +++----------- app/models/contact.rb | 2 ++ app/models/conversation.rb | 7 ++++-- .../20240213131252_add_blocked_to_contacts.rb | 6 +++++ db/schema.rb | 3 +++ .../v1/accounts/contacts_controller_spec.rb | 21 ++++++++++++++++++ spec/models/conversation_spec.rb | 22 ++++++++++++++----- 10 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 db/migrate/20240213131252_add_blocked_to_contacts.rb diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 98683ea25..71e9100e7 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -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 diff --git a/app/javascript/dashboard/components/widgets/conversation/specs/MoreActions.spec.js b/app/javascript/dashboard/components/widgets/conversation/specs/MoreActions.spec.js index dafe97cba..df3c85f1f 100644 --- a/app/javascript/dashboard/components/widgets/conversation/specs/MoreActions.spec.js +++ b/app/javascript/dashboard/components/widgets/conversation/specs/MoreActions.spec.js @@ -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 ); }); diff --git a/app/javascript/dashboard/i18n/locale/en/contact.json b/app/javascript/dashboard/i18n/locale/en/contact.json index cae908016..791279899 100644 --- a/app/javascript/dashboard/i18n/locale/en/contact.json +++ b/app/javascript/dashboard/i18n/locale/en/contact.json @@ -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": { diff --git a/app/models/concerns/conversation_mute_helpers.rb b/app/models/concerns/conversation_mute_helpers.rb index 525346869..c6ea4c7b1 100644 --- a/app/models/concerns/conversation_mute_helpers.rb +++ b/app/models/concerns/conversation_mute_helpers.rb @@ -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 diff --git a/app/models/contact.rb b/app/models/contact.rb index 23eca55e6..07b29fdf3 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -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)) diff --git a/app/models/conversation.rb b/app/models/conversation.rb index fb2ae8b77..72518f250 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -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? diff --git a/db/migrate/20240213131252_add_blocked_to_contacts.rb b/db/migrate/20240213131252_add_blocked_to_contacts.rb new file mode 100644 index 000000000..6d37484e4 --- /dev/null +++ b/db/migrate/20240213131252_add_blocked_to_contacts.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 546df444f..775e67f96 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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 diff --git a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb index 21511310b..d2527e6a9 100644 --- a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb @@ -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 diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index efeda965d..2f79b08f2 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -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) }