[REF] mail: introduce id & get() on all discuss models

1. Introduce `static id` to all models, to uniquely identify
   a record in a model. This allows normalizing discuss store
   for all models, which helps as preparation for managing
   record deletion. Can also be combined

```js
class Message {
    static id = "id";
    id;
}
class Thread {
    static id = AND("model", "id");
}
```

2. Introduce `Model.get()` to easily get a record of model
   from data that can identify the record. This prevent leaking
   the way the record are stored in `records`, as now all records
   are indexed by localId, and localId is technical detail.

```js
Message.get(messageId);
Thread.get({ model: "discuss.channel", id: 1 });
```

Part-of: odoo/odoo#134008
This commit is contained in:
Alexandre Kühn
2023-09-04 10:56:08 +00:00
parent 09b8c32e9e
commit a775b239ed
40 changed files with 242 additions and 162 deletions
@@ -2,13 +2,13 @@
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 { assignDefined } 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 isUnknown = !this.get(data);
const thread = super.insert(data);
if (thread.type === "livechat") {
if (data?.channel) {
@@ -1,7 +1,7 @@
/* @odoo-module */
import { Thread } from "@mail/core/common/thread_model";
import { createLocalId, onChange } from "@mail/utils/common/misc";
import { onChange } from "@mail/utils/common/misc";
import { patch } from "@web/core/utils/patch";
import { session } from "@web/session";
@@ -9,7 +9,7 @@ import { SESSION_STATE } from "./livechat_service";
patch(Thread, {
insert(data) {
const isUnknown = !(createLocalId(data.model, data.id) in this.records);
const isUnknown = !this.get(data);
const thread = super.insert(...arguments);
const livechatService = this.env.services["im_livechat.livechat"];
const chatbotService = this.env.services["im_livechat.chatbot"];
@@ -7,6 +7,7 @@ import { deserializeDateTime } from "@web/core/l10n/dates";
import { url } from "@web/core/utils/urls";
export class Attachment extends Record {
static id = "id";
/** @type {Object.<number, Attachment>} */
static records = {};
@@ -18,10 +19,11 @@ export class Attachment extends Record {
if (!("id" in data)) {
throw new Error("Cannot insert attachment: id is missing in data");
}
let attachment = this.records[data.id];
let attachment = this.get(data);
if (!attachment) {
this.records[data.id] = new Attachment();
attachment = this.records[data.id];
attachment = this.new(data);
this.records[attachment.localId] = attachment;
attachment = this.records[attachment.localId];
Object.assign(attachment, { _store: this.store, id: data.id });
}
this.env.services["mail.attachment"].update(attachment, data);
@@ -1,7 +1,7 @@
/* @odoo-module */
import { removeFromArrayWithPredicate } from "@mail/utils/common/arrays";
import { assignDefined, createLocalId } from "@mail/utils/common/misc";
import { assignDefined } from "@mail/utils/common/misc";
import { registry } from "@web/core/registry";
@@ -46,7 +46,7 @@ export class AttachmentService {
model: threadData.model,
id: threadData.id,
});
attachment.originThreadLocalId = createLocalId(threadData.model, threadData.id);
attachment.originThreadLocalId = this.store.Thread.localId(threadData);
const thread = attachment.originThread;
if (attachment.notIn(thread.attachments)) {
thread.attachments.push(attachment);
@@ -64,7 +64,7 @@ export class AttachmentService {
if (attachment.tmpUrl) {
URL.revokeObjectURL(attachment.tmpUrl);
}
delete this.store.Attachment.records[attachment.id];
delete this.store.Attachment.records[attachment.localId];
if (attachment.originThread) {
removeFromArrayWithPredicate(attachment.originThread.attachments, (att) =>
att.eq(attachment)
@@ -1,6 +1,5 @@
/* @odoo-module */
import { createLocalId } from "@mail/utils/common/misc";
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { Deferred } from "@web/core/utils/concurrency";
@@ -83,8 +82,7 @@ export class AttachmentUploadService {
}
const threadId = parseInt(upload.data.get("thread_id"));
const threadModel = upload.data.get("thread_model");
const originThread =
this.store.Thread.records[createLocalId(threadModel, threadId)];
const originThread = this.store.Thread.get({ model: threadModel, id: threadId });
const attachment = this.store.Attachment.insert({
...response,
extension: upload.title.split(".").pop(),
@@ -99,7 +97,7 @@ export class AttachmentUploadService {
}
}
const def = this.deferredByAttachmentId.get(tmpId);
this.unlink(this.store.Attachment.records[tmpId]);
this.unlink(this.store.Attachment.get(tmpId));
if (def) {
def.resolve(attachment);
this.deferredByAttachmentId.delete(tmpId);
@@ -3,6 +3,7 @@
import { Record } from "@mail/core/common/record";
export class CannedResponse extends Record {
static id = "id";
/** @type {Object.<number, CannedResponse>} */
static records = {};
/**
@@ -10,10 +11,11 @@ export class CannedResponse extends Record {
* @returns {CannedResponse}
*/
static insert(data) {
let cannedResponse = this.records[data.id];
let cannedResponse = this.get(data);
if (!cannedResponse) {
this.records[data.id] = new CannedResponse();
cannedResponse = this.records[data.id];
cannedResponse = this.new(data);
this.records[cannedResponse.localId] = cannedResponse;
cannedResponse = this.records[cannedResponse.localId];
}
Object.assign(cannedResponse, {
id: data.id,
@@ -1,7 +1,6 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { createLocalId } from "@mail/utils/common/misc";
/**
* @class ChannelMember
@@ -11,6 +10,7 @@ import { createLocalId } from "@mail/utils/common/misc";
* @property {number} threadId
*/
export class ChannelMember extends Record {
static id = "id";
/** @type {Object.<number, ChannelMember>} */
static records = {};
/**
@@ -19,10 +19,11 @@ export class ChannelMember extends Record {
*/
static insert(data) {
const memberData = Array.isArray(data) ? data[1] : data;
let member = this.records[memberData.id];
let member = this.get(memberData);
if (!member) {
this.records[memberData.id] = new ChannelMember();
member = this.records[memberData.id];
member = this.new(memberData);
this.records[member.localId] = member;
member = this.records[member.localId];
member._store = this.store;
}
this.env.services["discuss.channel.member"].update(member, data);
@@ -46,11 +47,11 @@ export class ChannelMember extends Record {
}
get rtcSession() {
return this._store.RtcSession.records[this.rtcSessionId];
return this._store.RtcSession.get(this.rtcSessionId);
}
get thread() {
return this._store.Thread.records[createLocalId("discuss.channel", this.threadId)];
return this._store.Thread.get({ model: "discuss.channel", id: this.threadId });
}
/**
@@ -8,6 +8,7 @@ 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 {
static id = "threadLocalId";
/** @type {ChatWindow[]} */
static records = [];
/**
@@ -17,7 +18,7 @@ export class ChatWindow extends Record {
static insert(data = {}) {
const chatWindow = this.records.find((c) => c.threadLocalId === data.thread?.localId);
if (!chatWindow) {
const chatWindow = new ChatWindow();
const chatWindow = this.new(data);
Object.assign(chatWindow, {
thread: data.thread,
_store: this.store,
@@ -554,8 +554,9 @@ export class Composer extends Component {
* @param {postData} postData Message meta data info
*/
async _sendMessage(value, postData) {
const thread = this.props.composer.thread;
await this.threadService.post(this.thread, value, postData);
if (this.props.composer.thread.type === "mailbox") {
if (thread.type === "mailbox") {
this.notifySendFromMailbox();
}
this.suggestion?.clearRawMentions();
@@ -1,12 +1,13 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { OR, Record } from "@mail/core/common/record";
/**
* @typedef {{partnerIds: Set<number>, threadIds: Set<number>}} RawMentions
*/
export class Composer extends Record {
static id = OR("threadLocalId", "messageLocalId");
/**
* @param {Object} data
* @returns {Composer}
@@ -18,7 +19,7 @@ export class Composer extends Record {
}
let composer = (thread ?? message)?.composer;
if (!composer) {
composer = new Composer();
composer = this.new(data);
const { message, thread } = data;
if (thread) {
composer.thread = thread;
@@ -51,8 +52,8 @@ export class Composer extends Record {
/** @type {import("@mail/core/common/attachment_model").Attachment[]} */
attachments = [];
/** @type {import("@mail/core/common/message_model").Message} */
message;
/** @type {import("@mail/core/common/message_model").Message.localId} */
messageLocalId;
/** @type {RawMentions} */
rawMentions = {
partnerIds: new Set(),
@@ -62,8 +63,8 @@ export class Composer extends Record {
cannedResponseIds = new Set();
/** @type {string} */
textInputContent;
/** @type {import("@mail/core/common/thread_model").Thread */
thread;
/** @type {import("@mail/core/common/thread_model").Thread.localId} */
threadLocalId;
/** @type {{ start: number, end: number, direction: "forward" | "backward" | "none"}}*/
selection = {
start: 0,
@@ -75,6 +76,26 @@ export class Composer extends Record {
/** @type {import("@mail/core/common/store_service").Store} */
_store;
isFocused = false;
/** @type {import("@mail/core/common/message_model").Message} */
get message() {
return this._store.Message.records[this.messageLocalId];
}
/** @param {import("@mail/core/common/message_model").Message} */
set message(newMessage) {
this.messageLocalId = newMessage?.localId;
}
/** @type {import("@mail/core/common/thread_model").Thread} */
get thread() {
return this._store.Thread.records[this.threadLocalId];
}
/** @param {import("@mail/core/common/thread_model").Thread} */
set thread(newThread) {
this.threadLocalId = newThread?.localId;
}
}
Composer.register();
@@ -11,6 +11,7 @@ import { Record } from "@mail/core/common/record";
*/
export class Follower extends Record {
static id = "id";
/** @type {Object.<number, Follower>} */
static records = {};
/**
@@ -18,10 +19,11 @@ export class Follower extends Record {
* @returns {Follower}
*/
static insert(data) {
let follower = this.records[data.id];
let follower = this.get(data);
if (!follower) {
this.records[data.id] = new Follower();
follower = this.records[data.id];
follower = this.new(data);
this.records[follower.localId] = follower;
follower = this.records[follower.localId];
}
Object.assign(follower, {
followedThread: data.followedThread,
@@ -3,6 +3,7 @@
import { Record } from "@mail/core/common/record";
export class LinkPreview extends Record {
static id = "id";
/**
* @param {Object} data
* @returns {LinkPreview}
@@ -12,9 +13,9 @@ export class LinkPreview extends Record {
if (linkPreview) {
return Object.assign(linkPreview, data);
}
linkPreview = new LinkPreview();
linkPreview = this.new(data);
Object.assign(linkPreview, data);
this.store.Message.records[data.message.id]?.linkPreviews.push(linkPreview);
this.store.Message.get(data.message.id)?.linkPreviews.push(linkPreview);
return linkPreview;
}
@@ -28,14 +28,14 @@ export class MailCoreCommon {
if (messageData) {
this.store.Message.insert({ ...messageData });
}
const attachment = this.store.Attachment.records[attachmentId];
const attachment = this.store.Attachment.get(attachmentId);
if (attachment) {
this.attachmentService.remove(attachment);
}
});
this.busService.subscribe("mail.link.preview/delete", (payload) => {
const { id, message_id } = payload;
const message = this.store.Message.records[message_id];
const message = this.store.Message.get(message_id);
if (message) {
removeFromArrayWithPredicate(
message.linkPreviews,
@@ -45,11 +45,11 @@ export class MailCoreCommon {
});
this.busService.subscribe("mail.message/delete", (payload) => {
for (const messageId of payload.message_ids) {
const message = this.store.Message.records[messageId];
const message = this.store.Message.get(messageId);
if (!message) {
continue;
}
delete this.store.Message.records[messageId];
delete this.store.Message.records[this.store.Message.localId(messageId)];
if (message.originThread) {
removeFromArrayWithPredicate(message.originThread.messages, (msg) =>
msg.eq(message)
@@ -100,14 +100,14 @@ export class MailCoreCommon {
const { LinkPreview: linkPreviews } = payload;
if (linkPreviews) {
for (const linkPreview of linkPreviews) {
this.store.Message.records[linkPreview.message.id]?.linkPreviews.push(
this.store.Message.get(linkPreview.message.id)?.linkPreviews.push(
this.store.LinkPreview.insert(linkPreview)
);
}
}
const { Message: messageData } = payload;
if (messageData) {
const isStarred = this.store.Message.records[messageData.id]?.isStarred;
const isStarred = this.store.Message.get(messageData.id)?.isStarred;
const message = this.store.Message.insert({
...messageData,
body: messageData.body ? markup(messageData.body) : messageData.body,
@@ -2,7 +2,6 @@
import { Record } from "@mail/core/common/record";
import { htmlToTextContentInline } from "@mail/utils/common/format";
import { createLocalId } from "@mail/utils/common/misc";
import { toRaw } from "@odoo/owl";
@@ -14,6 +13,7 @@ import { url } from "@web/core/utils/urls";
const { DateTime } = luxon;
export class Message extends Record {
static id = "id";
/** @type {Object.<number, Message>} */
static records = {};
/**
@@ -28,13 +28,12 @@ export class Message extends Record {
id: data.res_id,
});
}
if (data.id in this.records) {
message = this.records[data.id];
} else {
message = new Message();
message = this.get(data);
if (!message) {
message = this.new(data);
message._store = this.store;
this.records[data.id] = message;
message = this.records[data.id];
this.records[message.localId] = message;
message = this.records[message.localId];
}
this.env.services["mail.message"].update(message, data);
// return reactive version
@@ -196,7 +195,7 @@ export class Message extends Record {
}
get originThread() {
return this._store.Thread.records[createLocalId(this.resModel, this.resId)];
return this._store.Thread.get({ model: this.resModel, id: this.resId });
}
get resUrl() {
@@ -1,18 +1,19 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { AND, Record } from "@mail/core/common/record";
export class MessageReactions extends Record {
static id = AND("messageId", "content");
/**
* @param {Object} data
* @returns {MessageReactions}
*/
static insert(data) {
let reaction = this.store.Message.records[data.message.id]?.reactions.find(
let reaction = this.store.Message.get(data.message.id)?.reactions.find(
({ content }) => content === data.content
);
if (!reaction) {
reaction = new MessageReactions();
reaction = this.new(data);
reaction._store = this.store;
}
const personasToUnlink = new Set();
@@ -2,7 +2,7 @@
import { removeFromArrayWithPredicate, replaceArrayWithCompare } from "@mail/utils/common/arrays";
import { convertBrToLineBreak, prettifyMessageContent } from "@mail/utils/common/format";
import { assignDefined, createLocalId } from "@mail/utils/common/misc";
import { assignDefined } from "@mail/utils/common/misc";
import { markup } from "@odoo/owl";
@@ -96,7 +96,7 @@ export class MessageService {
const rawMentionedPartnerIds = rawMentions.partnerIds || [];
const rawMentionedThreadIds = rawMentions.threadIds || [];
for (const partnerId of rawMentionedPartnerIds) {
const partner = this.store.Persona.records[createLocalId("partner", partnerId)];
const partner = this.store.Persona.get({ type: "partner", id: partnerId });
const index = body.indexOf(`@${partner.name}`);
if (index === -1) {
continue;
@@ -104,7 +104,7 @@ export class MessageService {
partners.push(partner);
}
for (const threadId of rawMentionedThreadIds) {
const thread = this.store.Thread.records[createLocalId("discuss.channel", threadId)];
const thread = this.store.Thread.get({ model: "discuss.channel", id: threadId });
const index = body.indexOf(`#${thread.displayName}`);
if (index === -1) {
continue;
@@ -7,6 +7,7 @@ import { _t } from "@web/core/l10n/translation";
let nextId = 1;
export class NotificationGroup extends Record {
static id = "id";
/** @type {NotificationGroup[]} */
static records = [];
/**
@@ -22,10 +23,10 @@ export class NotificationGroup extends Record {
);
});
if (!group) {
group = new NotificationGroup();
group._store = this.store;
const id = nextId++;
group = this.new({ id });
Object.assign(group, { id, _store: this.store });
this.store.NotificationGroup.records.push(group);
group.id = nextId++;
// return reactive
group = this.store.NotificationGroup.records.find((g) => g.eq(group));
}
@@ -60,7 +61,7 @@ export class NotificationGroup extends Record {
}
get lastMessage() {
return this._store.Message.records[this.lastMessageId];
return this._store.Message.get(this.lastMessageId);
}
get datetime() {
@@ -5,6 +5,7 @@ import { Record } from "@mail/core/common/record";
import { _t } from "@web/core/l10n/translation";
export class Notification extends Record {
static id = "id";
/** @type {Object.<number, Notification>} */
static records = {};
/**
@@ -12,15 +13,15 @@ export class Notification extends Record {
* @returns {Notification}
*/
static insert(data) {
let notification = this.records[data.id];
let notification = this.get(data);
if (!notification) {
notification = new Notification();
this.records[data.id] = notification;
notification = this.new(data);
this.records[notification.localId] = notification;
Object.assign(notification, {
id: data.id,
_store: this.store,
});
notification = this.records[data.id];
notification = this.records[notification.localId];
}
this.env.services["mail.message"].updateNotification(notification, data);
return notification;
@@ -42,7 +43,7 @@ export class Notification extends Record {
_store;
get message() {
return this._store.Message.records[this.messageId];
return this._store.Message.get(this.messageId);
}
get isFailure() {
@@ -1,7 +1,6 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { createLocalId } from "@mail/utils/common/misc";
import { AND, Record } from "@mail/core/common/record";
/**
* @typedef {'offline' | 'bot' | 'online' | 'away' | 'im_partner' | undefined} ImStatus
@@ -14,6 +13,7 @@ import { createLocalId } from "@mail/utils/common/misc";
*/
export class Persona extends Record {
static id = AND("type", "id");
/** @type {Object.<number, Persona>} */
static records = {};
/**
@@ -21,17 +21,16 @@ export class Persona extends Record {
* @returns {Persona}
*/
static insert(data) {
const localId = createLocalId(data.type, data.id);
let persona = this.records[localId];
let persona = this.get(data);
if (!persona) {
persona = new Persona();
persona = this.new(data);
persona._store = this.store;
persona.localId = localId;
this.records[localId] = persona;
this.records[persona.localId] = persona;
persona = this.records[persona.localId];
}
this.env.services["mail.persona"].update(persona, data);
// return reactive version
return this.records[localId];
return persona;
}
/** @type {string} */
@@ -5,11 +5,71 @@ import { registry } from "@web/core/registry";
export const modelRegistry = registry.category("discuss.model");
const OR_SYM = Symbol("or");
const AND_SYM = Symbol("and");
export function AND(...args) {
return [AND_SYM, ...args];
}
export function OR(...args) {
return [OR_SYM, ...args];
}
export class Record {
static id;
static records = {};
static get(data) {
return this.records[this.localId(data)];
}
static modelFromLocalId(localId) {
return localId.split(",")[0];
}
static register() {
modelRegistry.add(this.name, this);
}
static localId(data) {
let idStr;
if (typeof data === "object") {
idStr = this._localId(this.id, data);
} else {
idStr = data; // non-object data => single id
}
return `${this.name},${idStr}`;
}
static _localId(expr, data, { brackets = false } = {}) {
if (!Array.isArray(expr)) {
return data[expr];
}
const vals = [];
for (let i = 1; i < expr.length; i++) {
vals.push(this._localId(expr[i], data, { brackets: true }));
}
let res = vals.join(expr[0] === OR_SYM ? " OR " : " AND ");
if (brackets) {
res = `(${res})`;
}
return res;
}
/**
* Technical attribute, DO NOT USE in business code.
* This class is almost equivalent to current class of model,
* except this is a function, so we can new() it, whereas
* `this` is not, because it's an object.
* (in order to comply with OWL reactivity)
*
* @type {typeof Record}
*/
static Class;
/**
* This method is almost equivalent to new Class, except that it properly
* setup relational fields of model with get/set, @see Class
*
* @returns {Record}
*/
static new(data) {
const obj = new this.Class();
return Object.assign(obj, { localId: this.localId(data) });
}
/**
* @param {Object} data
@@ -29,6 +89,9 @@ export class Record {
/** @param {Record[]} list */
in(list) {
if (!list) {
return false;
}
return list.some((record) => record.eq(this));
}
@@ -187,6 +187,7 @@ export const storeService = {
// 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.Class = Model;
entry.records = JSON.parse(JSON.stringify(Model.records));
res[name] = entry;
}
+1 -1
View File
@@ -202,7 +202,7 @@ export class Thread extends Component {
const { oeType, oeId } = ev.target.dataset;
if (oeType === "highlight") {
await this.env.messageHighlight?.highlightMessage(
this.store.Message.records[Number(oeId)],
this.store.Message.get(Number(oeId)),
this.props.thread
);
}
@@ -1,7 +1,5 @@
/* @odoo-module */
import { createLocalId } from "@mail/utils/common/misc";
import { useService } from "@web/core/utils/hooks";
import { Component, useState } from "@odoo/owl";
@@ -26,8 +24,6 @@ export class ThreadIcon extends Component {
}
get chatPartner() {
return this.store.Persona.records[
createLocalId("partner", this.props.thread.chatPartnerId)
];
return this.store.Persona.get({ type: "partner", id: this.props.thread.chatPartnerId });
}
}
@@ -1,8 +1,8 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { AND, Record } from "@mail/core/common/record";
import { ScrollPosition } from "@mail/core/common/scroll_position";
import { createLocalId, onChange } from "@mail/utils/common/misc";
import { onChange } from "@mail/utils/common/misc";
import { deserializeDateTime } from "@web/core/l10n/dates";
import { _t } from "@web/core/l10n/translation";
@@ -22,8 +22,20 @@ import { Deferred } from "@web/core/utils/concurrency";
*/
export class Thread extends Record {
static id = AND("model", "id");
/** @type {Object.<string, Thread>} */
static records = {};
/**
* @param {Thread.localId} localId
* @returns {string}
*/
static localIdToActiveId(localId) {
if (!localId) {
return undefined;
}
// Transform "Thread,<model> AND <id>" to "<model>_<id>""
return localId.split(",").slice(1).join("_").replace(" AND ", "_");
}
/**
* @param {Object} data
* @returns {Thread}
@@ -35,13 +47,12 @@ export class Thread extends Record {
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];
let thread = this.get(data);
if (thread) {
this.env.services["mail.thread"].update(thread, data);
return thread;
}
let thread = new Thread();
thread = this.new(data);
Object.assign(thread, {
id: data.id,
model: data.model,
@@ -192,7 +203,7 @@ export class Thread extends Record {
}
get activeRtcSession() {
return this._store.RtcSession.records[this.activeRtcSessionId];
return this._store.RtcSession.get(this.activeRtcSessionId);
}
set activeRtcSession(session) {
@@ -263,7 +274,7 @@ export class Thread extends Record {
if (this.type === "chat" && this.chatPartnerId) {
return (
this.customName ||
this._store.Persona.records[createLocalId("partner", this.chatPartnerId)]
this._store.Persona.get({ type: "partner", id: this.chatPartnerId })
.nameOrDisplayName
);
}
@@ -329,10 +340,6 @@ export class Thread extends Record {
return null;
}
get localId() {
return createLocalId(this.model, this.id);
}
get needactionCounter() {
return this.isChatChannel ? this.message_unread_counter : this.message_needaction_counter;
}
@@ -460,7 +467,7 @@ export class Thread extends Record {
}
get rtcInvitingSession() {
return this._store.RtcSession.records[this.invitingRtcSessionId];
return this._store.RtcSession.get(this.invitingRtcSessionId);
}
get hasNeedactionMessages() {
@@ -493,7 +500,7 @@ export class Thread extends Record {
if (previousMessages.length === 0) {
return false;
}
return this._store.Message.records[Math.max(...previousMessages.map((m) => m.id))];
return this._store.Message.get(Math.max(...previousMessages.map((m) => m.id)));
}
}
@@ -8,7 +8,7 @@ import {
replaceArrayWithCompare,
} from "@mail/utils/common/arrays";
import { prettifyMessageContent } from "@mail/utils/common/format";
import { assignDefined, createLocalId, nullifyClearCommands } from "@mail/utils/common/misc";
import { assignDefined, nullifyClearCommands } from "@mail/utils/common/misc";
import { markup } from "@odoo/owl";
@@ -340,8 +340,7 @@ export class ThreadService {
if (ids.length) {
const previews = await this.orm.call("discuss.channel", "channel_fetch_preview", [ids]);
for (const preview of previews) {
const thread =
this.store.Thread.records[createLocalId("discuss.channel", preview.id)];
const thread = this.store.Thread.get({ model: "discuss.channel", id: preview.id });
const data = Object.assign(preview.last_message, {
body: markup(preview.last_message.body),
});
@@ -836,7 +835,7 @@ export class ThreadService {
tmpData.guestAuthor = this.store.self;
}
if (parentId) {
tmpData.parentMessage = this.store.Message.records[parentId];
tmpData.parentMessage = this.store.Message.get(parentId);
}
const prettyContent = await prettifyMessageContent(body, params.validMentions);
const { emojis } = await loadEmoji();
@@ -865,7 +864,7 @@ export class ThreadService {
const data = await this.rpc(this.getMessagePostRoute(thread), params);
if (thread.type !== "chatter") {
removeFromArrayWithPredicate(thread.messages, (msg) => msg.eq(tmpMsg));
delete this.store.Message.records[tmpMsg.id];
delete this.store.Message.records[this.store.Message.localId(tmpMsg.id)];
}
if (!data) {
return;
@@ -34,6 +34,7 @@ import { assignDefined } from "@mail/utils/common/misc";
*/
export class Activity extends Record {
static id = "id";
/** @type {Object.<number, Activity>} */
static records = {};
@@ -44,16 +45,16 @@ export class Activity extends Record {
* @returns {Activity}
*/
static insert(data, { broadcast = true } = {}) {
let activity = this.records[data.id];
let activity = this.get(data);
if (!activity) {
activity = new Activity();
activity = this.new(data);
Object.assign(activity, {
id: data.id,
_store: this.store,
});
this.store.Activity.records[data.id] = activity;
this.records[activity.localId] = activity;
// return reactive
activity = this.store.Activity.records[data.id];
activity = this.records[activity.localId];
}
if (data.request_partner_id) {
data.request_partner_id = data.request_partner_id[0];
@@ -78,7 +78,7 @@ export class ActivityService {
}
delete(activity, { broadcast = true } = {}) {
delete this.store.Activity.records[activity.id];
delete this.store.Activity.records[this.store.Activity.localId(activity.id)];
if (broadcast) {
this.broadcastChannel?.postMessage({ type: "delete", payload: { id: activity.id } });
}
@@ -6,7 +6,6 @@ import { Component, onWillStart, onWillUpdateProps, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { createLocalId } from "@mail/utils/common/misc";
/**
* @typedef {Object} Props
@@ -45,7 +44,7 @@ export class DiscussClientAction extends Component {
const rawActiveId =
props.action.context.active_id ??
props.action.params?.active_id ??
this.store.discuss.threadLocalId?.replace(",", "_") ??
this.store.Thread.localIdToActiveId(this.store.discuss.threadLocalId) ??
"mail.box_inbox";
const activeId =
typeof rawActiveId === "number" ? `discuss.channel_${rawActiveId}` : rawActiveId;
@@ -54,10 +53,10 @@ export class DiscussClientAction extends Component {
// legacy format (sent in old emails, shared links, ...)
model = "discuss.channel";
}
const activeThreadLocalId = createLocalId(model, id);
const activeThreadLocalId = this.store.Thread.localId({ model, id });
if (activeThreadLocalId !== this.store.discuss.threadLocalId) {
const thread =
this.store.Thread.records[createLocalId(model, id)] ??
this.store.Thread.get({ model, id }) ??
(await this.threadService.fetchChannel(parseInt(id)));
if (!thread.is_pinned) {
await this.threadService.pin(thread);
@@ -92,7 +92,7 @@ export class MailCoreWeb {
// Furthermore, server should not send back all messageIds marked as read
// but something like last read messageId or something like that.
// (just imagine you mark 1000 messages as read ... )
const message = this.store.Message.records[messageId];
const message = this.store.Message.get(messageId);
if (!message) {
continue;
}
@@ -3,7 +3,6 @@
import { ImStatus } from "@mail/core/common/im_status";
import { NotificationItem } from "@mail/core/web/notification_item";
import { onExternalClick } from "@mail/utils/common/hooks";
import { createLocalId } from "@mail/utils/common/misc";
import { Component, useState } from "@odoo/owl";
@@ -64,10 +63,6 @@ export class MessagingMenu extends Component {
}
}
createLocalId(...args) {
return createLocalId(...args);
}
/**
* @param {'chat' | 'group'} tab
* @returns Thread types matching the given tab.
@@ -2,7 +2,6 @@
import { ThreadService, threadService } from "@mail/core/common/thread_service";
import { parseEmail } from "@mail/js/utils";
import { createLocalId } from "@mail/utils/common/misc";
import { markup } from "@odoo/owl";
@@ -102,15 +101,15 @@ patch(ThreadService.prototype, {
return result;
},
getThread(resModel, resId) {
const localId = createLocalId(resModel, resId);
if (localId in this.store.Thread.records) {
let thread = this.store.Thread.get({ model: resModel, id: resId });
if (thread) {
if (resId === false) {
return this.store.Thread.records[localId];
return thread;
}
// to force a reload
this.store.Thread.records[localId].status = "new";
thread.status = "new";
}
const thread = this.store.Thread.insert({
thread = this.store.Thread.insert({
id: resId,
model: resModel,
type: "chatter",
@@ -215,7 +214,7 @@ patch(ThreadService.prototype, {
} else {
thread.followers.delete(follower);
}
delete this.store.Follower.records[follower.id];
delete this.store.Follower.records[this.store.Follower.localId(follower.id)];
},
unpin(thread) {
const chatWindow = this.store.ChatWindow.records.find(
@@ -88,7 +88,7 @@ export class Call extends Component {
for (const memberId of this.props.thread.invitedMemberIds) {
invitationCards.push({
key: "member_" + memberId,
member: this.store.ChannelMember.records[memberId],
member: this.store.ChannelMember.get(memberId),
});
}
}
@@ -3,7 +3,7 @@
import { BlurManager } from "@mail/discuss/call/common/blur_manager";
import { monitorAudio } from "@mail/discuss/call/common/media_monitoring";
import { removeFromArray } from "@mail/utils/common/arrays";
import { closeStream, createLocalId, onChange } from "@mail/utils/common/misc";
import { closeStream, onChange } from "@mail/utils/common/misc";
import { reactive } from "@odoo/owl";
@@ -771,7 +771,7 @@ export class Rtc {
this.state.logs.clear();
this.state.channel = channel;
this.onThreadUpdate(this.state.channel, { rtcSessions, invitedMembers });
this.state.selfSession = this.store.RtcSession.records[sessionId];
this.state.selfSession = this.store.RtcSession.get(sessionId);
this.state.iceServers = iceServers || DEFAULT_ICE_SERVERS;
this.state.logs.set("channelId", this.state.channel?.id);
this.state.logs.set("selfSessionId", this.state.selfSession?.id);
@@ -1393,16 +1393,16 @@ export class Rtc {
* @param {import("@mail/discuss/call/common/rtc_session_model").id} id
*/
deleteSession(id) {
const session = this.store.RtcSession.records[id];
const session = this.store.RtcSession.get(id);
if (session) {
if (this.state.selfSession && session.eq(this.state.selfSession)) {
this.endCall();
}
delete this.store.Thread.records[createLocalId("discuss.channel", session.channelId)]
delete this.store.Thread.get({ model: "discuss.channel", id: session.channelId })
?.rtcSessions[id];
this.disconnect(session);
}
delete this.store.RtcSession.records[id];
delete this.store.RtcSession.records[this.store.RtcSession.localId(id)];
}
/**
@@ -1464,7 +1464,7 @@ export class Rtc {
}
updateRtcSessions(channelId, sessionsData, command) {
const channel = this.store.Thread.records[createLocalId("discuss.channel", channelId)];
const channel = this.store.Thread.get({ model: "discuss.channel", id: channelId });
if (!channel) {
return;
}
@@ -1,9 +1,9 @@
/* @odoo-module */
import { Record } from "@mail/core/common/record";
import { createLocalId } from "@mail/utils/common/misc";
export class RtcSession extends Record {
static id = "id";
/** @type {Object.<number, RtcSession>} */
static records = {};
/**
@@ -11,11 +11,9 @@ export class RtcSession extends Record {
* @returns {number, RtcSession}
*/
static insert(data) {
let session;
if (this.records[data.id]) {
session = this.records[data.id];
} else {
session = new RtcSession();
let session = this.get(data);
if (!session) {
session = this.new(data);
session._store = this.store;
}
const { channelMember, ...remainingData } = data;
@@ -33,9 +31,9 @@ export class RtcSession extends Record {
channelMemberRecord.thread.rtcSessions[session.id] = session;
}
}
this.records[session.id] = session;
this.records[session.localId] = session;
// return reactive version
return this.records[session.id];
return this.records[session.localId];
}
// Server data
@@ -79,11 +77,11 @@ export class RtcSession extends Record {
logStep;
get channelMember() {
return this._store.ChannelMember.records[this.channelMemberId];
return this._store.ChannelMember.get(this.channelMemberId);
}
get channel() {
return this._store.Thread.records[createLocalId("discuss.channel", this.channelId)];
return this._store.Thread.get({ model: "discuss.channel", id: this.channelId });
}
get isMute() {
@@ -1,7 +1,6 @@
/* @odoo-module */
import { removeFromArrayWithPredicate } from "@mail/utils/common/arrays";
import { createLocalId } from "@mail/utils/common/misc";
import { markup, reactive } from "@odoo/owl";
@@ -50,7 +49,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.records[createLocalId("discuss.channel", id)];
const channel = this.store.Thread.get({ model: "discuss.channel", id });
if (channel) {
this.threadService.update(channel, { last_interest_dt });
if (channel.type !== "channel") {
@@ -96,8 +95,10 @@ export class DiscussCoreCommon {
}
});
this.busService.subscribe("discuss.channel/transient_message", (payload) => {
const channel =
this.store.Thread.records[createLocalId("discuss.channel", payload.res_id)];
const channel = this.store.Thread.get({
model: "discuss.channel",
id: payload.res_id,
});
const message = this.messageService.createTransient(
Object.assign(payload, { body: markup(payload.body) })
);
@@ -105,8 +106,7 @@ export class DiscussCoreCommon {
channel.transientMessages.push(message);
});
this.busService.subscribe("discuss.channel/unpin", (payload) => {
const thread =
this.store.Thread.records[createLocalId("discuss.channel", payload.id)];
const thread = this.store.Thread.get({ model: "discuss.channel", id: payload.id });
if (thread) {
thread.is_pinned = false;
this.notificationService.add(
@@ -117,8 +117,7 @@ export class DiscussCoreCommon {
});
this.busService.subscribe("discuss.channel.member/fetched", (payload) => {
const { channel_id, last_message_id, partner_id } = payload;
const channel =
this.store.Thread.records[createLocalId("discuss.channel", channel_id)];
const channel = this.store.Thread.get({ model: "discuss.channel", id: channel_id });
if (channel) {
const seenInfo = channel.seenInfos.find(
(seenInfo) => seenInfo.partner.id === partner_id
@@ -130,8 +129,7 @@ export class DiscussCoreCommon {
});
this.busService.subscribe("discuss.channel.member/seen", (payload) => {
const { channel_id, last_message_id, partner_id } = payload;
const channel =
this.store.Thread.records[createLocalId("discuss.channel", channel_id)];
const channel = this.store.Thread.get({ model: "discuss.channel", id: channel_id });
if (!channel) {
// for example seen from another browser, the current one has no
// knowledge of the channel
@@ -216,7 +214,7 @@ export class DiscussCoreCommon {
async _handleNotificationNewMessage(notif) {
const { id, message: messageData } = notif.payload;
let channel = this.store.Thread.records[createLocalId("discuss.channel", id)];
let channel = this.store.Thread.get({ model: "discuss.channel", id });
if (!channel || !channel.type) {
const [channelData] = await this.rpc("/discuss/channel/info", { channel_id: id });
channel = this.store.Thread.insert({
@@ -229,7 +227,7 @@ export class DiscussCoreCommon {
this.threadService.pin(channel);
}
removeFromArrayWithPredicate(channel.messages, ({ id }) => id === messageData.temporary_id);
delete this.store.Message.records[messageData.temporary_id];
delete this.store.Message.records[this.store.Message.localId(messageData.temporary_id)];
messageData.temporary_id = null;
if ("parentMessage" in messageData && messageData.parentMessage.body) {
messageData.parentMessage.body = markup(messageData.parentMessage.body);
@@ -2,7 +2,6 @@
import { NavigableList } from "@mail/core/common/navigable_list";
import { cleanTerm } from "@mail/utils/common/format";
import { createLocalId } from "@mail/utils/common/misc";
import { Component, onMounted, useEffect, useRef, useState } from "@odoo/owl";
@@ -231,7 +230,7 @@ export class ChannelSelector extends Component {
get tagsList() {
const res = [];
for (const partnerId of this.state.selectedPartners) {
const partner = this.store.Persona.records[createLocalId("partner", partnerId)];
const partner = this.store.Persona.get({ type: "partner", id: partnerId });
res.push({
id: partner.id,
text: partner.name,
@@ -1,6 +1,5 @@
/* @odoo-module */
import { createLocalId } from "@mail/utils/common/misc";
import { reactive } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
@@ -87,7 +86,7 @@ export class DiscussCoreWeb {
this.busService.subscribe("mail.record/insert", async (payload) => {
if (payload.Thread) {
const data = payload.Thread;
const thread = this.store.Thread.records[createLocalId(data.model, data.id)];
const thread = this.store.Thread.get(data);
if (data.serverFoldState && thread && data.serverFoldState !== thread.state) {
thread.state = data.serverFoldState;
if (thread.state === "closed") {
@@ -2,10 +2,6 @@
import { reactive } from "@odoo/owl";
export function createLocalId(...args) {
return args.join(",");
}
export function nullifyClearCommands(data) {
for (const key in data) {
if (!Array.isArray(data[key])) {
@@ -1,6 +1,5 @@
/* @odoo-module */
import { createLocalId } from "@mail/utils/common/misc";
import { Command } from "@mail/../tests/helpers/command";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
@@ -174,8 +173,10 @@ QUnit.test("Channel member count update after user left", async (assert) => {
});
const { env, openDiscuss } = await start();
openDiscuss(channelId);
const thread =
env.services["mail.store"].Thread.records[createLocalId("discuss.channel", channelId)];
const thread = env.services["mail.store"].Thread.get({
model: "discuss.channel",
id: channelId,
});
assert.strictEqual(thread.memberCount, 2);
await pyEnv.withUser(userId, () =>
env.services.orm.call("discuss.channel", "action_unfollow", [channelId])
@@ -2,7 +2,6 @@
import { UPDATE_BUS_PRESENCE_DELAY } from "@bus/im_status_service";
import { createLocalId } from "@mail/utils/common/misc";
import { Command } from "@mail/../tests/helpers/command";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
@@ -93,7 +92,7 @@ QUnit.test("Can handle im_status of unknown partner", async (assert) => {
Partner: { im_status: "online", id: partnerId },
});
await nextTick();
const persona = env.services["mail.store"].Persona.records[createLocalId("partner", partnerId)];
const persona = env.services["mail.store"].Persona.get({ type: "partner", id: partnerId });
assert.ok(persona);
assert.ok(persona.im_status === "online");
});