feat: Add settings for audio alert notifications (#2415)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Sivin Varghese
2021-07-05 12:01:54 +05:30
committed by GitHub
parent 48127e00d7
commit 0bd48129b9
9 changed files with 130 additions and 42 deletions

View File

@@ -28,7 +28,9 @@
"AUDIO_NOTIFICATIONS_SECTION": { "AUDIO_NOTIFICATIONS_SECTION": {
"TITLE": "Audio Notifications", "TITLE": "Audio Notifications",
"NOTE": "Enable audio notifications in dashboard for new messages and conversations.", "NOTE": "Enable audio notifications in dashboard for new messages and conversations.",
"ENABLE_AUDIO": "Play audio notification when a new conversation is created or new messages arrives" "NONE": "None",
"ASSIGNED": "Assigned Conversations",
"ALL_CONVERSATIONS": "All Conversations"
}, },
"EMAIL_NOTIFICATIONS_SECTION": { "EMAIL_NOTIFICATIONS_SECTION": {
"TITLE": "Email Notifications", "TITLE": "Email Notifications",

View File

@@ -12,16 +12,45 @@
<div class="columns small-9"> <div class="columns small-9">
<div> <div>
<input <input
id="audio_enable_alert" id="audio_enable_alert_none"
v-model="enableAudioAlerts" v-model="enableAudioAlerts"
class="notification--checkbox" class="notification--checkbox"
type="checkbox" type="radio"
value="none"
@input="handleAudioInput" @input="handleAudioInput"
/> />
<label for="audio_enable_alert"> <label for="audio_enable_alert_none">
{{ $t('PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.NONE') }}
</label>
</div>
<div>
<input
id="audio_enable_alert_mine"
v-model="enableAudioAlerts"
class="notification--checkbox"
type="radio"
value="mine"
@input="handleAudioInput"
/>
<label for="audio_enable_alert_mine">
{{
$t('PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.ASSIGNED')
}}
</label>
</div>
<div>
<input
id="audio_enable_alert_all"
v-model="enableAudioAlerts"
class="notification--checkbox"
type="radio"
value="all"
@input="handleAudioInput"
/>
<label for="audio_enable_alert_all">
{{ {{
$t( $t(
'PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.ENABLE_AUDIO' 'PROFILE_SETTINGS.FORM.AUDIO_NOTIFICATIONS_SECTION.ALL_CONVERSATIONS'
) )
}} }}
</label> </label>
@@ -315,7 +344,7 @@ export default {
this.updateNotificationSettings(); this.updateNotificationSettings();
}, },
handleAudioInput(e) { handleAudioInput(e) {
this.enableAudioAlerts = e.target.checked; this.enableAudioAlerts = e.target.value;
this.updateUISettings({ this.updateUISettings({
enable_audio_alerts: this.enableAudioAlerts, enable_audio_alerts: this.enableAudioAlerts,
}); });

View File

@@ -63,6 +63,9 @@ const getters = {
}, },
getChatStatusFilter: ({ chatStatusFilter }) => chatStatusFilter, getChatStatusFilter: ({ chatStatusFilter }) => chatStatusFilter,
getSelectedInbox: ({ currentInbox }) => currentInbox, getSelectedInbox: ({ currentInbox }) => currentInbox,
getConversationById: _state => conversationId => {
return _state.allConversations.find(value => value.id === conversationId);
},
}; };
export default getters; export default getters;

View File

@@ -102,4 +102,16 @@ describe('#getters', () => {
]); ]);
}); });
}); });
describe('#getConversationById', () => {
it('get conversations based on id', () => {
const state = {
allConversations: [
{
id: 1,
},
],
};
expect(getters.getConversationById(state)(1)).toEqual({ id: 1 });
});
});
}); });

View File

