[FIX] mail: fix message unread counter

This PR fixes several issues with the unread counter:
- Notifications being considered as unread messages (which is not
the case on the server side which resulted in inconsistencies).
- Counter greater than it should be when the last seen message is
not loaded.
- Messages not marked as read on first thread opening.

task-3239957

closes odoo/odoo#116208

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2023-03-24 06:45:56 +01:00
parent c69bf9ac1d
commit a6c7739191
4 changed files with 50 additions and 5 deletions
@@ -5,6 +5,7 @@ import { sprintf } from "@web/core/utils/strings";
import { ScrollPosition } from "@mail/core/scroll_position_model";
import { createLocalId } from "../utils/misc";
import { Deferred } from "@web/core/utils/concurrency";
/**
* @typedef SeenInfo
@@ -55,6 +56,7 @@ export class Thread {
isAdmin = false;
loadMore = false;
isLoadingAttachments = false;
isLoadedDeferred = new Deferred();
isLoaded = false;
/** @type {import("@mail/attachments/attachment_model").Attachment} */
mainAttachment;
@@ -87,6 +89,8 @@ export class Thread {
canPostOnReadonly;
/** @type {String} */
last_interest_dt;
/** @type {number} */
lastServerMessageId;
constructor(store, data) {
Object.assign(this, {
+11 -5
View File
@@ -101,7 +101,10 @@ export class ThreadService {
/**
* @param {Thread} thread
*/
markAsRead(thread) {
async markAsRead(thread) {
if (!thread.isLoaded && thread.status === "loading") {
await thread.isLoadedDeferred;
}
const mostRecentNonTransientMessage = thread.mostRecentNonTransientMessage;
if (
this.isUnread(thread) &&
@@ -486,6 +489,7 @@ export class ThreadService {
"last_interest_dt",
"defaultDisplayMode",
]);
thread.lastServerMessageId = serverData.last_message_id ?? thread.lastServerMessageId;
if (thread.model === "mail.channel" && serverData.channel) {
thread.channel = assignDefined(thread.channel ?? {}, serverData.channel);
}
@@ -614,6 +618,7 @@ export class ThreadService {
return thread;
}
let thread = new Thread(this.store, data);
onChange(thread, "isLoaded", () => thread.isLoadedDeferred.resolve());
onChange(thread, "channelMembers", () => this.store.updateBusSubscription());
onChange(thread, "is_pinned", () => {
if (!thread.is_pinned && this.store.discuss.threadLocalId === thread.localId) {
@@ -672,7 +677,8 @@ export class ThreadService {
.map((recipient) => recipient.persona.id);
partner_ids?.push(...recipientIds);
}
const tmpId = `${thread.localId}_${new Date().valueOf()}`;
const lastMessageId = this.messageService.getLastMessageId();
const tmpId = lastMessageId + 0.01;
const params = {
post_data: {
body: await prettifyMessageContent(body, validMentions),
@@ -794,12 +800,12 @@ export class ThreadService {
*/
localMessageUnreadCounter(thread) {
let baseCounter = thread.serverMessageUnreadCounter;
let countFromId = thread.serverLastSeenMsgBySelf ? thread.serverLastSeenMsgBySelf : 0;
let countFromId = thread.lastServerMessageId ? thread.lastServerMessageId : 0;
const lastSeenMessageId = this.lastSeenBySelfMessageId(thread);
const firstMessage = thread.messages[0];
if (firstMessage && lastSeenMessageId && lastSeenMessageId >= firstMessage.id) {
if (firstMessage && (lastSeenMessageId === false || lastSeenMessageId >= firstMessage.id)) {
baseCounter = 0;
countFromId = lastSeenMessageId;
countFromId = lastSeenMessageId || 0;
}
return thread.messages.reduce((total, message) => {
if (message.id <= countFromId || message.temporary_id) {
@@ -334,6 +334,9 @@ patch(MockServer.prototype, "mail/models/mail_message", {
["is_read", "=", false],
["mail_message_id", "in", messages.map((messages) => messages.id)],
]);
if (notifications.length === 0) {
return;
}
this.pyEnv["mail.notification"].write(
notifications.map((notification) => notification.id),
{ is_read: true }
@@ -269,6 +269,7 @@ QUnit.test(
assert.verifySteps(["rpc:channel_fetch"]);
$(".o-mail-Composer-input")[0].focus();
await waitUntil(".o-mail-Message");
assert.verifySteps(["rpc:set_last_seen_message"]);
}
);
@@ -951,3 +952,34 @@ QUnit.test(
assert.verifySteps([]);
}
);
QUnit.test("can be marked as read while loading", async function (assert) {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["mail.channel"].create({
channel_member_ids: [
[0, 0, { message_unread_counter: 1, partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: partnerId }],
],
channel_type: "chat",
});
pyEnv["mail.message"].create({
author_id: partnerId,
body: "<p>Test</p>",
model: "mail.channel",
res_id: channelId,
});
const loadDeferred = makeDeferred();
const { openDiscuss } = await start({
async mockRPC(route) {
if (route === "/mail/channel/messages") {
await loadDeferred;
}
},
});
await openDiscuss(undefined, { waitUntilMessagesLoaded: false });
assert.containsOnce($, ".o-mail-DiscussCategoryItem-counter:contains(1)");
await click(".o-mail-DiscussCategoryItem:contains(Demo)");
await afterNextRender(loadDeferred.resolve);
assert.containsNone($, ".o-mail-DiscussCategoryItem-counter");
});