From 1b21e0d429da403aaf1a7295818c5a377639fdc9 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Wed, 7 Feb 2024 18:15:51 +0530 Subject: [PATCH] feat: Add `notification.updated` event (#8871) --- .../dashboard/helper/actionCable.js | 5 +++++ .../store/modules/notifications/actions.js | 9 ++++++-- .../store/modules/notifications/mutations.js | 11 +++++++++- .../specs/notifications/actions.spec.js | 12 ++++++----- .../specs/notifications/mutations.spec.js | 2 +- .../dashboard/store/mutation-types.js | 1 + app/listeners/action_cable_listener.rb | 6 ++++++ app/models/notification.rb | 8 ++++++- lib/events/types.rb | 1 + spec/listeners/action_cable_listener_spec.rb | 21 +++++++++++++++++++ 10 files changed, 66 insertions(+), 10 deletions(-) diff --git a/app/javascript/dashboard/helper/actionCable.js b/app/javascript/dashboard/helper/actionCable.js index 2b138a3fb..5f4570474 100644 --- a/app/javascript/dashboard/helper/actionCable.js +++ b/app/javascript/dashboard/helper/actionCable.js @@ -24,6 +24,7 @@ class ActionCableConnector extends BaseActionCableConnector { 'conversation.mentioned': this.onConversationMentioned, 'notification.created': this.onNotificationCreated, 'notification.deleted': this.onNotificationDeleted, + 'notification.updated': this.onNotificationUpdated, 'first.reply.created': this.onFirstReplyCreated, 'conversation.read': this.onConversationRead, 'conversation.updated': this.onConversationUpdated, @@ -200,6 +201,10 @@ class ActionCableConnector extends BaseActionCableConnector { this.app.$store.dispatch('notifications/deleteNotification', data); }; + onNotificationUpdated = data => { + this.app.$store.dispatch('notifications/updateNotification', data); + }; + // eslint-disable-next-line class-methods-use-this onFirstReplyCreated = () => { bus.$emit('fetch_overview_reports'); diff --git a/app/javascript/dashboard/store/modules/notifications/actions.js b/app/javascript/dashboard/store/modules/notifications/actions.js index 6d6fdf79d..47455e465 100644 --- a/app/javascript/dashboard/store/modules/notifications/actions.js +++ b/app/javascript/dashboard/store/modules/notifications/actions.js @@ -54,7 +54,7 @@ export const actions = { try { await NotificationsAPI.read(primaryActorType, primaryActorId); commit(types.SET_NOTIFICATIONS_UNREAD_COUNT, unreadCount - 1); - commit(types.UPDATE_NOTIFICATION, { id, read_at: new Date() }); + commit(types.READ_NOTIFICATION, { id, read_at: new Date() }); commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }); } catch (error) { commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }); @@ -64,7 +64,7 @@ export const actions = { commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }); try { await NotificationsAPI.unRead(id); - commit(types.UPDATE_NOTIFICATION, { id, read_at: null }); + commit(types.READ_NOTIFICATION, { id, read_at: null }); commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }); } catch (error) { commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }); @@ -127,6 +127,7 @@ export const actions = { id, snoozedUntil, }); + const { data: { snoozed_until = null }, } = response; @@ -140,6 +141,10 @@ export const actions = { } }, + updateNotification: async ({ commit }, data) => { + commit(types.UPDATE_NOTIFICATION, data); + }, + addNotification({ commit }, data) { commit(types.ADD_NOTIFICATION, data); }, diff --git a/app/javascript/dashboard/store/modules/notifications/mutations.js b/app/javascript/dashboard/store/modules/notifications/mutations.js index 4ad53c327..3493de279 100644 --- a/app/javascript/dashboard/store/modules/notifications/mutations.js +++ b/app/javascript/dashboard/store/modules/notifications/mutations.js @@ -34,7 +34,7 @@ export const mutations = { }); }); }, - [types.UPDATE_NOTIFICATION]: ($state, { id, read_at }) => { + [types.READ_NOTIFICATION]: ($state, { id, read_at }) => { Vue.set($state.records[id], 'read_at', read_at); }, [types.UPDATE_ALL_NOTIFICATIONS]: $state => { @@ -52,6 +52,15 @@ export const mutations = { Vue.set($state.meta, 'unreadCount', unreadCount); Vue.set($state.meta, 'count', count); }, + [types.UPDATE_NOTIFICATION]($state, data) { + const { notification, unread_count: unreadCount, count } = data; + Vue.set($state.records, notification.id, { + ...($state.records[notification.id] || {}), + ...notification, + }); + Vue.set($state.meta, 'unreadCount', unreadCount); + Vue.set($state.meta, 'count', count); + }, [types.DELETE_NOTIFICATION]($state, data) { const { notification, unread_count: unreadCount, count } = data; Vue.delete($state.records, notification.id); diff --git a/app/javascript/dashboard/store/modules/specs/notifications/actions.spec.js b/app/javascript/dashboard/store/modules/specs/notifications/actions.spec.js index 251f8f656..006c2e5af 100644 --- a/app/javascript/dashboard/store/modules/specs/notifications/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/notifications/actions.spec.js @@ -1,7 +1,6 @@ import axios from 'axios'; import { actions } from '../../notifications/actions'; import types from '../../../mutation-types'; - const commit = jest.fn(); global.axios = axios; jest.mock('axios'); @@ -101,7 +100,7 @@ describe('#actions', () => { expect(commit.mock.calls).toEqual([ [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }], [types.SET_NOTIFICATIONS_UNREAD_COUNT, 1], - [types.UPDATE_NOTIFICATION, { id: 1, read_at: expect.any(Date) }], + [types.READ_NOTIFICATION, { id: 1, read_at: expect.any(Date) }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }], ]); }); @@ -125,7 +124,7 @@ describe('#actions', () => { await actions.unread({ commit }, { id: 1 }); expect(commit.mock.calls).toEqual([ ['SET_NOTIFICATIONS_UI_FLAG', { isUpdating: true }], - ['UPDATE_NOTIFICATION', { id: 1, read_at: null }], + ['READ_NOTIFICATION', { id: 1, read_at: null }], ['SET_NOTIFICATIONS_UI_FLAG', { isUpdating: false }], ]); }); @@ -264,17 +263,20 @@ describe('#actions', () => { describe('snooze', () => { it('sends correct actions if API is success', async () => { - axios.post.mockResolvedValue({}); + axios.post.mockResolvedValue({ + data: { snoozed_until: '20 Jan, 5.04pm' }, + }); await actions.snooze({ commit }, { id: 1, snoozedUntil: 1703057715 }); expect(commit.mock.calls).toEqual([ [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }], - [types.SNOOZE_NOTIFICATION, { id: 1, snoozed_until: 1703057715 }], + [types.SNOOZE_NOTIFICATION, { id: 1, snoozed_until: '20 Jan, 5.04pm' }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }], ]); }); it('sends correct actions if API is error', async () => { axios.post.mockRejectedValue({ message: 'Incorrect header' }); await actions.snooze({ commit }, { id: 1, snoozedUntil: 1703057715 }); + expect(commit.mock.calls).toEqual([ [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }], diff --git a/app/javascript/dashboard/store/modules/specs/notifications/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/notifications/mutations.spec.js index 35fa1050f..3d1fa00c5 100644 --- a/app/javascript/dashboard/store/modules/specs/notifications/mutations.spec.js +++ b/app/javascript/dashboard/store/modules/specs/notifications/mutations.spec.js @@ -75,7 +75,7 @@ describe('#mutations', () => { 1: { id: 1, primary_actor_id: 1 }, }, }; - mutations[types.UPDATE_NOTIFICATION](state, { + mutations[types.READ_NOTIFICATION](state, { id: 1, read_at: true, }); diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index 7f625063d..0d4f0c010 100644 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -132,6 +132,7 @@ export default { SET_NOTIFICATIONS_UNREAD_COUNT: 'SET_NOTIFICATIONS_UNREAD_COUNT', SET_NOTIFICATIONS_UI_FLAG: 'SET_NOTIFICATIONS_UI_FLAG', UPDATE_NOTIFICATION: 'UPDATE_NOTIFICATION', + READ_NOTIFICATION: 'READ_NOTIFICATION', ADD_NOTIFICATION: 'ADD_NOTIFICATION', DELETE_NOTIFICATION: 'DELETE_NOTIFICATION', UPDATE_ALL_NOTIFICATIONS: 'UPDATE_ALL_NOTIFICATIONS', diff --git a/app/listeners/action_cable_listener.rb b/app/listeners/action_cable_listener.rb index 3d6b4a48c..5aac56db3 100644 --- a/app/listeners/action_cable_listener.rb +++ b/app/listeners/action_cable_listener.rb @@ -7,6 +7,12 @@ class ActionCableListener < BaseListener broadcast(account, tokens, NOTIFICATION_CREATED, { notification: notification.push_event_data, unread_count: unread_count, count: count }) end + def notification_updated(event) + notification, account, unread_count, count = extract_notification_and_account(event) + tokens = [event.data[:notification].user.pubsub_token] + broadcast(account, tokens, NOTIFICATION_UPDATED, { notification: notification.push_event_data, unread_count: unread_count, count: count }) + end + def notification_deleted(event) return if event.data[:notification].user.blank? diff --git a/app/models/notification.rb b/app/models/notification.rb index 21222079b..e0b31b457 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -46,6 +46,7 @@ class Notification < ApplicationRecord before_create :set_last_activity_at after_create_commit :process_notification_delivery, :dispatch_create_event after_destroy_commit :dispatch_destroy_event + after_update_commit :dispatch_update_event PRIMARY_ACTORS = ['Conversation'].freeze @@ -75,7 +76,8 @@ class Notification < ApplicationRecord def primary_actor_data { id: primary_actor.push_event_data[:id], - meta: primary_actor.push_event_data[:meta] + meta: primary_actor.push_event_data[:meta], + inbox_id: primary_actor.push_event_data[:inbox_id] } end @@ -142,6 +144,10 @@ class Notification < ApplicationRecord Rails.configuration.dispatcher.dispatch(NOTIFICATION_CREATED, Time.zone.now, notification: self) end + def dispatch_update_event + Rails.configuration.dispatcher.dispatch(NOTIFICATION_UPDATED, Time.zone.now, notification: self) + end + def dispatch_destroy_event Rails.configuration.dispatcher.dispatch(NOTIFICATION_DELETED, Time.zone.now, notification: self) end diff --git a/lib/events/types.rb b/lib/events/types.rb index 6e34fc358..ff7eaf778 100644 --- a/lib/events/types.rb +++ b/lib/events/types.rb @@ -49,6 +49,7 @@ module Events::Types # notification events NOTIFICATION_CREATED = 'notification.created' NOTIFICATION_DELETED = 'notification.deleted' + NOTIFICATION_UPDATED = 'notification.updated' # agent events AGENT_ADDED = 'agent.added' diff --git a/spec/listeners/action_cable_listener_spec.rb b/spec/listeners/action_cable_listener_spec.rb index 6275c8dc3..5802ce00a 100644 --- a/spec/listeners/action_cable_listener_spec.rb +++ b/spec/listeners/action_cable_listener_spec.rb @@ -152,6 +152,27 @@ describe ActionCableListener do end end + describe '#notification_updated' do + let(:event_name) { :'notification.updated' } + let!(:notification) { create(:notification, account: account, user: agent) } + let!(:event) { Events::Base.new(event_name, Time.zone.now, notification: notification) } + + it 'sends notification to account admins, inbox agents' do + expect(ActionCableBroadcastJob).to receive(:perform_later).with( + [agent.pubsub_token], + 'notification.updated', + { + account_id: notification.account_id, + notification: notification.push_event_data, + unread_count: 1, + count: 1 + } + ) + + listener.notification_updated(event) + end + end + describe '#conversation_updated' do let(:event_name) { :'conversation.updated' } let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation, user: agent, is_private: false) }