mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +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
	 Pranav Raj S
					Pranav Raj S