[FIX] mail, im_livechat: remove automatic thread pin when opened

Threads are automatically marked as pinned when they are opened but this
behavior is not always desirable. They are now implicitly pinned when it
makes sense to do so.

closes odoo/odoo#146841

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Killian Frappart
2023-12-29 12:27:45 +00:00
committed by Sébastien Theys
parent a42e42ff15
commit 6b78c08783
11 changed files with 101 additions and 65 deletions
@@ -5,7 +5,10 @@ import { discussSidebarCategoriesRegistry } from "@mail/discuss/core/web/discuss
discussSidebarCategoriesRegistry.add(
"livechats",
{
predicate: (store) => store.discuss.livechat.threads.some((thread) => thread?.is_pinned),
predicate: (store) =>
store.discuss.livechat.threads.some(
(thread) => thread.displayToSelf || thread.isLocallyPinned
),
value: (store) => store.discuss.livechat,
},
{ sequence: 20 }
@@ -7,7 +7,7 @@ import { start } from "@mail/../tests/helpers/test_utils";
import { url } from "@web/core/utils/urls";
import { nextTick } from "@web/../tests/helpers/utils";
import { click, contains } from "@web/../tests/utils";
import { click, contains, insertText } from "@web/../tests/utils";
QUnit.module("discuss sidebar (patch)");
@@ -472,3 +472,35 @@ QUnit.test("Message unread counter", async () => {
);
await contains(".o-mail-DiscussSidebarChannel .badge", { text: "1" });
});
QUnit.test("unknown livechat can be displayed and interacted with", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Jane" });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [[0, 0, { partner_id: partnerId }]],
channel_type: "livechat",
livechat_operator_id: partnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
await contains("button.o-active", { text: "Inbox" });
await contains(".o-mail-DiscussSidebarCategory-livechat", { count: 0 });
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
await openDiscuss(channelId);
await contains(
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel.o-active",
{
text: "Jane",
}
);
await insertText(".o-mail-Composer-input", "Hello", { replace: true });
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message", { text: "Hello" });
await click("button", { text: "Inbox" });
await contains(".o-mail-DiscussSidebarChannel:not(.o-active)", { text: "Jane" });
await click("div[title='Unpin Conversation']", {
parent: [".o-mail-DiscussSidebarChannel", { text: "Jane" }],
});
await contains(".o-mail-DiscussSidebarCategory-livechat", { count: 0 });
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
});
@@ -539,7 +539,7 @@ class Channel(models.Model):
# Last interest and is_pinned are updated for a channel when posting a message.
# So a notification is needed to update UI, and it should come before the
# notification of the message itself to ensure the channel automatically opens.
payload = {"id": self.id, "isServerPinned": True, "last_interest_dt": fields.Datetime.now()}
payload = {"id": self.id, "is_pinned": True, "last_interest_dt": fields.Datetime.now()}
bus_notifications = [
(self, "discuss.channel/last_interest_dt_changed", payload),
(self, "discuss.channel/new_message", {"id": self.id, "message": message_format}),
@@ -279,6 +279,8 @@ export class Thread extends Record {
custom_notifications = false;
/** @type {String} */
mute_until_dt;
/** @type {Boolean} */
isLocallyPinned = false;
get accessRestrictedToGroupText() {
if (!this.authorizedGroupFullName) {
@@ -403,15 +403,15 @@ export class ThreadService {
}
async unpin(thread) {
thread.isLocallyPinned = false;
if (thread.eq(this.store.discuss.thread)) {
this.router.replaceState({ active_id: undefined });
}
if (thread.model !== "discuss.channel") {
return;
if (thread.model === "discuss.channel" && thread.is_pinned) {
return this.orm.silent.call("discuss.channel", "channel_pin", [thread.id], {
pinned: false,
});
}
return this.orm.silent.call("discuss.channel", "channel_pin", [thread.id], {
pinned: false,
});
}
pin(thread) {
@@ -642,6 +642,9 @@ export class ThreadService {
if (pushState) {
this.router.pushState({ active_id: activeId });
}
if (!thread.is_pinned) {
thread.isLocallyPinned = true;
}
}
/**
@@ -53,18 +53,12 @@ export class DiscussClientAction extends Component {
// legacy format (sent in old emails, shared links, ...)
model = "discuss.channel";
}
const activeThread = this.store.Thread.get({ model, id });
if (!activeThread || activeThread.notEq(this.store.discuss.thread)) {
const thread =
this.store.Thread.get({ model, id }) ??
(await this.threadService.fetchChannel(parseInt(id)));
if (!thread) {
return;
}
if (!thread.is_pinned) {
await this.threadService.pin(thread);
}
this.threadService.setDiscussThread(thread);
let activeThread = this.store.Thread.get({ model, id });
if (!activeThread?.type && model === "discuss.channel") {
activeThread = await this.threadService.fetchChannel(parseInt(id));
}
if (activeThread && activeThread.notEq(this.store.discuss.thread)) {
this.threadService.setDiscussThread(activeThread);
}
}
}
@@ -51,11 +51,7 @@ export class DiscussCoreCommon {
}
});
this.busService.subscribe("discuss.channel/last_interest_dt_changed", (payload) => {
const { id, last_interest_dt } = payload;
const channel = this.store.Thread.get({ model: "discuss.channel", id });
if (channel) {
channel.last_interest_dt = last_interest_dt;
}
this.store.Thread.insert({ model: "discuss.channel", ...payload });
});
this.busService.subscribe("discuss.channel/leave", (payload) => {
const thread = this.store.Thread.insert({
@@ -252,9 +248,6 @@ export class DiscussCoreCommon {
return;
}
}
if (!channel.is_pinned) {
this.threadService.pin(channel);
}
this.store.Message.get(messageData.temporary_id)?.delete();
messageData.temporary_id = null;
const message = this.store.Message.insert(messageData, { html: true });
@@ -84,7 +84,7 @@ export class DiscussSidebarCategories extends Component {
filteredThreads(category) {
return category.threads.filter((thread) => {
return (
thread.displayToSelf &&
(thread.displayToSelf || thread.isLocallyPinned) &&
(!this.state.quickSearchVal ||
cleanTerm(thread.name).includes(cleanTerm(this.state.quickSearchVal)))
);
@@ -55,3 +55,33 @@ QUnit.test(
await contains(".o-mail-NavigableList-item", { text: "Create: # test" });
}
);
QUnit.test("unknown channel can be displayed and interacted with", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Jane" });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [[0, 0, { partner_id: partnerId }]],
channel_type: "channel",
name: "Not So Secret",
});
const { openDiscuss } = await start();
await openDiscuss();
await contains("button.o-active", { text: "Inbox" });
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
await openDiscuss(channelId);
await contains(
".o-mail-DiscussSidebarCategory-channel + .o-mail-DiscussSidebarChannel.o-active",
{
text: "Not So Secret",
}
);
await insertText(".o-mail-Composer-input", "Hello", { replace: true });
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message", { text: "Hello" });
await click("button", { text: "Inbox" });
await contains(".o-mail-DiscussSidebarChannel:not(.o-active)", { text: "Not So Secret" });
await click("div[title='Leave this channel']", {
parent: [".o-mail-DiscussSidebarChannel", { text: "Not So Secret" }],
});
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
});
@@ -204,18 +204,13 @@ patch(MockServer.prototype, {
_mockDiscussChannelMessagePost(id, kwargs, context) {
const message_type = kwargs.message_type || "notification";
const channel = this.getRecords("discuss.channel", [["id", "=", id]])[0];
if (channel.channel_type !== "channel") {
const memberOfCurrentUser = this._mockDiscussChannelMember__getAsSudoFromContext(
channel.id
);
if (memberOfCurrentUser) {
this.pyEnv["discuss.channel.member"].write([memberOfCurrentUser.id], {
last_interest_dt: serializeDateTime(today()),
is_pinned: true,
});
}
}
const members = this.pyEnv["discuss.channel.member"].search([
["channel_id", "=", channel.id],
]);
this.pyEnv["discuss.channel.member"].write(members, {
last_interest_dt: serializeDateTime(today()),
is_pinned: true,
});
const messageData = this._mockMailThreadMessagePost(
"discuss.channel",
[id],
@@ -286,34 +286,18 @@ patch(MockServer.prototype, {
const messageFormat = this._mockMailMessageMessageFormat([messageId])[0];
const notifications = [];
if (model === "discuss.channel") {
// members
const channels = this.getRecords("discuss.channel", [["id", "=", message.res_id]]);
for (const channel of channels) {
// notify update of last_interest_dt
const now = serializeDateTime(today());
const members = this.getRecords("discuss.channel.member", [
["id", "in", channel.channel_member_ids],
notifications.push([
channel,
"discuss.channel/last_interest_dt_changed",
{
id: channel.id,
is_pinned: true,
last_interest_dt: now,
},
]);
this.pyEnv["discuss.channel.member"].write(
members.map((member) => member.id),
{ last_interest_dt: now }
);
for (const member of members) {
const target = member.guest_id
? this.pyEnv["mail.guest"].searchRead([["id", "=", member.guest_id]])[0]
: this.pyEnv["res.partner"].searchRead([["id", "=", member.partner_id]], {
context: { active_test: false },
})[0];
notifications.push([
target,
"discuss.channel/last_interest_dt_changed",
{
id: channel.id,
isServerPinned: member.is_pinned,
last_interest_dt: member.last_interest_dt,
},
]);
}
notifications.push([
channel,
"discuss.channel/new_message",