feat: Add notification.updated event (#8871)

This commit is contained in:
Muhsin Keloth
2024-02-07 18:15:51 +05:30
committed by GitHub
parent 7776b74126
commit 1b21e0d429
10 changed files with 66 additions and 10 deletions

View File

@@ -24,6 +24,7 @@ class ActionCableConnector extends BaseActionCableConnector {
'conversation.mentioned': this.onConversationMentioned, 'conversation.mentioned': this.onConversationMentioned,
'notification.created': this.onNotificationCreated, 'notification.created': this.onNotificationCreated,
'notification.deleted': this.onNotificationDeleted, 'notification.deleted': this.onNotificationDeleted,
'notification.updated': this.onNotificationUpdated,
'first.reply.created': this.onFirstReplyCreated, 'first.reply.created': this.onFirstReplyCreated,
'conversation.read': this.onConversationRead, 'conversation.read': this.onConversationRead,
'conversation.updated': this.onConversationUpdated, 'conversation.updated': this.onConversationUpdated,
@@ -200,6 +201,10 @@ class ActionCableConnector extends BaseActionCableConnector {
this.app.$store.dispatch('notifications/deleteNotification', data); this.app.$store.dispatch('notifications/deleteNotification', data);
}; };
onNotificationUpdated = data => {
this.app.$store.dispatch('notifications/updateNotification', data);
};
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
onFirstReplyCreated = () => { onFirstReplyCreated = () => {
bus.$emit('fetch_overview_reports'); bus.$emit('fetch_overview_reports');

View File

@@ -54,7 +54,7 @@ export const actions = {
try { try {
await NotificationsAPI.read(primaryActorType, primaryActorId); await NotificationsAPI.read(primaryActorType, primaryActorId);
commit(types.SET_NOTIFICATIONS_UNREAD_COUNT, unreadCount - 1); 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 }); commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false });
} catch (error) { } catch (error) {
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }); commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false });
@@ -64,7 +64,7 @@ export const actions = {
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }); commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true });
try { try {
await NotificationsAPI.unRead(id); 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 }); commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false });
} catch (error) { } catch (error) {
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }); commit(types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false });
@@ -127,6 +127,7 @@ export const actions = {
id, id,
snoozedUntil, snoozedUntil,
}); });
const { const {
data: { snoozed_until = null }, data: { snoozed_until = null },
} = response; } = response;
@@ -140,6 +141,10 @@ export const actions = {
} }
}, },
updateNotification: async ({ commit }, data) => {
commit(types.UPDATE_NOTIFICATION, data);
},
addNotification({ commit }, data) { addNotification({ commit }, data) {
commit(types.ADD_NOTIFICATION, data); commit(types.ADD_NOTIFICATION, data);
}, },

View File