@@ -1,16 +1,7 @@
import { MESSAGE_TYPE } from 'shared/constants/messages'; import { MESSAGE_TYPE } from 'shared/constants/messages';
const notificationAudio = require('shared/assets/audio/ding.mp3');
import axios from 'axios'; import axios from 'axios';
import { showBadgeOnFavicon } from './faviconHelper'; import { showBadgeOnFavicon } from './faviconHelper';
export const playNotificationAudio = () => {
try {
new Audio(notificationAudio).play();
} catch (error) {
// error
}
};
export const getAlertAudio = async () => { export const getAlertAudio = async () => {
window.playAudioAlert = () => {}; window.playAudioAlert = () => {};
const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
@@ -35,11 +26,21 @@ export const getAlertAudio = async () => {
} }
}; };
export const notificationEnabled = (enableAudioAlerts, id, userId) => {
if (enableAudioAlerts === 'mine') {
return userId === id;
}
if (enableAudioAlerts === 'all') {
return true;
}
return false;
};
export const shouldPlayAudio = ( export const shouldPlayAudio = (
message, message,
conversationId, conversationId,
userId, userId,
isDocHiddden isDocHidden
) => { ) => {
const { const {
conversation_id: incomingConvId, conversation_id: incomingConvId,
@@ -51,29 +52,44 @@ export const shouldPlayAudio = (
const playAudio = const playAudio =
!isFromCurrentUser && (messageType === MESSAGE_TYPE.INCOMING || isPrivate); !isFromCurrentUser && (messageType === MESSAGE_TYPE.INCOMING || isPrivate);
if (isDocHidden) return playAudio;
if (isDocHiddden) return playAudio;
if (conversationId !== incomingConvId) return playAudio; if (conversationId !== incomingConvId) return playAudio;
return false; return false;
}; };
export const getAssigneeFromNotification = currentConv => {
let id;
if (currentConv.meta) {
const assignee = currentConv.meta.assignee;
if (assignee) {
id = assignee.id;
}
}
return id;
};
export const newMessageNotification = data => { export const newMessageNotification = data => {
const { conversation_id: currentConvId } = window.WOOT.$route.params; const { conversation_id: currentConvId } = window.WOOT.$route.params;
const currentUserId = window.WOOT.$store.getters.getCurrentUserID; const currentUserId = window.WOOT.$store.getters.getCurrentUserID;
const isDocHiddden = document.hidden; const { conversation_id: incomingConvId } = data;
const currentConv =
window.WOOT.$store.getters.getConversationById(incomingConvId) || {};
const assigneeId = getAssigneeFromNotification(currentConv);
const isDocHidden = document.hidden;
const { const {
enable_audio_alerts: enableAudioAlerts = false, enable_audio_alerts: enableAudioAlerts = false,
} = window.WOOT.$store.getters.getUISettings; } = window.WOOT.$store.getters.getUISettings;
const playAudio = shouldPlayAudio( const playAudio = shouldPlayAudio(
data, data,
currentConvId, currentConvId,
currentUserId, currentUserId,
isDocHiddden isDocHidden
); );
const isNotificationEnabled = notificationEnabled(
if (enableAudioAlerts && playAudio) { enableAudioAlerts,
currentUserId,
assigneeId
);
if (playAudio && isNotificationEnabled) {
window.playAudioAlert(); window.playAudioAlert();
showBadgeOnFavicon(); showBadgeOnFavicon();
} }

View File

@@ -2,7 +2,11 @@
* @jest-environment jsdom * @jest-environment jsdom
*/ */
import { shouldPlayAudio } from '../AudioNotificationHelper'; import {
shouldPlayAudio,
notificationEnabled,
getAssigneeFromNotification,
} from '../AudioNotificationHelper';
describe('shouldPlayAudio', () => { describe('shouldPlayAudio', () => {
describe('Document active', () => { describe('Document active', () => {
@@ -107,3 +111,41 @@ describe('shouldPlayAudio', () => {
}); });
}); });
}); });
describe('notificationEnabled', () => {
it('returns true if mine', () => {
const [enableAudioAlerts, userId, id] = ['mine', 1, 1];
const result = notificationEnabled(enableAudioAlerts, userId, id);
expect(result).toBe(true);
});
it('returns true if all', () => {
const [enableAudioAlerts, userId, id] = ['all', 1, 2];
const result = notificationEnabled(enableAudioAlerts, userId, id);
expect(result).toBe(true);
});
it('returns false if none', () => {
const [enableAudioAlerts, userId, id] = ['none', 1, 2];
const result = notificationEnabled(enableAudioAlerts, userId, id);
expect(result).toBe(false);
});
});
describe('getAssigneeFromNotification', () => {
it('Retuns true if gets notification from assignee', () => {
const currentConv = {
id: 1,
accountId: 1,
meta: {
assignee: {
id: 1,
name: 'John',
},
},
};
const result = getAssigneeFromNotification(currentConv);
expect(result).toBe(1);
});
it('Retuns true if gets notification from assignee is udefined', () => {
const currentConv = {};
const result = getAssigneeFromNotification(currentConv);
expect(result).toBe(undefined);
});
});

View File

@@ -8,7 +8,7 @@ import {
} from 'widget/api/conversation'; } from 'widget/api/conversation';
import { refreshActionCableConnector } from '../../../helpers/actionCable'; import { refreshActionCableConnector } from '../../../helpers/actionCable';
import { createTemporaryMessage, onNewMessageCreated } from './helpers'; import { createTemporaryMessage } from './helpers';
export const actions = { export const actions = {
createConversation: async ({ commit, dispatch }, params) => { createConversation: async ({ commit, dispatch }, params) => {
@@ -78,7 +78,6 @@ export const actions = {
addMessage: async ({ commit }, data) => { addMessage: async ({ commit }, data) => {
commit('pushMessageToConversation', data); commit('pushMessageToConversation', data);
onNewMessageCreated(data);
}, },
updateMessage({ commit }, data) { updateMessage({ commit }, data) {

View File

@@ -1,5 +1,4 @@
import { MESSAGE_TYPE } from 'widget/helpers/constants'; import { MESSAGE_TYPE } from 'widget/helpers/constants';
import { playNotificationAudio } from 'shared/helpers/AudioNotificationHelper';
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper'; import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
import getUuid from '../../../helpers/uuid'; import getUuid from '../../../helpers/uuid';
@@ -47,12 +46,3 @@ export const findUndeliveredMessage = (messageInbox, { content }) =>
Object.values(messageInbox).filter( Object.values(messageInbox).filter(
message => message.content === content && message.status === 'in_progress' message => message.content === content && message.status === 'in_progress'
); );
export const onNewMessageCreated = data => {
const { message_type: messageType } = data;
const isIncomingMessage = messageType === MESSAGE_TYPE.OUTGOING;
if (isIncomingMessage) {
playNotificationAudio();
}
};

View File

@@ -1,12 +1,8 @@
import { playNotificationAudio } from 'shared/helpers/AudioNotificationHelper';
import { actions } from '../../conversation/actions'; import { actions } from '../../conversation/actions';
import getUuid from '../../../../helpers/uuid'; import getUuid from '../../../../helpers/uuid';
import { API } from 'widget/helpers/axios'; import { API } from 'widget/helpers/axios';
jest.mock('../../../../helpers/uuid'); jest.mock('../../../../helpers/uuid');
jest.mock('shared/helpers/AudioNotificationHelper', () => ({
playNotificationAudio: jest.fn(),
}));
jest.mock('widget/helpers/axios'); jest.mock('widget/helpers/axios');
const commit = jest.fn(); const commit = jest.fn();
@@ -20,7 +16,6 @@ describe('#actions', () => {
it('plays audio when agent sends a message', () => { it('plays audio when agent sends a message', () => {
actions.addMessage({ commit }, { id: 1, message_type: 1 }); actions.addMessage({ commit }, { id: 1, message_type: 1 });
expect(playNotificationAudio).toBeCalled();
expect(commit).toBeCalledWith('pushMessageToConversation', { expect(commit).toBeCalledWith('pushMessageToConversation', {
id: 1, id: 1,
message_type: 1, message_type: 1,