[FIX] mail: only display followers with mail.mt_comment activated
Before this commit, recipient list in chatter when using composer in "Send message" mode where listing emails of all followers, instead of only subscribers of "Send message" (= `mail.mt_comment`). This commit solves this issue by loading and showing followers that are recipients in this list. Because this feature and follower list lazy-load some data, they need to manage they own list for the "load-more" aspect. That's why the diff shows some duplicated code as with `followers` field of JS thread model. closes odoo/odoo#134225 X-original-commit: ba43502b22f8f00e7a2318b341302b809e7129dc Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -3966,12 +3966,18 @@ class MailThread(models.AbstractModel):
|
||||
|
||||
return True
|
||||
|
||||
def message_get_followers(self, after=None, limit=100):
|
||||
def message_get_followers(self, after=None, limit=100, filter_recipients=False):
|
||||
self.ensure_one()
|
||||
domain = [
|
||||
("res_id", "=", self.id),
|
||||
("res_model", "=", self._name),
|
||||
]
|
||||
if filter_recipients:
|
||||
subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
|
||||
domain = expression.AND([domain, [
|
||||
('subtype_ids', '=', subtype_id),
|
||||
('partner_id', '!=', self.env.user.partner_id.id),
|
||||
]])
|
||||
if after:
|
||||
domain = expression.AND([domain, [('id', '>', after)]])
|
||||
return self.env["mail.followers"].search(domain, limit=limit, order='id ASC')._format_for_chatter()
|
||||
@@ -4145,6 +4151,14 @@ class MailThread(models.AbstractModel):
|
||||
])._format_for_chatter()
|
||||
res['selfFollower'] = self_follower[0] if len(self_follower) > 0 else None
|
||||
res['followers'] = self.message_get_followers()
|
||||
subtype_id = self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment')
|
||||
res['recipientsCount'] = self.env['mail.followers'].search_count([
|
||||
("res_id", "=", self.id),
|
||||
("res_model", "=", self._name),
|
||||
('partner_id', '!=', self.env.user.partner_id.id),
|
||||
('subtype_ids', '=', subtype_id),
|
||||
])
|
||||
res['recipients'] = self.message_get_followers(filter_recipients=True)
|
||||
if 'suggestedRecipients' in request_list:
|
||||
res['suggestedRecipients'] = self._message_get_suggested_recipients()[self.id]
|
||||
return res
|
||||
|
||||
@@ -258,6 +258,7 @@ class Users(models.Model):
|
||||
'initBusId': self.env['bus.bus'].sudo()._bus_last_id(),
|
||||
'internalUserGroupId': self.env.ref('base.group_user').id,
|
||||
'menu_id': self.env['ir.model.data']._xmlid_to_res_id('mail.menu_root_discuss'),
|
||||
'mt_comment_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
|
||||
'needaction_inbox_counter': self.partner_id._get_needaction_count(),
|
||||
'odoobot': odoobot.sudo().mail_partner_format().get(odoobot),
|
||||
'shortcodes': self.env['mail.shortcode'].sudo().search_read([], ['source', 'substitution']),
|
||||
|
||||
@@ -87,6 +87,7 @@ export class Messaging {
|
||||
this.store.discuss.inbox.counter = data.needaction_inbox_counter;
|
||||
this.store.internalUserGroupId = data.internalUserGroupId;
|
||||
this.store.discuss.starred.counter = data.starred_counter;
|
||||
this.store.mt_comment_id = data.mt_comment_id;
|
||||
this.store.discuss.isActive =
|
||||
data.menu_id === this.router.current.hash?.menu_id ||
|
||||
this.router.hash?.action === "mail.action_discuss";
|
||||
|
||||
@@ -76,6 +76,13 @@ export class Thread extends Record {
|
||||
return thread;
|
||||
}
|
||||
|
||||
setup() {}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.setup();
|
||||
}
|
||||
|
||||
/** @type {number} */
|
||||
id;
|
||||
/** @type {string} */
|
||||
|
||||
@@ -30,7 +30,7 @@ import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { usePopover } from "@web/core/popover/popover_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { escape } from "@web/core/utils/strings";
|
||||
import { escapeHTML } from "@web/core/utils/strings";
|
||||
import { useThrottleForAnimation } from "@web/core/utils/timing";
|
||||
import { FileUploader } from "@web/views/fields/file_handler";
|
||||
|
||||
@@ -220,17 +220,9 @@ export class Chatter extends Component {
|
||||
* @returns {string}
|
||||
*/
|
||||
get toRecipientsText() {
|
||||
const allFollowers = [];
|
||||
if (this.state.thread.selfFollower) {
|
||||
allFollowers.push(this.state.thread.selfFollower);
|
||||
}
|
||||
allFollowers.push(...this.state.thread.followers);
|
||||
const followers = allFollowers.slice(0, 5).map(({ partner }) => {
|
||||
if (partner.eq(this.store.self)) {
|
||||
return `<span class="text-muted" title="${escape(partner.email)}">me</span>`;
|
||||
}
|
||||
const recipients = [...this.state.thread.recipients].slice(0, 5).map(({ partner }) => {
|
||||
const text = partner.email ? partner.emailWithoutDomain : partner.name;
|
||||
return `<span class="text-muted" title="${escape(partner.email)}">${escape(
|
||||
return `<span class="text-muted" title="${escapeHTML(partner.email)}">${escapeHTML(
|
||||
text
|
||||
)}</span>`;
|
||||
});
|
||||
@@ -238,10 +230,10 @@ export class Chatter extends Component {
|
||||
this.store.env.services["user"].lang?.replace("_", "-"),
|
||||
{ type: "unit" }
|
||||
);
|
||||
if (allFollowers.length > 5) {
|
||||
followers.push("…");
|
||||
if (this.state.thread.recipients.size > 5) {
|
||||
recipients.push("…");
|
||||
}
|
||||
return markup(formatter.format(followers));
|
||||
return markup(formatter.format(recipients));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ export class FollowerSubtypeDialog extends Component {
|
||||
|
||||
setup() {
|
||||
this.rpc = useService("rpc");
|
||||
this.store = useState(useService("mail.store"));
|
||||
this.state = useState({
|
||||
/** @type {SubtypeData[]} */
|
||||
subtypes: [],
|
||||
@@ -60,6 +61,9 @@ export class FollowerSubtypeDialog extends Component {
|
||||
subtype_ids: selectedSubtypes.map((subtype) => subtype.id),
|
||||
}
|
||||
);
|
||||
if (!selectedSubtypes.some((subtype) => subtype.id === this.store.mt_comment_id)) {
|
||||
this.env.services["mail.thread"].removeRecipient(this.props.follower);
|
||||
}
|
||||
this.env.services.notification.add(
|
||||
_t("The subscription preferences were successfully applied."),
|
||||
{ type: "success" }
|
||||
|
||||
@@ -18,7 +18,7 @@ export class RecipientList extends Component {
|
||||
this.threadService = useState(useService("mail.thread"));
|
||||
this.loadMoreState = useVisible("load-more", () => {
|
||||
if (this.loadMoreState.isVisible) {
|
||||
this.threadService.loadMoreFollowers(this.props.thread);
|
||||
this.threadService.loadMoreRecipients(this.props.thread);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
<t t-name="mail.RecipientList">
|
||||
<div class="o-mail-RecipientList p-2 overflow-auto">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li t-if="props.thread.selfFollower" t-out="props.thread.selfFollower.partner.email"/>
|
||||
<li t-foreach="props.thread.followers" t-as="follower" t-key="follower.id">
|
||||
<t t-out="follower.partner.email"/>
|
||||
<li t-foreach="props.thread.recipients" t-as="recipient" t-key="recipient.id">
|
||||
<t t-out="recipient.partner.email"/>
|
||||
</li>
|
||||
<span t-if="!props.thread.followersFullyLoaded" class="btn btn-link w-100" t-on-click="() => threadService.loadMoreFollowers(props.thread)" t-ref="load-more">Load more</span>
|
||||
<span t-if="!props.thread.recipientsFullyLoaded" class="btn btn-link w-100" t-on-click="() => threadService.loadMoreRecipients(props.thread)" t-ref="load-more">Load more</span>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -5,6 +5,17 @@ import { Thread } from "@mail/core/common/thread_model";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Thread.prototype, {
|
||||
/** @type {integer|undefined} */
|
||||
recipientsCount: undefined,
|
||||
/** @type {Number} */
|
||||
mt_comment_id: undefined,
|
||||
recipients: undefined,
|
||||
setup() {
|
||||
this.recipients = new Set();
|
||||
},
|
||||
get recipientsFullyLoaded() {
|
||||
return this.recipientsCount === this.recipients.size;
|
||||
},
|
||||
/**
|
||||
* @returns {import("@mail/core/web/activity_model").Activity[]}
|
||||
*/
|
||||
|
||||
@@ -94,6 +94,15 @@ patch(ThreadService.prototype, {
|
||||
thread.followers.add(follower);
|
||||
}
|
||||
}
|
||||
thread.recipientsCount = result.recipientsCount;
|
||||
for (const recipientData of result.recipients) {
|
||||
thread.recipients.add(
|
||||
this.store.Follower.insert({
|
||||
followedThread: thread,
|
||||
...recipientData,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
if ("suggestedRecipients" in result) {
|
||||
this.insertSuggestedRecipients(thread, result.suggestedRecipients);
|
||||
@@ -180,6 +189,22 @@ patch(ThreadService.prototype, {
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadMoreRecipients(thread) {
|
||||
const recipients = await this.orm.call(
|
||||
thread.model,
|
||||
"message_get_followers",
|
||||
[[thread.id], Array.from(thread.recipients).at(-1).id],
|
||||
{ filter_recipients: true }
|
||||
);
|
||||
for (const data of recipients) {
|
||||
thread.recipients.add(
|
||||
this.store.Follower.insert({
|
||||
followedThread: thread,
|
||||
...data,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
open(thread, replaceNewMessageChatWindow) {
|
||||
if (!this.store.discuss.isActive && !this.ui.isSmall) {
|
||||
this._openChatWindow(thread, replaceNewMessageChatWindow);
|
||||
@@ -200,6 +225,12 @@ patch(ThreadService.prototype, {
|
||||
}
|
||||
super.open(thread, replaceNewMessageChatWindow);
|
||||
},
|
||||
/**
|
||||
* @param {import("@mail/core/common/follower_model").Follower} follower
|
||||
*/
|
||||
removeRecipient(recipient) {
|
||||
recipient.followedThread.recipients.delete(recipient);
|
||||
},
|
||||
/**
|
||||
* @param {import("@mail/core/common/follower_model").Follower} follower
|
||||
*/
|
||||
@@ -214,6 +245,7 @@ patch(ThreadService.prototype, {
|
||||
} else {
|
||||
thread.followers.delete(follower);
|
||||
}
|
||||
this.removeRecipient(follower);
|
||||
follower.delete();
|
||||
},
|
||||
unpin(thread) {
|
||||
|
||||
@@ -753,22 +753,6 @@ QUnit.test("remove an uploading attachment", async () => {
|
||||
await contains(".o-mail-Composer .o-mail-AttachmentCard", { count: 0 });
|
||||
});
|
||||
|
||||
QUnit.test("Show a thread name in the recipient status text.", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "test name", email: "test@odoo.com" });
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
const { openFormView } = await start();
|
||||
openFormView("res.partner", partnerId);
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Chatter div", { text: "To: test" });
|
||||
await contains('span[title="test@odoo.com"]');
|
||||
});
|
||||
|
||||
QUnit.test("Show recipient list when there is more than 5 followers.", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerIds = pyEnv["res.partner"].create([
|
||||
|
||||
@@ -560,6 +560,14 @@ patch(MockServer.prototype, {
|
||||
? this._mockMailFollowers_FormatForChatter(selfFollower.id)[0]
|
||||
: false;
|
||||
res["followers"] = this._mockMailThreadMessageGetFollowers(thread_model, [thread_id]);
|
||||
res["recipientsCount"] = (thread.message_follower_ids || []).length - 1;
|
||||
res["recipients"] = this._mockMailThreadMessageGetFollowers(
|
||||
thread_model,
|
||||
[thread_id],
|
||||
undefined,
|
||||
100,
|
||||
{ filter_recipients: true }
|
||||
);
|
||||
}
|
||||
if (request_list.includes("suggestedRecipients")) {
|
||||
res["suggestedRecipients"] = this._mockMailThread_MessageGetSuggestedRecipients(
|
||||
|
||||
@@ -123,7 +123,7 @@ patch(MockServer.prototype, {
|
||||
* @param {integer} [limit=100]
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_mockMailThreadMessageGetFollowers(model, ids, after, limit = 100) {
|
||||
_mockMailThreadMessageGetFollowers(model, ids, after, limit = 100, kwargs = {}) {
|
||||
const domain = [
|
||||
["res_id", "=", ids[0]],
|
||||
["res_model", "=", model],
|
||||
@@ -131,6 +131,9 @@ patch(MockServer.prototype, {
|
||||
if (after) {
|
||||
domain.push(["id", ">", after]);
|
||||
}
|
||||
if (kwargs.filter_recipients) {
|
||||
domain.push(["partner_id", "!=", this.pyEnv.currentPartnerId]);
|
||||
}
|
||||
const followers = this.getRecords("mail.followers", domain).sort(
|
||||
(f1, f2) => (f1.id < f2.id ? -1 : 1) // sorted from lowest ID to highest ID (i.e. from oldest to youngest)
|
||||
);
|
||||
|
||||
@@ -253,11 +253,11 @@ QUnit.test("Load 100 recipients at once", async () => {
|
||||
);
|
||||
const { openFormView } = await start();
|
||||
await openFormView("res.partner", partnerIds[0]);
|
||||
await contains("button[title='Show Followers']", { text: "210" });
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Chatter div", {
|
||||
text: "To: me, partner1, partner2, partner3, partner4, …",
|
||||
});
|
||||
await contains("button[title='Show Followers']:contains(210)");
|
||||
await click("button:contains(Send message)");
|
||||
await contains(
|
||||
".o-mail-Chatter:contains('partner1, partner2, partner3, partner4, partner5, …')"
|
||||
);
|
||||
await contains("button[title='Show all recipients']");
|
||||
await click("button[title='Show all recipients']");
|
||||
await contains(".o-mail-RecipientList li", { count: 100 });
|
||||
@@ -266,7 +266,7 @@ QUnit.test("Load 100 recipients at once", async () => {
|
||||
await contains(".o-mail-RecipientList li", { count: 200 });
|
||||
await new Promise(setTimeout); // give enough time for the useVisible hook to register load more as hidden
|
||||
await scroll(".o-mail-RecipientList", "bottom");
|
||||
await contains(".o-mail-RecipientList li", { count: 210 });
|
||||
await contains(".o-mail-RecipientList li", { count: 209 });
|
||||
await contains(".o-mail-RecipientList span", { count: 0, text: "Load more" });
|
||||
});
|
||||
|
||||
|
||||
@@ -1008,6 +1008,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
],
|
||||
'internalUserGroupId': self.env.ref('base.group_user').id,
|
||||
'menu_id': self.env['ir.model.data']._xmlid_to_res_id('mail.menu_root_discuss'),
|
||||
'mt_comment_id': self.env['ir.model.data']._xmlid_to_res_id('mail.mt_comment'),
|
||||
'odoobot': {
|
||||
'active': False,
|
||||
'email': 'odoobot@example.com',
|
||||
|
||||
Reference in New Issue
Block a user