mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 02:02:27 +00:00
chore: Add controllers for conversation participants (#6462)
Co-authored-by: Aswin Dev P.S <aswindevps@gmail.com> Co-authored-by: Sojan Jose <sojan@chatwoot.com>
This commit is contained in:
@@ -17,7 +17,6 @@ Metrics/ClassLength:
|
|||||||
- 'app/builders/messages/facebook/message_builder.rb'
|
- 'app/builders/messages/facebook/message_builder.rb'
|
||||||
- 'app/controllers/api/v1/accounts/contacts_controller.rb'
|
- 'app/controllers/api/v1/accounts/contacts_controller.rb'
|
||||||
- 'app/listeners/action_cable_listener.rb'
|
- 'app/listeners/action_cable_listener.rb'
|
||||||
- 'app/models/conversation.rb'
|
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 25
|
Max: 25
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
@@ -188,4 +187,3 @@ AllCops:
|
|||||||
- db/migrate/20200927135222_add_last_activity_at_to_conversation.rb
|
- db/migrate/20200927135222_add_last_activity_at_to_conversation.rb
|
||||||
- db/migrate/20210306170117_add_last_activity_at_to_contacts.rb
|
- db/migrate/20210306170117_add_last_activity_at_to_contacts.rb
|
||||||
- db/migrate/20220809104508_revert_cascading_indexes.rb
|
- db/migrate/20220809104508_revert_cascading_indexes.rb
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
class Api::V1::Accounts::Conversations::ParticipantsController < Api::V1::Accounts::Conversations::BaseController
|
||||||
|
def show
|
||||||
|
@participants = @conversation.conversation_participants
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
@participants = participants_to_be_added_ids.map { |user_id| @conversation.conversation_participants.find_or_create_by(user_id: user_id) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
participants_to_be_added_ids.each { |user_id| @conversation.conversation_participants.find_or_create_by(user_id: user_id) }
|
||||||
|
participants_to_be_removed_ids.each { |user_id| @conversation.conversation_participants.find_by(user_id: user_id)&.destroy }
|
||||||
|
end
|
||||||
|
@participants = @conversation.conversation_participants
|
||||||
|
render action: 'show'
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
params[:user_ids].map { |user_id| @conversation.conversation_participants.find_by(user_id: user_id)&.destroy }
|
||||||
|
end
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def participants_to_be_added_ids
|
||||||
|
params[:user_ids] - current_participant_ids
|
||||||
|
end
|
||||||
|
|
||||||
|
def participants_to_be_removed_ids
|
||||||
|
current_participant_ids - params[:user_ids]
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_participant_ids
|
||||||
|
@current_participant_ids ||= @conversation.conversation_participants.pluck(:user_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -97,6 +97,8 @@ class ConversationFinder
|
|||||||
when 'mention'
|
when 'mention'
|
||||||
conversation_ids = current_account.mentions.where(user: current_user).pluck(:conversation_id)
|
conversation_ids = current_account.mentions.where(user: current_user).pluck(:conversation_id)
|
||||||
@conversations = @conversations.where(id: conversation_ids)
|
@conversations = @conversations.where(id: conversation_ids)
|
||||||
|
when 'participating'
|
||||||
|
@conversations = current_user.participating_conversations.where(account_id: current_account.id)
|
||||||
when 'unattended'
|
when 'unattended'
|
||||||
@conversations = @conversations.where(first_reply_created_at: nil)
|
@conversations = @conversations.where(first_reply_created_at: nil)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ class AgentNotifications::ConversationNotificationsMailer < ApplicationMailer
|
|||||||
send_mail_with_liquid(to: @agent.email, subject: subject) and return
|
send_mail_with_liquid(to: @agent.email, subject: subject) and return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def participating_conversation_new_message(message, agent)
|
||||||
|
return unless smtp_config_set_or_development?
|
||||||
|
# Don't spam with email notifications if agent is online
|
||||||
|
return if ::OnlineStatusTracker.get_presence(message.account_id, 'User', agent.id)
|
||||||
|
|
||||||
|
@agent = agent
|
||||||
|
@conversation = message.conversation
|
||||||
|
subject = "#{@agent.available_name}, New message in your participating conversation [ID - #{@conversation.display_id}]."
|
||||||
|
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||||
|
send_mail_with_liquid(to: @agent.email, subject: subject) and return
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def liquid_droppables
|
def liquid_droppables
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module AssignmentHandler
|
|||||||
|
|
||||||
included do
|
included do
|
||||||
before_save :ensure_assignee_is_from_team
|
before_save :ensure_assignee_is_from_team
|
||||||
after_commit :notify_assignment_change, :process_assignment_activities
|
after_commit :notify_assignment_change, :process_assignment_changes
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -36,6 +36,11 @@ module AssignmentHandler
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_assignment_changes
|
||||||
|
process_assignment_activities
|
||||||
|
process_participant_assignment
|
||||||
|
end
|
||||||
|
|
||||||
def process_assignment_activities
|
def process_assignment_activities
|
||||||
user_name = Current.user.name if Current.user.present?
|
user_name = Current.user.name if Current.user.present?
|
||||||
if saved_change_to_team_id?
|
if saved_change_to_team_id?
|
||||||
@@ -44,4 +49,10 @@ module AssignmentHandler
|
|||||||
create_assignee_change_activity(user_name)
|
create_assignee_change_activity(user_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_participant_assignment
|
||||||
|
return unless saved_change_to_assignee_id? && assignee_id.present?
|
||||||
|
|
||||||
|
conversation_participants.find_or_create_by!(user_id: assignee_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
53
app/models/concerns/user_attribute_helpers.rb
Normal file
53
app/models/concerns/user_attribute_helpers.rb
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
module UserAttributeHelpers
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def available_name
|
||||||
|
self[:display_name].presence || name
|
||||||
|
end
|
||||||
|
|
||||||
|
def availability_status
|
||||||
|
current_account_user&.availability_status
|
||||||
|
end
|
||||||
|
|
||||||
|
def auto_offline
|
||||||
|
current_account_user&.auto_offline
|
||||||
|
end
|
||||||
|
|
||||||
|
def inviter
|
||||||
|
current_account_user&.inviter
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_account_user
|
||||||
|
account_users.order(active_at: :desc)&.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_account_user
|
||||||
|
# We want to avoid subsequent queries in case where the association is preloaded.
|
||||||
|
# using where here will trigger n+1 queries.
|
||||||
|
account_users.find { |ac_usr| ac_usr.account_id == Current.account.id } if Current.account
|
||||||
|
end
|
||||||
|
|
||||||
|
def account
|
||||||
|
current_account_user&.account
|
||||||
|
end
|
||||||
|
|
||||||
|
def administrator?
|
||||||
|
current_account_user&.administrator?
|
||||||
|
end
|
||||||
|
|
||||||
|
def agent?
|
||||||
|
current_account_user&.agent?
|
||||||
|
end
|
||||||
|
|
||||||
|
def role
|
||||||
|
current_account_user&.role
|
||||||
|
end
|
||||||
|
|
||||||
|
# Used internally for Chatwoot in Chatwoot
|
||||||
|
def hmac_identifier
|
||||||
|
hmac_key = GlobalConfig.get('CHATWOOT_INBOX_HMAC_KEY')['CHATWOOT_INBOX_HMAC_KEY']
|
||||||
|
return OpenSSL::HMAC.hexdigest('sha256', hmac_key, email) if hmac_key.present?
|
||||||
|
|
||||||
|
''
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -86,6 +86,7 @@ class Conversation < ApplicationRecord
|
|||||||
has_many :mentions, dependent: :destroy_async
|
has_many :mentions, dependent: :destroy_async
|
||||||
has_many :messages, dependent: :destroy_async, autosave: true
|
has_many :messages, dependent: :destroy_async, autosave: true
|
||||||
has_one :csat_survey_response, dependent: :destroy_async
|
has_one :csat_survey_response, dependent: :destroy_async
|
||||||
|
has_many :conversation_participants, dependent: :destroy_async
|
||||||
has_many :notifications, as: :primary_actor, dependent: :destroy_async
|
has_many :notifications, as: :primary_actor, dependent: :destroy_async
|
||||||
|
|
||||||
before_save :ensure_snooze_until_reset
|
before_save :ensure_snooze_until_reset
|
||||||
|
|||||||
41
app/models/conversation_participant.rb
Normal file
41
app/models/conversation_participant.rb
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: conversation_participants
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# conversation_id :bigint not null
|
||||||
|
# user_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_conversation_participants_on_account_id (account_id)
|
||||||
|
# index_conversation_participants_on_conversation_id (conversation_id)
|
||||||
|
# index_conversation_participants_on_user_id (user_id)
|
||||||
|
# index_conversation_participants_on_user_id_and_conversation_id (user_id,conversation_id) UNIQUE
|
||||||
|
#
|
||||||
|
class ConversationParticipant < ApplicationRecord
|
||||||
|
validates :account_id, presence: true
|
||||||
|
validates :conversation_id, presence: true
|
||||||
|
validates :user_id, presence: true
|
||||||
|
validates :user_id, uniqueness: { scope: [:conversation_id] }
|
||||||
|
validate :ensure_inbox_access
|
||||||
|
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :conversation
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
before_validation :ensure_account_id
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_account_id
|
||||||
|
self.account_id = conversation&.account_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_inbox_access
|
||||||
|
errors.add(:user, 'must have inbox access') if conversation && conversation.inbox.assignable_agents.exclude?(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -34,7 +34,8 @@ class Notification < ApplicationRecord
|
|||||||
conversation_creation: 1,
|
conversation_creation: 1,
|
||||||
conversation_assignment: 2,
|
conversation_assignment: 2,
|
||||||
assigned_conversation_new_message: 3,
|
assigned_conversation_new_message: 3,
|
||||||
conversation_mention: 4
|
conversation_mention: 4,
|
||||||
|
participating_conversation_new_message: 5
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
enum notification_type: NOTIFICATION_TYPES
|
enum notification_type: NOTIFICATION_TYPES
|
||||||
@@ -94,7 +95,7 @@ class Notification < ApplicationRecord
|
|||||||
I18n.t('notifications.notification_title.conversation_creation', display_id: primary_actor.display_id, inbox_name: primary_actor.inbox.name)
|
I18n.t('notifications.notification_title.conversation_creation', display_id: primary_actor.display_id, inbox_name: primary_actor.inbox.name)
|
||||||
when 'conversation_assignment'
|
when 'conversation_assignment'
|
||||||
I18n.t('notifications.notification_title.conversation_assignment', display_id: primary_actor.display_id)
|
I18n.t('notifications.notification_title.conversation_assignment', display_id: primary_actor.display_id)
|
||||||
when 'assigned_conversation_new_message'
|
when 'assigned_conversation_new_message', 'participating_conversation_new_message'
|
||||||
I18n.t(
|
I18n.t(
|
||||||
'notifications.notification_title.assigned_conversation_new_message',
|
'notifications.notification_title.assigned_conversation_new_message',
|
||||||
display_id: conversation.display_id,
|
display_id: conversation.display_id,
|
||||||
@@ -109,7 +110,11 @@ class Notification < ApplicationRecord
|
|||||||
# rubocop:enable Metrics/CyclomaticComplexity
|
# rubocop:enable Metrics/CyclomaticComplexity
|
||||||
|
|
||||||
def conversation
|
def conversation
|
||||||
return primary_actor.conversation if %w[assigned_conversation_new_message conversation_mention].include? notification_type
|
return primary_actor.conversation if %w[
|
||||||
|
assigned_conversation_new_message
|
||||||
|
participating_conversation_new_message
|
||||||
|
conversation_mention
|
||||||
|
].include? notification_type
|
||||||
|
|
||||||
primary_actor
|
primary_actor
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class User < ApplicationRecord
|
|||||||
include Rails.application.routes.url_helpers
|
include Rails.application.routes.url_helpers
|
||||||
include Reportable
|
include Reportable
|
||||||
include SsoAuthenticatable
|
include SsoAuthenticatable
|
||||||
|
include UserAttributeHelpers
|
||||||
|
|
||||||
devise :database_authenticatable,
|
devise :database_authenticatable,
|
||||||
:registerable,
|
:registerable,
|
||||||
@@ -76,6 +77,8 @@ class User < ApplicationRecord
|
|||||||
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify
|
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify
|
||||||
alias_attribute :conversations, :assigned_conversations
|
alias_attribute :conversations, :assigned_conversations
|
||||||
has_many :csat_survey_responses, foreign_key: 'assigned_agent_id', dependent: :nullify
|
has_many :csat_survey_responses, foreign_key: 'assigned_agent_id', dependent: :nullify
|
||||||
|
has_many :conversation_participants, dependent: :destroy_async
|
||||||
|
has_many :participating_conversations, through: :conversation_participants, source: :conversation
|
||||||
|
|
||||||
has_many :inbox_members, dependent: :destroy_async
|
has_many :inbox_members, dependent: :destroy_async
|
||||||
has_many :inboxes, through: :inbox_members, source: :inbox
|
has_many :inboxes, through: :inbox_members, source: :inbox
|
||||||
@@ -110,60 +113,10 @@ class User < ApplicationRecord
|
|||||||
self.uid = email
|
self.uid = email
|
||||||
end
|
end
|
||||||
|
|
||||||
def active_account_user
|
|
||||||
account_users.order(active_at: :desc)&.first
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_account_user
|
|
||||||
# We want to avoid subsequent queries in case where the association is preloaded.
|
|
||||||
# using where here will trigger n+1 queries.
|
|
||||||
account_users.find { |ac_usr| ac_usr.account_id == Current.account.id } if Current.account
|
|
||||||
end
|
|
||||||
|
|
||||||
def available_name
|
|
||||||
self[:display_name].presence || name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Used internally for Chatwoot in Chatwoot
|
|
||||||
def hmac_identifier
|
|
||||||
hmac_key = GlobalConfig.get('CHATWOOT_INBOX_HMAC_KEY')['CHATWOOT_INBOX_HMAC_KEY']
|
|
||||||
return OpenSSL::HMAC.hexdigest('sha256', hmac_key, email) if hmac_key.present?
|
|
||||||
|
|
||||||
''
|
|
||||||
end
|
|
||||||
|
|
||||||
def account
|
|
||||||
current_account_user&.account
|
|
||||||
end
|
|
||||||
|
|
||||||
def assigned_inboxes
|
def assigned_inboxes
|
||||||
administrator? ? Current.account.inboxes : inboxes.where(account_id: Current.account.id)
|
administrator? ? Current.account.inboxes : inboxes.where(account_id: Current.account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def administrator?
|
|
||||||
current_account_user&.administrator?
|
|
||||||
end
|
|
||||||
|
|
||||||
def agent?
|
|
||||||
current_account_user&.agent?
|
|
||||||
end
|
|
||||||
|
|
||||||
def role
|
|
||||||
current_account_user&.role
|
|
||||||
end
|
|
||||||
|
|
||||||
def availability_status
|
|
||||||
current_account_user&.availability_status
|
|
||||||
end
|
|
||||||
|
|
||||||
def auto_offline
|
|
||||||
current_account_user&.auto_offline
|
|
||||||
end
|
|
||||||
|
|
||||||
def inviter
|
|
||||||
current_account_user&.inviter
|
|
||||||
end
|
|
||||||
|
|
||||||
def serializable_hash(options = nil)
|
def serializable_hash(options = nil)
|
||||||
super(options).merge(confirmed: confirmed?)
|
super(options).merge(confirmed: confirmed?)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Messages::MentionService
|
|||||||
|
|
||||||
Conversations::UserMentionJob.perform_later(validated_mentioned_ids, message.conversation.id, message.account.id)
|
Conversations::UserMentionJob.perform_later(validated_mentioned_ids, message.conversation.id, message.account.id)
|
||||||
generate_notifications_for_mentions(validated_mentioned_ids)
|
generate_notifications_for_mentions(validated_mentioned_ids)
|
||||||
|
add_mentioned_users_as_participants(validated_mentioned_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -38,4 +39,10 @@ class Messages::MentionService
|
|||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_mentioned_users_as_participants(validated_mentioned_ids)
|
||||||
|
validated_mentioned_ids.each do |user_id|
|
||||||
|
message.conversation.conversation_participants.find_or_create_by!(user_id: user_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class Messages::NewMessageNotificationService
|
|||||||
def perform
|
def perform
|
||||||
return unless message.notifiable?
|
return unless message.notifiable?
|
||||||
|
|
||||||
|
notify_participating_users
|
||||||
notify_conversation_assignee
|
notify_conversation_assignee
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -11,8 +12,23 @@ class Messages::NewMessageNotificationService
|
|||||||
|
|
||||||
delegate :conversation, :sender, :account, to: :message
|
delegate :conversation, :sender, :account, to: :message
|
||||||
|
|
||||||
|
def notify_participating_users
|
||||||
|
participating_users = conversation.conversation_participants.map(&:user)
|
||||||
|
participating_users -= [sender] if sender.is_a?(User)
|
||||||
|
|
||||||
|
participating_users.uniq.each do |participant|
|
||||||
|
NotificationBuilder.new(
|
||||||
|
notification_type: 'participating_conversation_new_message',
|
||||||
|
user: participant,
|
||||||
|
account: account,
|
||||||
|
primary_actor: message
|
||||||
|
).perform
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def notify_conversation_assignee
|
def notify_conversation_assignee
|
||||||
return if conversation.assignee.blank?
|
return if conversation.assignee.blank?
|
||||||
|
return if assignee_already_notified_via_participation?
|
||||||
return if conversation.assignee == sender
|
return if conversation.assignee == sender
|
||||||
|
|
||||||
NotificationBuilder.new(
|
NotificationBuilder.new(
|
||||||
@@ -22,4 +38,13 @@ class Messages::NewMessageNotificationService
|
|||||||
primary_actor: message
|
primary_actor: message
|
||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assignee_already_notified_via_participation?
|
||||||
|
return unless conversation.conversation_participants.map(&:user).include?(conversation.assignee)
|
||||||
|
|
||||||
|
# check whether participation notifcation is disabled for assignee
|
||||||
|
notification_setting = conversation.assignee.notification_settings.find_by(account_id: account.id)
|
||||||
|
notification_setting.public_send(:email_participating_conversation_new_message?) || notification_setting
|
||||||
|
.public_send(:push_participating_conversation_new_message?)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
json.array! @participants do |participant|
|
||||||
|
json.partial! 'api/v1/models/agent', format: :json, resource: participant.user
|
||||||
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
json.array! @participants do |participant|
|
||||||
|
json.partial! 'api/v1/models/agent', format: :json, resource: participant.user
|
||||||
|
end
|
||||||
@@ -11,7 +11,11 @@ json.data do
|
|||||||
json.notification_type notification.notification_type
|
json.notification_type notification.notification_type
|
||||||
json.push_message_title notification.push_message_title
|
json.push_message_title notification.push_message_title
|
||||||
# TODO: front end assumes primary actor to be conversation. should fix in future
|
# TODO: front end assumes primary actor to be conversation. should fix in future
|
||||||
if %w[assigned_conversation_new_message conversation_mention].include? notification.notification_type
|
if %w[
|
||||||
|
assigned_conversation_new_message
|
||||||
|
participating_conversation_new_message
|
||||||
|
conversation_mention
|
||||||
|
].include? notification.notification_type
|
||||||
json.primary_actor_type 'Conversation'
|
json.primary_actor_type 'Conversation'
|
||||||
json.primary_actor_id notification.conversation.id
|
json.primary_actor_id notification.conversation.id
|
||||||
json.primary_actor notification.conversation.push_event_data
|
json.primary_actor notification.conversation.push_event_data
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<p>Hi {{user.available_name}},</p>
|
||||||
|
|
||||||
|
<p>You have received a new message in a conversation you are participating.</p>
|
||||||
|
|
||||||
|
<p>Click <a href="{{action_url}}">here</a> to get cracking.</p>
|
||||||
@@ -77,6 +77,7 @@ Rails.application.routes.draw do
|
|||||||
resources :messages, only: [:index, :create, :destroy]
|
resources :messages, only: [:index, :create, :destroy]
|
||||||
resources :assignments, only: [:create]
|
resources :assignments, only: [:create]
|
||||||
resources :labels, only: [:create, :index]
|
resources :labels, only: [:create, :index]
|
||||||
|
resource :participants, only: [:show, :create, :update, :destroy]
|
||||||
resource :direct_uploads, only: [:create]
|
resource :direct_uploads, only: [:create]
|
||||||
end
|
end
|
||||||
member do
|
member do
|
||||||
|
|||||||
11
db/migrate/20220808193420_add_conversation_participants.rb
Normal file
11
db/migrate/20220808193420_add_conversation_participants.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class AddConversationParticipants < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_table 'conversation_participants', force: :cascade do |t|
|
||||||
|
t.references :account, null: false
|
||||||
|
t.references :user, null: false
|
||||||
|
t.references :conversation, null: false
|
||||||
|
t.datetime 'created_at', precision: 6, null: false
|
||||||
|
t.datetime 'updated_at', precision: 6, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddUniqueIndexToConversationParticipants < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_index :conversation_participants, [:user_id, :conversation_id], unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
12
db/schema.rb
12
db/schema.rb
@@ -388,6 +388,18 @@ ActiveRecord::Schema.define(version: 2023_02_09_033203) do
|
|||||||
t.index ["phone_number", "account_id"], name: "index_contacts_on_phone_number_and_account_id"
|
t.index ["phone_number", "account_id"], name: "index_contacts_on_phone_number_and_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "conversation_participants", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.bigint "user_id", null: false
|
||||||
|
t.bigint "conversation_id", null: false
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.index ["account_id"], name: "index_conversation_participants_on_account_id"
|
||||||
|
t.index ["conversation_id"], name: "index_conversation_participants_on_conversation_id"
|
||||||
|
t.index ["user_id", "conversation_id"], name: "index_conversation_participants_on_user_id_and_conversation_id", unique: true
|
||||||
|
t.index ["user_id"], name: "index_conversation_participants_on_user_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "conversations", id: :serial, force: :cascade do |t|
|
create_table "conversations", id: :serial, force: :cascade do |t|
|
||||||
t.integer "account_id", null: false
|
t.integer "account_id", null: false
|
||||||
t.integer "inbox_id", null: false
|
t.integer "inbox_id", null: false
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ RSpec.describe 'Api::V1::Accounts::BulkActionsController', type: :request do
|
|||||||
create(:conversation, account_id: account.id, status: :open, team_id: team_1.id)
|
create(:conversation, account_id: account.id, status: :open, team_id: team_1.id)
|
||||||
create(:conversation, account_id: account.id, status: :open)
|
create(:conversation, account_id: account.id, status: :open)
|
||||||
create(:conversation, account_id: account.id, status: :open)
|
create(:conversation, account_id: account.id, status: :open)
|
||||||
|
Conversation.all.each do |conversation|
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: agent_1)
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: agent_2)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST /api/v1/accounts/{account.id}/bulk_action' do
|
describe 'POST /api/v1/accounts/{account.id}/bulk_action' do
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ RSpec.describe 'Conversation Assignment API', type: :request do
|
|||||||
let(:agent) { create(:user, account: account, role: :agent) }
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
conversation.update!(assignee: agent)
|
|
||||||
create(:inbox_member, inbox: conversation.inbox, user: agent)
|
create(:inbox_member, inbox: conversation.inbox, user: agent)
|
||||||
|
conversation.update!(assignee: agent)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'unassigns the assignee from the conversation' do
|
it 'unassigns the assignee from the conversation' do
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Conversation Participants API', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:conversation) { create(:conversation, account: account) }
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: agent)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/conversations/<id>/paricipants' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id)
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user with access to the conversation' do
|
||||||
|
let(:participant1) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:participant2) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: participant1)
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: participant2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns all the partipants for the conversation' do
|
||||||
|
create(:conversation_participant, conversation: conversation, user: participant1)
|
||||||
|
create(:conversation_participant, conversation: conversation, user: participant2)
|
||||||
|
get api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id),
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include(participant1.email)
|
||||||
|
expect(response.body).to include(participant2.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/conversations/<id>/participants' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id)
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:participant) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: participant)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new participants when its authorized agent' do
|
||||||
|
params = { user_ids: [participant.id] }
|
||||||
|
|
||||||
|
expect(conversation.conversation_participants.count).to eq(0)
|
||||||
|
post api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id),
|
||||||
|
params: params,
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include(participant.email)
|
||||||
|
expect(conversation.conversation_participants.count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'PUT /api/v1/accounts/{account.id}/conversations/<id>/participants' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
put api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id)
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:participant) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:participant_to_be_added) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:participant_to_be_removed) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: participant)
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: participant_to_be_added)
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: participant_to_be_removed)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates participants when its authorized agent' do
|
||||||
|
params = { user_ids: [participant.id, participant_to_be_added.id] }
|
||||||
|
create(:conversation_participant, conversation: conversation, user: participant)
|
||||||
|
create(:conversation_participant, conversation: conversation, user: participant_to_be_removed)
|
||||||
|
|
||||||
|
expect(conversation.conversation_participants.count).to eq(2)
|
||||||
|
put api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id),
|
||||||
|
params: params,
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include(participant.email)
|
||||||
|
expect(response.body).to include(participant_to_be_added.email)
|
||||||
|
expect(conversation.conversation_participants.count).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /api/v1/accounts/{account.id}/conversations/<id>/participants' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
delete api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id)
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:participant) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: participant)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes participants when its authorized agent' do
|
||||||
|
params = { user_ids: [participant.id] }
|
||||||
|
create(:conversation_participant, conversation: conversation, user: participant)
|
||||||
|
|
||||||
|
expect(conversation.conversation_participants.count).to eq(1)
|
||||||
|
delete api_v1_account_conversation_participants_url(account_id: account.id, conversation_id: conversation.display_id),
|
||||||
|
params: params,
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(conversation.conversation_participants.count).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -111,11 +111,11 @@ RSpec.describe 'Reports API', type: :request do
|
|||||||
context 'when an agent1 associated to conversation having first reply from agent2' do
|
context 'when an agent1 associated to conversation having first reply from agent2' do
|
||||||
let(:listener) { ReportingEventListener.instance }
|
let(:listener) { ReportingEventListener.instance }
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
let(:agent2) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
it 'returns unattended conversation count zero for agent1' do
|
it 'returns unattended conversation count zero for agent1' do
|
||||||
agent1 = create(:user, account: account, role: :agent)
|
create(:inbox_member, user: agent, inbox: inbox)
|
||||||
agent2 = create(:user, account: account, role: :agent)
|
create(:inbox_member, user: agent2, inbox: inbox)
|
||||||
conversation = create(:conversation, account: account,
|
conversation = create(:conversation, account: account,
|
||||||
inbox: inbox, assignee: agent2)
|
inbox: inbox, assignee: agent2)
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ RSpec.describe 'Reports API', type: :request do
|
|||||||
event = Events::Base.new('first.reply.created', Time.zone.now, message: first_reply_message)
|
event = Events::Base.new('first.reply.created', Time.zone.now, message: first_reply_message)
|
||||||
listener.first_reply_created(event)
|
listener.first_reply_created(event)
|
||||||
|
|
||||||
conversation.assignee_id = agent1.id
|
conversation.assignee_id = agent.id
|
||||||
conversation.save!
|
conversation.save!
|
||||||
|
|
||||||
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
||||||
@@ -140,7 +140,7 @@ RSpec.describe 'Reports API', type: :request do
|
|||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
user_metrics = json_response.find { |item| item['name'] == agent1[:name] }
|
user_metrics = json_response.find { |item| item['name'] == agent[:name] }
|
||||||
expect(user_metrics.present?).to be true
|
expect(user_metrics.present?).to be true
|
||||||
|
|
||||||
expect(user_metrics['metric']['open']).to eq(1)
|
expect(user_metrics['metric']['open']).to eq(1)
|
||||||
|
|||||||
13
spec/factories/conversation_participants.rb
Normal file
13
spec/factories/conversation_participants.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FactoryBot.define do
|
||||||
|
factory :conversation_participant do
|
||||||
|
conversation
|
||||||
|
account
|
||||||
|
|
||||||
|
before(:build) do |conversation|
|
||||||
|
if conversation.user.blank?
|
||||||
|
conversation.user = create(:user, account: conversation.account)
|
||||||
|
create(:inbox_member, user: conversation.user, inbox: conversation.conversation.inbox)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -15,6 +15,12 @@ RSpec.describe BulkActionsJob, type: :job do
|
|||||||
let!(:conversation_2) { create(:conversation, account_id: account.id, status: :open) }
|
let!(:conversation_2) { create(:conversation, account_id: account.id, status: :open) }
|
||||||
let!(:conversation_3) { create(:conversation, account_id: account.id, status: :open) }
|
let!(:conversation_3) { create(:conversation, account_id: account.id, status: :open) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Conversation.all.each do |conversation|
|
||||||
|
create(:inbox_member, inbox: conversation.inbox, user: agent)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'enqueues the job' do
|
it 'enqueues the job' do
|
||||||
expect { job }.to have_enqueued_job(described_class)
|
expect { job }.to have_enqueued_job(described_class)
|
||||||
.with(account: account, params: params, user: agent)
|
.with(account: account, params: params, user: agent)
|
||||||
|
|||||||
@@ -87,4 +87,22 @@ RSpec.describe AgentNotifications::ConversationNotificationsMailer, type: :maile
|
|||||||
expect(mail).to be_nil
|
expect(mail).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'participating_conversation_new_message' do
|
||||||
|
let(:message) { create(:message, conversation: conversation, account: account) }
|
||||||
|
let(:mail) { described_class.with(account: account).participating_conversation_new_message(message, agent).deliver_now }
|
||||||
|
|
||||||
|
it 'renders the subject' do
|
||||||
|
expect(mail.subject).to eq("#{agent.available_name}, New message in your participating conversation [ID - #{message.conversation.display_id}].")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders the receiver email' do
|
||||||
|
expect(mail.to).to eq([agent.email])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'will not send email if agent is online' do
|
||||||
|
::OnlineStatusTracker.update_presence(conversation.account.id, 'User', agent.id)
|
||||||
|
expect(mail).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ shared_examples_for 'assignment_handler' do
|
|||||||
end
|
end
|
||||||
let(:assignment_mailer) { instance_double(AgentNotifications::ConversationNotificationsMailer, deliver: true) }
|
let(:assignment_mailer) { instance_double(AgentNotifications::ConversationNotificationsMailer, deliver: true) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
||||||
|
end
|
||||||
|
|
||||||
it 'assigns the agent to conversation' do
|
it 'assigns the agent to conversation' do
|
||||||
expect(update_assignee).to be(true)
|
expect(update_assignee).to be(true)
|
||||||
expect(conversation.reload.assignee).to eq(agent)
|
expect(conversation.reload.assignee).to eq(agent)
|
||||||
@@ -85,6 +89,10 @@ shared_examples_for 'assignment_handler' do
|
|||||||
expect(update_assignee).to be(true)
|
expect(update_assignee).to be(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'adds assignee to conversation participants' do
|
||||||
|
expect { update_assignee }.to change { conversation.conversation_participants.count }.by(1)
|
||||||
|
end
|
||||||
|
|
||||||
context 'when agent is current user' do
|
context 'when agent is current user' do
|
||||||
before do
|
before do
|
||||||
Current.user = agent
|
Current.user = agent
|
||||||
|
|||||||
32
spec/models/conversation_participants_spec.rb
Normal file
32
spec/models/conversation_participants_spec.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe ConversationParticipant, type: :model do
|
||||||
|
context 'with validations' do
|
||||||
|
it { is_expected.to validate_presence_of(:account_id) }
|
||||||
|
it { is_expected.to validate_presence_of(:conversation_id) }
|
||||||
|
it { is_expected.to validate_presence_of(:user_id) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'associations' do
|
||||||
|
it { is_expected.to belong_to(:account) }
|
||||||
|
it { is_expected.to belong_to(:conversation) }
|
||||||
|
it { is_expected.to belong_to(:user) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
it 'ensure account is present' do
|
||||||
|
conversation = create(:conversation)
|
||||||
|
conversation_participant = build(:conversation_participant, conversation: conversation, account_id: nil)
|
||||||
|
conversation_participant.valid?
|
||||||
|
expect(conversation_participant.account_id).to eq(conversation.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'throws error if inbox member does not belongs to account' do
|
||||||
|
conversation = create(:conversation)
|
||||||
|
user = create(:user, account: conversation.account)
|
||||||
|
participant = build(:conversation_participant, user: user, conversation: conversation)
|
||||||
|
expect { participant.save! }.to raise_error(ActiveRecord::RecordInvalid)
|
||||||
|
expect(participant.errors.messages[:user]).to eq(['must have inbox access'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -105,6 +105,8 @@ RSpec.describe Conversation, type: :model do
|
|||||||
let(:label) { create(:label, account: account) }
|
let(:label) { create(:label, account: account) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
create(:inbox_member, user: old_assignee, inbox: conversation.inbox)
|
||||||
|
create(:inbox_member, user: new_assignee, inbox: conversation.inbox)
|
||||||
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
allow(Rails.configuration.dispatcher).to receive(:dispatch)
|
||||||
Current.user = old_assignee
|
Current.user = old_assignee
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ RSpec.describe Notification do
|
|||||||
expect(notification.push_message_title).to eq "[New message] - ##{notification.conversation.display_id} "
|
expect(notification.push_message_title).to eq "[New message] - ##{notification.conversation.display_id} "
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns appropriate title suited for the notification type participating_conversation_new_message' do
|
||||||
|
message = create(:message, sender: create(:user), content: Faker::Lorem.paragraphs(number: 2))
|
||||||
|
notification = create(:notification, notification_type: 'participating_conversation_new_message', primary_actor: message)
|
||||||
|
|
||||||
|
expect(notification.push_message_title).to eq "[New message] - ##{notification.conversation.display_id} \
|
||||||
|
#{message.content.truncate_words(10)}"
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns appropriate title suited for the notification type conversation_mention' do
|
it 'returns appropriate title suited for the notification type conversation_mention' do
|
||||||
message = create(:message, sender: create(:user), content: 'Hey [@John](mention://user/1/john), can you check this ticket?')
|
message = create(:message, sender: create(:user), content: 'Hey [@John](mention://user/1/john), can you check this ticket?')
|
||||||
notification = create(:notification, notification_type: 'conversation_mention', primary_actor: message, secondary_actor: message.sender)
|
notification = create(:notification, notification_type: 'conversation_mention', primary_actor: message, secondary_actor: message.sender)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ RSpec.describe AutoAssignment::AgentAssignmentService do
|
|||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
inbox_members.each { |inbox_member| create(:account_user, account: account, user: inbox_member.user) }
|
||||||
allow(::OnlineStatusTracker).to receive(:get_available_users).and_return(online_users)
|
allow(::OnlineStatusTracker).to receive(:get_available_users).and_return(online_users)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -61,5 +61,10 @@ describe Messages::MentionService do
|
|||||||
account: account,
|
account: account,
|
||||||
primary_actor: message)
|
primary_actor: message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'add the users to the participants list' do
|
||||||
|
described_class.new(message: message).perform
|
||||||
|
expect(conversation.conversation_participants.map(&:user_id)).to match_array([first_agent.id, second_agent.id])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,13 +13,18 @@ describe Messages::NewMessageNotificationService do
|
|||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:assignee) { create(:user, account: account) }
|
let(:assignee) { create(:user, account: account) }
|
||||||
let(:participating_agent_1) { create(:user, account: account) }
|
let(:participating_agent_1) { create(:user, account: account) }
|
||||||
|
let(:participating_agent_2) { create(:user, account: account) }
|
||||||
let(:inbox) { create(:inbox, account: account) }
|
let(:inbox) { create(:inbox, account: account) }
|
||||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: assignee) }
|
let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: assignee) }
|
||||||
let(:builder) { double }
|
let(:builder) { double }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
create(:inbox_member, inbox: inbox, user: participating_agent_1)
|
create(:inbox_member, inbox: inbox, user: participating_agent_1)
|
||||||
|
create(:inbox_member, inbox: inbox, user: participating_agent_2)
|
||||||
create(:inbox_member, inbox: inbox, user: assignee)
|
create(:inbox_member, inbox: inbox, user: assignee)
|
||||||
|
create(:conversation_participant, conversation: conversation, user: participating_agent_1)
|
||||||
|
create(:conversation_participant, conversation: conversation, user: participating_agent_2)
|
||||||
|
create(:conversation_participant, conversation: conversation, user: assignee)
|
||||||
allow(NotificationBuilder).to receive(:new).and_return(builder)
|
allow(NotificationBuilder).to receive(:new).and_return(builder)
|
||||||
allow(builder).to receive(:perform)
|
allow(builder).to receive(:perform)
|
||||||
end
|
end
|
||||||
@@ -31,12 +36,26 @@ describe Messages::NewMessageNotificationService do
|
|||||||
described_class.new(message: message).perform
|
described_class.new(message: message).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates notifications for other participating users' do
|
||||||
|
expect(NotificationBuilder).to have_received(:new).with(notification_type: 'participating_conversation_new_message',
|
||||||
|
user: participating_agent_2,
|
||||||
|
account: account,
|
||||||
|
primary_actor: message)
|
||||||
|
end
|
||||||
|
|
||||||
it 'creates notifications for assignee' do
|
it 'creates notifications for assignee' do
|
||||||
expect(NotificationBuilder).to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
|
expect(NotificationBuilder).to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
|
||||||
user: assignee,
|
user: assignee,
|
||||||
account: account,
|
account: account,
|
||||||
primary_actor: message)
|
primary_actor: message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'will not create notifications for the user who created the message' do
|
||||||
|
expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'participating_conversation_new_message',
|
||||||
|
user: participating_agent_1,
|
||||||
|
account: account,
|
||||||
|
primary_actor: message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when message is created by a contact' do
|
context 'when message is created by a contact' do
|
||||||
@@ -52,6 +71,34 @@ describe Messages::NewMessageNotificationService do
|
|||||||
account: account,
|
account: account,
|
||||||
primary_actor: message)
|
primary_actor: message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'creates notifications for all participating users' do
|
||||||
|
expect(NotificationBuilder).to have_received(:new).with(notification_type: 'participating_conversation_new_message',
|
||||||
|
user: participating_agent_1,
|
||||||
|
account: account,
|
||||||
|
primary_actor: message)
|
||||||
|
expect(NotificationBuilder).to have_received(:new).with(notification_type: 'participating_conversation_new_message',
|
||||||
|
user: participating_agent_2,
|
||||||
|
account: account,
|
||||||
|
primary_actor: message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with multiple notifications are subscribed' do
|
||||||
|
let(:message) { create(:message, conversation: conversation, account: account) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
assignee.notification_settings.find_by(account_id: account.id).update(selected_email_flags: %w[email_assigned_conversation_new_message
|
||||||
|
email_participating_conversation_new_message])
|
||||||
|
described_class.new(message: message).perform
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'will not create assignee notifications for the assignee if participating notification was send' do
|
||||||
|
expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
|
||||||
|
user: assignee,
|
||||||
|
account: account,
|
||||||
|
primary_actor: message)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when message is created by assignee' do
|
context 'when message is created by assignee' do
|
||||||
@@ -62,6 +109,10 @@ describe Messages::NewMessageNotificationService do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'will not create notifications for the user who created the message' do
|
it 'will not create notifications for the user who created the message' do
|
||||||
|
expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'participating_conversation_new_message',
|
||||||
|
user: assignee,
|
||||||
|
account: account,
|
||||||
|
primary_actor: message)
|
||||||
expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
|
expect(NotificationBuilder).not_to have_received(:new).with(notification_type: 'assigned_conversation_new_message',
|
||||||
user: assignee,
|
user: assignee,
|
||||||
account: account,
|
account: account,
|
||||||
|
|||||||
Reference in New Issue
Block a user