feat: Add settings for audio alert notifications (#2415)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user