[FIX] mail: do not reload chatter when not needed

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) <seb@odoo.com>
This commit is contained in:
Zelong Lin
2023-11-23 17:50:37 +00:00
parent 56e37a6f55
commit 5d99de10ec
7 changed files with 89 additions and 92 deletions
+7 -4
View File
@@ -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() {
@@ -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);
}
}
@@ -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"]);
}
}
}
+53 -80
View File
@@ -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?.();
@@ -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);
}
}
@@ -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();
}
@@ -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;