diff --git a/kolibri/core/assets/src/kolibri_app.js b/kolibri/core/assets/src/kolibri_app.js index 70ec3c7eb17..1cb45ab1e65 100644 --- a/kolibri/core/assets/src/kolibri_app.js +++ b/kolibri/core/assets/src/kolibri_app.js @@ -104,7 +104,6 @@ export default class KolibriApp extends KolibriModule { ready() { this.setupVue(); return heartbeat.startPolling().then(() => { - this.store.dispatch('getNotifications'); return Promise.all([ // Invoke each of the state setters before initializing the app. ...this.stateSetters.map(setter => setter(this.store)), diff --git a/kolibri/core/assets/src/state/modules/core/actions.js b/kolibri/core/assets/src/state/modules/core/actions.js index b087aa05174..c9007dbb8ad 100644 --- a/kolibri/core/assets/src/state/modules/core/actions.js +++ b/kolibri/core/assets/src/state/modules/core/actions.js @@ -7,8 +7,6 @@ import { FacilityResource, FacilityDatasetResource, UserSyncStatusResource, - PingbackNotificationResource, - PingbackNotificationDismissedResource, } from 'kolibri.resources'; import { setServerTime } from 'kolibri.utils.serverClock'; import urls from 'kolibri.urls'; @@ -16,8 +14,6 @@ import redirectBrowser from 'kolibri.utils.redirectBrowser'; import CatchErrors from 'kolibri.utils.CatchErrors'; import Vue from 'kolibri.lib.vue'; import Lockr from 'lockr'; -import { get } from '@vueuse/core'; -import useUser from 'kolibri.coreVue.composables.useUser'; import { DisconnectionErrorCodes, LoginErrors, @@ -36,16 +32,6 @@ const logging = logger.getLogger(__filename); * the API to state in the Vuex store */ -function _notificationListState(data) { - return data.map(notification => ({ - id: notification.id, - version_range: notification.version_range, - timestamp: notification.timestamp, - link_url: notification.link_url, - i18n: notification.i18n, - })); -} - /** * Actions * @@ -179,36 +165,6 @@ export function setPageVisibility(store) { _setPageVisibility(store, document.visibilityState === 'visible'); } -export function getNotifications(store) { - const { isAdmin, isSuperuser } = useUser(); - if (get(isAdmin) || get(isSuperuser)) { - return PingbackNotificationResource.fetchCollection() - .then(notifications => { - logging.info('Notifications set.'); - store.commit('CORE_SET_NOTIFICATIONS', _notificationListState(notifications)); - }) - .catch(error => { - store.dispatch('handleApiError', { error }); - }); - } - return Promise.resolve(); -} - -export function saveDismissedNotification(store, notification_id) { - const { user_id } = useUser(); - const dismissedNotificationData = { - user: get(user_id), - notification: notification_id, - }; - return PingbackNotificationDismissedResource.saveModel({ data: dismissedNotificationData }) - .then(() => { - store.commit('CORE_REMOVE_NOTIFICATION', notification_id); - }) - .catch(error => { - store.dispatch('handleApiError', { error }); - }); -} - export function getFacilities(store) { return FacilityResource.fetchCollection({ force: true }).then(facilities => { store.commit('CORE_SET_FACILITIES', [...facilities]); diff --git a/kolibri/core/assets/src/views/NotificationsRoot.vue b/kolibri/core/assets/src/views/NotificationsRoot.vue index 6cd1cbe415b..2f3adabd88a 100644 --- a/kolibri/core/assets/src/views/NotificationsRoot.vue +++ b/kolibri/core/assets/src/views/NotificationsRoot.vue @@ -44,6 +44,10 @@ import { mapState } from 'vuex'; import Lockr from 'lockr'; + import { + PingbackNotificationResource, + PingbackNotificationDismissedResource, + } from 'kolibri.resources'; import { UPDATE_MODAL_DISMISSED } from 'kolibri.coreVue.vuex.constants'; import { currentLanguage, defaultLanguage } from 'kolibri.utils.i18n'; import AuthMessage from 'kolibri.coreVue.components.AuthMessage'; @@ -63,11 +67,12 @@ UpdateNotification, }, setup() { - const { isAdmin, isSuperuser } = useUser(); + const { isAdmin, isSuperuser, user_id } = useUser(); return { isAdmin, isSuperuser, + user_id, }; }, props: { @@ -95,13 +100,13 @@ }, data() { return { + notifications: [], notificationModalShown: true, }; }, computed: { ...mapState({ error: state => state.core.error, - notifications: state => state.core.notifications, }), notAuthorized() { // catch "not authorized" error, display AuthMessage @@ -127,6 +132,9 @@ return false; }, mostRecentNotification() { + if (this.notifications.length === 0) { + return null; + } let languageCode = defaultLanguage.id; // notifications should already be ordered by timestamp const notification = this.notifications[0]; @@ -147,16 +155,65 @@ return null; }, }, + created() { + this.getNotifications(); + }, + methods: { + async getNotifications() { + const { isAdmin, isSuperuser } = useUser(); + if (isAdmin || isSuperuser) { + try { + const notifications = await PingbackNotificationResource.fetchCollection(); + this.notifications = _notificationListState(notifications); + } catch (error) { + this.dispatchError(error); + } + } + }, + async saveDismissedNotification(notificationId) { + try { + await PingbackNotificationDismissedResource.saveModel({ + data: { + user: this.user_id, + notification: notificationId, + }, + }); + this.removeNotification(notificationId); + } catch (error) { + this.dispatchError(error); + } + }, dismissUpdateModal() { if (this.notifications.length === 0) { this.notificationModalShown = false; Lockr.set(UPDATE_MODAL_DISMISSED, true); + } else { + this.saveDismissedNotification(this.mostRecentNotification.id); } }, + dispatchError(error) { + this.$store.dispatch('handleApiError', { error }); + }, + removeNotification(notificationId) { + this.notifications = this.notifications.filter(n => n.id !== notificationId); + }, }, }; + function _notificationListState(data) { + if (!data || data.length === 0) { + return []; + } + return data.map(notification => ({ + id: notification.id, + version_range: notification.version_range, + timestamp: notification.timestamp, + link_url: notification.link_url, + i18n: notification.i18n, + })); + } + diff --git a/kolibri/core/assets/test/kolibri_app.spec.js b/kolibri/core/assets/test/kolibri_app.spec.js index 7bd1af62ec4..dcfad872703 100644 --- a/kolibri/core/assets/test/kolibri_app.spec.js +++ b/kolibri/core/assets/test/kolibri_app.spec.js @@ -58,7 +58,6 @@ describe('KolibriApp', function () { core: { actions: { getCurrentSession: jest.fn().mockResolvedValue(), - getNotifications: jest.fn().mockResolvedValue(), }, }, }, diff --git a/kolibri/core/assets/test/views/NotificationsRoot.spec.js b/kolibri/core/assets/test/views/NotificationsRoot.spec.js index 92aed531b73..d554e0e6bbc 100644 --- a/kolibri/core/assets/test/views/NotificationsRoot.spec.js +++ b/kolibri/core/assets/test/views/NotificationsRoot.spec.js @@ -6,6 +6,7 @@ import { coreStoreFactory as makeStore } from '../../src/state/store'; import coreModule from '../../src/state/modules/core'; jest.mock('kolibri.coreVue.composables.useUser'); +jest.mock('kolibri.resources'); function makeWrapper(useUserMockObj = null) { const store = makeStore(); @@ -72,7 +73,7 @@ describe('NotificationsRoot', function () { it('notification modal should be rendered if the user is an admin/superuser, a notification exists, and there is a recent notification', async () => { const { wrapper, store } = makeWrapper({ isAdmin: true, isSuperuser: true }); store.state.core.loading = false; - store.state.core.notifications = [ + wrapper.vm.notifications = [ { id: 2, title: 'title', @@ -90,7 +91,7 @@ describe('NotificationsRoot', function () { const { wrapper, store } = makeWrapper(); store.commit('CORE_SET_SESSION', { kind: [UserKinds.ADMIN] }); store.state.core.loading = false; - store.state.core.notifications = []; + wrapper.vm.notifications = []; await wrapper.vm.$nextTick(); expect(wrapper.findComponent({ name: 'UpdateNotification' }).exists()).toBeFalsy();