From 5d99de10ec09244e10e4130573ad2e0c38e86009 Mon Sep 17 00:00:00 2001 From: Zelong Lin Date: Thu, 23 Nov 2023 10:26:42 +0100 Subject: [PATCH] [FIX] mail: do not reload chatter when not needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This RPC /mail/thread/data is called on every form view change, even when not saved. It comes from the chatter onWillUpdateProps and calling `load` then `fetchData` We don't want to reload chatter when every time props updates. Do it when the record `onWillLoadRoot` closes odoo/odoo#140662 Task: 3569123 Related: odoo/enterprise#50607 Signed-off-by: Sébastien Theys (seb) --- addons/mail/static/src/core/web/activity.js | 11 +- .../src/core/web/activity_mail_template.js | 13 +- .../core/web/activity_markasdone_popover.js | 6 +- addons/mail/static/src/core/web/chatter.js | 133 +++++++----------- .../mail/static/src/core/web/follower_list.js | 5 +- .../src/core/web/follower_subtype_dialog.js | 3 +- .../src/views/web/form/form_controller.js | 10 ++ 7 files changed, 89 insertions(+), 92 deletions(-) diff --git a/addons/mail/static/src/core/web/activity.js b/addons/mail/static/src/core/web/activity.js index e6c86cd3b04..9cb575c1845 100644 --- a/addons/mail/static/src/core/web/activity.js +++ b/addons/mail/static/src/core/web/activity.js @@ -86,10 +86,11 @@ export class Activity extends Component { } async onFileUploaded(data) { + const thread = this.thread; const { id: attachmentId } = await this.attachmentUploader.uploadData(data); await this.activityService.markAsDone(this.props.data, [attachmentId]); - this.props.onUpdate(); - await this.threadService.fetchNewMessages(this.thread); + this.props.onUpdate(thread); + await this.threadService.fetchNewMessages(thread); } onClickAvatar(ev) { @@ -102,15 +103,17 @@ export class Activity extends Component { } async edit() { + const thread = this.thread; const id = this.props.data.id; await this.env.services["mail.activity"].edit(id); - this.props.onUpdate(); + this.props.onUpdate(thread); } async unlink() { + const thread = this.thread; this.activityService.delete(this.props.data); await this.env.services.orm.unlink("mail.activity", [this.props.data.id]); - this.props.onUpdate(); + this.props.onUpdate(thread); } get thread() { diff --git a/addons/mail/static/src/core/web/activity_mail_template.js b/addons/mail/static/src/core/web/activity_mail_template.js index d0a22b08d96..243630c7678 100644 --- a/addons/mail/static/src/core/web/activity_mail_template.js +++ b/addons/mail/static/src/core/web/activity_mail_template.js @@ -1,7 +1,8 @@ /* @odoo-module */ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; import { _t } from "@web/core/l10n/translation"; /** @@ -19,6 +20,10 @@ export class ActivityMailTemplate extends Component { static props = ["activity", "onClickButtons?", "onUpdate?"]; static template = "mail.ActivityMailTemplate"; + setup() { + this.store = useState(useService("mail.store")); + } + /** * @param {MouseEvent} ev * @param {Object} mailTemplate @@ -54,10 +59,14 @@ export class ActivityMailTemplate extends Component { ev.stopPropagation(); ev.preventDefault(); this.props.onClickButtons(); + const thread = this.store.Thread.get({ + model: this.props.activity.res_model, + id: this.props.activity.res_id, + }); await this.env.services.orm.call(this.props.activity.res_model, "activity_send_mail", [ [this.props.activity.res_id], mailTemplate.id, ]); - this.props.onUpdate(); + this.props.onUpdate(thread); } } diff --git a/addons/mail/static/src/core/web/activity_markasdone_popover.js b/addons/mail/static/src/core/web/activity_markasdone_popover.js index ec23c32c34a..3b3c5ce9bd5 100644 --- a/addons/mail/static/src/core/web/activity_markasdone_popover.js +++ b/addons/mail/static/src/core/web/activity_markasdone_popover.js @@ -35,7 +35,7 @@ export class ActivityMarkAsDone extends Component { const thread = this.threadService.getThread(res_model, res_id); await this.env.services["mail.activity"].markAsDone(this.props.activity); if (this.props.reload) { - this.props.reload(this.props.activity.res_id, ["activities"]); + this.props.reload(thread, ["activities"]); } await this.threadService.fetchNewMessages(thread); } @@ -54,7 +54,7 @@ export class ActivityMarkAsDone extends Component { ); this.threadService.fetchNewMessages(thread); if (this.props.reload) { - this.props.reload(this.props.activity.res_id, ["activities", "attachments"]); + this.props.reload(thread, ["activities", "attachments"]); } if (!action) { return; @@ -65,7 +65,7 @@ export class ActivityMarkAsDone extends Component { }); }); if (this.props.reload) { - this.props.reload(this.props.activity.res_id, ["activities"]); + this.props.reload(thread, ["activities"]); } } } diff --git a/addons/mail/static/src/core/web/chatter.js b/addons/mail/static/src/core/web/chatter.js index 2887774547b..ae673003507 100644 --- a/addons/mail/static/src/core/web/chatter.js +++ b/addons/mail/static/src/core/web/chatter.js @@ -125,7 +125,7 @@ export class Chatter extends Component { } if (isDragSourceExternalFile(ev.dataTransfer)) { const files = [...ev.dataTransfer.files]; - if (!this.props.threadId) { + if (!this.state.thread.id) { const saved = await this.props.saveRecord?.(); if (!saved) { return; @@ -138,36 +138,20 @@ export class Chatter extends Component { "o-mail-Chatter-dropzone" ); - onMounted(async () => { - if (this.props.threadId) { - this.state.thread = this.store.Thread.insert({ - id: this.props.threadId, - model: this.props.threadModel, - name: this.props.webRecord?.data?.display_name || undefined, - }); - } - await this.load(this.props.threadId, [ - "followers", - "attachments", - "suggestedRecipients", - ]); + onMounted(() => { + this.changeThread(this.props.threadModel, this.props.threadId, this.props.webRecord); + this.load(this.state.thread, ["followers", "attachments", "suggestedRecipients"]); }); onWillUpdateProps((nextProps) => { - this.load(nextProps.threadId, ["followers", "attachments", "suggestedRecipients"]); - if (nextProps.threadId === false) { - this.state.composerType = false; + if ( + this.props.threadId !== nextProps.threadId || + this.props.threadModel !== nextProps.threadModel + ) { + this.changeThread(nextProps.threadModel, nextProps.threadId, nextProps.webRecord); } - this.attachmentUploader.thread = this.threadService.getThread( - nextProps.threadModel, - nextProps.threadId - ); - if (this.onNextUpdate) { - if (!this.onNextUpdate(nextProps)) { - this.onNextUpdate = null; - } - } - if (nextProps.threadId) { - this.closeSearch(); + if (this.env.chatter?.fetchOnWillUpdateProps) { + this.env.chatter.fetchOnWillUpdateProps = false; + this.load(this.state.thread, ["followers", "attachments", "suggestedRecipients"]); } }); useEffect( @@ -223,7 +207,7 @@ export class Chatter extends Component { * @returns {boolean} */ get isDisabled() { - return !this.props.threadId || !this.state.thread?.hasReadAccess; + return !this.state.thread.id || !this.state.thread?.hasReadAccess; } get attachments() { @@ -252,56 +236,60 @@ export class Chatter extends Component { return markup(formatter.format(recipients)); } + changeThread(threadModel, threadId, webRecord) { + this.state.thread = this.threadService.getThread(threadModel, threadId); + this.state.thread.name = webRecord?.data?.display_name || undefined; + this.attachmentUploader.thread = this.state.thread; + if (threadId === false) { + this.state.composerType = false; + } else { + this.onThreadCreated?.(this.state.thread); + this.onThreadCreated = null; + this.closeSearch(); + } + } + /** - * @param {number} threadId + * Fetch data for the thread according to the request list. + * @param {import("models").Thread} thread * @param {['activities'|'followers'|'attachments'|'messages'|'suggestedRecipients']} requestList */ - load( - threadId = this.props.threadId, - requestList = ["followers", "attachments", "messages", "suggestedRecipients"] - ) { - const { threadModel } = this.props; - this.state.thread = this.threadService.getThread(threadModel, threadId); - if (!threadId) { + load(thread, requestList = ["followers", "attachments", "messages", "suggestedRecipients"]) { + if (!thread.id || !this.state.thread?.eq(thread)) { return; } if (this.props.hasActivities && !requestList.includes("activities")) { requestList.push("activities"); } - this.threadService.fetchData(this.state.thread, requestList); + this.threadService.fetchData(thread, requestList); } - async _follow(threadModel, threadId) { - await this.orm.call(threadModel, "message_subscribe", [[threadId]], { + async _follow(thread) { + await this.orm.call(thread.model, "message_subscribe", [[thread.id]], { partner_ids: [this.store.self.id], }); - this.onFollowerChanged(); + this.onFollowerChanged(thread); } async onClickFollow() { - if (this.props.threadId) { - this._follow(this.props.threadModel, this.props.threadId); + if (this.state.thread.id) { + this._follow(this.state.thread); } else { - this.onNextUpdate = (nextProps) => { - if (nextProps.threadId) { - this._follow(nextProps.threadModel, nextProps.threadId); - } else { - return true; - } - }; + this.onThreadCreated = this._follow; await this.props.saveRecord?.(); } } async onClickUnfollow() { - await this.threadService.removeFollower(this.state.thread.selfFollower); - this.onFollowerChanged(); + const thread = this.state.thread; + await this.threadService.removeFollower(thread.selfFollower); + this.onFollowerChanged(thread); } - onFollowerChanged() { + onFollowerChanged(thread) { document.body.click(); // hack to close dropdown this.reloadParentView(); - this.load(this.props.threadId, ["followers", "suggestedRecipients"]); + this.load(thread, ["followers", "suggestedRecipients"]); } onPostCallback() { @@ -311,11 +299,11 @@ export class Chatter extends Component { this.toggleComposer(); this.state.jumpThreadPresent++; // Load new messages to fetch potential new messages from other users (useful due to lack of auto-sync in chatter). - this.load(this.props.threadId, ["followers", "messages", "suggestedRecipients"]); + this.load(this.state.thread, ["followers", "messages", "suggestedRecipients"]); } onAddFollowers() { - this.load(this.state.thread.id, ["followers", "suggestedRecipients"]); + this.load(this.state.thread, ["followers", "suggestedRecipients"]); if (this.props.hasParentReloadOnFollowersUpdate) { this.reloadParentView(); } @@ -337,19 +325,10 @@ export class Chatter extends Component { this.state.composerType = mode; } }; - if (this.props.threadId) { + if (this.state.thread.id) { toggle(); } else { - this.onNextUpdate = (nextProps) => { - // @returns {boolean} retry on next update - // if there is no threadId, the save operation probably failed - // probably because some required field is not set - if (nextProps.threadId) { - toggle(); - } else { - return true; - } - }; + this.onThreadCreated = toggle; this.props.saveRecord?.(); } } @@ -360,20 +339,14 @@ export class Chatter extends Component { async scheduleActivity() { this.closeSearch(); - const schedule = async (threadId) => { - await this.activityService.schedule(this.props.threadModel, [threadId]); - this.load(this.props.threadId, ["activities", "messages"]); + const schedule = async (thread) => { + await this.activityService.schedule(thread.model, [thread.id]); + this.load(thread, ["activities", "messages"]); }; - if (this.props.threadId) { - schedule(this.props.threadId); + if (this.state.thread.id) { + schedule(this.state.thread); } else { - this.onNextUpdate = (nextProps) => { - if (nextProps.threadId) { - schedule(nextProps.threadId); - } else { - return true; - } - }; + this.onThreadCreated = schedule; this.props.saveRecord?.(); } } @@ -420,7 +393,7 @@ export class Chatter extends Component { } async onClickAttachFile(ev) { - if (this.props.threadId) { + if (this.state.thread.id) { return; } const saved = await this.props.saveRecord?.(); diff --git a/addons/mail/static/src/core/web/follower_list.js b/addons/mail/static/src/core/web/follower_list.js index 708610ca8fb..30a1836ffa8 100644 --- a/addons/mail/static/src/core/web/follower_list.js +++ b/addons/mail/static/src/core/web/follower_list.js @@ -65,7 +65,7 @@ export class FollowerList extends Component { async onClickEdit(ev, follower) { this.env.services.dialog.add(FollowerSubtypeDialog, { follower, - onFollowerChanged: () => this.props.onFollowerChanged?.(), + onFollowerChanged: () => this.props.onFollowerChanged?.(this.props.thread), }); document.body.click(); // hack to close dropdown } @@ -75,7 +75,8 @@ export class FollowerList extends Component { * @param {import("models").Follower} follower */ async onClickRemove(ev, follower) { + const thread = this.props.thread; await this.threadService.removeFollower(follower); - this.props.onFollowerChanged?.(); + this.props.onFollowerChanged?.(thread); } } diff --git a/addons/mail/static/src/core/web/follower_subtype_dialog.js b/addons/mail/static/src/core/web/follower_subtype_dialog.js index dd1430ff2a3..7aa9e196271 100644 --- a/addons/mail/static/src/core/web/follower_subtype_dialog.js +++ b/addons/mail/static/src/core/web/follower_subtype_dialog.js @@ -49,6 +49,7 @@ export class FollowerSubtypeDialog extends Component { async onClickApply() { const selectedSubtypes = this.state.subtypes.filter((s) => s.followed); + const thread = this.props.follower.followedThread; if (selectedSubtypes.length === 0) { await this.env.services["mail.thread"].removeFollower(this.props.follower); } else { @@ -69,7 +70,7 @@ export class FollowerSubtypeDialog extends Component { { type: "success" } ); } - this.props.onFollowerChanged(); + this.props.onFollowerChanged(thread); this.props.close(); } diff --git a/addons/mail/static/src/views/web/form/form_controller.js b/addons/mail/static/src/views/web/form/form_controller.js index 549759aef6c..c43d0c395c2 100644 --- a/addons/mail/static/src/views/web/form/form_controller.js +++ b/addons/mail/static/src/views/web/form/form_controller.js @@ -3,10 +3,20 @@ import { patch } from "@web/core/utils/patch"; import { FormController } from "@web/views/form/form_controller"; import { x2ManyCommands } from "@web/core/orm_service"; +import { useSubEnv } from "@odoo/owl"; patch(FormController.prototype, { + setup() { + super.setup(...arguments); + useSubEnv({ + chatter: { + fetchOnWillUpdateProps: false, + }, + }); + }, onWillLoadRoot(nextConfiguration) { super.onWillLoadRoot(...arguments); + this.env.chatter.fetchOnWillUpdateProps = true; const isSameThread = this.model.root?.resId === nextConfiguration.resId && this.model.root?.resModel === nextConfiguration.resModel;