diff --git a/.rubocop.yml b/.rubocop.yml index 3ee387090..c27b9651b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -75,6 +75,7 @@ Metrics/AbcSize: - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' - 'db/migrate/20161123131628_devise_token_auth_create_users.rb' Metrics/CyclomaticComplexity: + Max: 7 Exclude: - 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb' Rails/ReversibleMigration: diff --git a/Gemfile.lock b/Gemfile.lock index 3001534e6..0c9cda2ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,56 +18,56 @@ GEM specs: action-cable-testing (0.6.1) actioncable (>= 5.0) - actioncable (6.0.3.2) - actionpack (= 6.0.3.2) + actioncable (6.0.3.3) + actionpack (= 6.0.3.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.2) - actionpack (= 6.0.3.2) - activejob (= 6.0.3.2) - activerecord (= 6.0.3.2) - activestorage (= 6.0.3.2) - activesupport (= 6.0.3.2) + actionmailbox (6.0.3.3) + actionpack (= 6.0.3.3) + activejob (= 6.0.3.3) + activerecord (= 6.0.3.3) + activestorage (= 6.0.3.3) + activesupport (= 6.0.3.3) mail (>= 2.7.1) - actionmailer (6.0.3.2) - actionpack (= 6.0.3.2) - actionview (= 6.0.3.2) - activejob (= 6.0.3.2) + actionmailer (6.0.3.3) + actionpack (= 6.0.3.3) + actionview (= 6.0.3.3) + activejob (= 6.0.3.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.2) - actionview (= 6.0.3.2) - activesupport (= 6.0.3.2) + actionpack (6.0.3.3) + actionview (= 6.0.3.3) + activesupport (= 6.0.3.3) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.2) - actionpack (= 6.0.3.2) - activerecord (= 6.0.3.2) - activestorage (= 6.0.3.2) - activesupport (= 6.0.3.2) + actiontext (6.0.3.3) + actionpack (= 6.0.3.3) + activerecord (= 6.0.3.3) + activestorage (= 6.0.3.3) + activesupport (= 6.0.3.3) nokogiri (>= 1.8.5) - actionview (6.0.3.2) - activesupport (= 6.0.3.2) + actionview (6.0.3.3) + activesupport (= 6.0.3.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.2) - activesupport (= 6.0.3.2) + activejob (6.0.3.3) + activesupport (= 6.0.3.3) globalid (>= 0.3.6) - activemodel (6.0.3.2) - activesupport (= 6.0.3.2) - activerecord (6.0.3.2) - activemodel (= 6.0.3.2) - activesupport (= 6.0.3.2) - activestorage (6.0.3.2) - actionpack (= 6.0.3.2) - activejob (= 6.0.3.2) - activerecord (= 6.0.3.2) + activemodel (6.0.3.3) + activesupport (= 6.0.3.3) + activerecord (6.0.3.3) + activemodel (= 6.0.3.3) + activesupport (= 6.0.3.3) + activestorage (6.0.3.3) + actionpack (= 6.0.3.3) + activejob (= 6.0.3.3) + activerecord (= 6.0.3.3) marcel (~> 0.3.1) - activesupport (6.0.3.2) + activesupport (6.0.3.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -299,7 +299,7 @@ GEM mini_magick (4.10.1) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.14.1) + minitest (5.14.2) momentjs-rails (2.20.1) railties (>= 3.1) msgpack (1.3.3) @@ -307,7 +307,7 @@ GEM multi_xml (0.6.0) multipart-post (2.1.1) netrc (0.11.0) - nio4r (2.5.2) + nio4r (2.5.3) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) oauth (0.5.4) @@ -336,29 +336,29 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.0.3.2) - actioncable (= 6.0.3.2) - actionmailbox (= 6.0.3.2) - actionmailer (= 6.0.3.2) - actionpack (= 6.0.3.2) - actiontext (= 6.0.3.2) - actionview (= 6.0.3.2) - activejob (= 6.0.3.2) - activemodel (= 6.0.3.2) - activerecord (= 6.0.3.2) - activestorage (= 6.0.3.2) - activesupport (= 6.0.3.2) + rails (6.0.3.3) + actioncable (= 6.0.3.3) + actionmailbox (= 6.0.3.3) + actionmailer (= 6.0.3.3) + actionpack (= 6.0.3.3) + actiontext (= 6.0.3.3) + actionview (= 6.0.3.3) + activejob (= 6.0.3.3) + activemodel (= 6.0.3.3) + activerecord (= 6.0.3.3) + activestorage (= 6.0.3.3) + activesupport (= 6.0.3.3) bundler (>= 1.3.0) - railties (= 6.0.3.2) + railties (= 6.0.3.3) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.0.3.2) - actionpack (= 6.0.3.2) - activesupport (= 6.0.3.2) + railties (6.0.3.3) + actionpack (= 6.0.3.3) + activesupport (= 6.0.3.3) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) diff --git a/app/controllers/api/v1/widget/conversations_controller.rb b/app/controllers/api/v1/widget/conversations_controller.rb index ac41c86c2..bafd008f9 100644 --- a/app/controllers/api/v1/widget/conversations_controller.rb +++ b/app/controllers/api/v1/widget/conversations_controller.rb @@ -8,7 +8,7 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController def update_last_seen head :ok && return if conversation.nil? - conversation.user_last_seen_at = DateTime.now.utc + conversation.contact_last_seen_at = DateTime.now.utc conversation.save! head :ok end diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index f39997b6e..0ac7e8d61 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -26,7 +26,8 @@ "TITLE": "Email Notifications", "NOTE": "Update your email notification preferences here", "CONVERSATION_ASSIGNMENT": "Send email notifications when a conversation is assigned to me", - "CONVERSATION_CREATION": "Send email notifications when a new conversation is created" + "CONVERSATION_CREATION": "Send email notifications when a new conversation is created", + "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation" }, "API": { "UPDATE_SUCCESS": "Your notification preferences are updated successfully", @@ -37,6 +38,7 @@ "NOTE": "Update your push notification preferences here", "CONVERSATION_ASSIGNMENT": "Send push notifications when a conversation is assigned to me", "CONVERSATION_CREATION": "Send push notifications when a new conversation is created", + "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation", "HAS_ENABLED_PUSH": "You have enabled push for this browser.", "REQUEST_PUSH": "Enable push notifications" }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/NotificationSettings.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/NotificationSettings.vue index 704978a00..0caf29589 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/profile/NotificationSettings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/profile/NotificationSettings.vue @@ -43,6 +43,23 @@ }} + +
Hi {{user.available_name}},
+ +You have received a new message in your assigned conversation.
+ ++Click here to get cracking. +
diff --git a/db/migrate/20200907094912_rename_user_last_seen.rb b/db/migrate/20200907094912_rename_user_last_seen.rb new file mode 100644 index 000000000..66740c509 --- /dev/null +++ b/db/migrate/20200907094912_rename_user_last_seen.rb @@ -0,0 +1,5 @@ +class RenameUserLastSeen < ActiveRecord::Migration[6.0] + def change + rename_column :conversations, :user_last_seen_at, :contact_last_seen_at + end +end diff --git a/db/schema.rb b/db/schema.rb index b582ed1a1..bd8c4e7d1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_08_28_175931) do +ActiveRecord::Schema.define(version: 2020_09_07_094912) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" @@ -219,7 +219,7 @@ ActiveRecord::Schema.define(version: 2020_08_28_175931) do t.datetime "updated_at", null: false t.bigint "contact_id" t.integer "display_id", null: false - t.datetime "user_last_seen_at" + t.datetime "contact_last_seen_at" t.datetime "agent_last_seen_at" t.boolean "locked", default: false t.jsonb "additional_attributes" diff --git a/docs/channels/api-channel/callback-url.md b/docs/channels/api-channel/callback-url.md index 09373e09f..54d587340 100644 --- a/docs/channels/api-channel/callback-url.md +++ b/docs/channels/api-channel/callback-url.md @@ -33,7 +33,7 @@ When a new message is created in the API channel, you will get a POST request to "inbox_id": 0, "status": "open", "agent_last_seen_at": 0, - "user_last_seen_at": 0, + "contact_last_seen_at": 0, "timestamp": 0 }, "account": { diff --git a/lib/integrations/facebook/delivery_status.rb b/lib/integrations/facebook/delivery_status.rb index 16ab5bdee..d38ece092 100644 --- a/lib/integrations/facebook/delivery_status.rb +++ b/lib/integrations/facebook/delivery_status.rb @@ -26,7 +26,7 @@ class Integrations::Facebook::DeliveryStatus def update_message_status return unless conversation - conversation.user_last_seen_at = @params.at + conversation.contact_last_seen_at = @params.at conversation.save! end end diff --git a/package.json b/package.json index 1a67e9c97..68ea85738 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "git add" ], "!(*schema).rb": [ - "rubocop -a", + "bundle exec rubocop -a", "git add" ], "*.scss": [ diff --git a/spec/controllers/api/v1/widget/conversations_controller_spec.rb b/spec/controllers/api/v1/widget/conversations_controller_spec.rb index 89ce60734..a08b9b7bf 100644 --- a/spec/controllers/api/v1/widget/conversations_controller_spec.rb +++ b/spec/controllers/api/v1/widget/conversations_controller_spec.rb @@ -47,7 +47,7 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do context 'with a conversation' do it 'returns the correct conversation params' do allow(Rails.configuration.dispatcher).to receive(:dispatch) - expect(conversation.user_last_seen_at).to eq(nil) + expect(conversation.contact_last_seen_at).to eq(nil) post '/api/v1/widget/conversations/update_last_seen', headers: { 'X-Auth-Token' => token }, @@ -56,7 +56,7 @@ RSpec.describe '/api/v1/widget/conversations/toggle_typing', type: :request do expect(response).to have_http_status(:success) - expect(conversation.reload.user_last_seen_at).not_to eq(nil) + expect(conversation.reload.contact_last_seen_at).not_to eq(nil) end end end diff --git a/spec/mailboxes/conversation_mailbox_spec.rb b/spec/mailboxes/conversation_mailbox_spec.rb index 65a757058..239d6e689 100644 --- a/spec/mailboxes/conversation_mailbox_spec.rb +++ b/spec/mailboxes/conversation_mailbox_spec.rb @@ -4,9 +4,10 @@ RSpec.describe ConversationMailbox, type: :mailbox do include ActionMailbox::TestHelper describe 'add mail as reply in a conversation' do - let(:agent) { create(:user, email: 'agent1@example.com') } + let(:account) { create(:account) } + let(:agent) { create(:user, email: 'agent1@example.com', account: account) } let(:reply_mail) { create_inbound_email_from_fixture('reply.eml') } - let(:conversation) { create(:conversation, assignee: agent, inbox: create(:inbox, greeting_enabled: false)) } + let(:conversation) { create(:conversation, assignee: agent, inbox: create(:inbox, account: account, greeting_enabled: false), account: account) } let(:described_subject) { described_class.receive reply_mail } let(:serialized_attributes) { %w[text_content html_content number_of_attachments subject date to from in_reply_to cc bcc message_id] } diff --git a/spec/mailers/agent_notifications/conversation_notifications_mailer_spec.rb b/spec/mailers/agent_notifications/conversation_notifications_mailer_spec.rb index 2fb72d141..9182da9d8 100644 --- a/spec/mailers/agent_notifications/conversation_notifications_mailer_spec.rb +++ b/spec/mailers/agent_notifications/conversation_notifications_mailer_spec.rb @@ -36,4 +36,21 @@ RSpec.describe AgentNotifications::ConversationNotificationsMailer, type: :maile expect(mail.to).to eq([agent.email]) end end + + describe 'assigned_conversation_new_message' do + let(:mail) { described_class.assigned_conversation_new_message(conversation, agent).deliver_now } + + it 'renders the subject' do + expect(mail.subject).to eq("#{agent.available_name}, New message in your assigned conversation [ID - #{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 eq nil + end + end end diff --git a/spec/mailers/conversation_reply_mailer_spec.rb b/spec/mailers/conversation_reply_mailer_spec.rb index 3761f02b9..78262c22d 100644 --- a/spec/mailers/conversation_reply_mailer_spec.rb +++ b/spec/mailers/conversation_reply_mailer_spec.rb @@ -14,9 +14,9 @@ RSpec.describe ConversationReplyMailer, type: :mailer do end context 'with summary' do - let(:conversation) { create(:conversation, assignee: agent) } - let(:message) { create(:message, conversation: conversation) } - let(:private_message) { create(:message, content: 'This is a private message', conversation: conversation) } + let(:conversation) { create(:conversation, account: account, assignee: agent) } + let(:message) { create(:message, account: account, conversation: conversation) } + let(:private_message) { create(:message, account: account, content: 'This is a private message', conversation: conversation) } let(:mail) { described_class.reply_with_summary(message.conversation, Time.zone.now).deliver_now } it 'renders the subject' do @@ -31,6 +31,12 @@ RSpec.describe ConversationReplyMailer, type: :mailer do expect(mail.body.decoded).not_to include(private_message.content) expect(mail.body.decoded).to include(message.content) end + + it 'will not send email if conversation is already viewed by contact' do + create(:message, message_type: 'outgoing', account: account, conversation: conversation) + conversation.update(contact_last_seen_at: Time.zone.now) + expect(mail).to eq nil + end end context 'without assignee' do @@ -75,6 +81,12 @@ RSpec.describe ConversationReplyMailer, type: :mailer do expect(mail.body.decoded).not_to include(message_1.content) expect(mail.body.decoded).to include(message_2.content) end + + it 'will not send email if conversation is already viewed by contact' do + create(:message, message_type: 'outgoing', account: account, conversation: conversation) + conversation.update(contact_last_seen_at: Time.zone.now) + expect(mail).to eq nil + end end context 'when custom domain and email is not enabled' do diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index b1e298b3d..4ec6717c8 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -69,7 +69,7 @@ RSpec.describe Conversation, type: :model do conversation.update( status: :resolved, locked: true, - user_last_seen_at: Time.now, + contact_last_seen_at: Time.now, assignee: new_assignee ) end @@ -317,7 +317,7 @@ RSpec.describe Conversation, type: :model do timestamp: conversation.created_at.to_i, can_reply: true, channel: 'Channel::WebWidget', - user_last_seen_at: conversation.user_last_seen_at.to_i, + contact_last_seen_at: conversation.contact_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i, unread_count: 0 } diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb index 7c3dea709..b7b78da9e 100644 --- a/spec/models/message_spec.rb +++ b/spec/models/message_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Message, type: :model do end context 'when message is created' do - let(:message) { build(:message) } + let(:message) { build(:message, account: create(:account)) } it 'triggers ::MessageTemplates::HookExecutionService' do hook_execution_service = double @@ -23,10 +23,25 @@ RSpec.describe Message, type: :model do expect(hook_execution_service).to have_received(:perform) end - it 'calls notify email method on after save' do - allow(message).to receive(:notify_via_mail).and_return(true) + it 'calls notify email method on after save for outgoing messages' do + allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true) + message.message_type = 'outgoing' message.save! - expect(message).to have_received(:notify_via_mail) + expect(ConversationReplyEmailWorker).to have_received(:perform_in) + end + + it 'wont call notify email method for private notes' do + message.private = true + allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true) + message.save! + expect(ConversationReplyEmailWorker).not_to have_received(:perform_in) + end + + it 'wont call notify email method unless its website or email channel' do + message.inbox = create(:inbox, account: message.account, channel: build(:channel_api, account: message.account)) + allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true) + message.save! + expect(ConversationReplyEmailWorker).not_to have_received(:perform_in) end end end diff --git a/spec/presenters/conversations/event_data_presenter_spec.rb b/spec/presenters/conversations/event_data_presenter_spec.rb index dea4ae6b8..430c72805 100644 --- a/spec/presenters/conversations/event_data_presenter_spec.rb +++ b/spec/presenters/conversations/event_data_presenter_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Conversations::EventDataPresenter do can_reply: conversation.can_reply?, channel: conversation.inbox.channel_type, timestamp: conversation.created_at.to_i, - user_last_seen_at: conversation.user_last_seen_at.to_i, + contact_last_seen_at: conversation.contact_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i, unread_count: 0 } diff --git a/swagger/definitions/resource/conversation.yml b/swagger/definitions/resource/conversation.yml index 05126e1ab..46c2efd2e 100644 --- a/swagger/definitions/resource/conversation.yml +++ b/swagger/definitions/resource/conversation.yml @@ -18,7 +18,7 @@ properties: timestamp: type: string description: The time at which conversation was created - user_last_seen_at: + contact_last_seen_at: type: string agent_last_seen_at: type: agent_last_seen_at diff --git a/swagger/swagger.json b/swagger/swagger.json index de008e55e..02e024190 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -1088,7 +1088,7 @@ "type": "string", "description": "The time at which conversation was created" }, - "user_last_seen_at": { + "contact_last_seen_at": { "type": "string" }, "agent_last_seen_at": {