diff --git a/app/controllers/api/v1/accounts/notifications_controller.rb b/app/controllers/api/v1/accounts/notifications_controller.rb
index 54a03c783..0eeff5695 100644
--- a/app/controllers/api/v1/accounts/notifications_controller.rb
+++ b/app/controllers/api/v1/accounts/notifications_controller.rb
@@ -39,6 +39,15 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
head :ok
end
+ def destroy_all
+ if params[:type] == 'read'
+ ::Notification::DeleteNotificationJob.perform_later(Current.user, type: :read)
+ else
+ ::Notification::DeleteNotificationJob.perform_later(Current.user, type: :all)
+ end
+ head :ok
+ end
+
def unread_count
@unread_count = notification_finder.unread_count
render json: @unread_count
diff --git a/app/javascript/dashboard/api/notifications.js b/app/javascript/dashboard/api/notifications.js
index aa6413483..e6a4edf3e 100644
--- a/app/javascript/dashboard/api/notifications.js
+++ b/app/javascript/dashboard/api/notifications.js
@@ -36,6 +36,12 @@ class NotificationsAPI extends ApiClient {
delete(id) {
return axios.delete(`${this.url}/${id}`);
}
+
+ deleteAll({ type = 'all' }) {
+ return axios.post(`${this.url}/destroy_all`, {
+ type,
+ });
+ }
}
export default new NotificationsAPI();
diff --git a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxDisplayMenu.vue b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxDisplayMenu.vue
index 24316270c..ba4c313ba 100644
--- a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxDisplayMenu.vue
+++ b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxDisplayMenu.vue
@@ -115,18 +115,6 @@ export default {
value: 'read',
selected: true,
},
- {
- id: 3,
- name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.LABELS'),
- value: 'labels',
- selected: false,
- },
- {
- id: 4,
- name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.CONVERSATION_ID'),
- value: 'conversationId',
- selected: false,
- },
],
sortOptions: [
{
@@ -137,10 +125,6 @@ export default {
name: this.$t('INBOX.DISPLAY_MENU.SORT_OPTIONS.OLDEST'),
key: 'oldest',
},
- {
- name: this.$t('INBOX.DISPLAY_MENU.SORT_OPTIONS.PRIORITY'),
- key: 'priority',
- },
],
activeSort: 'newest',
};
diff --git a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue
index d58e89230..6e7f3b894 100644
--- a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue
+++ b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxListHeader.vue
@@ -49,6 +49,7 @@
v-if="showInboxOptionMenu"
v-on-clickaway="openInboxOptionsMenu"
class="absolute top-9"
+ @option-click="onInboxOptionMenuClick"
/>
@@ -57,6 +58,7 @@
diff --git a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxOptionMenu.vue b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxOptionMenu.vue
index 308225a49..1ad9385bf 100644
--- a/app/javascript/dashboard/routes/dashboard/inbox/components/InboxOptionMenu.vue
+++ b/app/javascript/dashboard/routes/dashboard/inbox/components/InboxOptionMenu.vue
@@ -7,15 +7,7 @@
v-for="item in menuItems"
:key="item.key"
:label="item.label"
- @click="onMenuItemClick(item.key)"
- />
-
-
-
@@ -30,24 +22,6 @@ export default {
data() {
return {
menuItems: [
- {
- key: 'mark_as_read',
- label: this.$t('INBOX.MENU_ITEM.MARK_AS_READ'),
- },
- {
- key: 'mark_as_unread',
- label: this.$t('INBOX.MENU_ITEM.MARK_AS_UNREAD'),
- },
- {
- key: 'snooze',
- label: this.$t('INBOX.MENU_ITEM.SNOOZE'),
- },
- {
- key: 'delete',
- label: this.$t('INBOX.MENU_ITEM.DELETE'),
- },
- ],
- commonMenuItems: [
{
key: 'mark_all_read',
label: this.$t('INBOX.MENU_ITEM.MARK_ALL_READ'),
@@ -64,7 +38,7 @@ export default {
};
},
methods: {
- onMenuItemClick(key) {
+ onClick(key) {
this.$emit('option-click', key);
},
},
diff --git a/app/javascript/dashboard/store/modules/notifications/actions.js b/app/javascript/dashboard/store/modules/notifications/actions.js
index 559b560d4..47d2e1f1f 100644
--- a/app/javascript/dashboard/store/modules/notifications/actions.js
+++ b/app/javascript/dashboard/store/modules/notifications/actions.js
@@ -95,6 +95,30 @@ export const actions = {
}
},
+ deleteAllRead: async ({ commit }) => {
+ commit(types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: true });
+ try {
+ await NotificationsAPI.deleteAll({
+ type: 'read',
+ });
+ commit(types.DELETE_READ_NOTIFICATIONS);
+ commit(types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: false });
+ } catch (error) {
+ commit(types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: false });
+ }
+ },
+ deleteAll: async ({ commit }) => {
+ commit(types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: true });
+ try {
+ await NotificationsAPI.deleteAll({
+ type: 'all',
+ });
+ commit(types.DELETE_ALL_NOTIFICATIONS);
+ commit(types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: false });
+ } catch (error) {
+ commit(types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: false });
+ }
+ },
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 14f2bbb0f..55036b1d5 100644
--- a/app/javascript/dashboard/store/modules/notifications/mutations.js
+++ b/app/javascript/dashboard/store/modules/notifications/mutations.js
@@ -61,4 +61,15 @@ export const mutations = {
[types.SET_ALL_NOTIFICATIONS_LOADED]: $state => {
Vue.set($state.uiFlags, 'isAllNotificationsLoaded', true);
},
+
+ [types.DELETE_READ_NOTIFICATIONS]: $state => {
+ Object.values($state.records).forEach(item => {
+ if (item.read_at) {
+ Vue.delete($state.records, item.id);
+ }
+ });
+ },
+ [types.DELETE_ALL_NOTIFICATIONS]: $state => {
+ Vue.set($state, 'records', {});
+ },
};
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 be3af6f06..8e2712a3d 100644
--- a/app/javascript/dashboard/store/modules/specs/notifications/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/notifications/actions.spec.js
@@ -219,4 +219,44 @@ describe('#actions', () => {
expect(commit.mock.calls).toEqual([[types.CLEAR_NOTIFICATIONS]]);
});
});
+
+ describe('deleteAllRead', () => {
+ it('sends correct actions if API is success', async () => {
+ axios.delete.mockResolvedValue({});
+ await actions.deleteAllRead({ commit });
+ expect(commit.mock.calls).toEqual([
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: true }],
+ [types.DELETE_READ_NOTIFICATIONS],
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: false }],
+ ]);
+ });
+ it('sends correct actions if API is error', async () => {
+ axios.delete.mockRejectedValue({ message: 'Incorrect header' });
+ await actions.deleteAllRead({ commit });
+ expect(commit.mock.calls).toEqual([
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: true }],
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: false }],
+ ]);
+ });
+ });
+
+ describe('deleteAll', () => {
+ it('sends correct actions if API is success', async () => {
+ axios.delete.mockResolvedValue({});
+ await actions.deleteAll({ commit });
+ expect(commit.mock.calls).toEqual([
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: true }],
+ [types.DELETE_ALL_NOTIFICATIONS],
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: false }],
+ ]);
+ });
+ it('sends correct actions if API is error', async () => {
+ axios.delete.mockRejectedValue({ message: 'Incorrect header' });
+ await actions.deleteAll({ commit });
+ expect(commit.mock.calls).toEqual([
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: true }],
+ [types.SET_NOTIFICATIONS_UI_FLAG, { isDeleting: 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 1efb06e59..35fa1050f 100644
--- a/app/javascript/dashboard/store/modules/specs/notifications/mutations.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/notifications/mutations.spec.js
@@ -155,4 +155,32 @@ describe('#mutations', () => {
expect(state.uiFlags).toEqual({ isAllNotificationsLoaded: true });
});
});
+
+ describe('#DELETE_READ_NOTIFICATIONS', () => {
+ it('delete read notifications', () => {
+ const state = {
+ records: {
+ 1: { id: 1, primary_actor_id: 1, read_at: true },
+ 2: { id: 2, primary_actor_id: 2 },
+ },
+ };
+ mutations[types.DELETE_READ_NOTIFICATIONS](state);
+ expect(state.records).toEqual({
+ 2: { id: 2, primary_actor_id: 2 },
+ });
+ });
+ });
+
+ describe('#DELETE_ALL_NOTIFICATIONS', () => {
+ it('delete all notifications', () => {
+ const state = {
+ records: {
+ 1: { id: 1, primary_actor_id: 1, read_at: true },
+ 2: { id: 2, primary_actor_id: 2 },
+ },
+ };
+ mutations[types.DELETE_ALL_NOTIFICATIONS](state);
+ expect(state.records).toEqual({});
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index 4f702105f..aae2b3bc4 100644
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -141,6 +141,8 @@ export default {
EDIT_NOTIFICATIONS: 'EDIT_NOTIFICATIONS',
UPDATE_NOTIFICATIONS_PRESENCE: 'UPDATE_NOTIFICATIONS_PRESENCE',
SET_ALL_NOTIFICATIONS_LOADED: 'SET_ALL_NOTIFICATIONS_LOADED',
+ DELETE_READ_NOTIFICATIONS: 'DELETE_READ_NOTIFICATIONS',
+ DELETE_ALL_NOTIFICATIONS: 'DELETE_ALL_NOTIFICATIONS',
// Contact Conversation
SET_CONTACT_CONVERSATIONS_UI_FLAG: 'SET_CONTACT_CONVERSATIONS_UI_FLAG',
diff --git a/app/jobs/notification/delete_notification_job.rb b/app/jobs/notification/delete_notification_job.rb
new file mode 100644
index 000000000..9efaf472c
--- /dev/null
+++ b/app/jobs/notification/delete_notification_job.rb
@@ -0,0 +1,15 @@
+class Notification::DeleteNotificationJob < ApplicationJob
+ queue_as :low
+
+ def perform(user, type: :all)
+ ActiveRecord::Base.transaction do
+ if type == :all
+ # Delete all notifications
+ user.notifications.destroy_all
+ elsif type == :read
+ # Delete only read notifications
+ user.notifications.where.not(read_at: nil).destroy_all
+ end
+ end
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index e0c636e0f..653ba8277 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -173,6 +173,7 @@ Rails.application.routes.draw do
collection do
post :read_all
get :unread_count
+ post :destroy_all
end
member do
post :snooze
diff --git a/spec/controllers/api/v1/accounts/notifications_controller_spec.rb b/spec/controllers/api/v1/accounts/notifications_controller_spec.rb
index 55ebf997c..3dcb47f71 100644
--- a/spec/controllers/api/v1/accounts/notifications_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/notifications_controller_spec.rb
@@ -207,4 +207,43 @@ RSpec.describe 'Notifications API', type: :request do
end
end
end
+
+ describe 'POST /api/v1/accounts/{account.id}/notifications/destroy_all' do
+ let(:admin) { create(:user, account: account, role: :administrator) }
+ let(:notification1) { create(:notification, account: account, user: admin) }
+ let(:notification2) { create(:notification, account: account, user: admin) }
+
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ post "/api/v1/accounts/#{account.id}/notifications/destroy_all"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated user' do
+ let(:admin) { create(:user, account: account, role: :administrator) }
+
+ it 'deletes all the read notifications' do
+ expect(Notification::DeleteNotificationJob).to receive(:perform_later).with(admin, type: :read)
+
+ post "/api/v1/accounts/#{account.id}/notifications/destroy_all",
+ headers: admin.create_new_auth_token,
+ params: { type: 'read' },
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'deletes all the notifications' do
+ expect(Notification::DeleteNotificationJob).to receive(:perform_later).with(admin, type: :all)
+
+ post "/api/v1/accounts/#{account.id}/notifications/destroy_all",
+ headers: admin.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+ end
end
diff --git a/spec/jobs/notification/delete_notification_job_spec.rb b/spec/jobs/notification/delete_notification_job_spec.rb
new file mode 100644
index 000000000..194a24732
--- /dev/null
+++ b/spec/jobs/notification/delete_notification_job_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+RSpec.describe Notification::DeleteNotificationJob do
+ let(:user) { create(:user) }
+ let(:conversation) { create(:conversation) }
+
+ context 'when enqueuing the job' do
+ it 'enqueues the job to delete all notifications' do
+ expect do
+ described_class.perform_later(user.id, type: :all)
+ end.to have_enqueued_job(described_class).on_queue('low')
+ end
+
+ it 'enqueues the job to delete read notifications' do
+ expect do
+ described_class.perform_later(user.id, type: :read)
+ end.to have_enqueued_job(described_class).on_queue('low')
+ end
+ end
+
+ context 'when performing the job' do
+ before do
+ create(:notification, user: user, read_at: nil)
+ create(:notification, user: user, read_at: Time.current)
+ end
+
+ it 'deletes all notifications' do
+ described_class.perform_now(user, type: :all)
+ expect(user.notifications.count).to eq(0)
+ end
+
+ it 'deletes only read notifications' do
+ described_class.perform_now(user, type: :read)
+ expect(user.notifications.count).to eq(1)
+ expect(user.notifications.where(read_at: nil).count).to eq(1)
+ end
+ end
+end