diff --git a/changelog/unreleased/bugfix-space-notification-links b/changelog/unreleased/bugfix-space-notification-links new file mode 100644 index 00000000000..36f33152f41 --- /dev/null +++ b/changelog/unreleased/bugfix-space-notification-links @@ -0,0 +1,6 @@ +Bugfix: Space notification links + +Links for incoming new space notifications have been fixed. We also made sure links are being removed if a user has been removed from a space and the space had been loaded before. + +https://github.com/owncloud/web/issues/10397 +https://github.com/owncloud/web/pull/10603 diff --git a/packages/web-runtime/src/components/Topbar/Notifications.vue b/packages/web-runtime/src/components/Topbar/Notifications.vue index b75edc711f3..91cdea3dd1e 100644 --- a/packages/web-runtime/src/components/Topbar/Notifications.vue +++ b/packages/web-runtime/src/components/Topbar/Notifications.vue @@ -104,6 +104,7 @@ import { useGettext } from 'vue3-gettext' import { useTask } from 'vue-concurrency' import { createFileRouteOptions } from '@ownclouders/web-pkg' import { MESSAGE_TYPE } from '@ownclouders/web-client/src/sse' +import { buildSpace } from '@ownclouders/web-client/src/helpers' const POLLING_INTERVAL = 30000 @@ -159,7 +160,7 @@ export default { } return message } - const getLink = ({ messageRichParameters, object_type }: Notification) => { + const getLink = async ({ messageRichParameters, object_type, subject }: Notification) => { if (!messageRichParameters) { return null } @@ -175,21 +176,47 @@ export default { const space = store.getters['runtime/spaces/spaces'].find( (s) => s.fileId === messageRichParameters?.space?.id.split('!')[0] && !s.disabled ) - if (space) { + + if (space && subject === 'Removed from Space') { + store.commit('runtime/spaces/REMOVE_SPACE', space) + return null + } else if (space) { + return { + name: 'files-spaces-generic', + ...createFileRouteOptions(space, { path: '', fileId: space.fileId }) + } + } + + try { + const { data } = await clientService.graphAuthenticated.drives.getDrive( + messageRichParameters.space.id + ) + + const space = buildSpace(data) + if (!space.isMember(store.getters.user)) { + return null + } + + store.commit('runtime/spaces/UPSERT_SPACE', space) + return { name: 'files-spaces-generic', ...createFileRouteOptions(space, { path: '', fileId: space.fileId }) } + } catch (error) { + // user has probably been removed from space + console.error(error) + return null } } return null } - const setAdditionalData = () => { + const setAdditionalData = async () => { loading.value = true for (const notification of unref(notifications)) { notification.computedMessage = getMessage(notification) - notification.computedLink = getLink(notification) + notification.computedLink = await getLink(notification) } loading.value = false } @@ -215,7 +242,7 @@ export default { console.error(e) } finally { if (unref(dropdownOpened)) { - setAdditionalData() + yield setAdditionalData() } loading.value = false } diff --git a/packages/web-runtime/tests/unit/components/Topbar/Notifications.spec.ts b/packages/web-runtime/tests/unit/components/Topbar/Notifications.spec.ts index df2dfb16512..6789cf46de9 100644 --- a/packages/web-runtime/tests/unit/components/Topbar/Notifications.spec.ts +++ b/packages/web-runtime/tests/unit/components/Topbar/Notifications.spec.ts @@ -6,10 +6,12 @@ import { defaultComponentMocks, defaultPlugins, shallowMount, - defaultStoreMockOptions + defaultStoreMockOptions, + mockAxiosResolve } from 'web-test-helpers' import { OwnCloudSdk } from '@ownclouders/web-client/src/types' import { SpaceResource } from '@ownclouders/web-client' +import { Drive } from '@ownclouders/web-client/src/generated' const selectors = { notificationBellStub: 'notification-bell-stub', @@ -21,7 +23,8 @@ const selectors = { notificationSubject: '.oc-notifications-subject', notificationMessage: '.oc-notifications-message', notificationLink: '.oc-notifications-link', - notificationActions: '.oc-notifications-actions' + notificationActions: '.oc-notifications-actions', + notificationRouterLinkStub: '.oc-notifications-item router-link-stub' } jest.mock('@ownclouders/web-pkg', () => ({ @@ -160,15 +163,14 @@ describe('Notification component', () => { await wrapper.vm.fetchNotificationsTask.last wrapper.vm.showDrop() await wrapper.vm.$nextTick() - const routerLink = wrapper.findComponent( - `${selectors.notificationItem} router-link-stub` - ) + await wrapper.vm.$nextTick() + const routerLink = wrapper.findComponent(selectors.notificationRouterLinkStub) expect(routerLink.props('to').name).toEqual('files-shares-with-me') expect(routerLink.props('to').query).toEqual({ scrollTo: notification.messageRichParameters.share.id }) }) - it('renders notification as link for spaces', async () => { + it('renders notification as link for already loaded spaces', async () => { const spaceMock = mock({ fileId: '1', getDriveAliasAndItem: () => 'driveAlias', @@ -187,13 +189,66 @@ describe('Notification component', () => { await wrapper.vm.fetchNotificationsTask.last wrapper.vm.showDrop() await wrapper.vm.$nextTick() - const routerLink = wrapper.findComponent( - `${selectors.notificationItem} router-link-stub` - ) + await wrapper.vm.$nextTick() + const routerLink = wrapper.findComponent(selectors.notificationRouterLinkStub) expect(routerLink.props('to').params).toEqual({ driveAliasAndItem: 'driveAlias' }) }) + it('renders notification as link for new spaces after fetching them', async () => { + const spaceMock = mock({ + fileId: '1', + getDriveAliasAndItem: () => 'driveAlias', + disabled: false + }) + const notification = mock({ + messageRich: '{user} added you to space {space}', + object_type: 'storagespace', + messageRichParameters: { + user: { displayname: 'Albert Einstein' }, + space: { name: 'someFile.txt', id: `${spaceMock.fileId}!2` } + } + }) + + const { wrapper, mocks } = getWrapper({ notifications: [notification] }) + mocks.$clientService.graphAuthenticated.drives.getDrive.mockResolvedValue( + mockAxiosResolve(mock({ special: [] })) + ) + + await wrapper.vm.fetchNotificationsTask.perform() + await wrapper.vm.fetchNotificationsTask.last + wrapper.vm.showDrop() + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() + const routerLink = wrapper.findComponent(selectors.notificationRouterLinkStub) + + expect(mocks.$clientService.graphAuthenticated.drives.getDrive).toHaveBeenCalledTimes(1) + expect(routerLink.exists()).toBeTruthy() + }) + it('does not render notification as link if user has been removed from space', async () => { + const spaceMock = mock({ + fileId: '1', + getDriveAliasAndItem: () => 'driveAlias', + disabled: false + }) + const notification = mock({ + messageRich: '{user} removed you from space {space}', + object_type: 'storagespace', + subject: 'Removed from Space', + messageRichParameters: { + user: { displayname: 'Albert Einstein' }, + space: { name: 'someFile.txt', id: `${spaceMock.fileId}!2` } + } + }) + const { wrapper } = getWrapper({ notifications: [notification], spaces: [spaceMock] }) + await wrapper.vm.fetchNotificationsTask.perform() + await wrapper.vm.fetchNotificationsTask.last + wrapper.vm.showDrop() + await wrapper.vm.$nextTick() + await wrapper.vm.$nextTick() + const routerLink = wrapper.findComponent(selectors.notificationRouterLinkStub) + expect(routerLink.exists()).toBeFalsy() + }) }) }) describe('actions', () => {