Last active
October 16, 2023 09:06
-
-
Save misteral/31ecc06c11baf9d68b60bf40c51266ba to your computer and use it in GitHub Desktop.
Meeting Model in Consulting System
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# Broadcasts functions | |
module Meeting::Broadcasts # rubocop:disable Style/ClassAndModuleChildren | |
def broadcast_to_replace_customer_text | |
broadcast_replace_to self, :customer_text, | |
target: 'customer_text', | |
partial: 'meetings/customer_text', | |
locals: { customer_text: customer_text } | |
end | |
private | |
def broadcast_to_replace_badge | |
broadcast_replace_to 'queued_count', | |
target: nil, | |
targets: '.queued-count', | |
locals: { queued_count: Meeting.badge }, | |
partial: 'shared/badge' | |
end | |
def broadcast_to_replace_active_and_queued_count | |
broadcast_replace_to 'active_queued_count', | |
target: 'active-queued-count', | |
locals: { | |
active_count: Meeting.with_state(:active).count, | |
queued_count: Meeting.badge | |
}, | |
partial: 'shared/active_queued_count' | |
end | |
def broadcast_to_replace_customer_meeting_page | |
broadcast_replace_to self, :customer_meeting, | |
target: 'customer_meeting', | |
partial: 'meetings/active' | |
end | |
def broadcast_to_replace_customer_when_agent_finish | |
broadcast_replace_to self, :customer_meeting, | |
target: 'customer_meeting', | |
partial: 'meetings/thanks' | |
end | |
def broadcast_to_prepend_in_active_meetings | |
broadcast_prepend_to 'active_meetings', | |
locals: { meeting: self }, | |
partial: 'admin/meetings/active_meeting_line', | |
target: 'active-meetings-target' | |
end | |
def broadcast_to_remove_in_active_meetings | |
broadcast_remove_to 'active_meetings', | |
target: "meeting_#{id}" | |
end | |
def broadcast_to_replace_in_active_meetings | |
broadcast_replace_to 'active_meetings', | |
locals: { meeting: self }, | |
partial: 'admin/meetings/active_meeting_line', | |
target: "meeting_#{id}" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require 'application_system_test_case' | |
class CustomerMeetingTest < ApplicationSystemTestCase | |
include VirtualConsultation::TestHelper | |
include ActiveJob::TestHelper | |
setup do | |
@agent = users(:agent) | |
@meeting = stub_chime | |
@customer = customers(:one) | |
@agent.set_online | |
end | |
test 'Customer initiate call' do | |
@agent.set_offline | |
manual_create_meeitng | |
wait_to_broadcasts | |
match_text(t('customer.mobile.wait_page.sorry'), | |
t('customer.wait.all_offline')) | |
assert_equal 'queued', current_meeting.state | |
assert_predicate all('div.loading-box').length, :zero? | |
end | |
test 'should create customer' do | |
manual_create_meeitng | |
assert_text t('customer.mobile.wait_page.connecting') | |
assert_text t('customer.wait.please_wait') | |
assert_predicate all('div.loading-box').length, :positive? | |
end | |
test 'Should update text via turbo' do | |
manual_create_meeitng | |
assert_enqueued_with(job: UpdateCustomerTextJob) | |
assert_broadcasts_text(text_before: t('customer.wait.please_wait'), | |
text_after: t('customer.wait.please_wait'), | |
to: :customer_text) do | |
@agent.set_offline | |
perform_enqueued_jobs | |
end | |
end | |
test 'Update meeting page if agent is take call' do | |
manual_create_meeitng | |
agent_serve(@customer.meeting) | |
video_buttons = all('.mobile-call-buttons') | |
assert_predicate video_buttons, :present? | |
end | |
test 'customer leave call' do | |
manual_create_meeitng | |
agent_serve(current_meeting) | |
accept_confirm do | |
find('button[data-action="chime#leave"]').click | |
end | |
wait_to_broadcasts | |
assert_text t('customer.mobile.leave_page.button_name') | |
end | |
test 'Auto finish call when agent finish call' do | |
manual_create_meeitng | |
agent_serve(current_meeting) | |
agent_finish(current_meeting) | |
assert_text t('customer.mobile.leave_page.button_name') | |
end | |
test "it contains 'Privacy Policy' and 'Terms and Conditions'" do | |
manual_create_meeitng | |
assert_text t('customer.privacy_policy') | |
assert_text t('customer.terms_and_conditions') | |
end | |
private | |
def current_meeting | |
Meeting.find_by(aws_id: @meeting[:meeting_id]) | |
end | |
def match_text(first_text, second_text) | |
assert_text first_text | |
assert_text second_text | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# == Schema Information | |
# | |
# Table name: meetings | |
# | |
# id :bigint not null, primary key | |
# agent_finish :datetime | |
# agent_start :datetime | |
# client_finish :datetime | |
# client_start :datetime | |
# coupon_code :string | |
# interested_service :integer | |
# notes :text | |
# state :string | |
# system_finish :datetime | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# aws_id :string | |
# customer_id :bigint not null | |
# user_id :bigint | |
# | |
# Indexes | |
# | |
# index_meetings_on_customer_id (customer_id) | |
# index_meetings_on_user_id (user_id) | |
# | |
# Foreign Keys | |
# | |
# fk_rails_... (customer_id => customers.id) | |
# fk_rails_... (user_id => users.id) | |
# | |
class Meeting < ApplicationRecord | |
NOT_PICKED_TIMEOUT = 5.minutes | |
include Meeting::Broadcasts | |
include Meeting::States | |
include Paginatable | |
include Notifiable | |
belongs_to :user, optional: true, inverse_of: :meetings | |
belongs_to :customer, inverse_of: :meetings | |
scope :not_finished, -> { without_state(:archived) } | |
scope :finished, -> { with_state(:archived) } | |
scope :queued, -> { with_state(:queued).order(client_start: :desc) } | |
enum interested_service: { | |
need_haircut_recommendations: 0, | |
need_hair_color_recommendations: 1, | |
need_product_recommendations: 2, | |
need_advice_on_how_to_self_color_my_hair: 3, | |
need_advice_on_hair_styling: 4 | |
} | |
ransacker :client_start do | |
Arel.sql('DATE(meetings.client_start)') | |
end | |
def customer_text | |
return { title: title, message: message } if User.all_agents_offline? | |
return { title: title, message: message } if User.all_agents_on_call? | |
return unless client_start | |
{ title: title, message: message } | |
end | |
def wait_duration | |
return nil if agent_start.nil? && client_start.nil? | |
((agent_start - client_start) / 1.minute).round | |
end | |
def finish_time | |
return nil if agent_finish.nil? && client_finish.nil? | |
return client_finish if agent_finish.nil? | |
return agent_finish if client_finish.nil? | |
[client_finish, agent_finish].min_by(&:to_i) | |
end | |
def duration | |
return nil if agent_start.nil? || finish_time.nil? | |
((finish_time - agent_start) / 1.minute).round | |
end | |
def initial_time_and_served | |
"#{client_start&.strftime('%H:%M %p') || '-'} / #{ | |
agent_start&.strftime('%H:%M %p') || '-' | |
}" | |
end | |
def self.badge | |
queued.count | |
end | |
private | |
def title | |
if User.all_agents_offline? || User.all_agents_on_call? || | |
(User.at_least_one_online? && | |
DateTime.current - NOT_PICKED_TIMEOUT >= client_start) | |
return I18n.t('customer.mobile.wait_page.sorry') | |
end | |
I18n.t('customer.mobile.wait_page.connecting') | |
end | |
def message | |
if User.all_agents_offline? | |
I18n.t('customer.wait.all_offline') | |
elsif User.all_agents_on_call? | |
I18n.t('customer.wait.all_on_call') | |
elsif User.at_least_one_online? && | |
DateTime.current - NOT_PICKED_TIMEOUT >= client_start | |
I18n.t('customer.wait.not_picked') | |
else | |
I18n.t('customer.wait.please_wait') | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# == Schema Information | |
# | |
# Table name: meetings | |
# | |
# id :bigint not null, primary key | |
# agent_finish :datetime | |
# agent_start :datetime | |
# client_finish :datetime | |
# client_start :datetime | |
# coupon_code :string | |
# interested_service :integer | |
# notes :text | |
# state :string | |
# system_finish :datetime | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# aws_id :string | |
# customer_id :bigint not null | |
# user_id :bigint | |
# | |
# Indexes | |
# | |
# index_meetings_on_customer_id (customer_id) | |
# index_meetings_on_user_id (user_id) | |
# | |
# Foreign Keys | |
# | |
# fk_rails_... (customer_id => customers.id) | |
# fk_rails_... (user_id => users.id) | |
# | |
require 'test_helper' | |
class MeetingTest < ActiveSupport::TestCase | |
include ActionCable::TestHelper | |
def setup | |
@customer = customers(:one) | |
@meeting = meetings(:one) | |
@meeting.update!(customer: @customer, state: 'initialized') | |
@archived_meeting = meetings(:archived) | |
@agent = users(:agent) | |
end | |
test '#customer_text - agent online and super not' do | |
super_admin = users(:super_admin) | |
super_admin.update!(state: 'online') | |
@agent.update!(state: 'offline') | |
@meeting.update!(state: 'queued', client_start: DateTime.current) | |
assert_equal t('customer.mobile.wait_page.connecting'), | |
@meeting.customer_text[:title] | |
assert_equal t('customer.wait.please_wait'), | |
@meeting.customer_text[:message] | |
end | |
test '#customer_text - all agents offline' do | |
User.agents.update!(state: 'offline') | |
@meeting.update!(state: 'queued') | |
assert_equal t('customer.mobile.wait_page.sorry'), | |
@meeting.customer_text[:title] | |
assert_equal t('customer.wait.all_offline'), | |
@meeting.customer_text[:message] | |
end | |
test '#customer_text - all agents on call' do | |
User.agents.update!(state: 'on_call') | |
assert_equal t('customer.mobile.wait_page.sorry'), | |
@meeting.customer_text[:title] | |
assert_equal t('customer.wait.all_on_call'), | |
@meeting.customer_text[:message] | |
end | |
test '#customer_text - call not picked' do | |
User.agents.update(state: 'offline') | |
@agent.update!(state: 'online') | |
start_time = DateTime.current - Meeting::NOT_PICKED_TIMEOUT - 1.minute | |
@meeting.update!(state: 'queued', client_start: start_time) | |
assert_equal t('customer.mobile.wait_page.sorry'), | |
@meeting.customer_text[:title] | |
assert_equal t('customer.wait.not_picked'), | |
@meeting.customer_text[:message] | |
end | |
test '#customer_text - Please wait while we connect' do | |
User.agents.update(state: 'offline') | |
@agent.update!(state: 'online') | |
@meeting.update!(state: 'queued', client_start: DateTime.current) | |
assert_equal t('customer.mobile.wait_page.connecting'), | |
@meeting.customer_text[:title] | |
assert_equal t('customer.wait.please_wait'), | |
@meeting.customer_text[:message] | |
end | |
test '#finish_time both' do | |
@meeting.client_finish = DateTime.current + 6.minutes | |
@meeting.agent_finish = DateTime.current + 1.minute | |
assert_equal @meeting.agent_finish, @meeting.finish_time | |
end | |
test '#wait_duration' do | |
@meeting.client_start = DateTime.current | |
@meeting.agent_start = DateTime.current + 3.minutes | |
assert_equal 3, @meeting.wait_duration | |
end | |
test '#duration with agent_finish' do | |
@meeting.client_start = DateTime.current | |
@meeting.agent_start = DateTime.current | |
@meeting.agent_finish = DateTime.current + 3.minutes | |
assert_equal 3, @meeting.duration | |
end | |
test '#duration with client_finish' do | |
@meeting.client_start = DateTime.current | |
@meeting.agent_start = DateTime.current | |
@meeting.client_finish = DateTime.current + 3.minutes | |
assert_equal 3, @meeting.duration | |
end | |
test '#duration with both finish' do | |
@meeting.client_start = DateTime.current | |
@meeting.agent_start = DateTime.current | |
@meeting.client_finish = DateTime.current + 3.minutes | |
@meeting.agent_finish = DateTime.current + 5.minutes | |
assert_equal 3, @meeting.duration | |
end | |
test '#duration w/o agent_start' do | |
@meeting.client_start = DateTime.current | |
@meeting.agent_start = nil | |
@meeting.agent_finish = nil | |
assert_nil @meeting.duration | |
end | |
test '#duration w/o agent_finish && client_finish' do | |
@meeting.client_start = DateTime.current | |
@meeting.agent_start = DateTime.current | |
@meeting.agent_finish = nil | |
@meeting.client_finish = nil | |
assert_nil @meeting.duration | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
# Describing states for meeting | |
module Meeting::States # rubocop:disable Style/ClassAndModuleChildren | |
extend ActiveSupport::Concern | |
included do | |
# Current states | |
# 'initialized' - model is created but meeting not created at Chime | |
# 'queued' - customer initiate meeting | |
# 'active' - customer and agent in call | |
# 'client_finished' - customer finish call | |
# 'agent_finished' - agent finish call | |
# 'system_finished' - system finish call | |
# 'archived' - archived call | |
state_machine :state, initial: :initialized do | |
event :client_start_call do | |
transition initialized: :queued | |
end | |
event :agent_start_call do | |
transition queued: :active | |
end | |
event :client_finish_call do | |
transition %i[queued active agent_finished] => :client_finished | |
end | |
event :agent_finish_call do | |
transition %i[active client_finished] => :agent_finished | |
end | |
event :system_finish_call do | |
transition to: :system_finished | |
end | |
event :archived do | |
transition %i[client_finished | |
agent_finished | |
system_finished] => :archived | |
end | |
after_transition to: :queued, do: :queued_callback | |
after_transition to: :active, do: :active_callback | |
before_transition from: :active, do: :before_from_active_callback | |
after_transition from: :active, | |
do: %i[from_active_callback call_state_changed_callback] | |
after_transition to: :client_finished, do: :client_finish_callback | |
after_transition to: :agent_finished, do: :agent_finish_callback | |
after_transition to: :system_finished, do: :system_finish_callback | |
after_transition to: :archived, do: :archived_callback | |
after_transition to: %i[queued active], do: :call_state_changed_callback | |
end | |
end | |
private | |
def before_from_active_callback | |
user.set_online! if user&.on_call? && user.meeting == self | |
end | |
# meeting is finished | |
def from_active_callback | |
broadcast_to_remove_in_active_meetings | |
end | |
# when client init call | |
def queued_callback | |
update!(client_start: DateTime.current) | |
broadcast_to_prepend_in_active_meetings | |
create_notifications | |
UpdateCustomerTextJob.set(wait_until: DateTime.current + | |
Meeting::NOT_PICKED_TIMEOUT) | |
.perform_later(self) | |
end | |
# when agent serve call | |
def active_callback | |
update(agent_start: DateTime.current) | |
disable_notifications | |
broadcast_to_replace_customer_meeting_page | |
broadcast_to_replace_in_active_meetings | |
end | |
def system_finish_callback | |
update(system_finish: DateTime.current) | |
archived! | |
end | |
def client_finish_callback | |
update(client_finish: DateTime.current) | |
archived! | |
end | |
def agent_finish_callback | |
update(agent_finish: DateTime.current) | |
archived! | |
end | |
def archived_callback | |
disable_notifications | |
broadcast_to_remove_in_active_meetings | |
broadcast_to_replace_customer_when_agent_finish | |
end | |
def call_state_changed_callback | |
broadcast_to_replace_badge | |
broadcast_to_replace_active_and_queued_count | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require 'test_helper' | |
class Meeting::StatesTest < ActiveSupport::TestCase # rubocop:disable Style/ClassAndModuleChildren | |
include ActiveJob::TestHelper | |
def setup | |
@customer = customers(:one) | |
@meeting = meetings(:one) | |
@meeting.update!(customer: @customer, state: 'initialized') | |
@archived_meeting = meetings(:archived) | |
@agent = users(:agent) | |
end | |
test '#client_start_call! - fill client_start' do | |
@meeting.client_start_call! | |
assert_equal 'queued', @meeting.state | |
assert_predicate @meeting.client_start, :present? | |
end | |
test '#agent_start_call!' do | |
@meeting.state = 'queued' | |
@meeting.agent_start_call! | |
assert_equal 'active', @meeting.state | |
assert_predicate @meeting.agent_start, :present? | |
end | |
test '#client_finish_call!' do | |
@meeting.state = 'active' | |
@meeting.client_finish_call! | |
assert_equal 'archived', @meeting.state | |
assert_predicate @meeting.client_finish, :present? | |
end | |
test '#agent_finish_call!' do | |
@meeting.state = 'active' | |
@meeting.agent_finish_call! | |
assert_equal 'archived', @meeting.state | |
assert_predicate @meeting.agent_finish, :present? | |
end | |
test '#system_finish_call!' do | |
agent = users(:agent) | |
agent.update(state: 'on_call') | |
@meeting.state = 'active' | |
@meeting.update(user: agent) | |
@meeting.system_finish_call! | |
assert_equal 'archived', @meeting.state | |
assert_predicate @meeting.system_finish, :present? | |
end | |
test '#system_finish_call! - disable notifications' do | |
meeting = meetings(:queued) | |
assert_equal 1, meeting.notifications.enabled.count | |
meeting.system_finish_call! | |
assert_equal 0, meeting.reload.notifications.enabled.count | |
end | |
test 'online update online_time' do | |
@agent.set_online! | |
assert_not_nil @agent.last_seen | |
end | |
test 'switch user to online if meeting not active' do | |
meeting = meetings(:queued) | |
customer = meeting.customer | |
@agent.set_online! | |
@agent.start_callback_for(meeting) | |
assert_equal 'on_call', @agent.reload.state | |
customer.finish_callback_for(meeting) | |
assert_equal 'online', @agent.reload.state | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment