Merge pull request #6579 from Ocelot-Social-Community/chat-pagniate-rooms

feat(webapp): chat paginate rooms
This commit is contained in:
Ulf Gebhardt 2023-07-17 10:09:05 +02:00 committed by GitHub
commit 640e713f0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 254 additions and 63 deletions

View File

@ -13,8 +13,8 @@ export const createRoomMutation = () => {
export const roomQuery = () => { export const roomQuery = () => {
return gql` return gql`
query { query Room($first: Int, $offset: Int, $id: ID) {
Room { Room(first: $first, offset: $offset, id: $id, orderBy: createdAt_desc) {
id id
roomId roomId
roomName roomName

View File

@ -48,6 +48,14 @@ describe('Room', () => {
id: 'not-chatting-user', id: 'not-chatting-user',
name: 'Not Chatting User', name: 'Not Chatting User',
}), }),
Factory.build('user', {
id: 'second-chatting-user',
name: 'Second Chatting User',
}),
Factory.build('user', {
id: 'third-chatting-user',
name: 'Third Chatting User',
}),
]) ])
}) })
@ -260,4 +268,178 @@ describe('Room', () => {
}) })
}) })
}) })
describe('query several rooms', () => {
beforeAll(async () => {
authenticatedUser = await chattingUser.toJson()
await mutate({
mutation: createRoomMutation(),
variables: {
userId: 'second-chatting-user',
},
})
await mutate({
mutation: createRoomMutation(),
variables: {
userId: 'third-chatting-user',
},
})
})
it('returns the rooms paginated', async () => {
expect(await query({ query: roomQuery(), variables: { first: 2, offset: 0 } })).toMatchObject(
{
errors: undefined,
data: {
Room: [
{
id: expect.any(String),
roomId: expect.any(String),
roomName: 'Third Chatting User',
users: expect.arrayContaining([
{
_id: 'chatting-user',
id: 'chatting-user',
name: 'Chatting User',
avatar: {
url: expect.any(String),
},
},
{
_id: 'third-chatting-user',
id: 'third-chatting-user',
name: 'Third Chatting User',
avatar: {
url: expect.any(String),
},
},
]),
},
{
id: expect.any(String),
roomId: expect.any(String),
roomName: 'Second Chatting User',
users: expect.arrayContaining([
{
_id: 'chatting-user',
id: 'chatting-user',
name: 'Chatting User',
avatar: {
url: expect.any(String),
},
},
{
_id: 'second-chatting-user',
id: 'second-chatting-user',
name: 'Second Chatting User',
avatar: {
url: expect.any(String),
},
},
]),
},
],
},
},
)
expect(await query({ query: roomQuery(), variables: { first: 2, offset: 2 } })).toMatchObject(
{
errors: undefined,
data: {
Room: [
{
id: expect.any(String),
roomId: expect.any(String),
roomName: 'Other Chatting User',
users: expect.arrayContaining([
{
_id: 'chatting-user',
id: 'chatting-user',
name: 'Chatting User',
avatar: {
url: expect.any(String),
},
},
{
_id: 'other-chatting-user',
id: 'other-chatting-user',
name: 'Other Chatting User',
avatar: {
url: expect.any(String),
},
},
]),
},
],
},
},
)
})
})
describe('query single room', () => {
let result: any = null
beforeAll(async () => {
authenticatedUser = await chattingUser.toJson()
result = await query({ query: roomQuery() })
})
describe('as chatter of room', () => {
it('returns the room', async () => {
expect(
await query({
query: roomQuery(),
variables: { first: 2, offset: 0, id: result.data.Room[0].id },
}),
).toMatchObject({
errors: undefined,
data: {
Room: [
{
id: expect.any(String),
roomId: expect.any(String),
roomName: 'Third Chatting User',
users: expect.arrayContaining([
{
_id: 'chatting-user',
id: 'chatting-user',
name: 'Chatting User',
avatar: {
url: expect.any(String),
},
},
{
_id: 'third-chatting-user',
id: 'third-chatting-user',
name: 'Third Chatting User',
avatar: {
url: expect.any(String),
},
},
]),
},
],
},
})
})
describe('as not chatter of room', () => {
beforeAll(async () => {
authenticatedUser = await notChattingUser.toJson()
})
it('returns no room', async () => {
authenticatedUser = await notChattingUser.toJson()
expect(
await query({
query: roomQuery(),
variables: { first: 2, offset: 0, id: result.data.Room[0].id },
}),
).toMatchObject({
errors: undefined,
data: {
Room: [],
},
})
})
})
})
})
}) })

View File

