Skip to content

Commit

Permalink
Merge pull request #11254 from nextcloud/feat/noid/optimize-participants
Browse files Browse the repository at this point in the history
fix(participantsStore): optimize store updates
  • Loading branch information
Antreesy authored Dec 21, 2023
2 parents daf8a30 + 4784f6f commit 89a8f2e
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -614,10 +614,6 @@ export default {
return this.participant.attendeeId
},

label() {
return this.participant.label
},

shareWithDisplayNameUnique() {
return this.participant.shareWithDisplayNameUnique
},
Expand Down Expand Up @@ -678,10 +674,6 @@ export default {
return this.participantSpeakingInformation?.speaking
},

lastPing() {
return this.participant.lastPing
},

attendeePin() {
return this.canBeModerated && this.participant.attendeePin ? readableNumber(this.participant.attendeePin) : ''
},
Expand Down
8 changes: 7 additions & 1 deletion src/components/RightSidebar/Participants/ParticipantsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,12 @@ export default {
return
}

if (this.participants.find(participant => participant.actorId === state.userId)) {
const participant = this.participants.find(participant => participant.actorId === state.userId)
if (participant && (participant.status !== state.status
|| participant.statusMessage !== state.message
|| participant.statusIcon !== state.icon
|| participant.statusClearAt !== state.clearAt
)) {
this.$store.dispatch('updateUser', {
token: this.token,
participantIdentifier: {
Expand All @@ -296,6 +301,7 @@ export default {
status: state.status,
statusIcon: state.icon,
statusMessage: state.message,
statusClearAt: state.clearAt,
},
})
}
Expand Down
83 changes: 71 additions & 12 deletions src/store/participantsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js'
import { useGuestNameStore } from '../stores/guestName.js'
import CancelableRequest from '../utils/cancelableRequest.js'

/**
* Emit global event for user status update with the status from a participant
*
* @param {object} participant - a participant object
*/
function emitUserStatusUpdated(participant) {
if (participant.actorType === 'users') {
emit('user_status:status.updated', {
status: participant.status,
message: participant.statusMessage,
icon: participant.statusIcon,
clearAt: participant.statusClearAt,
userId: participant.actorId,
})
}
}

const state = {
attendees: {
},
Expand Down Expand Up @@ -600,12 +617,25 @@ const actions = {

try {
const response = await request(token)
context.dispatch('purgeParticipantsStore', token)

const hasUserStatuses = !!response.headers['x-nextcloud-has-user-statuses']

response.data.ocs.data.forEach(participant => {
context.dispatch('addParticipant', { token, participant })
const currentParticipants = context.state.attendees[token]
const newParticipants = response.data.ocs.data
for (const attendeeId of Object.keys(Object(currentParticipants))) {
if (!newParticipants.some(participant => participant.attendeeId === +attendeeId)) {
context.commit('deleteParticipant', { token, attendeeId })
}
}

newParticipants.forEach(participant => {
if (context.state.attendees[token]?.[participant.attendeeId]) {
context.dispatch('updateParticipantIfHasChanged', { token, participant, hasUserStatuses })
} else {
context.dispatch('addParticipant', { token, participant })
if (hasUserStatuses) {
emitUserStatusUpdated(participant)
}
}

if (participant.participantType === PARTICIPANT.TYPE.GUEST
|| participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR) {
Expand All @@ -614,14 +644,6 @@ const actions = {
actorId: Hex.stringify(SHA1(participant.sessionIds[0])),
actorDisplayName: participant.displayName,
}, { noUpdate: false })
} else if (participant.actorType === 'users' && hasUserStatuses) {
emit('user_status:status.updated', {
status: participant.status,
message: participant.statusMessage,
icon: participant.statusIcon,
clearAt: participant.statusClearAt,
userId: participant.actorId,
})
}
})

Expand All @@ -640,6 +662,43 @@ const actions = {
}
},

/**
* Update participant in store according to a new participant object
*
* @param {object} context store context
* @param {object} data the wrapping object;
* @param {string} data.token the conversation token;
* @param {object} data.participant the new participant object;
* @param {boolean} data.hasUserStatuses whether user status is enabled or not;
* @return {boolean} whether the participant was changed
*/
updateParticipantIfHasChanged(context, { token, participant, hasUserStatuses }) {
const { attendeeId } = participant
const oldParticipant = context.state.attendees[token][attendeeId]

// Check if any property has changed
const changedEntries = Object.entries(participant).filter(([key, value]) => {
// "sessionIds" is the only property with non-primitive (array) value and cannot be compared by ===
return key === 'sessionIds'
? JSON.stringify(oldParticipant[key]) !== JSON.stringify(value)
: oldParticipant[key] !== value
})

if (changedEntries.length === 0) {
return false
}

const updatedData = Object.fromEntries(changedEntries)
context.commit('updateParticipant', { token, attendeeId, updatedData })

// check if status-related properties have been changed
if (hasUserStatuses && changedEntries.some(([key]) => key.startsWith('status'))) {
emitUserStatusUpdated(participant)
}

return true
},

/**
* Cancels a previously running "fetchParticipants" action if applicable.
*
Expand Down
33 changes: 33 additions & 0 deletions src/store/participantsStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,39 @@ describe('participantsStore', () => {
expect(store.getters.participantsList(TOKEN)).toMatchObject(payload)
})

test('populates store for the fetched conversation', async () => {
// Arrange
const payloadFirst = [
{ attendeeId: 1, actorType: 'users', sessionIds: ['session-id-1'], inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED }, // delete
{ attendeeId: 2, actorType: 'users', sessionIds: ['session-id-2-1', 'session-id-2-2'], inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED },
{ attendeeId: 3, actorType: 'users', sessionIds: ['session-id-3'], inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED },
{ attendeeId: 4, actorType: 'users', sessionIds: ['session-id-4'], inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED },
]
const payloadSecond = [
{ attendeeId: 2, actorType: 'users', sessionIds: ['session-id-2-2', 'session-id-2-1'], inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED }, // change
{ attendeeId: 3, actorType: 'users', sessionIds: ['session-id-3'], inCall: PARTICIPANT.CALL_FLAG.IN_CALL }, // change
{ attendeeId: 4, actorType: 'users', sessionIds: ['session-id-4'], inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED, status: 'online' }, // change
{ attendeeId: 5, actorType: 'guests', sessionIds: ['session-id-5'], inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED }, // add
]
fetchParticipants
.mockResolvedValueOnce(generateOCSResponse({
headers: { 'x-nextcloud-has-user-statuses': true },
payload: payloadFirst,
}))
.mockResolvedValueOnce(generateOCSResponse({
headers: { 'x-nextcloud-has-user-statuses': true },
payload: payloadSecond,
}))

// Act
await store.dispatch('fetchParticipants', { token: TOKEN })
await store.dispatch('fetchParticipants', { token: TOKEN })

// Assert
expect(emit).toHaveBeenCalledTimes(5) // 4 added users and 1 status changed
expect(store.getters.participantsList(TOKEN)).toMatchObject(payloadSecond)
})

test('saves a guest name from response', async () => {
// Arrange
const payload = [{
Expand Down

0 comments on commit 89a8f2e

Please sign in to comment.