[FIX] mail: opening chat with deleted new message should mark as read

Before this commit, when a user of in chat posted a message and
deleted it, all other users kept the chat as unread.

Steps to reproduce:
- Connect as Admin and March Demo
- Send a message to Admin as Demo in DM chat
- Demo deletes this message
- Marc opens the chat
=> the unread counter is 1 and cannot be removed

This happens because when a message is deleted, there's still a
trace of it but the message is empty. However, empty messages could
not be candidate of setting the last message being seing by a member,
thus members were unable to mark the chat as read until someone else
posted a newer message (and did not delete it).

This commit fixes the issue by taking empty messages into account for
setting last message message of member, which allow to mark thread as
read even when newer messages have been deleted.

opw-3764410

closes odoo/odoo#158943

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Alexandre Kühn
2024-03-25 11:43:40 +00:00
parent 751fe5282c
commit 0aae1ae147
3 changed files with 56 additions and 6 deletions
@@ -456,13 +456,25 @@ export class Thread extends Record {
return [...this.messages].reverse().find((msg) => Number.isInteger(msg.id));
}
newestPersistentNotEmptyOfAllMessage = Record.one("Message", {
newestPersistentAllMessages = Record.many("Message", {
compute() {
const allPersistentMessages = this.allMessages.filter(
(message) => Number.isInteger(message.id) && !message.isEmpty
const allPersistentMessages = this.allMessages.filter((message) =>
Number.isInteger(message.id)
);
allPersistentMessages.sort((m1, m2) => m2.id - m1.id);
return allPersistentMessages[0];
return allPersistentMessages;
},
});
newestPersistentOfAllMessage = Record.one("Message", {
compute() {
return this.newestPersistentAllMessages[0];
},
});
newestPersistentNotEmptyOfAllMessage = Record.one("Message", {
compute() {
return this.newestPersistentAllMessages.find((message) => !message.isEmpty);
},
});
@@ -100,7 +100,7 @@ export class ThreadService {
* @param {import("models").Thread} thread
*/
markAsRead(thread) {
const newestPersistentMessage = thread.newestPersistentNotEmptyOfAllMessage;
const newestPersistentMessage = thread.newestPersistentOfAllMessage;
if (!newestPersistentMessage && !thread.isLoaded) {
thread.isLoadedDeferred
.then(() => new Promise(setTimeout))
@@ -132,7 +132,7 @@ export class ThreadService {
}
}
updateSeen(thread, lastSeenId = thread.newestPersistentNotEmptyOfAllMessage?.id) {
updateSeen(thread, lastSeenId = thread.newestPersistentOfAllMessage?.id) {
const lastReadIndex = thread.messages.findIndex((message) => message.id === lastSeenId);
let newNeedactionCounter = 0;
let newUnreadCounter = 0;
@@ -2056,3 +2056,41 @@ QUnit.test("Newly created chat should be at the top of the direct message list",
before: [".o-mail-DiscussSidebar-item", { text: "Albert" }],
});
});
QUnit.test("Read of unread chat where new message is deleted should mark as read.", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Marc Demo" });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
const messageId = pyEnv["mail.message"].create({
author_id: partnerId,
body: "Heyo",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", pyEnv.currentPartnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], {
seen_message_id: messageId,
message_unread_counter: 1,
});
const { env, openDiscuss } = await start();
await openDiscuss();
await contains("button", { text: "Marc Demo", contains: [".badge", { text: "1" }] });
// simulate deleted message
await env.services.rpc("/mail/message/update_content", {
message_id: messageId,
body: "",
attachment_ids: [],
});
await click("button", { text: "Marc Demo" });
await contains("button", { text: "Marc Demo", contains: [".badge", { count: 0 }] });
});