diff --git a/backend/src/graphql/messages.ts b/backend/src/graphql/messages.ts index e042fc600..865f0c8cf 100644 --- a/backend/src/graphql/messages.ts +++ b/backend/src/graphql/messages.ts @@ -20,10 +20,11 @@ export const createMessageMutation = () => { export const messageQuery = () => { return gql` - query ($roomId: ID!) { - Message(roomId: $roomId) { + query ($roomId: ID!, $first: Int, $offset: Int) { + Message(roomId: $roomId, first: $first, offset: $offset, orderBy: createdAt_desc) { _id id + indexId content senderId username diff --git a/backend/src/schema/resolvers/messages.spec.ts b/backend/src/schema/resolvers/messages.spec.ts index 68b84ad69..628034be5 100644 --- a/backend/src/schema/resolvers/messages.spec.ts +++ b/backend/src/schema/resolvers/messages.spec.ts @@ -280,6 +280,7 @@ describe('Message', () => { { id: expect.any(String), _id: result.data.Message[0].id, + indexId: 0, content: 'Some nice message to other chatting user', senderId: 'chatting-user', username: 'Chatting User', @@ -324,9 +325,10 @@ describe('Message', () => { ).resolves.toMatchObject({ errors: undefined, data: { - Message: expect.arrayContaining([ + Message: [ expect.objectContaining({ id: expect.any(String), + indexId: 0, content: 'Some nice message to other chatting user', senderId: 'chatting-user', username: 'Chatting User', @@ -338,6 +340,7 @@ describe('Message', () => { }), expect.objectContaining({ id: expect.any(String), + indexId: 1, content: 'A nice response message to chatting user', senderId: 'other-chatting-user', username: 'Other Chatting User', @@ -349,6 +352,7 @@ describe('Message', () => { }), expect.objectContaining({ id: expect.any(String), + indexId: 2, content: 'And another nice message to other chatting user', senderId: 'chatting-user', username: 'Chatting User', @@ -358,7 +362,70 @@ describe('Message', () => { distributed: false, seen: false, }), - ]), + ], + }, + }) + }) + + it('returns the messages paginated', async () => { + await expect( + query({ + query: messageQuery(), + variables: { + roomId, + first: 2, + offset: 0, + }, + }), + ).resolves.toMatchObject({ + errors: undefined, + data: { + Message: [ + expect.objectContaining({ + id: expect.any(String), + indexId: 1, + content: 'A nice response message to chatting user', + senderId: 'other-chatting-user', + username: 'Other Chatting User', + avatar: expect.any(String), + date: expect.any(String), + }), + expect.objectContaining({ + id: expect.any(String), + indexId: 2, + content: 'And another nice message to other chatting user', + senderId: 'chatting-user', + username: 'Chatting User', + avatar: expect.any(String), + date: expect.any(String), + }), + ], + }, + }) + + await expect( + query({ + query: messageQuery(), + variables: { + roomId, + first: 2, + offset: 2, + }, + }), + ).resolves.toMatchObject({ + errors: undefined, + data: { + Message: [ + expect.objectContaining({ + id: expect.any(String), + indexId: 0, + content: 'Some nice message to other chatting user', + senderId: 'chatting-user', + username: 'Chatting User', + avatar: expect.any(String), + date: expect.any(String), + }), + ], }, }) }) diff --git a/backend/src/schema/resolvers/messages.ts b/backend/src/schema/resolvers/messages.ts index fa80a7b3f..984d17cc2 100644 --- a/backend/src/schema/resolvers/messages.ts +++ b/backend/src/schema/resolvers/messages.ts @@ -13,6 +13,7 @@ export default { id: context.user.id, }, } + const resolved = await neo4jgraphql(object, params, context, resolveInfo) if (resolved) { @@ -41,7 +42,7 @@ export default { // send subscription to author to updated the messages } } - return resolved + return resolved.reverse() }, }, Mutation: { @@ -55,9 +56,12 @@ export default { const createMessageCypher = ` MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId }) OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image) + OPTIONAL MATCH (m:Message)-[:INSIDE]->(room) + WITH MAX(m.indexId) as maxIndex, room, currentUser, image CREATE (currentUser)-[:CREATED]->(message:Message { createdAt: toString(datetime()), id: apoc.create.uuid(), + indexId: CASE WHEN maxIndex IS NOT NULL THEN maxIndex + 1 ELSE 0 END, content: $content, saved: true, distributed: false, diff --git a/backend/src/schema/types/type/Message.gql b/backend/src/schema/types/type/Message.gql index 8b9263336..671c5523a 100644 --- a/backend/src/schema/types/type/Message.gql +++ b/backend/src/schema/types/type/Message.gql @@ -2,8 +2,14 @@ # room: _RoomFilter # } +enum _MessageOrdering { + createdAt_asc + createdAt_desc +} + type Message { id: ID! + indexId: Int! createdAt: String updatedAt: String @@ -32,5 +38,10 @@ type Mutation { } type Query { - Message(roomId: ID!): [Message] + Message( + roomId: ID!, + first: Int + offset: Int + orderBy: [_MessageOrdering] + ): [Message] } diff --git a/webapp/components/Chat/Chat.vue b/webapp/components/Chat/Chat.vue index cca6c4319..95bf5da95 100644 --- a/webapp/components/Chat/Chat.vue +++ b/webapp/components/Chat/Chat.vue @@ -8,6 +8,7 @@ :template-actions="JSON.stringify(templatesText)" :menu-actions="JSON.stringify(menuActions)" :text-messages="JSON.stringify(textMessages)" + :message-actions="messageActions" :messages="JSON.stringify(messages)" :messages-loaded="messagesLoaded" :rooms="JSON.stringify(rooms)" @@ -21,6 +22,7 @@ @fetch-messages="fetchMessages($event.detail[0])" :responsive-breakpoint="responsiveBreakpoint" :single-room="singleRoom" + show-reaction-emojis="false" @show-demo-options="showDemoOptions = $event" >
@@ -91,9 +93,11 @@ export default { { name: 'deleteRoom', title: 'Delete Room', - }, */ + }, + */ ], messageActions: [ + /* { name: 'addMessageToFavorite', title: 'Add To Favorite', @@ -102,6 +106,7 @@ export default { name: 'shareMessage', title: 'Share Message', }, + */ ], templatesText: [ { @@ -144,6 +149,10 @@ export default { showDemoOptions: true, responsiveBreakpoint: 600, singleRoom: !!this.singleRoomId || false, + messagePage: 0, + messagePageSize: 20, + roomPage: 0, + roomPageSize: 999, // TODO pagination is a problem with single rooms - cant use selectedRoom: null, } }, @@ -178,32 +187,48 @@ export default { }, }, methods: { - fetchMessages({ room, options = {} }) { - this.messagesLoaded = false - setTimeout(async () => { - try { - const { - data: { Message }, - } = await this.$apollo.query({ - query: messageQuery(), - variables: { - roomId: room.id, - }, - fetchPolicy: 'no-cache', - }) - this.messages = Message - } catch (error) { - this.messages = [] - this.$toast.error(error.message) - } - this.messagesLoaded = true + async fetchMessages({ room, options = {} }) { + if (this.selectedRoom !== room.id) { + this.messages = [] + this.messagePage = 0 + this.selectedRoom = room.id + } + this.messagesLoaded = options.refetch ? this.messagesLoaded : false + const offset = (options.refetch ? 0 : this.messagePage) * this.messagePageSize + try { + const { + data: { Message }, + } = await this.$apollo.query({ + query: messageQuery(), + variables: { + roomId: room.id, + first: this.messagePageSize, + offset, + }, + fetchPolicy: 'no-cache', + }) - this.selectedRoom = room - }) + const msgs = [] + ;[...this.messages, ...Message].forEach((m) => { + msgs[m.indexId] = m + }) + this.messages = msgs.filter(Boolean) + + if (Message.length < this.messagePageSize) { + this.messagesLoaded = true + } + this.messagePage += 1 + } catch (error) { + this.messages = [] + this.$toast.error(error.message) + } }, refetchMessage(roomId) { - this.fetchMessages({ room: this.rooms.find((r) => r.roomId === roomId) }) + this.fetchMessages({ + room: this.rooms.find((r) => r.roomId === roomId), + options: { refetch: true }, + }) }, async sendMessage(message) { @@ -231,6 +256,12 @@ export default { query() { return roomQuery() }, + variables() { + return { + first: this.roomPageSize, + offset: this.roomPage * this.roomPageSize, + } + }, update({ Room }) { if (!Room) { this.rooms = [] diff --git a/webapp/graphql/Messages.js b/webapp/graphql/Messages.js index 41d647d4b..d017f816c 100644 --- a/webapp/graphql/Messages.js +++ b/webapp/graphql/Messages.js @@ -2,10 +2,11 @@ import gql from 'graphql-tag' export const messageQuery = () => { return gql` - query ($roomId: ID!) { - Message(roomId: $roomId) { + query ($roomId: ID!, $first: Int, $offset: Int) { + Message(roomId: $roomId, first: $first, offset: $offset, orderBy: createdAt_desc) { _id id + indexId senderId content author { diff --git a/webapp/graphql/Rooms.js b/webapp/graphql/Rooms.js index 7bab25509..e28702f77 100644 --- a/webapp/graphql/Rooms.js +++ b/webapp/graphql/Rooms.js @@ -1,8 +1,8 @@ import gql from 'graphql-tag' export const roomQuery = () => gql` - query { - Room { + query Room($first: Int, $offset: Int) { + Room(first: $first, offset: $offset) { id roomId roomName