@@ -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); Vue.set($state.records[id], 'read_at', read_at);
}, },
[types.UPDATE_ALL_NOTIFICATIONS]: $state => { [types.UPDATE_ALL_NOTIFICATIONS]: $state => {
@@ -52,6 +52,15 @@ export const mutations = {
Vue.set($state.meta, 'unreadCount', unreadCount); Vue.set($state.meta, 'unreadCount', unreadCount);
Vue.set($state.meta, 'count', count); 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) { [types.DELETE_NOTIFICATION]($state, data) {
const { notification, unread_count: unreadCount, count } = data; const { notification, unread_count: unreadCount, count } = data;
Vue.delete($state.records, notification.id); Vue.delete($state.records, notification.id);

View File

@@ -1,7 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { actions } from '../../notifications/actions'; import { actions } from '../../notifications/actions';
import types from '../../../mutation-types'; import types from '../../../mutation-types';
const commit = jest.fn(); const commit = jest.fn();
global.axios = axios; global.axios = axios;
jest.mock('axios'); jest.mock('axios');
@@ -101,7 +100,7 @@ describe('#actions', () => {
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
[types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }],
[types.SET_NOTIFICATIONS_UNREAD_COUNT, 1], [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 }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }],
]); ]);
}); });
@@ -125,7 +124,7 @@ describe('#actions', () => {
await actions.unread({ commit }, { id: 1 }); await actions.unread({ commit }, { id: 1 });
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
['SET_NOTIFICATIONS_UI_FLAG', { isUpdating: true }], ['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 }], ['SET_NOTIFICATIONS_UI_FLAG', { isUpdating: false }],
]); ]);
}); });
@@ -264,17 +263,20 @@ describe('#actions', () => {
describe('snooze', () => { describe('snooze', () => {
it('sends correct actions if API is success', async () => { 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 }); await actions.snooze({ commit }, { id: 1, snoozedUntil: 1703057715 });
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
[types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }], [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 }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }],
]); ]);
}); });
it('sends correct actions if API is error', async () => { it('sends correct actions if API is error', async () => {
axios.post.mockRejectedValue({ message: 'Incorrect header' }); axios.post.mockRejectedValue({ message: 'Incorrect header' });
await actions.snooze({ commit }, { id: 1, snoozedUntil: 1703057715 }); await actions.snooze({ commit }, { id: 1, snoozedUntil: 1703057715 });
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([
[types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: true }],
[types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }], [types.SET_NOTIFICATIONS_UI_FLAG, { isUpdating: false }],

View File

@@ -75,7 +75,7 @@ describe('#mutations', () => {
1: { id: 1, primary_actor_id: 1 }, 1: { id: 1, primary_actor_id: 1 },
}, },
}; };
mutations[types.UPDATE_NOTIFICATION](state, { mutations[types.READ_NOTIFICATION](state, {
id: 1, id: 1,
read_at: true, read_at: true,
}); });

View File

@@ -132,6 +132,7 @@ export default {
SET_NOTIFICATIONS_UNREAD_COUNT: 'SET_NOTIFICATIONS_UNREAD_COUNT', SET_NOTIFICATIONS_UNREAD_COUNT: 'SET_NOTIFICATIONS_UNREAD_COUNT',
SET_NOTIFICATIONS_UI_FLAG: 'SET_NOTIFICATIONS_UI_FLAG', SET_NOTIFICATIONS_UI_FLAG: 'SET_NOTIFICATIONS_UI_FLAG',
UPDATE_NOTIFICATION: 'UPDATE_NOTIFICATION', UPDATE_NOTIFICATION: 'UPDATE_NOTIFICATION',
READ_NOTIFICATION: 'READ_NOTIFICATION',
ADD_NOTIFICATION: 'ADD_NOTIFICATION', ADD_NOTIFICATION: 'ADD_NOTIFICATION',
DELETE_NOTIFICATION: 'DELETE_NOTIFICATION', DELETE_NOTIFICATION: 'DELETE_NOTIFICATION',
UPDATE_ALL_NOTIFICATIONS: 'UPDATE_ALL_NOTIFICATIONS', UPDATE_ALL_NOTIFICATIONS: 'UPDATE_ALL_NOTIFICATIONS',

View File

@@ -7,6 +7,12 @@ class ActionCableListener < BaseListener
broadcast(account, tokens, NOTIFICATION_CREATED, { notification: notification.push_event_data, unread_count: unread_count, count: count }) broadcast(account, tokens, NOTIFICATION_CREATED, { notification: notification.push_event_data, unread_count: unread_count, count: count })
end 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) def notification_deleted(event)
return if event.data[:notification].user.blank? return if event.data[:notification].user.blank?

View File

@@ -46,6 +46,7 @@ class Notification < ApplicationRecord
before_create :set_last_activity_at before_create :set_last_activity_at
after_create_commit :process_notification_delivery, :dispatch_create_event after_create_commit :process_notification_delivery, :dispatch_create_event
after_destroy_commit :dispatch_destroy_event after_destroy_commit :dispatch_destroy_event
after_update_commit :dispatch_update_event
PRIMARY_ACTORS = ['Conversation'].freeze PRIMARY_ACTORS = ['Conversation'].freeze
@@ -75,7 +76,8 @@ class Notification < ApplicationRecord
def primary_actor_data def primary_actor_data
{ {
id: primary_actor.push_event_data[:id], 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 end
@@ -142,6 +144,10 @@ class Notification < ApplicationRecord
Rails.configuration.dispatcher.dispatch(NOTIFICATION_CREATED, Time.zone.now, notification: self) Rails.configuration.dispatcher.dispatch(NOTIFICATION_CREATED, Time.zone.now, notification: self)
end end
def dispatch_update_event
Rails.configuration.dispatcher.dispatch(NOTIFICATION_UPDATED, Time.zone.now, notification: self)
end
def dispatch_destroy_event def dispatch_destroy_event
Rails.configuration.dispatcher.dispatch(NOTIFICATION_DELETED, Time.zone.now, notification: self) Rails.configuration.dispatcher.dispatch(NOTIFICATION_DELETED, Time.zone.now, notification: self)
end end

View File

@@ -49,6 +49,7 @@ module Events::Types
# notification events # notification events
NOTIFICATION_CREATED = 'notification.created' NOTIFICATION_CREATED = 'notification.created'
NOTIFICATION_DELETED = 'notification.deleted' NOTIFICATION_DELETED = 'notification.deleted'
NOTIFICATION_UPDATED = 'notification.updated'
# agent events # agent events
AGENT_ADDED = 'agent.added' AGENT_ADDED = 'agent.added'

View File

@@ -152,6 +152,27 @@ describe ActionCableListener do
end end
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 describe '#conversation_updated' do
let(:event_name) { :'conversation.updated' } let(:event_name) { :'conversation.updated' }
let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation, user: agent, is_private: false) } let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation, user: agent, is_private: false) }