@ -5,6 +5,11 @@
# users_some: _UserFilter # users_some: _UserFilter
# } # }
# TODO change this to last message date
enum _RoomOrdering {
createdAt_desc
}
type Room { type Room {
id: ID! id: ID!
createdAt: String createdAt: String
@ -24,5 +29,8 @@ type Mutation {
} }
type Query { type Query {
Room: [Room] Room(
id: ID
orderBy: [_RoomOrdering]
): [Room]
} }

View File

@ -13,13 +13,15 @@
:messages-loaded="messagesLoaded" :messages-loaded="messagesLoaded"
:rooms="JSON.stringify(rooms)" :rooms="JSON.stringify(rooms)"
:room-actions="JSON.stringify(roomActions)" :room-actions="JSON.stringify(roomActions)"
:rooms-loaded="true" :rooms-loaded="roomsLoaded"
:loading-rooms="loadingRooms"
show-files="false" show-files="false"
show-audio="false" show-audio="false"
:styles="JSON.stringify(computedChatStyle)" :styles="JSON.stringify(computedChatStyle)"
:show-footer="true" :show-footer="true"
@send-message="sendMessage($event.detail[0])" @send-message="sendMessage($event.detail[0])"
@fetch-messages="fetchMessages($event.detail[0])" @fetch-messages="fetchMessages($event.detail[0])"
@fetch-more-rooms="fetchRooms"
:responsive-breakpoint="responsiveBreakpoint" :responsive-breakpoint="responsiveBreakpoint"
:single-room="singleRoom" :single-room="singleRoom"
show-reaction-emojis="false" show-reaction-emojis="false"
@ -143,17 +145,20 @@ export default {
{ name: 'deleteRoom', title: 'Delete Room' }, { name: 'deleteRoom', title: 'Delete Room' },
*/ */
], ],
rooms: [],
messages: [],
messagesLoaded: true,
showDemoOptions: true, showDemoOptions: true,
responsiveBreakpoint: 600, responsiveBreakpoint: 600,
rooms: [],
roomsLoaded: false,
roomPage: 0,
roomPageSize: 10, // TODO pagination is a problem with single rooms - cant use
singleRoom: !!this.singleRoomId || false, singleRoom: !!this.singleRoomId || false,
selectedRoom: null,
loadingRooms: true,
messagesLoaded: false,
messagePage: 0, messagePage: 0,
messagePageSize: 20, messagePageSize: 20,
roomPage: 0, messages: [],
roomPageSize: 999, // TODO pagination is a problem with single rooms - cant use
selectedRoom: null,
} }
}, },
mounted() { mounted() {
@ -165,8 +170,8 @@ export default {
userId: this.singleRoomId, userId: this.singleRoomId,
}, },
}) })
.then(() => { .then(({ data: { CreateRoom } }) => {
this.$apollo.queries.Rooms.refetch() this.fetchRooms({ room: CreateRoom })
}) })
.catch((error) => { .catch((error) => {
this.$toast.error(error) this.$toast.error(error)
@ -174,6 +179,8 @@ export default {
.finally(() => { .finally(() => {
// this.loading = false // this.loading = false
}) })
} else {
this.fetchRooms()
} }
}, },
computed: { computed: {
@ -181,12 +188,49 @@ export default {
currentUser: 'auth/user', currentUser: 'auth/user',
}), }),
computedChatStyle() { computedChatStyle() {
// TODO light/dark theme still needed?
// return this.theme === 'light' ? chatStyle.STYLE.light : chatStyle.STYLE.dark
return chatStyle.STYLE.light return chatStyle.STYLE.light
}, },
}, },
methods: { methods: {
async fetchRooms({ room } = {}) {
this.roomsLoaded = false
const offset = this.roomPage * this.roomPageSize
try {
const {
data: { Room },
} = await this.$apollo.query({
query: roomQuery(),
variables: {
id: room?.id,
first: this.roomPageSize,
offset,
},
fetchPolicy: 'no-cache',
})
const newRooms = Room.map((r) => {
return {
...r,
users: r.users.map((u) => {
return { ...u, username: u.name, avatar: u.avatar?.url }
}),
}
})
this.rooms = [...this.rooms, ...newRooms]
if (Room.length < this.roomPageSize) {
this.roomsLoaded = true
}
this.roomPage += 1
} catch (error) {
this.rooms = []
this.$toast.error(error.message)
}
// must be set false after initial rooms are loaded and never changed again
this.loadingRooms = false
},
async fetchMessages({ room, options = {} }) { async fetchMessages({ room, options = {} }) {
if (this.selectedRoom?.id !== room.id) { if (this.selectedRoom?.id !== room.id) {
this.messages = [] this.messages = []
@ -224,13 +268,6 @@ export default {
} }
}, },
refetchMessage(roomId) {
this.fetchMessages({
room: this.rooms.find((r) => r.roomId === roomId),
options: { refetch: true },
})
},
async sendMessage(message) { async sendMessage(message) {
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
@ -243,7 +280,10 @@ export default {
} catch (error) { } catch (error) {
this.$toast.error(error.message) this.$toast.error(error.message)
} }
this.refetchMessage(message.roomId) this.fetchMessages({
room: this.rooms.find((r) => r.roomId === message.roomId),
options: { refetch: true },
})
}, },
getInitialsName(fullname) { getInitialsName(fullname) {
@ -251,45 +291,6 @@ export default {
return fullname.match(/\b\w/g).join('').substring(0, 3).toUpperCase() return fullname.match(/\b\w/g).join('').substring(0, 3).toUpperCase()
}, },
}, },
apollo: {
Rooms: {
query() {
return roomQuery()
},
variables() {
return {
first: this.roomPageSize,
offset: this.roomPage * this.roomPageSize,
}
},
update({ Room }) {
if (!Room) {
this.rooms = []
return
}
// Backend result needs mapping of the following values
// room[i].users[j].name -> room[i].users[j].username
// room[i].users[j].avatar.url -> room[i].users[j].avatar
// also filter rooms for the single room
this.rooms = Room.map((r) => {
return {
...r,
users: r.users.map((u) => {
return { ...u, username: u.name, avatar: u.avatar?.url }
}),
}
}).filter((r) =>
this.singleRoom ? r.users.filter((u) => u.id === this.singleRoomId).length > 0 : true,
)
},
error(error) {
this.rooms = []
this.$toast.error(error.message)
},
fetchPolicy: 'no-cache',
},
},
} }
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,8 +1,8 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const roomQuery = () => gql` export const roomQuery = () => gql`
query Room($first: Int, $offset: Int) { query Room($first: Int, $offset: Int, $id: ID) {
Room(first: $first, offset: $offset) { Room(first: $first, offset: $offset, id: $id, orderBy: createdAt_desc) {
id id
roomId roomId
roomName roomName