chore: Automate conversation display_id generation with db triggers (#1412)
Automate conversation display_id generation with db triggers Co-authored-by: Saurabh Mehta <saurabh1.mehta@airtel.com> Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
parent
45059b6fe9
commit
627d3a575a
10 changed files with 102 additions and 18 deletions
3
Gemfile
3
Gemfile
|
@ -96,6 +96,9 @@ gem 'geocoder'
|
||||||
# to parse maxmind db
|
# to parse maxmind db
|
||||||
gem 'maxminddb'
|
gem 'maxminddb'
|
||||||
|
|
||||||
|
# to create db triggers
|
||||||
|
gem 'hairtrigger'
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'annotate'
|
gem 'annotate'
|
||||||
gem 'bullet'
|
gem 'bullet'
|
||||||
|
|
18
Gemfile.lock
18
Gemfile.lock
|
@ -237,6 +237,10 @@ GEM
|
||||||
groupdate (5.1.0)
|
groupdate (5.1.0)
|
||||||
activesupport (>= 5)
|
activesupport (>= 5)
|
||||||
haikunator (1.1.0)
|
haikunator (1.1.0)
|
||||||
|
hairtrigger (0.2.23)
|
||||||
|
activerecord (>= 5.0, < 7)
|
||||||
|
ruby2ruby (~> 2.4)
|
||||||
|
ruby_parser (~> 3.10)
|
||||||
hana (1.3.6)
|
hana (1.3.6)
|
||||||
hashdiff (1.0.1)
|
hashdiff (1.0.1)
|
||||||
hashie (4.1.0)
|
hashie (4.1.0)
|
||||||
|
@ -427,13 +431,19 @@ GEM
|
||||||
parser (>= 2.7.1.4)
|
parser (>= 2.7.1.4)
|
||||||
rubocop-performance (1.7.1)
|
rubocop-performance (1.7.1)
|
||||||
rubocop (>= 0.82.0)
|
rubocop (>= 0.82.0)
|
||||||
rubocop-rails (2.7.1)
|
rubocop-rails (2.8.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 0.87.0)
|
rubocop (>= 0.87.0)
|
||||||
rubocop-rspec (1.43.2)
|
rubocop-rspec (1.43.2)
|
||||||
rubocop (~> 0.87)
|
rubocop (~> 0.87)
|
||||||
ruby-progressbar (1.10.1)
|
ruby-progressbar (1.10.1)
|
||||||
|
ruby2ruby (2.4.4)
|
||||||
|
ruby_parser (~> 3.1)
|
||||||
|
sexp_processor (~> 4.6)
|
||||||
|
ruby_parser (3.15.0)
|
||||||
|
rubocop (>= 0.87.0)
|
||||||
|
sexp_processor (~> 4.9)
|
||||||
safe_yaml (1.0.5)
|
safe_yaml (1.0.5)
|
||||||
sass (3.7.4)
|
sass (3.7.4)
|
||||||
sass-listen (~> 4.0.0)
|
sass-listen (~> 4.0.0)
|
||||||
|
@ -459,6 +469,7 @@ GEM
|
||||||
semantic_range (2.3.0)
|
semantic_range (2.3.0)
|
||||||
sentry-raven (3.0.3)
|
sentry-raven (3.0.3)
|
||||||
faraday (>= 1.0)
|
faraday (>= 1.0)
|
||||||
|
sexp_processor (4.15.1)
|
||||||
shoulda-matchers (4.4.1)
|
shoulda-matchers (4.4.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
sidekiq (6.1.1)
|
sidekiq (6.1.1)
|
||||||
|
@ -511,7 +522,7 @@ GEM
|
||||||
nokogiri (>= 1.6, < 2.0)
|
nokogiri (>= 1.6, < 2.0)
|
||||||
twitty (0.1.1)
|
twitty (0.1.1)
|
||||||
oauth
|
oauth
|
||||||
tzinfo (1.2.7)
|
tzinfo (1.2.8)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2020.1)
|
tzinfo-data (1.2020.1)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
|
@ -554,7 +565,7 @@ GEM
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
wisper (2.0.0)
|
wisper (2.0.0)
|
||||||
zeitwerk (2.4.0)
|
zeitwerk (2.4.1)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
@ -589,6 +600,7 @@ DEPENDENCIES
|
||||||
google-cloud-storage
|
google-cloud-storage
|
||||||
groupdate
|
groupdate
|
||||||
haikunator
|
haikunator
|
||||||
|
hairtrigger
|
||||||
hashie
|
hashie
|
||||||
jbuilder
|
jbuilder
|
||||||
json_refs!
|
json_refs!
|
||||||
|
|
|
@ -91,4 +91,8 @@ class Account < ApplicationRecord
|
||||||
def notify_creation
|
def notify_creation
|
||||||
Rails.configuration.dispatcher.dispatch(ACCOUNT_CREATED, Time.zone.now, account: self)
|
Rails.configuration.dispatcher.dispatch(ACCOUNT_CREATED, Time.zone.now, account: self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trigger.after(:insert).for_each(:row) do
|
||||||
|
"execute format('create sequence IF NOT EXISTS conv_dpid_seq_%s', NEW.id);"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
# agent_last_seen_at :datetime
|
# agent_last_seen_at :datetime
|
||||||
# contact_last_seen_at :datetime
|
# contact_last_seen_at :datetime
|
||||||
# identifier :string
|
# identifier :string
|
||||||
# last_activity_at :datetime not null
|
# last_activity_at :datetime
|
||||||
# locked :boolean default(FALSE)
|
# locked :boolean default(FALSE)
|
||||||
# status :integer default("open"), not null
|
# status :integer default("open"), not null
|
||||||
# uuid :uuid not null
|
# uuid :uuid not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime
|
||||||
# account_id :integer not null
|
# account_id :integer not null
|
||||||
# assignee_id :integer
|
# assignee_id :integer
|
||||||
# contact_id :bigint
|
# contact_id :bigint
|
||||||
|
@ -52,12 +52,13 @@ class Conversation < ApplicationRecord
|
||||||
has_many :messages, dependent: :destroy, autosave: true
|
has_many :messages, dependent: :destroy, autosave: true
|
||||||
|
|
||||||
before_create :set_bot_conversation
|
before_create :set_bot_conversation
|
||||||
before_create :set_display_id, unless: :display_id?
|
|
||||||
# wanted to change this to after_update commit. But it ended up creating a loop
|
# wanted to change this to after_update commit. But it ended up creating a loop
|
||||||
# reinvestigate in future and identity the implications
|
# reinvestigate in future and identity the implications
|
||||||
after_update :notify_status_change, :create_activity
|
after_update :notify_status_change, :create_activity
|
||||||
after_create_commit :notify_conversation_creation, :queue_conversation_auto_resolution_job
|
after_create_commit :notify_conversation_creation, :queue_conversation_auto_resolution_job
|
||||||
after_save :run_round_robin
|
after_save :run_round_robin
|
||||||
|
after_commit :set_display_id, unless: :display_id?
|
||||||
|
|
||||||
delegate :auto_resolve_duration, to: :account
|
delegate :auto_resolve_duration, to: :account
|
||||||
|
|
||||||
|
@ -154,10 +155,7 @@ class Conversation < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_display_id
|
def set_display_id
|
||||||
self.display_id = loop do
|
reload
|
||||||
next_display_id = account.conversations.maximum('display_id').to_i + 1
|
|
||||||
break next_display_id unless account.conversations.exists?(display_id: next_display_id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_activity
|
def create_activity
|
||||||
|
@ -289,4 +287,9 @@ class Conversation < ApplicationRecord
|
||||||
def mute_period
|
def mute_period
|
||||||
6.hours
|
6.hours
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# creating db triggers
|
||||||
|
trigger.before(:insert).for_each(:row) do
|
||||||
|
"NEW.display_id := nextval('conv_dpid_seq_' || NEW.account_id);"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# This migration was auto-generated via `rake db:generate_trigger_migration'.
|
||||||
|
# While you can edit this file, any changes you make to the definitions here
|
||||||
|
# will be undone by the next auto-generated trigger migration.
|
||||||
|
|
||||||
|
class CreateTriggersAccountsInsertOrConversationsInsert < ActiveRecord::Migration[6.0]
|
||||||
|
def up
|
||||||
|
create_trigger('accounts_after_insert_row_tr', generated: true, compatibility: 1)
|
||||||
|
.on('accounts')
|
||||||
|
.after(:insert)
|
||||||
|
.for_each(:row) do
|
||||||
|
"execute format('create sequence IF NOT EXISTS conv_dpid_seq_%s', NEW.id);"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_trigger('conversations_before_insert_row_tr', generated: true, compatibility: 1)
|
||||||
|
.on('conversations')
|
||||||
|
.before(:insert)
|
||||||
|
.for_each(:row) do
|
||||||
|
"NEW.display_id := nextval('conv_dpid_seq_' || NEW.account_id);"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_trigger('accounts_after_insert_row_tr', 'accounts', generated: true)
|
||||||
|
|
||||||
|
drop_trigger('conversations_before_insert_row_tr', 'conversations', generated: true)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
class ConvDpidSeqForExistingAccnts < ActiveRecord::Migration[6.0]
|
||||||
|
def up
|
||||||
|
::Account.find_in_batches do |accounts_batch|
|
||||||
|
Rails.logger.info "migrated till #{accounts_batch.first.id}\n"
|
||||||
|
accounts_batch.each do |account|
|
||||||
|
display_id = Conversation.where(account_id: account.id).maximum('display_id')
|
||||||
|
display_id ||= 0 # for accounts with out conversations
|
||||||
|
ActiveRecord::Base.connection.exec_query("create sequence IF NOT EXISTS conv_dpid_seq_#{account.id} START #{display_id + 1}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
::Account.find_in_batches do |accounts_batch|
|
||||||
|
Rails.logger.info "migrated till #{accounts_batch.first.id}\n"
|
||||||
|
accounts_batch.each do |account|
|
||||||
|
ActiveRecord::Base.connection.exec_query("drop sequence IF EXISTS conv_dpid_seq_#{account.id}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
22
db/schema.rb
22
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2020_10_27_135006) do
|
ActiveRecord::Schema.define(version: 2020_11_25_123131) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
|
@ -219,8 +219,8 @@ ActiveRecord::Schema.define(version: 2020_10_27_135006) do
|
||||||
t.integer "inbox_id", null: false
|
t.integer "inbox_id", null: false
|
||||||
t.integer "status", default: 0, null: false
|
t.integer "status", default: 0, null: false
|
||||||
t.integer "assignee_id"
|
t.integer "assignee_id"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at"
|
||||||
t.bigint "contact_id"
|
t.bigint "contact_id"
|
||||||
t.integer "display_id", null: false
|
t.integer "display_id", null: false
|
||||||
t.datetime "contact_last_seen_at"
|
t.datetime "contact_last_seen_at"
|
||||||
|
@ -230,7 +230,7 @@ ActiveRecord::Schema.define(version: 2020_10_27_135006) do
|
||||||
t.bigint "contact_inbox_id"
|
t.bigint "contact_inbox_id"
|
||||||
t.uuid "uuid", default: -> { "gen_random_uuid()" }, null: false
|
t.uuid "uuid", default: -> { "gen_random_uuid()" }, null: false
|
||||||
t.string "identifier"
|
t.string "identifier"
|
||||||
t.datetime "last_activity_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
|
t.datetime "last_activity_at", default: -> { "CURRENT_TIMESTAMP" }
|
||||||
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
|
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
|
||||||
t.index ["account_id"], name: "index_conversations_on_account_id"
|
t.index ["account_id"], name: "index_conversations_on_account_id"
|
||||||
t.index ["contact_inbox_id"], name: "index_conversations_on_contact_inbox_id"
|
t.index ["contact_inbox_id"], name: "index_conversations_on_contact_inbox_id"
|
||||||
|
@ -536,4 +536,18 @@ ActiveRecord::Schema.define(version: 2020_10_27_135006) do
|
||||||
add_foreign_key "contact_inboxes", "contacts"
|
add_foreign_key "contact_inboxes", "contacts"
|
||||||
add_foreign_key "contact_inboxes", "inboxes"
|
add_foreign_key "contact_inboxes", "inboxes"
|
||||||
add_foreign_key "conversations", "contact_inboxes"
|
add_foreign_key "conversations", "contact_inboxes"
|
||||||
|
create_trigger("accounts_after_insert_row_tr", :generated => true, :compatibility => 1).
|
||||||
|
on("accounts").
|
||||||
|
after(:insert).
|
||||||
|
for_each(:row) do
|
||||||
|
"execute format('create sequence IF NOT EXISTS conv_dpid_seq_%s', NEW.id);"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_trigger("conversations_before_insert_row_tr", :generated => true, :compatibility => 1).
|
||||||
|
on("conversations").
|
||||||
|
before(:insert).
|
||||||
|
for_each(:row) do
|
||||||
|
"NEW.display_id := nextval('conv_dpid_seq_' || NEW.account_id);"
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :conversation do
|
factory :conversation do
|
||||||
status { 'open' }
|
status { 'open' }
|
||||||
display_id { rand(10_000_000) }
|
|
||||||
agent_last_seen_at { Time.current }
|
agent_last_seen_at { Time.current }
|
||||||
locked { false }
|
locked { false }
|
||||||
identifier { SecureRandom.hex }
|
identifier { SecureRandom.hex }
|
||||||
|
|
|
@ -109,8 +109,8 @@ RSpec.describe Conversation, type: :model do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'adds a message for system auto resolution if marked resolved by system' do
|
it 'adds a message for system auto resolution if marked resolved by system' do
|
||||||
conversation2 = create(:conversation, status: 'open', account: account, assignee: old_assignee)
|
|
||||||
account.update(auto_resolve_duration: 40)
|
account.update(auto_resolve_duration: 40)
|
||||||
|
conversation2 = create(:conversation, status: 'open', account: account, assignee: old_assignee)
|
||||||
Current.user = nil
|
Current.user = nil
|
||||||
conversation2.update(status: :resolved)
|
conversation2.update(status: :resolved)
|
||||||
system_resolved_message = "Conversation was marked resolved by system due to #{account.auto_resolve_duration} days of inactivity"
|
system_resolved_message = "Conversation was marked resolved by system due to #{account.auto_resolve_duration} days of inactivity"
|
||||||
|
@ -124,7 +124,7 @@ RSpec.describe Conversation, type: :model do
|
||||||
|
|
||||||
it 'does trigger AutoResolutionJob if conversation reopened and account has auto resolve duration' do
|
it 'does trigger AutoResolutionJob if conversation reopened and account has auto resolve duration' do
|
||||||
account.update(auto_resolve_duration: 40)
|
account.update(auto_resolve_duration: 40)
|
||||||
expect { conversation.update(status: :open) }
|
expect { conversation.reload.update(status: :open) }
|
||||||
.to have_enqueued_job(AutoResolveConversationsJob).with(conversation.id)
|
.to have_enqueued_job(AutoResolveConversationsJob).with(conversation.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,7 @@ RSpec.describe Message, type: :model do
|
||||||
let(:message) { build(:message, account: create(:account)) }
|
let(:message) { build(:message, account: create(:account)) }
|
||||||
|
|
||||||
it 'updates conversation last_activity_at when created' do
|
it 'updates conversation last_activity_at when created' do
|
||||||
|
message.save!
|
||||||
expect(message.created_at).to eq message.conversation.last_activity_at
|
expect(message.created_at).to eq message.conversation.last_activity_at
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue