Skip to content

Commit

Permalink
Merge pull request #12464 from nextcloud/backport/12340/stable29
Browse files Browse the repository at this point in the history
[stable29] fix(federation): use a value from response header to count invites
  • Loading branch information
Antreesy authored Jun 7, 2024
2 parents 24a42ee + 552d8b3 commit 0bdcdd6
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 24 deletions.
34 changes: 31 additions & 3 deletions src/components/LeftSidebar/InvitationHandler.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<p class="inbox__disclaimer">
{{ t('spreed', 'Join conversations from remote Nextcloud servers') }}
</p>
<ul class="inbox__list">
<ul v-if="invitationsLoadedCount" class="inbox__list">
<li v-for="(item, id) in invitations"
:key="`invitation_${id}`"
class="inbox__item">
Expand Down Expand Up @@ -67,15 +67,27 @@
</NcButton>
</li>
</ul>
<NcEmptyContent v-else class="inbox__placeholder">
<template #icon>
<NcLoadingIcon v-if="isLoading" />
<WebIcon v-else />
</template>

<template #description>
<p>{{ isLoading ? t('spreed', 'Loading …') : t('spreed', 'No pending invitations') }}</p>
</template>
</NcEmptyContent>
</div>
</NcModal>
</template>

<script>
import CancelIcon from 'vue-material-design-icons/Cancel.vue'
import CheckIcon from 'vue-material-design-icons/Check.vue'
import WebIcon from 'vue-material-design-icons/Web.vue'

import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import NcRichText from '@nextcloud/vue/dist/Components/NcRichText.js'
Expand All @@ -90,6 +102,7 @@ export default {
name: 'InvitationHandler',

components: {
NcEmptyContent,
NcRichText,
ConversationIcon,
NcButton,
Expand All @@ -98,6 +111,7 @@ export default {
// Icons
CancelIcon,
CheckIcon,
WebIcon,
},

setup() {
Expand All @@ -109,6 +123,7 @@ export default {
data() {
return {
modal: false,
isLoading: true,
}
},

Expand All @@ -125,13 +140,21 @@ export default {
}
return invitations
},

invitationsLoadedCount() {
return Object.keys(this.invitations).length
}
},

expose: ['showModal'],

methods: {
showModal() {
async showModal() {
this.modal = true

this.isLoading = true
await this.federationStore.getShares()
this.isLoading = false
},

closeModal() {
Expand All @@ -152,7 +175,7 @@ export default {
},

checkIfNoMoreInvitations() {
if (Object.keys(this.invitations).length === 0) {
if (this.invitationsLoadedCount === 0) {
this.closeModal()
}
},
Expand Down Expand Up @@ -188,6 +211,11 @@ export default {
color: var(--color-text-maxcontrast)
}

& > &__placeholder {
margin: 20px 0;
padding: 0;
}

&__list {
display: flex;
flex-direction: column;
Expand Down
6 changes: 1 addition & 5 deletions src/components/LeftSidebar/LeftSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ export default {

pendingInvitationsCount() {
return isFederationEnabled
? Object.keys(this.federationStore.pendingShares).length
? this.federationStore.pendingSharesCount
: 0
},

Expand Down Expand Up @@ -609,10 +609,6 @@ export default {
this.refreshTimer = window.setInterval(() => {
this.fetchConversations()
}, 30000)

if (isFederationEnabled) {
this.federationStore.getShares()
}
})

talkBroadcastChannel.addEventListener('message', (event) => {
Expand Down
3 changes: 3 additions & 0 deletions src/store/conversationsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js'
import { useBreakoutRoomsStore } from '../stores/breakoutRooms.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
import { useFederationStore } from '../stores/federation.ts'
import { useReactionsStore } from '../stores/reactions.js'
import { useTalkHashStore } from '../stores/talkHash.js'

Expand Down Expand Up @@ -866,6 +867,7 @@ const actions = {

async fetchConversations({ dispatch }, { modifiedSince }) {
const talkHashStore = useTalkHashStore()
const federationStore = useFederationStore()
try {
talkHashStore.clearMaintenanceMode()
modifiedSince = modifiedSince || 0
Expand All @@ -881,6 +883,7 @@ const actions = {

const response = await fetchConversations(options)
talkHashStore.updateTalkVersionHash(response)
federationStore.updatePendingSharesCount(response.headers['x-nextcloud-talk-federation-invites'])

dispatch('patchConversations', {
conversations: response.data.ocs.data,
Expand Down
16 changes: 2 additions & 14 deletions src/store/conversationsStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -542,13 +542,7 @@ describe('conversationsStore', () => {
// add conversation that should be removed
store.dispatch('addConversation', testConversation)

const response = {
data: {
ocs: {
data: testConversations,
},
},
}
const response = generateOCSResponse({ payload: testConversations })

fetchConversations.mockResolvedValue(response)

Expand Down Expand Up @@ -590,13 +584,7 @@ describe('conversationsStore', () => {
}
const modifiedSince = 1675209600 // 2023-02-01T00:00:00.000Z

const response = {
data: {
ocs: {
data: [newConversation1, newConversation2],
},
},
}
const response = generateOCSResponse({ payload: [newConversation1, newConversation2] })

fetchConversations.mockResolvedValue(response)

Expand Down
22 changes: 22 additions & 0 deletions src/stores/__tests__/federation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,24 @@ describe('federationStore', () => {
expect(getShares).toHaveBeenCalled()
expect(federationStore.pendingShares).toMatchObject({ [invites[0].id]: invites[0] })
expect(federationStore.acceptedShares).toMatchObject({ [invites[1].id]: invites[1] })
expect(federationStore.pendingSharesCount).toBe(1)
})

it('processes a response from server and remove outdated invites', async () => {
// Arrange
const response = generateOCSResponse({ payload: invites })
getShares.mockResolvedValueOnce(response)
await federationStore.getShares()

// Act: load empty response from server
const responseEmpty = generateOCSResponse({ payload: [] })
getShares.mockResolvedValueOnce(responseEmpty)
await federationStore.getShares()

// Assert
expect(federationStore.pendingShares).toStrictEqual({})
expect(federationStore.acceptedShares).toStrictEqual({})
expect(federationStore.pendingSharesCount).toBe(0)
})

it('handles error in server request for getShares', async () => {
Expand All @@ -131,6 +149,7 @@ describe('federationStore', () => {
// Assert: store hasn't changed
expect(federationStore.pendingShares).toStrictEqual({})
expect(federationStore.acceptedShares).toStrictEqual({})
expect(federationStore.pendingSharesCount).toBe(0)
})

it('updates invites in the store after receiving a notification', async () => {
Expand All @@ -148,6 +167,7 @@ describe('federationStore', () => {
[notifications[1].objectId]: { id: notifications[1].objectId },
})
expect(federationStore.acceptedShares).toMatchObject({ [invites[1].id]: invites[1] })
expect(federationStore.pendingSharesCount).toBe(2)
})

it('accepts invitation and modify store', async () => {
Expand All @@ -174,6 +194,7 @@ describe('federationStore', () => {
[invites[0].id]: { ...invites[0], state: 1 },
[invites[1].id]: invites[1],
})
expect(federationStore.pendingSharesCount).toBe(0)
})

it('skip already accepted invitations', async () => {
Expand Down Expand Up @@ -224,6 +245,7 @@ describe('federationStore', () => {
expect(rejectShare).toHaveBeenCalledWith(invites[0].id)
expect(federationStore.pendingShares).toStrictEqual({})
expect(federationStore.acceptedShares).toMatchObject({ [invites[1].id]: invites[1] })
expect(federationStore.pendingSharesCount).toBe(1)
})

it('skip already rejected invitations', async () => {
Expand Down
33 changes: 31 additions & 2 deletions src/stores/federation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ import type { Conversation, FederationInvite, NotificationInvite } from '../type
type State = {
pendingShares: Record<string, FederationInvite & { loading?: 'accept' | 'reject' }>,
acceptedShares: Record<string, FederationInvite>,
pendingSharesCount: number,
}
export const useFederationStore = defineStore('federation', {
state: (): State => ({
pendingShares: {},
acceptedShares: {},
pendingSharesCount: 0,
}),

actions: {
Expand All @@ -47,13 +49,18 @@ export const useFederationStore = defineStore('federation', {
async getShares() {
try {
const response = await getShares()
const acceptedShares: State['acceptedShares'] = {}
const pendingShares: State['pendingShares'] = {}
response.data.ocs.data.forEach(item => {
if (item.state === FEDERATION.STATE.ACCEPTED) {
Vue.set(this.acceptedShares, item.id, item)
acceptedShares[item.id] = item
} else {
Vue.set(this.pendingShares, item.id, item)
pendingShares[item.id] = item
}
})
Vue.set(this, 'acceptedShares', acceptedShares)
Vue.set(this, 'pendingShares', pendingShares)
this.updatePendingSharesCount(Object.keys(this.pendingShares).length)
} catch (error) {
console.error(error)
}
Expand Down Expand Up @@ -84,6 +91,7 @@ export const useFederationStore = defineStore('federation', {
inviterDisplayName: name,
}
Vue.set(this.pendingShares, invitation.id, invitation)
this.updatePendingSharesCount(Object.keys(this.pendingShares).length)
},

/**
Expand Down Expand Up @@ -118,10 +126,16 @@ export const useFederationStore = defineStore('federation', {
Vue.set(this.pendingShares[id], 'loading', 'accept')
const response = await acceptShare(id)
this.markInvitationAccepted(id, response.data.ocs.data)
this.updatePendingSharesCount(Object.keys(this.pendingShares).length)
return response.data.ocs.data
} catch (error) {
console.error(error)
showError(t('spreed', 'An error occurred while accepting an invitation'))
// Dismiss the loading state, refresh the list
await this.getShares()
if (this.pendingShares[id]) {
Vue.delete(this.pendingShares[id], 'loading')
}
}
},

Expand All @@ -137,11 +151,26 @@ export const useFederationStore = defineStore('federation', {
try {
Vue.set(this.pendingShares[id], 'loading', 'reject')
await rejectShare(id)
this.updatePendingSharesCount(Object.keys(this.pendingShares).length)
Vue.delete(this.pendingShares, id)
} catch (error) {
console.error(error)
showError(t('spreed', 'An error occurred while rejecting an invitation'))
// Dismiss the loading state, refresh the list
await this.getShares()
if (this.pendingShares[id]) {
Vue.delete(this.pendingShares[id], 'loading')
}
}
},

/**
* Update pending shares count.
*
* @param value amount of pending shares
*/
updatePendingSharesCount(value?: string|number) {
Vue.set(this, 'pendingSharesCount', value ? +value : 0)
},
},
})

0 comments on commit 0bdcdd6

Please sign in to comment.