[REF] mail: move imp of service.insert() to Model.insert()

In preparation to clean code of models, e.g. by simplifying model
insertion and reducing side-effects in record insertion.

This commit moves the implementation of the `insert()` in discuss
services to the models, and drop support of the service methods
`insert()`. The only way to insert a model is through the store
entry, e.g. `store.Thread.insert()`.

closes odoo/odoo#133509

Related: odoo/enterprise#46499
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2023-08-30 23:45:41 +00:00
parent 3d39b57f60
commit 13b852d2e2
35 changed files with 509 additions and 526 deletions
@@ -0,0 +1,15 @@
/** @odoo-module */
import { Activity } from "@mail/core/web/activity_model";
import { patch } from "@web/core/utils/patch";
patch(Activity, {
insert(data) {
const activity = super.insert(...arguments);
const { calendar_event_id: calendarEventId } = data;
if (calendarEventId) {
activity["calendar_event_id"] = calendarEventId;
}
return activity;
},
});
@@ -4,15 +4,6 @@ import { ActivityService } from "@mail/core/web/activity_service";
import { patch } from "@web/core/utils/patch";
patch(ActivityService.prototype, {
insert(data) {
const activity = super.insert(...arguments);
const { calendar_event_id: calendarEventId } = data;
if (calendarEventId) {
activity["calendar_event_id"] = calendarEventId;
}
return activity;
},
async rescheduleMeeting(activityId) {
const action = await this.orm.call("mail.activity", "action_create_calendar_event", [
[activityId],
@@ -2,9 +2,34 @@
import { DEFAULT_AVATAR } from "@mail/core/common/persona_service";
import { Thread } from "@mail/core/common/thread_model";
import { assignDefined, createLocalId } from "@mail/utils/common/misc";
import { patch } from "@web/core/utils/patch";
patch(Thread, {
insert(data) {
const isUnknown = !(createLocalId(data.model, data.id) in this.records);
const thread = super.insert(data);
if (thread.type === "livechat") {
if (data?.channel) {
assignDefined(thread, data.channel, ["anonymous_name"]);
}
if (data?.operator_pid) {
thread.operator = this.store.Persona.insert({
type: "partner",
id: data.operator_pid[0],
displayName: data.operator_pid[1],
});
}
if (isUnknown) {
this.store.discuss.livechat.threads.push(thread.localId);
this.env.services["mail.thread"].sortChannels();
}
}
return thread;
},
});
patch(Thread.prototype, {
get isChannel() {
return this.type === "livechat" || super.isChannel;
@@ -2,32 +2,10 @@
import { ThreadService } from "@mail/core/common/thread_service";
import { removeFromArray } from "@mail/utils/common/arrays";
import { assignDefined, createLocalId } from "@mail/utils/common/misc";
import { patch } from "@web/core/utils/patch";
patch(ThreadService.prototype, {
insert(data) {
const isUnknown = !(createLocalId(data.model, data.id) in this.store.Thread.records);
const thread = super.insert(data);
if (thread.type === "livechat") {
if (data?.channel) {
assignDefined(thread, data.channel, ["anonymous_name"]);
}
if (data?.operator_pid) {
thread.operator = this.store.Persona.insert({
type: "partner",
id: data.operator_pid[0],
displayName: data.operator_pid[1],
});
}
if (isUnknown) {
this.store.discuss.livechat.threads.push(thread.localId);
this.sortChannels();
}
}
return thread;
},
/**
* @override
* @param {import("@mail/core/common/thread_model").Thread} thread
@@ -1,10 +1,22 @@
/* @odoo-module */
import { ChatbotStep } from "@im_livechat/embed/chatbot/chatbot_step_model";
import { Message } from "@mail/core/common/message_model";
import { patch } from "@web/core/utils/patch";
import { session } from "@web/session";
patch(Message, {
insert(data) {
const message = super.insert(data);
if (data.chatbotStep) {
message.chatbotStep = new ChatbotStep(data.chatbotStep);
}
return message;
},
});
patch(Message.prototype, {
get isSelfAuthored() {
if (this.originThread.type !== "livechat") {
@@ -1,17 +0,0 @@
/* @odoo-module */
import { ChatbotStep } from "@im_livechat/embed/chatbot/chatbot_step_model";
import { MessageService } from "@mail/core/common/message_service";
import { patch } from "@web/core/utils/patch";
patch(MessageService.prototype, {
insert(data) {
const message = super.insert(data);
if (data.chatbotStep) {
message.chatbotStep = new ChatbotStep(data.chatbotStep);
}
return message;
},
});
@@ -1,9 +1,63 @@
/* @odoo-module */
import { Thread } from "@mail/core/common/thread_model";
import { createLocalId, onChange } from "@mail/utils/common/misc";
import { patch } from "@web/core/utils/patch";
import { session } from "@web/session";
import { SESSION_STATE } from "./livechat_service";
patch(Thread, {
insert(data) {
const isUnknown = !(createLocalId(data.model, data.id) in this.records);
const thread = super.insert(...arguments);
const livechatService = this.env.services["im_livechat.livechat"];
const chatbotService = this.env.services["im_livechat.chatbot"];
const messageService = this.env.services["mail.message"];
if (thread.type === "livechat" && isUnknown) {
if (livechatService.displayWelcomeMessage && !chatbotService.isChatbotThread(thread)) {
livechatService.welcomeMessage = this.store.Message.insert({
id: messageService.getNextTemporaryId(),
body: livechatService.options.default_message,
res_id: thread.id,
model: thread.model,
author: thread.operator,
});
}
if (chatbotService.isChatbotThread(thread)) {
chatbotService.typingMessage = this.store.Message.insert({
id: messageService.getNextTemporaryId(),
res_id: thread.id,
model: thread.model,
author: thread.operator,
});
}
onChange(thread, "state", () => {
if (![SESSION_STATE.CLOSED, SESSION_STATE.NONE].includes(livechatService.state)) {
livechatService.updateSession({
state: thread.state,
});
}
});
onChange(thread, "seen_message_id", () => {
if (![SESSION_STATE.CLOSED, SESSION_STATE.NONE].includes(livechatService.state)) {
livechatService.updateSession({
seen_message_id: thread.seen_message_id,
});
}
});
onChange(thread, "message_unread_counter", () => {
if (![SESSION_STATE.CLOSED, SESSION_STATE.NONE].includes(livechatService.state)) {
livechatService.updateSession({
channel: thread.channel,
});
}
});
this.store.livechatThread = thread;
}
return thread;
},
});
patch(Thread.prototype, {
chatbotScriptId: null,
@@ -4,7 +4,6 @@ import { SESSION_STATE } from "@im_livechat/embed/core/livechat_service";
import { ThreadService, threadService } from "@mail/core/common/thread_service";
import { prettifyMessageContent } from "@mail/utils/common/format";
import { createLocalId, onChange } from "@mail/utils/common/misc";
import { markup } from "@odoo/owl";
@@ -110,56 +109,6 @@ patch(ThreadService.prototype, {
}
},
insert(data) {
const isUnknown = !(createLocalId(data.model, data.id) in this.store.Thread.records);
const thread = super.insert(...arguments);
if (thread.type === "livechat" && isUnknown) {
if (
this.livechatService.displayWelcomeMessage &&
!this.chatbotService.isChatbotThread(thread)
) {
this.livechatService.welcomeMessage = this.store.Message.insert({
id: this.messageService.getNextTemporaryId(),
body: this.livechatService.options.default_message,
res_id: thread.id,
model: thread.model,
author: thread.operator,
});
}
if (this.chatbotService.isChatbotThread(thread)) {
this.chatbotService.typingMessage = this.store.Message.insert({
id: this.messageService.getNextTemporaryId(),
res_id: thread.id,
model: thread.model,
author: thread.operator,
});
}
onChange(thread, "state", () => {
if (
![SESSION_STATE.CLOSED, SESSION_STATE.NONE].includes(this.livechatService.state)
) {
this.livechatService.updateSession({ state: thread.state });
}
});
onChange(thread, "seen_message_id", () => {
if (
![SESSION_STATE.CLOSED, SESSION_STATE.NONE].includes(this.livechatService.state)
) {
this.livechatService.updateSession({ seen_message_id: thread.seen_message_id });
}
});
onChange(thread, "message_unread_counter", () => {
if (
![SESSION_STATE.CLOSED, SESSION_STATE.NONE].includes(this.livechatService.state)
) {
this.livechatService.updateSession({ channel: thread.channel });
}
});
this.store.livechatThread = thread;
}
return thread;
},
async update(thread, data) {
super.update(...arguments);
if (data.operator_pid) {
@@ -1,12 +1,33 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { assignDefined } from "@mail/utils/common/misc";
import { deserializeDateTime } from "@web/core/l10n/dates";
import { url } from "@web/core/utils/urls";
export class Attachment extends Record {
/** @type {Object.<number, Attachment>} */
static records = {};
/**
* @param {Object} data
* @returns {Attachment}
*/
static insert(data) {
if (!("id" in data)) {
throw new Error("Cannot insert attachment: id is missing in data");
}
let attachment = this.records[data.id];
if (!attachment) {
this.records[data.id] = new Attachment();
attachment = this.records[data.id];
Object.assign(attachment, { _store: this.store, id: data.id });
}
this.env.services["mail.attachment"].update(attachment, data);
return attachment;
}
/** @type {import("@mail/core/common/store_service").Store} */
_store;
accessToken;
@@ -151,3 +172,5 @@ export class Attachment extends Record {
);
}
}
modelRegistry.add(Attachment.name, Attachment);
@@ -1,6 +1,5 @@
/* @odoo-module */
import { Attachment } from "@mail/core/common/attachment_model";
import { removeFromArrayWithPredicate } from "@mail/utils/common/arrays";
import { assignDefined, createLocalId } from "@mail/utils/common/misc";
@@ -21,20 +20,6 @@ export class AttachmentService {
this.rpc = services["rpc"];
}
insert(data) {
if (!("id" in data)) {
throw new Error("Cannot insert attachment: id is missing in data");
}
let attachment = this.store.Attachment.records[data.id];
if (!attachment) {
this.store.Attachment.records[data.id] = new Attachment();
attachment = this.store.Attachment.records[data.id];
Object.assign(attachment, { _store: this.store, id: data.id });
}
this.update(attachment, data);
return attachment;
}
update(attachment, data) {
assignDefined(attachment, data, [
"checksum",
@@ -1,8 +1,28 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
export class CannedResponse extends Record {
/** @type {Object.<number, CannedResponse>} */
static records = {};
/**
* @param {Object} data
* @returns {CannedResponse}
*/
static insert(data) {
let cannedResponse = this.records[data.id];
if (!cannedResponse) {
this.records[data.id] = new CannedResponse();
cannedResponse = this.records[data.id];
}
Object.assign(cannedResponse, {
id: data.id,
name: data.source,
substitution: data.substitution,
});
return cannedResponse;
}
/** @type {number} */
id;
/** @type {string} */
@@ -10,3 +30,5 @@ export class CannedResponse extends Record {
/** @type {string} */
substitution;
}
modelRegistry.add(CannedResponse.name, CannedResponse);
@@ -1,6 +1,6 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { createLocalId } from "@mail/utils/common/misc";
/**
@@ -11,6 +11,24 @@ import { createLocalId } from "@mail/utils/common/misc";
* @property {number} threadId
*/
export class ChannelMember extends Record {
/** @type {Object.<number, ChannelMember>} */
static records = {};
/**
* @param {Object|Array} data
* @returns {ChannelMember}
*/
static insert(data) {
const memberData = Array.isArray(data) ? data[1] : data;
let member = this.records[memberData.id];
if (!member) {
this.records[memberData.id] = new ChannelMember();
member = this.records[memberData.id];
member._store = this.store;
}
this.env.services["discuss.channel.member"].update(member, data);
return member;
}
/** @type {number} */
id;
personaLocalId;
@@ -42,3 +60,5 @@ export class ChannelMember extends Record {
return this.persona.lang_name;
}
}
modelRegistry.add(ChannelMember.name, ChannelMember);
@@ -1,6 +1,5 @@
/* @odoo-module */
import { ChannelMember } from "@mail/core/common/channel_member_model";
import { removeFromArray } from "@mail/utils/common/arrays";
import { registry } from "@web/core/registry";
@@ -15,22 +14,6 @@ export class ChannelMemberService {
this.store = services["mail.store"];
}
/**
* @param {Object|Array} data
* @returns {ChannelMember}
*/
insert(data) {
const memberData = Array.isArray(data) ? data[1] : data;
let member = this.store.ChannelMember.records[memberData.id];
if (!member) {
this.store.ChannelMember.records[memberData.id] = new ChannelMember();
member = this.store.ChannelMember.records[memberData.id];
member._store = this.store;
}
this.update(member, data);
return member;
}
update(member, data) {
const [command, memberData] = Array.isArray(data) ? data : ["insert", data];
member.id = memberData.id;
@@ -1,12 +1,52 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { assignDefined } from "@mail/utils/common/misc";
import { _t } from "@web/core/l10n/translation";
/** @typedef {{ thread?: import("@mail/core/common/thread_model").Thread, folded?: boolean, replaceNewMessageChatWindow?: boolean }} ChatWindowData */
export class ChatWindow extends Record {
/** @type {ChatWindow[]} */
static records = [];
/**
* @param {ChatWindowData} [data]
* @returns {ChatWindow}
*/
static insert(data = {}) {
const chatWindow = this.records.find((c) => c.threadLocalId === data.thread?.localId);
if (!chatWindow) {
const chatWindow = new ChatWindow(this.store, data);
assignDefined(chatWindow, data);
let index;
const visible = this.env.services["mail.chat_window"].visible;
const maxVisible = this.env.services["mail.chat_window"].maxVisible;
if (!data.replaceNewMessageChatWindow) {
if (maxVisible <= this.records.length) {
const swaped = visible[visible.length - 1];
index = visible.length - 1;
this.env.services["mail.chat_window"].hide(swaped);
} else {
index = this.records.length;
}
} else {
const newMessageChatWindowIndex = this.records.findIndex((cw) => !cw.thread);
index =
newMessageChatWindowIndex !== -1
? newMessageChatWindowIndex
: this.records.length;
}
this.records.splice(index, data.replaceNewMessageChatWindow ? 1 : 0, chatWindow);
return this.records[index]; // return reactive version
}
if (chatWindow.hidden) {
this.env.services["mail.chat_window"].makeVisible(chatWindow);
}
assignDefined(chatWindow, data);
return chatWindow;
}
/** @type {import("@mail/core/common/store_service").Store} */
_store;
@@ -45,3 +85,5 @@ export class ChatWindow extends Record {
return !this.folded && !this.hidden;
}
}
modelRegistry.add(ChatWindow.name, ChatWindow);
@@ -1,8 +1,5 @@
/* @odoo-module */
import { ChatWindow } from "@mail/core/common/chat_window_model";
import { assignDefined } from "@mail/utils/common/misc";
import { browser } from "@web/core/browser/browser";
import { registry } from "@web/core/registry";
@@ -82,49 +79,6 @@ export class ChatWindowService {
return maxAmountWithoutHidden;
}
/**
* @param {ChatWindowData} [data]
* @returns {ChatWindow}
*/
insert(data = {}) {
const chatWindow = this.store.ChatWindow.records.find(
(c) => c.threadLocalId === data.thread?.localId
);
if (!chatWindow) {
const chatWindow = new ChatWindow(this.store, data);
assignDefined(chatWindow, data);
let index;
if (!data.replaceNewMessageChatWindow) {
if (this.maxVisible <= this.store.ChatWindow.records.length) {
const swaped = this.visible[this.visible.length - 1];
index = this.visible.length - 1;
this.hide(swaped);
} else {
index = this.store.ChatWindow.records.length;
}
} else {
const newMessageChatWindowIndex = this.store.ChatWindow.records.findIndex(
(chatWindow) => !chatWindow.thread
);
index =
newMessageChatWindowIndex !== -1
? newMessageChatWindowIndex
: this.store.ChatWindow.records.length;
}
this.store.ChatWindow.records.splice(
index,
data.replaceNewMessageChatWindow ? 1 : 0,
chatWindow
);
return this.store.ChatWindow.records[index]; // return reactive version
}
if (chatWindow.hidden) {
this.makeVisible(chatWindow);
}
assignDefined(chatWindow, data);
return chatWindow;
}
focus(chatWindow) {
chatWindow.autofocus++;
}
@@ -1,6 +1,6 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
/**
* @typedef Data
@@ -11,6 +11,28 @@ import { Record } from "@mail/core/common/record";
*/
export class Follower extends Record {
/** @type {Object.<number, Follower>} */
static records = {};
/**
* @param {Data} data
* @returns {Follower}
*/
static insert(data) {
let follower = this.records[data.id];
if (!follower) {
this.records[data.id] = new Follower();
follower = this.records[data.id];
}
Object.assign(follower, {
followedThread: data.followedThread,
id: data.id,
isActive: data.is_active,
partner: this.store.Persona.insert({ ...data.partner, type: "partner" }),
_store: this.store,
});
return follower;
}
/** @type {import("@mail/core/common/thread_model").Thread} */
followedThread;
/** @type {number} */
@@ -32,3 +54,5 @@ export class Follower extends Record {
: hasWriteAccess;
}
}
modelRegistry.add(Follower.name, Follower);
@@ -1,6 +1,6 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { htmlToTextContentInline } from "@mail/utils/common/format";
import { createLocalId } from "@mail/utils/common/misc";
@@ -14,6 +14,33 @@ import { url } from "@web/core/utils/urls";
const { DateTime } = luxon;
export class Message extends Record {
/** @type {Object.<number, Message>} */
static records = {};
/**
* @param {Object} data
* @returns {Message}
*/
static insert(data) {
let message;
if (data.res_id) {
this.store.Thread.insert({
model: data.model,
id: data.res_id,
});
}
if (data.id in this.records) {
message = this.records[data.id];
} else {
message = new Message();
message._store = this.store;
this.records[data.id] = message;
message = this.records[data.id];
}
this.env.services["mail.message"].update(message, data);
// return reactive version
return message;
}
/** @type {Object[]} */
attachments = [];
/** @type {import("@mail/core/common/persona_model").Persona} */
@@ -243,3 +270,5 @@ export class Message extends Record {
);
}
}
modelRegistry.add(Message.name, Message);
@@ -1,10 +1,7 @@
/* @odoo-module */
import { LinkPreview } from "@mail/core/common/link_preview_model";
import { Message } from "@mail/core/common/message_model";
import { MessageReactions } from "@mail/core/common/message_reactions_model";
import { NotificationGroup } from "@mail/core/common/notification_group_model";
import { Notification } from "@mail/core/common/notification_model";
import { removeFromArrayWithPredicate, replaceArrayWithCompare } from "@mail/utils/common/arrays";
import { convertBrToLineBreak, prettifyMessageContent } from "@mail/utils/common/format";
import { assignDefined, createLocalId } from "@mail/utils/common/misc";
@@ -207,31 +204,6 @@ export class MessageService {
}
}
/**
* @param {Object} data
* @returns {Message}
*/
insert(data) {
let message;
if (data.res_id) {
this.store.Thread.insert({
model: data.model,
id: data.res_id,
});
}
if (data.id in this.store.Message.records) {
message = this.store.Message.records[data.id];
} else {
message = new Message();
message._store = this.store;
this.store.Message.records[data.id] = message;
message = this.store.Message.records[data.id];
}
this.update(message, data);
// return reactive version
return message;
}
/**
* @param {import("@mail/core/common/message_model").Message} message
* @param {Object} data
@@ -426,20 +398,6 @@ export class MessageService {
return reaction;
}
/**
* @param {Object} data
* @returns {Notification}
*/
insertNotification(data) {
let notification = this.store.Notification.records[data.id];
if (!notification) {
this.store.Notification.records[data.id] = new Notification(this.store, data);
notification = this.store.Notification.records[data.id];
}
this.updateNotification(notification, data);
return notification;
}
updateNotification(notification, data) {
Object.assign(notification, {
messageId: data.messageId,
@@ -470,26 +428,6 @@ export class MessageService {
});
}
insertNotificationGroups(data) {
let group = this.store.NotificationGroup.records.find((group) => {
return (
group.resModel === data.resModel &&
group.type === data.type &&
(group.resModel !== "discuss.channel" || group.resIds.has(data.resId))
);
});
if (!group) {
group = new NotificationGroup(this.store);
}
this.updateNotificationGroup(group, data);
if (group.notifications.length === 0) {
removeFromArrayWithPredicate(this.store.NotificationGroup.records, (gr) =>
gr.eq(group)
);
}
return group;
}
updateNotificationGroup(group, data) {
Object.assign(group, {
modelName: data.modelName ?? group.modelName,
@@ -1,6 +1,5 @@
/* @odoo-module */
import { CannedResponse } from "@mail/core/common/canned_response_model";
import { cleanTerm } from "@mail/utils/common/format";
import { reactive } from "@odoo/owl";
@@ -157,20 +156,6 @@ export class Messaging {
res_id: id,
});
}
insertCannedResponse(data) {
let cannedResponse = this.store.CannedResponse.records[data.id];
if (!cannedResponse) {
this.store.CannedResponse.records[data.id] = new CannedResponse();
cannedResponse = this.store.CannedResponse.records[data.id];
}
Object.assign(cannedResponse, {
id: data.id,
name: data.source,
substitution: data.substitution,
});
return cannedResponse;
}
}
export const messagingService = {
@@ -1,11 +1,36 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { removeFromArrayWithPredicate } from "@mail/utils/common/arrays";
import { _t } from "@web/core/l10n/translation";
let nextId = 1;
export class NotificationGroup extends Record {
/** @type {NotificationGroup[]} */
static records = [];
/**
* @param {Object} data
* @returns {NotificationGroup}
*/
static insert(data) {
let group = this.records.find((group) => {
return (
group.resModel === data.resModel &&
group.type === data.type &&
(group.resModel !== "discuss.channel" || group.resIds.has(data.resId))
);
});
if (!group) {
group = new NotificationGroup(this.store);
}
this.env.services["mail.message"].updateNotificationGroup(group, data);
if (group.notifications.length === 0) {
removeFromArrayWithPredicate(this.records, (gr) => gr.eq(group));
}
return group;
}
/** @type {import("@mail/core/common/notification_model").Notification[]} */
notifications = [];
/** @type {string} */
@@ -46,3 +71,5 @@ export class NotificationGroup extends Record {
return this.lastMessage?.datetime;
}
}
modelRegistry.add(NotificationGroup.name, NotificationGroup);
@@ -1,10 +1,26 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { _t } from "@web/core/l10n/translation";
export class Notification extends Record {
/** @type {Object.<number, Notification>} */
static records = {};
/**
* @param {Object} data
* @returns {Notification}
*/
static insert(data) {
let notification = this.records[data.id];
if (!notification) {
this.records[data.id] = new Notification(this.store, data);
notification = this.records[data.id];
}
this.env.services["mail.message"].updateNotification(notification, data);
return notification;
}
/** @type {number} */
id;
/** @type {number} */
@@ -79,3 +95,5 @@ export class Notification extends Record {
return "";
}
}
modelRegistry.add(Notification.name, Notification);
@@ -1,6 +1,7 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { createLocalId } from "@mail/utils/common/misc";
/**
* @typedef {'offline' | 'bot' | 'online' | 'away' | 'im_partner' | undefined} ImStatus
@@ -13,6 +14,26 @@ import { Record } from "@mail/core/common/record";
*/
export class Persona extends Record {
/** @type {Object.<number, Persona>} */
static records = {};
/**
* @param {Data} data
* @returns {Persona}
*/
static insert(data) {
const localId = createLocalId(data.type, data.id);
let persona = this.records[localId];
if (!persona) {
persona = new Persona();
persona._store = this.store;
persona.localId = localId;
this.records[localId] = persona;
}
this.env.services["mail.persona"].update(persona, data);
// return reactive version
return this.records[localId];
}
/** @type {string} */
localId;
/** @type {number} */
@@ -45,3 +66,5 @@ export class Persona extends Record {
return this.email.substring(0, this.email.lastIndexOf("@"));
}
}
modelRegistry.add(Persona.name, Persona);
@@ -1,7 +1,6 @@
/* @odoo-module */
import { Persona } from "@mail/core/common/persona_model";
import { assignDefined, createLocalId, nullifyClearCommands } from "@mail/utils/common/misc";
import { assignDefined, nullifyClearCommands } from "@mail/utils/common/misc";
import { registry } from "@web/core/registry";
import { useSequential } from "@mail/utils/common/hooks";
@@ -39,24 +38,6 @@ export class PersonaService {
});
}
/**
* @param {import("@mail/core/common/persona_model").Data} data
* @returns {import("@mail/core/common/persona_model").Persona}
*/
insert(data) {
const localId = createLocalId(data.type, data.id);
let persona = this.store.Persona.records[localId];
if (!persona) {
persona = new Persona();
persona._store = this.store;
persona.localId = localId;
this.store.Persona.records[localId] = persona;
}
this.update(persona, data);
// return reactive version
return this.store.Persona.records[localId];
}
update(persona, data) {
nullifyClearCommands(data);
assignDefined(persona, { ...data });
@@ -96,13 +77,13 @@ export class PersonaService {
}
);
for (const { id, is_company } of partnerData) {
this.insert({ id, is_company, type: "partner" });
this.store.Persona.insert({ id, is_company, type: "partner" });
ongoing.delete(id);
this._sQueue.todo.delete(id);
}
for (const id of ongoing) {
// no is_company found => assumes persona is not a company
this.insert({ id, is_company: false, type: "partner" });
this.store.Persona.insert({ id, is_company: false, type: "partner" });
this._sQueue.todo.delete(id);
}
});
@@ -1,8 +1,19 @@
/* @odoo-module */
import { toRaw } from "@odoo/owl";
import { registry } from "@web/core/registry";
export const modelRegistry = registry.category("discuss.model");
export class Record {
static records = {};
/**
* @param {Object} data
* @returns {Record}
*/
static insert(data) {}
/** @param {Record} record */
eq(record) {
return toRaw(this) === toRaw(record);
@@ -23,3 +34,5 @@ export class Record {
return !this.in(list);
}
}
modelRegistry.add(Record.name, Record);
@@ -7,119 +7,33 @@ import { reactive } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { debounce } from "@web/core/utils/timing";
import { modelRegistry } from "./record";
export class Store {
Activity = {
/** @type {Object.<number, import("@mail/core/web/activity_model").Activity>} */
records: {},
/**
* @param {import("@mail/core/web/activity_model").Data} data
* @param {Object} [param1]
* @param {boolean} param1.broadcast
* @returns {import("@mail/core/web/activity_model").Activity}
*/
insert: (data, { broadcast = true } = {}) =>
this.env.services["mail.activity"].insert(data, { broadcast }),
};
Attachment = {
/** @type {Object.<number, import("@mail/core/common/attachment_model").Attachment>} */
records: {},
/**
* @param {Object} data
* @returns {import("@mail/core/common/attachment_model").Attachment}
*/
insert: (data) => this.env.services["mail.attachment"].insert(data),
};
CannedResponse = {
/** @type {Object.<number, import("@mail/core/common/canned_response_model").CannedResponse>} */
records: {},
/**
* @param {Object} data
* @returns {import("@mail/core/common/canned_response_model").CannedResponse}
*/
insert: (data) => this.env.services["mail.messaging"].insertCannedResponse(data),
};
ChannelMember = {
/** @type {Object.<number, import("@mail/core/common/channel_member_model").ChannelMember>} */
records: {},
/**
* @param {Object|Array} data
* @returns {import("@mail/core/common/channel_member_model").ChannelMember}
*/
insert: (data) => this.env.services["discuss.channel.member"].insert(data),
};
ChatWindow = {
/** @type {import("@mail/core/common/chat_window_model").ChatWindow[]} */
records: [],
/**
* @param {ChatWindowData} [data]
* @returns {import("@mail/core/common/chat_window_model").ChatWindow}
*/
insert: (data = {}) => this.env.services["mail.chat_window"].insert(data),
};
Follower = {
/** @type {Object.<number, import("@mail/core/common/follower_model").Follower>} */
records: {},
/**
* @param {import("@mail/core/common/follower_model").Data} data
* @returns {import("@mail/core/common/follower_model").Follower}
*/
insert: (data) => this.env.services["mail.thread"].insertFollower(data),
};
Message = {
/** @type {Object.<number, import("@mail/core/common/message_model").Message>} */
records: {},
/**
* @param {Object} data
* @returns {import("@mail/core/common/message_model").Message}
*/
insert: (data) => this.env.services["mail.message"].insert(data),
};
Notification = {
/** @type {Object.<number, import("@mail/core/common/notification_model").Notification>} */
records: {},
/**
* @param {Object} data
* @returns {import("@mail/core/common/notification_model").Notification}
*/
insert: (data) => this.env.services["mail.message"].insertNotification(data),
};
NotificationGroup = {
/** @type {import("@mail/core/common/notification_group_model").NotificationGroup[]} */
records: [],
/**
* @param {Object} data
* @returns {import("@mail/core/common/notification_group_model").NotificationGroup}
*/
insert: (data) => this.env.services["mail.message"].insertNotificationGroups(data),
};
Persona = {
/** @type {Object.<number, import("@mail/core/common/persona_model").Persona>} */
records: {},
/**
* @param {import("@mail/core/common/persona_model").Data} data
* @returns {import("@mail/core/common/persona_model").Persona}
*/
insert: (data) => this.env.services["mail.persona"].insert(data),
};
RtcSession = {
/** @type {Object.<number, import("@mail/discuss/call/common/rtc_session_model").RtcSession>} */
records: {},
/**
* @param {Object} data
* @returns {number, import("@mail/discuss/call/common/rtc_session_model").RtcSession}
*/
insert: (data) => this.env.services["discuss.rtc"].insertSession(data),
};
Thread = {
/** @type {Object.<string, import("@mail/core/common/thread_model").Thread>} */
records: {},
/**
* @param {Object} data
* @returns {import("@mail/core/common/thread_model").Thread}
*/
insert: (data) => this.env.services["mail.thread"].insert(data),
};
/** @type {typeof import("@mail/core/web/activity_model").Activity} */
Activity;
/** @type {typeof import("@mail/core/common/attachment_model").Attachment} */
Attachment;
/** @type {typeof import("@mail/core/common/canned_response_model").CannedResponse} */
CannedResponse;
/** @type {typeof import("@mail/core/common/channel_member_model").ChannelMember} */
ChannelMember;
/** @type {typeof import("@mail/core/common/chat_window_model").ChatWindow} */
ChatWindow;
/** @type {typeof import("@mail/core/common/follower_model").Follower} */
Follower;
/** @type {typeof import("@mail/core/common/message_model").Message} */
Message;
/** @type {typeof import("@mail/core/common/notification_model").Notification} */
Notification;
/** @type {typeof import("@mail/core/common/notification_group_model").NotificationGroup} */
NotificationGroup;
/** @type {typeof import("@mail/core/common/persona_model").Persona} */
Persona;
/** @type {typeof import("@mail/discuss/call/common/rtc_session_model").RtcSession} */
RtcSession;
/** @type {typeof import("@mail/core/common/thread_model").Thread} */
Thread;
/**
* @param {import("@web/env").OdooEnv} env
@@ -257,6 +171,19 @@ export const storeService = {
*/
start(env, services) {
const res = reactive(new Store(env, services));
for (const [name, Model] of modelRegistry.getEntries()) {
if (res[name]) {
throw new Error(
`There must be no duplicated Model Names (duplicate found: ${name})`
);
}
// classes cannot be made reactive because they are functions and they are not supported.
// work-around: make an object whose prototype is the class, so that static props become
// instance props.
const entry = Object.assign(Object.create(Model), { env, store: res });
entry.records = JSON.parse(JSON.stringify(Model.records));
res[name] = entry;
}
onChange(res.Thread, "records", () => res.updateBusSubscription());
services.ui.bus.addEventListener("resize", () => {
if (!services.ui.isSmall) {
@@ -1,8 +1,8 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { ScrollPosition } from "@mail/core/common/scroll_position_model";
import { createLocalId } from "@mail/utils/common/misc";
import { createLocalId, onChange } from "@mail/utils/common/misc";
import { deserializeDateTime } from "@web/core/l10n/dates";
import { _t } from "@web/core/l10n/translation";
@@ -22,6 +22,44 @@ import { Deferred } from "@web/core/utils/concurrency";
*/
export class Thread extends Record {
/** @type {Object.<string, Thread>} */
static records = {};
/**
* @param {Object} data
* @returns {Thread}
*/
static insert(data) {
if (!("id" in data)) {
throw new Error("Cannot insert thread: id is missing in data");
}
if (!("model" in data)) {
throw new Error("Cannot insert thread: model is missing in data");
}
const localId = createLocalId(data.model, data.id);
if (localId in this.records) {
const thread = this.records[localId];
this.env.services["mail.thread"].update(thread, data);
return thread;
}
const thread = new Thread(this.store, data);
onChange(thread, "message_unread_counter", () => {
if (thread.channel) {
thread.channel.message_unread_counter = thread.message_unread_counter;
}
});
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) {
this.store.discuss.threadLocalId = null;
}
});
this.env.services["mail.thread"].update(thread, data);
this.env.services["mail.thread"].insertComposer({ thread });
// return reactive version.
return this.records[thread.localId];
}
/** @type {number} */
id;
/** @type {string} */
@@ -461,3 +499,5 @@ export class Thread extends Record {
return this._store.Message.records[Math.max(...previousMessages.map((m) => m.id))];
}
}
modelRegistry.add(Thread.name, Thread);
@@ -3,19 +3,13 @@
import { Composer } from "@mail/core/common/composer_model";
import { loadEmoji } from "@web/core/emoji_picker/emoji_picker";
import { DEFAULT_AVATAR } from "@mail/core/common/persona_service";
import { Thread } from "@mail/core/common/thread_model";
import {
removeFromArray,
removeFromArrayWithPredicate,
replaceArrayWithCompare,
} from "@mail/utils/common/arrays";
import { prettifyMessageContent } from "@mail/utils/common/format";
import {
assignDefined,
createLocalId,
onChange,
nullifyClearCommands,
} from "@mail/utils/common/misc";
import { assignDefined, createLocalId, nullifyClearCommands } from "@mail/utils/common/misc";
import { markup } from "@odoo/owl";
@@ -803,42 +797,6 @@ export class ThreadService {
this.env.bus.trigger("mail.thread/onUpdate", { thread, data });
}
/**
* @param {Object} data
* @returns {Thread}
*/
insert(data) {
if (!("id" in data)) {
throw new Error("Cannot insert thread: id is missing in data");
}
if (!("model" in data)) {
throw new Error("Cannot insert thread: model is missing in data");
}
const localId = createLocalId(data.model, data.id);
if (localId in this.store.Thread.records) {
const thread = this.store.Thread.records[localId];
this.update(thread, data);
return thread;
}
const thread = new Thread(this.store, data);
onChange(thread, "message_unread_counter", () => {
if (thread.channel) {
thread.channel.message_unread_counter = thread.message_unread_counter;
}
});
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) {
this.store.discuss.threadLocalId = null;
}
});
this.update(thread, data);
this.insertComposer({ thread });
// return reactive version.
return this.store.Thread.records[thread.localId];
}
/**
* @param {Object} data
* @returns {Composer}
@@ -31,7 +31,6 @@ export class ActivityListPopover extends Component {
setup() {
this.orm = useService("orm");
this.user = useService("user");
this.activity = useService("mail.activity");
this.store = useState(useService("mail.store"));
this.updateFromProps(this.props);
onWillUpdateProps((props) => this.updateFromProps(props));
@@ -84,7 +83,7 @@ export class ActivityListPopover extends Component {
}
);
for (const activityData of activitiesData) {
this.activity.insert(activityData);
this.store.Activity.insert(activityData);
}
}
}
@@ -1,6 +1,7 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { assignDefined } from "@mail/utils/common/misc";
/**
* @typedef Data
@@ -33,6 +34,30 @@ import { Record } from "@mail/core/common/record";
*/
export class Activity extends Record {
/** @type {Object.<number, Activity>} */
static records = {};
/**
* @param {Data} data
* @param {Object} [param1]
* @param {boolean} param1.broadcast
* @returns {Activity}
*/
static insert(data, { broadcast = true } = {}) {
const activity = this.records[data.id] ?? new Activity(this.store, data.id);
if (data.request_partner_id) {
data.request_partner_id = data.request_partner_id[0];
}
assignDefined(activity, data);
if (broadcast) {
this.env.services["mail.activity"].broadcastChannel?.postMessage({
type: "insert",
payload: this.env.services["mail.activity"]._serialize(activity),
});
}
return activity;
}
/** @type {string} */
activity_category;
/** @type {[number, string]} */
@@ -105,3 +130,5 @@ export class Activity extends Record {
return store.Activity.records[id];
}
}
modelRegistry.add(Activity.name, Activity);
@@ -1,8 +1,5 @@
/* @odoo-module */
import { Activity } from "@mail/core/web/activity_model";
import { assignDefined } from "@mail/utils/common/misc";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { browser } from "@web/core/browser/browser";
@@ -80,27 +77,6 @@ export class ActivityService {
});
}
/**
* @param {import("@mail/core/web/activity_model").Data} data
* @param {Object} [param1]
* @param {boolean} param1.broadcast
* @returns {import("@mail/core/web/activity_model").Activity}
*/
insert(data, { broadcast = true } = {}) {
const activity = this.store.Activity.records[data.id] ?? new Activity(this.store, data.id);
if (data.request_partner_id) {
data.request_partner_id = data.request_partner_id[0];
}
assignDefined(activity, data);
if (broadcast) {
this.broadcastChannel?.postMessage({
type: "insert",
payload: this._serialize(activity),
});
}
return activity;
}
delete(activity, { broadcast = true } = {}) {
delete this.store.Activity.records[activity.id];
if (broadcast) {
@@ -1,6 +1,5 @@
/* @odoo-module */
import { Follower } from "@mail/core/common/follower_model";
import { ThreadService, threadService } from "@mail/core/common/thread_service";
import { parseEmail } from "@mail/js/utils";
import { createLocalId } from "@mail/utils/common/misc";
@@ -44,7 +43,7 @@ patch(ThreadService.prototype, {
if ("attachments" in result) {
result["attachments"] = result["attachments"].map((attachment) => ({
...attachment,
originThread: this.insert(attachment.originThread[0][1]),
originThread: this.store.Thread.insert(attachment.originThread[0][1]),
}));
}
thread.canPostOnReadonly = result.canPostOnReadonly;
@@ -111,7 +110,7 @@ patch(ThreadService.prototype, {
// to force a reload
this.store.Thread.records[localId].status = "new";
}
const thread = this.insert({
const thread = this.store.Thread.insert({
id: resId,
model: resModel,
type: "chatter",
@@ -132,25 +131,6 @@ patch(ThreadService.prototype, {
}
return thread;
},
/**
* @param {import("@mail/core/common/follower_model").Data} data
* @returns {import("@mail/core/common/follower_model").Follower}
*/
insertFollower(data) {
let follower = this.store.Follower.records[data.id];
if (!follower) {
this.store.Follower.records[data.id] = new Follower();
follower = this.store.Follower.records[data.id];
}
Object.assign(follower, {
followedThread: data.followedThread,
id: data.id,
isActive: data.is_active,
partner: this.store.Persona.insert({ ...data.partner, type: "partner" }),
_store: this.store,
});
return follower;
},
/**
* @param {import("@mail/core/common/thread_model").Thread} thread
* @param {import("@mail/core/web/suggested_recipient").SuggestedRecipient[]} dataList
@@ -2,7 +2,6 @@
import { BlurManager } from "@mail/discuss/call/common/blur_manager";
import { monitorAudio } from "@mail/discuss/call/common/media_monitoring";
import { RtcSession } from "@mail/discuss/call/common/rtc_session_model";
import { removeFromArray } from "@mail/utils/common/arrays";
import { closeStream, createLocalId, onChange } from "@mail/utils/common/misc";
@@ -1397,38 +1396,6 @@ export class Rtc {
await this.refreshAudioStatus();
}
/**
* @param {Object} data
* @returns {RtcSession}
*/
insertSession(data) {
let session;
if (this.store.RtcSession.records[data.id]) {
session = this.store.RtcSession.records[data.id];
} else {
session = new RtcSession();
session._store = this.store;
}
const { channelMember, ...remainingData } = data;
for (const key in remainingData) {
session[key] = remainingData[key];
}
if (channelMember?.channel) {
session.channelId = channelMember.channel.id;
}
if (channelMember) {
const channelMemberRecord = this.store.ChannelMember.insert(channelMember);
channelMemberRecord.rtcSessionId = session.id;
session.channelMemberId = channelMemberRecord.id;
if (channelMemberRecord.thread) {
channelMemberRecord.thread.rtcSessions[session.id] = session;
}
}
this.store.RtcSession.records[session.id] = session;
// return reactive version
return this.store.RtcSession.records[session.id];
}
/**
* @param {import("@mail/discuss/call/common/rtc_session_model").id} id
*/
@@ -1,9 +1,43 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { Record, modelRegistry } from "@mail/core/common/record";
import { createLocalId } from "@mail/utils/common/misc";
export class RtcSession extends Record {
/** @type {Object.<number, RtcSession>} */
static records = {};
/**
* @param {Object} data
* @returns {number, RtcSession}
*/
static insert(data) {
let session;
if (this.records[data.id]) {
session = this.records[data.id];
} else {
session = new RtcSession();
session._store = this.store;
}
const { channelMember, ...remainingData } = data;
for (const key in remainingData) {
session[key] = remainingData[key];
}
if (channelMember?.channel) {
session.channelId = channelMember.channel.id;
}
if (channelMember) {
const channelMemberRecord = this.store.ChannelMember.insert(channelMember);
channelMemberRecord.rtcSessionId = session.id;
session.channelMemberId = channelMemberRecord.id;
if (channelMemberRecord.thread) {
channelMemberRecord.thread.rtcSessions[session.id] = session;
}
}
this.records[session.id] = session;
// return reactive version
return this.records[session.id];
}
// Server data
channelId;
channelMemberId;
@@ -141,3 +175,5 @@ export class RtcSession extends Record {
}
}
}
modelRegistry.add(RtcSession.name, RtcSession);
@@ -39,7 +39,6 @@ export function isDragSourceExternalFile(dataTransfer) {
return false;
}
/**
* @param {Object} target
* @param {string} key
@@ -7,7 +7,7 @@ QUnit.module("attachment model test", {});
QUnit.test("Attachment model properties", async (assert) => {
const { env } = await start();
const attachment = env.services["mail.attachment"].insert({
const attachment = env.services["mail.store"].Attachment.insert({
filename: "test.txt",
id: 750,
mimetype: "text/plain",