diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index dfb77517a23..ca5b7a827ce 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -254,7 +254,7 @@ * isFavorite: bool, * lastActivity: int, * lastCommonReadMessage: int, - * lastMessage: TalkRoomLastMessage|array, + * lastMessage?: TalkRoomLastMessage, * lastPing: int, * lastReadMessage: int, * listable: int, diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php index e9311c2c768..22fcff9852d 100644 --- a/lib/Service/RoomFormatter.php +++ b/lib/Service/RoomFormatter.php @@ -118,7 +118,6 @@ public function formatRoomV4( 'lobbyTimer' => 0, 'lastPing' => 0, 'sessionId' => '0', - 'lastMessage' => [], 'sipEnabled' => Webinary::SIP_DISABLED, 'actorType' => '', 'actorId' => '', @@ -376,15 +375,17 @@ public function formatRoomV4( } } - $roomData['lastMessage'] = []; $lastMessage = $room->getLastMessage(); if (!$room->isFederatedConversation() && $lastMessage instanceof IComment) { - $roomData['lastMessage'] = $this->formatLastMessage( + $lastMessageData = $this->formatLastMessage( $responseFormat, $room, $currentParticipant, $lastMessage, ); + if ($lastMessageData !== null) { + $roomData['lastMessage'] = $lastMessageData; + } } elseif ($room->isFederatedConversation()) { $roomData['lastCommonReadMessage'] = 0; try { @@ -414,25 +415,25 @@ public function formatRoomV4( } /** - * @return TalkRoomLastMessage|array + * @return TalkRoomLastMessage|null */ public function formatLastMessage( string $responseFormat, Room $room, Participant $participant, IComment $lastMessage, - ): array { + ): ?array { $message = $this->messageParser->createMessage($room, $participant, $lastMessage, $this->l10n); $this->messageParser->parseMessage($message); if (!$message->getVisibility()) { - return []; + return null; } $now = $this->timeFactory->getDateTime(); $expireDate = $message->getComment()->getExpireDate(); if ($expireDate instanceof \DateTime && $expireDate < $now) { - return []; + return null; } return $message->toArray($responseFormat); diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index dbd4f998202..570e6a185dc 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -553,7 +553,6 @@ "isFavorite", "lastActivity", "lastCommonReadMessage", - "lastMessage", "lastPing", "lastReadMessage", "listable", @@ -676,15 +675,7 @@ "format": "int64" }, "lastMessage": { - "anyOf": [ - { - "$ref": "#/components/schemas/RoomLastMessage" - }, - { - "type": "array", - "maxItems": 0 - } - ] + "$ref": "#/components/schemas/RoomLastMessage" }, "lastPing": { "type": "integer", diff --git a/openapi-federation.json b/openapi-federation.json index fefaa268686..24d43cfc44a 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -607,7 +607,6 @@ "isFavorite", "lastActivity", "lastCommonReadMessage", - "lastMessage", "lastPing", "lastReadMessage", "listable", @@ -730,15 +729,7 @@ "format": "int64" }, "lastMessage": { - "anyOf": [ - { - "$ref": "#/components/schemas/RoomLastMessage" - }, - { - "type": "array", - "maxItems": 0 - } - ] + "$ref": "#/components/schemas/RoomLastMessage" }, "lastPing": { "type": "integer", diff --git a/openapi-full.json b/openapi-full.json index 9ae8fdf22de..985cdd01c2f 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -1184,7 +1184,6 @@ "isFavorite", "lastActivity", "lastCommonReadMessage", - "lastMessage", "lastPing", "lastReadMessage", "listable", @@ -1307,15 +1306,7 @@ "format": "int64" }, "lastMessage": { - "anyOf": [ - { - "$ref": "#/components/schemas/RoomLastMessage" - }, - { - "type": "array", - "maxItems": 0 - } - ] + "$ref": "#/components/schemas/RoomLastMessage" }, "lastPing": { "type": "integer", diff --git a/openapi.json b/openapi.json index a3fbf7df4e0..59fd16106e1 100644 --- a/openapi.json +++ b/openapi.json @@ -1071,7 +1071,6 @@ "isFavorite", "lastActivity", "lastCommonReadMessage", - "lastMessage", "lastPing", "lastReadMessage", "listable", @@ -1194,15 +1193,7 @@ "format": "int64" }, "lastMessage": { - "anyOf": [ - { - "$ref": "#/components/schemas/RoomLastMessage" - }, - { - "type": "array", - "maxItems": 0 - } - ] + "$ref": "#/components/schemas/RoomLastMessage" }, "lastPing": { "type": "integer", diff --git a/src/components/LeftSidebar/ConversationsList/Conversation.spec.js b/src/components/LeftSidebar/ConversationsList/Conversation.spec.js index 4ba6b58c529..ce09fa78a88 100644 --- a/src/components/LeftSidebar/ConversationsList/Conversation.spec.js +++ b/src/components/LeftSidebar/ConversationsList/Conversation.spec.js @@ -137,7 +137,7 @@ describe('Conversation.vue', () => { }) test('displays nothing when there is no last chat message', () => { - item.lastMessage = {} + delete item.lastMessage testConversationLabel(item, 'No messages') }) diff --git a/src/components/LeftSidebar/ConversationsList/Conversation.vue b/src/components/LeftSidebar/ConversationsList/Conversation.vue index 67b1a95a5d0..2c5ecb2f3d9 100644 --- a/src/components/LeftSidebar/ConversationsList/Conversation.vue +++ b/src/components/LeftSidebar/ConversationsList/Conversation.vue @@ -284,7 +284,6 @@ export default { type: 0, displayName: '', isFavorite: false, - lastMessage: {}, notificationLevel: PARTICIPANT.NOTIFY.DEFAULT, notificationCalls: PARTICIPANT.NOTIFY_CALLS.ON, canDeleteConversation: false, diff --git a/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue b/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue index 788c6c70286..41aee3fd9d3 100644 --- a/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue +++ b/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue @@ -54,7 +54,6 @@ export default { displayName: '', isFavorite: false, notificationLevel: 0, - lastMessage: {}, } }, }, diff --git a/src/components/LeftSidebar/LeftSidebar.spec.js b/src/components/LeftSidebar/LeftSidebar.spec.js index 4f28d1b59ca..4c5dfacf264 100644 --- a/src/components/LeftSidebar/LeftSidebar.spec.js +++ b/src/components/LeftSidebar/LeftSidebar.spec.js @@ -225,7 +225,6 @@ describe('LeftSidebar.vue', () => { isFavorite: false, name: 'one', displayName: 'the searched one by display name', - lastMessage: {}, }, { id: 200, token: 't200', @@ -233,7 +232,6 @@ describe('LeftSidebar.vue', () => { isFavorite: false, name: 'searched by name', displayName: 'another one', - lastMessage: {}, }, { id: 300, token: 't300', @@ -241,20 +239,17 @@ describe('LeftSidebar.vue', () => { isFavorite: true, name: 'excluded', displayName: 'excluded from results', - lastMessage: {}, }] listedResults = [{ id: 1000, name: 'listed one searched', displayName: 'listed one searched', - lastMessage: {}, token: 'listed-token-1', }, { id: 1001, name: 'listed two searched', displayName: 'listed two searched', - lastMessage: {}, token: 'listed-token-2', }] usersResults = [{ diff --git a/src/composables/useConversationInfo.js b/src/composables/useConversationInfo.js index 8e6404e8363..f94280ce429 100644 --- a/src/composables/useConversationInfo.js +++ b/src/composables/useConversationInfo.js @@ -43,7 +43,7 @@ export function useConversationInfo({ }) const hasLastMessage = computed(() => { - return !!Object.keys(Object(item.value?.lastMessage)).length + return !!item.value?.lastMessage && !!Object.keys(Object(item.value?.lastMessage)).length }) /** diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js index 2a16c8465d5..fa83bb010a4 100644 --- a/src/store/conversationsStore.js +++ b/src/store/conversationsStore.js @@ -317,6 +317,13 @@ const actions = { updateConversationIfHasChanged(context, conversation) { const oldConversation = context.state.conversations[conversation.token] + // Update conversation if the number of attributes differ + // (e.g. lastMessage was added or removed, because we don't keep lastMessage as an empty object) + if (Object.keys(oldConversation).length !== Object.keys(conversation).length) { + context.commit('updateConversation', conversation) + return true + } + // Update 1-1 conversation, if its status was changed if (conversation.type === CONVERSATION.TYPE.ONE_TO_ONE && (oldConversation.status !== conversation.status @@ -336,7 +343,7 @@ const actions = { return true } - // Check if any property were changed (no properties except status-related supposed to be added or deleted) + // Check if any property were changed (no properties except status-related and lastMessage supposed to be added or deleted) for (const key of Object.keys(conversation)) { // "lastMessage" is the only property with non-primitive (object) value and cannot be compared by === // If "lastMessage" was actually changed, it is already checked by "lastActivity" @@ -760,8 +767,8 @@ const actions = { } const conversation = Object.assign({}, getters.conversations[token]) - if (conversation.lastMessage.id === parseInt(messageId, 10) - || conversation.lastMessage.timestamp >= Date.parse(notification.datetime) / 1000) { + if (conversation.lastMessage?.id === parseInt(messageId, 10) + || conversation.lastMessage?.timestamp >= Date.parse(notification.datetime) / 1000) { // Already updated from other source, skipping return } diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index 9eb625a6276..b4b34c8c344 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -151,7 +151,7 @@ const getters = { return false } - return getters.getLastKnownMessageId(token) < conversation.lastMessage.id + return getters.getLastKnownMessageId(token) < conversation.lastMessage?.id }, isMessagesListPopulated: (state) => (token) => { @@ -552,7 +552,7 @@ const actions = { if (message.systemMessage === 'message_edited' || message.systemMessage === 'message_deleted') { // update conversation lastMessage, if it was edited or deleted - if (message.parent.id === context.getters.conversation(token).lastMessage.id) { + if (message.parent.id === context.getters.conversation(token).lastMessage?.id) { context.dispatch('updateConversationLastMessage', { token, lastMessage: message.parent }) } // Check existing messages for having a deleted / edited message as parent, and update them @@ -1118,7 +1118,7 @@ const actions = { // Overwrite the conversation.hasCall property so people can join // after seeing the message in the chat. - if (conversation && conversation.lastMessage && message.id > conversation.lastMessage.id) { + if (conversation?.lastMessage && message.id > conversation.lastMessage.id) { if (['call_started', 'call_ended', 'call_ended_everyone', 'call_missed'].includes(message.systemMessage)) { context.dispatch('overwriteHasCallByChat', { token, @@ -1150,7 +1150,7 @@ const actions = { id: parseInt(response.headers['x-chat-last-given'], 10), }) - if (conversation && conversation.lastMessage && lastMessage.id > conversation.lastMessage.id) { + if (conversation?.lastMessage && lastMessage.id > conversation.lastMessage.id) { context.dispatch('updateConversationLastMessage', { token, lastMessage, @@ -1237,7 +1237,7 @@ const actions = { // update lastMessage and lastReadMessage // do it conditionally because there could have been more messages appearing concurrently - if (conversation && conversation.lastMessage && message.id > conversation.lastMessage.id) { + if (conversation?.lastMessage && message.id > conversation.lastMessage.id) { context.dispatch('updateConversationLastMessage', { token, lastMessage: message, diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts index 6afa31e75bd..4189947bcc0 100644 --- a/src/types/openapi/openapi-backend-sipbridge.ts +++ b/src/types/openapi/openapi-backend-sipbridge.ts @@ -291,7 +291,7 @@ export type components = { lastActivity: number; /** Format: int64 */ lastCommonReadMessage: number; - lastMessage: components["schemas"]["RoomLastMessage"] | unknown[]; + lastMessage?: components["schemas"]["RoomLastMessage"]; /** Format: int64 */ lastPing: number; /** Format: int64 */ diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index 27053a8ec6d..04ae262c943 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -338,7 +338,7 @@ export type components = { lastActivity: number; /** Format: int64 */ lastCommonReadMessage: number; - lastMessage: components["schemas"]["RoomLastMessage"] | unknown[]; + lastMessage?: components["schemas"]["RoomLastMessage"]; /** Format: int64 */ lastPing: number; /** Format: int64 */ diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 3cedf764829..a675fd8d073 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -2253,7 +2253,7 @@ export type components = { lastActivity: number; /** Format: int64 */ lastCommonReadMessage: number; - lastMessage: components["schemas"]["RoomLastMessage"] | unknown[]; + lastMessage?: components["schemas"]["RoomLastMessage"]; /** Format: int64 */ lastPing: number; /** Format: int64 */ diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index d1943905894..a17fd174c6a 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1734,7 +1734,7 @@ export type components = { lastActivity: number; /** Format: int64 */ lastCommonReadMessage: number; - lastMessage: components["schemas"]["RoomLastMessage"] | unknown[]; + lastMessage?: components["schemas"]["RoomLastMessage"]; /** Format: int64 */ lastPing: number; /** Format: int64 */ diff --git a/src/utils/getMessageIcon.ts b/src/utils/getMessageIcon.ts index dcee379afb0..80112093388 100644 --- a/src/utils/getMessageIcon.ts +++ b/src/utils/getMessageIcon.ts @@ -22,10 +22,10 @@ const iconSvgTemplate = (path: string) => { } export const getMessageIcon = (lastMessage: Conversation['lastMessage']): string => { - if (Array.isArray(lastMessage)) { + if (!lastMessage || Array.isArray(lastMessage)) { return '' } - const file = lastMessage?.messageParameters?.file + const file = lastMessage.messageParameters?.file if (file) { if (file.mimetype?.startsWith('video')) { return iconSvgTemplate(mdiMovie) // Media - Videos diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index ad88f6c3269..26db9d526fe 100644 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -545,7 +545,11 @@ private function assertRooms(array $rooms, TableNode $formData, bool $shouldOrde $data['attendeePin'] = $room['attendeePin'] ? '**PIN**' : ''; } if (isset($expectedRoom['lastMessage'])) { - $data['lastMessage'] = $room['lastMessage'] ? $room['lastMessage']['message'] : ''; + if (isset($room['lastMessage'])) { + $data['lastMessage'] = $room['lastMessage'] ? $room['lastMessage']['message'] : ''; + } else { + $data['lastMessage'] = 'UNSET'; + } } if (isset($expectedRoom['lastMessageActorType'])) { $data['lastMessageActorType'] = $room['lastMessage'] ? $room['lastMessage']['actorType'] : ''; diff --git a/tests/integration/features/conversation-3/lobby.feature b/tests/integration/features/conversation-3/lobby.feature index 8ea69ed0955..38d74f41456 100644 --- a/tests/integration/features/conversation-3/lobby.feature +++ b/tests/integration/features/conversation-3/lobby.feature @@ -250,16 +250,16 @@ Feature: conversation/lobby | room | the description | 3 | 2 | {actor} set the description | And user "participant3" is participant of room "room" (v4) | name | description | type | participantType | lastMessage | - | room | the description | 3 | 3 | | + | room | the description | 3 | 3 | UNSET | And user "participant4" is participant of room "room" (v4) | name | description | type | participantType | lastMessage | - | room | the description | 3 | 5 | | + | room | the description | 3 | 5 | UNSET | And user "guest" is participant of room "room" (v4) | name | description | type | participantType | lastMessage | | room | the description | 3 | 6 | {actor} set the description | And user "guest2" is participant of room "room" (v4) | name | description | type | participantType | lastMessage | - | room | the description | 3 | 4 | | + | room | the description | 3 | 4 | UNSET | # Not all the values are checked in the test, only the most relevant ones