[REF] mail: store all discuss models in Record.records as object

In preparation to improve insert in relational field with data, so
that there's less risk for cyclic code execution (hint: there'll be
a preinsert).

closes odoo/odoo#136308

Related: odoo/enterprise#47777
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Alexandre Kühn
2023-09-27 13:11:58 +00:00
parent bef4069195
commit 98491e3b43
22 changed files with 80 additions and 86 deletions
@@ -5,14 +5,11 @@ import { ChatWindow } from "@mail/core/common/chat_window";
import { patch } from "@web/core/utils/patch";
patch(ChatWindow.prototype, {
close(options) {
super.close(options);
if (
this.thread?.type === "livechat" &&
this.thread.isLoaded &&
this.thread.messages.length === 0
) {
this.threadService.unpin(this.thread);
async close(options) {
const thread = this.thread;
await super.close(options);
if (thread?.type === "livechat" && thread.isLoaded && thread.messages.length === 0) {
this.threadService.unpin(thread);
}
},
});
@@ -22,7 +22,7 @@ patch(ChatWindow.prototype, {
});
},
close() {
async close() {
if (this.thread?.type !== "livechat") {
return super.close();
}
@@ -31,7 +31,7 @@ patch(ChatWindow.prototype, {
this.chatWindowService.show(this.props.chatWindow);
} else {
this.thread?.delete();
super.close();
await super.close();
}
this.livechatService.leaveSession();
this.chatbotService.stop();
@@ -58,7 +58,7 @@ export class AutopopupService {
*/
async shouldOpenChatWindow() {
const thread = await this.livechatService.thread;
return this.storeService.ChatWindow.records.every((cw) => !cw.thread?.eq(thread));
return this.storeService.discuss.chatWindows.every((cw) => !cw.thread?.eq(thread));
}
get allowAutoPopup() {
@@ -153,13 +153,13 @@ export class LivechatService {
} finally {
this.persistThreadPromise = null;
}
const chatWindow = this.store.ChatWindow.records.find(
const chatWindow = this.store.discuss.chatWindows.find(
(c) => c.thread.id === this.TEMPORARY_ID
);
if (chatWindow) {
chatWindow.thread?.delete();
if (!this.thread) {
this.chatWindowService.close(chatWindow);
await this.chatWindowService.close(chatWindow);
return;
}
chatWindow.thread = this.thread;
@@ -68,7 +68,7 @@ export class LivechatButton extends Component {
this.livechatService.initialized &&
this.livechatService.available &&
!this.livechatService.shouldRestoreSession &&
this.store.ChatWindow.records.length === 0
this.store.discuss.chatWindows.length === 0
);
}
}
@@ -4,7 +4,7 @@ import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { start } from "@mail/../tests/helpers/test_utils";
import { click } from "@web/../tests/utils";
import { click, contains } from "@web/../tests/utils";
QUnit.module("chat window (patch)");
@@ -12,7 +12,7 @@ QUnit.test("closing a chat window with no message from admin side unpins it", as
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
pyEnv["res.users"].create({ partner_id: partnerId });
const channelId = pyEnv["discuss.channel"].create({
pyEnv["discuss.channel"].create({
channel_member_ids: [
[
0,
@@ -27,14 +27,9 @@ QUnit.test("closing a chat window with no message from admin side unpins it", as
channel_type: "livechat",
uuid: "channel-10-uuid",
});
const { env } = await start();
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await click(".o-mail-ChatWindow-command[title='Close Chat Window']");
const [channel] = await env.services.rpc(
"/discuss/channel/info",
{ channel_id: channelId },
{ silent: true }
);
assert.strictEqual(channel.is_pinned, false, "Livechat channel should not be pinned");
await contains(".o_notification", { text: "You unpinned your conversation with Demo" });
});
+6
View File
@@ -2,6 +2,7 @@ declare module "models" {
import { Attachment as AttachmentClass } from "@mail/core/common/attachment_model";
import { CannedResponse as CannedResponseClass } from "@mail/core/common/canned_response_model";
import { ChannelMember as ChannelMemberClass } from "@mail/core/common/channel_member_model";
import { ChatWindow as ChatWindowClass } from "@mail/core/common/chat_window_model";
import { Composer as ComposerClass } from "@mail/core/common/composer_model";
import { DiscussApp as DiscussAppClass } from "@mail/core/common/discuss_app_model";
import { DiscussAppCategory as DiscussAppCategoryClass } from "@mail/core/common/discuss_app_category_model";
@@ -10,6 +11,7 @@ declare module "models" {
import { Message as MessageClass } from "@mail/core/common/message_model";
import { MessageReactions as MessageReactionsClass } from "@mail/core/common/message_reactions_model";
import { Notification as NotificationClass } from "@mail/core/common/notification_model";
import { NotificationGroup as NotificationGroupClass } from "@mail/core/common/notification_group_model";
import { Persona as PersonaClass } from "@mail/core/common/persona_model";
import { Thread as ThreadClass } from "@mail/core/common/thread_model";
@@ -17,6 +19,7 @@ declare module "models" {
export interface Attachment extends AttachmentClass {}
export interface Attachment extends CannedResponseClass {}
export interface ChannelMember extends ChannelMemberClass {}
export interface ChatWindow extends ChatWindowClass {}
export interface Composer extends ComposerClass {}
export interface DiscussApp extends DiscussAppClass {}
export interface DiscussAppCategory extends DiscussAppCategoryClass {}
@@ -25,6 +28,7 @@ declare module "models" {
export interface Message extends MessageClass {}
export interface MessageReactions extends MessageReactionsClass {}
export interface Notification extends NotificationClass {}
export interface NotificationGroup extends NotificationGroupClass {}
export interface Persona extends PersonaClass {}
export interface Thread extends ThreadClass {}
@@ -33,6 +37,7 @@ declare module "models" {
"Attachment": Attachment,
"CannedResponse": CannedResponse,
"ChannelMember": ChannelMember,
"ChatWindow": ChatWindow,
"Composer": Composer,
"DiscussApp": DiscussApp,
"DiscussAppCategory": DiscussAppCategory,
@@ -41,6 +46,7 @@ declare module "models" {
"Message": Message,
"MessageReactions": MessageReactions,
"Notification": Notification,
"NotificationGroup": NotificationGroup,
"Persona": Persona,
"Thread": Thread,
}
@@ -136,8 +136,8 @@ export class ChatWindow extends Component {
}
}
close(options) {
this.chatWindowService.close(this.props.chatWindow, options);
async close(options) {
await this.chatWindowService.close(this.props.chatWindow, options);
}
get actionsMenuTitleText() {
@@ -9,8 +9,8 @@ import { _t } from "@web/core/l10n/translation";
export class ChatWindow extends Record {
static id = "thread";
/** @type {import("models").ChatWindow[]} */
static records = [];
/** @type {Object<number, import("models").ChatWindow} */
static records = {};
/** @returns {import("models").ChatWindow} */
static new(data) {
return super.new(data);
@@ -24,7 +24,7 @@ export class ChatWindow extends Record {
* @returns {import("models").ChatWindow}
*/
static insert(data = {}) {
const chatWindow = this.records.find((c) => c.thread?.eq(data.thread));
const chatWindow = this.store.discuss.chatWindows.find((c) => c.thread?.eq(data.thread));
if (!chatWindow) {
const chatWindow = this.new(data);
Object.assign(chatWindow, { thread: data.thread });
@@ -33,22 +33,28 @@ export class ChatWindow extends Record {
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) {
if (maxVisible <= this.store.discuss.chatWindows.length) {
const swaped = visible[visible.length - 1];
index = visible.length - 1;
this.env.services["mail.chat_window"].hide(swaped);
} else {
index = this.records.length;
index = this.store.discuss.chatWindows.length;
}
} else {
const newMessageChatWindowIndex = this.records.findIndex((cw) => !cw.thread);
const newMessageChatWindowIndex = this.store.discuss.chatWindows.findIndex(
(cw) => !cw.thread
);
index =
newMessageChatWindowIndex !== -1
? newMessageChatWindowIndex
: this.records.length;
: this.store.discuss.chatWindows.length;
}
this.records.splice(index, data.replaceNewMessageChatWindow ? 1 : 0, chatWindow);
return this.records[index]; // return reactive version
this.store.discuss.chatWindows.splice(
index,
data.replaceNewMessageChatWindow ? 1 : 0,
chatWindow
);
return this.store.discuss.chatWindows[index]; // return reactive version
}
if (chatWindow.hidden) {
this.env.services["mail.chat_window"].makeVisible(chatWindow);
@@ -42,7 +42,7 @@ export class ChatWindowService {
}
openNewMessage() {
if (this.store.ChatWindow.records.some(({ thread }) => !thread)) {
if (this.store.discuss.chatWindows.some(({ thread }) => !thread)) {
// New message chat window is already opened.
return;
}
@@ -50,18 +50,18 @@ export class ChatWindowService {
}
closeNewMessage() {
const newMessageChatWindow = this.store.ChatWindow.records.find(({ thread }) => !thread);
const newMessageChatWindow = this.store.discuss.chatWindows.find(({ thread }) => !thread);
if (newMessageChatWindow) {
this.close(newMessageChatWindow);
}
}
get visible() {
return this.store.ChatWindow.records.filter((chatWindow) => !chatWindow.hidden);
return this.store.discuss.chatWindows.filter((chatWindow) => !chatWindow.hidden);
}
get hidden() {
return this.store.ChatWindow.records.filter((chatWindow) => chatWindow.hidden);
return this.store.discuss.chatWindows.filter((chatWindow) => chatWindow.hidden);
}
get maxVisible() {
@@ -109,24 +109,27 @@ export class ChatWindowService {
chatWindow.thread.state = "folded";
}
close(chatWindow, { escape = false } = {}) {
if (!chatWindow.hidden && this.maxVisible < this.store.ChatWindow.records.length) {
async close(chatWindow, { escape = false } = {}) {
if (!chatWindow.hidden && this.maxVisible < this.store.discuss.chatWindows.length) {
const swaped = this.hidden[0];
swaped.hidden = false;
swaped.folded = false;
}
const index = this.store.ChatWindow.records.findIndex((c) => c.eq(chatWindow));
const index = this.store.discuss.chatWindows.findIndex((c) => c.eq(chatWindow));
if (index > -1) {
this.store.ChatWindow.records.splice(index, 1);
this.store.discuss.chatWindows.splice(index, 1);
}
const thread = chatWindow.thread;
if (thread) {
thread.state = "closed";
}
if (escape && this.store.ChatWindow.records.length > 0) {
this.focus(this.store.ChatWindow.records[index - 1]);
if (escape && this.store.discuss.chatWindows.length > 0) {
this.focus(this.store.discuss.chatWindows[index - 1]);
}
await this._onClose(chatWindow);
chatWindow.delete();
}
async _onClose(chatWindow) {}
}
export const chatWindowService = {
@@ -45,6 +45,8 @@ export class DiscussApp extends Record {
/** @type {'mailbox'|'all'|'channel'|'chat'|'livechat'} */
activeTab = "all";
chatWindows = Record.many("ChatWindow");
notificationGroups = Record.many("NotificationGroup");
isActive = false;
thread = Record.one("Thread");
channels = Record.one("DiscussAppCategory");
@@ -1,15 +1,14 @@
/* @odoo-module */
import { Record } 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 {
static id = "id";
/** @type {import("models").NotificationGroup[]} */
static records = [];
/** @type {Object.<number, import("models").NotificationGroup>} */
static records = {};
/** @returns {import("models").NotificationGroup} */
static new(data) {
return super.new(data);
@@ -23,7 +22,7 @@ export class NotificationGroup extends Record {
* @returns {import("models").NotificationGroup}
*/
static insert(data) {
let group = this.records.find((group) => {
let group = this.store.discuss.notificationGroups.find((group) => {
return (
group.resModel === data.resModel &&
group.type === data.type &&
@@ -34,13 +33,11 @@ export class NotificationGroup extends Record {
const id = nextId++;
group = this.new({ id });
Object.assign(group, { id });
this.store.NotificationGroup.records.push(group);
// return reactive
group = this.store.NotificationGroup.records.find((g) => g.eq(group));
this.store.discuss.notificationGroups.add(group);
}
group.update(data);
if (group.notifications.length === 0) {
removeFromArrayWithPredicate(this.records, (gr) => gr.eq(group));
group.delete();
}
return group;
}
+4 -13
View File
@@ -342,11 +342,9 @@ export class Record {
const obj = new this.Class();
let record = Object.assign(obj, { localId: this.localId(data), Model: this });
Object.assign(record, { _store: this.store });
if (!Array.isArray(this.records)) {
this.records[record.localId] = record;
// return reactive version
record = this.records[record.localId];
}
this.records[record.localId] = record;
// return reactive version
record = this.records[record.localId];
return record;
}
/**
@@ -435,14 +433,7 @@ export class Record {
}
}
if (this.Model) {
if (Array.isArray(this.Model.records)) {
const index = this.Model.records.findIndex((r) => r.eq(r1));
if (index !== -1) {
this.Model.records.splice(index, 1);
}
} else {
delete this.Model.records[r1.localId];
}
delete this.Model.records[r1.localId];
delete this.Model;
}
}
@@ -53,9 +53,6 @@ export class Store {
return undefined;
}
const modelName = Record.modelFromLocalId(localId);
if (Array.isArray(this[modelName].records)) {
return this[modelName].records.find((r) => r.localId === localId);
}
return this[modelName].records[localId];
}
@@ -411,7 +411,7 @@ export class ThreadService {
thread.pendingNewMessages = [];
}
unpin(thread) {
async unpin(thread) {
if (thread.eq(this.store.discuss.thread)) {
this.router.replaceState({ active_id: undefined });
}
@@ -5,7 +5,7 @@ import { ChatWindowService } from "@mail/core/common/chat_window_service";
import { patch } from "@web/core/utils/patch";
patch(ChatWindowService.prototype, {
async close() {
async _onClose(chatWindow) {
if (this.ui.isSmall && !this.store.discuss.isActive) {
// If we are in mobile and discuss is not open, it means the
// chat window was opened from the messaging menu. In that
@@ -15,6 +15,6 @@ patch(ChatWindowService.prototype, {
// ensure messaging menu is opened before chat window is closed
await Promise.resolve();
}
await super.close(...arguments);
await super._onClose(...arguments);
},
});
@@ -33,7 +33,7 @@ export class MailCoreWeb {
author: this.store.user,
})
);
this.store.NotificationGroup.records.sort(
this.store.discuss.notificationGroups.sort(
(n1, n2) => n2.lastMessage?.id - n1.lastMessage?.id
);
});
@@ -74,7 +74,7 @@ export class MessagingMenu extends Component {
get hasPreviews() {
return (
this.threads.length > 0 ||
(this.store.NotificationGroup.records.length > 0 &&
(this.store.discuss.notificationGroups.length > 0 &&
this.store.discuss.activeTab === "all") ||
(this.notification.permission === "prompt" && this.store.discuss.activeTab === "all")
);
@@ -86,7 +86,7 @@ export class MessagingMenu extends Component {
displayName: _t("%s has a request", this.store.odoobot.name),
iconSrc: this.threadService.avatarUrl(this.store.odoobot),
partner: this.store.odoobot,
isLast: this.threads.length === 0 && this.store.NotificationGroup.records.length === 0,
isLast: this.threads.length === 0 && this.store.discuss.notificationGroups.length === 0,
isShown:
this.store.discuss.activeTab === "all" && this.notification.permission === "prompt",
};
@@ -234,7 +234,7 @@ export class MessagingMenu extends Component {
});
// Close the related chat window as having both the form view
// and the chat window does not look good.
this.store.ChatWindow.records.find(({ thr }) => thr?.eq(thread))?.close();
this.store.discuss.chatWindows.find(({ thr }) => thr?.eq(thread))?.close();
} else {
this.threadService.open(thread);
}
@@ -297,7 +297,7 @@ export class MessagingMenu extends Component {
Object.values(this.store.Thread.records).filter(
(thread) => thread.is_pinned && thread.message_unread_counter > 0
).length +
Object.values(this.store.NotificationGroup.records).reduce(
this.store.discuss.notificationGroups.reduce(
(acc, ng) => acc + parseInt(ng.notifications.length),
0
);
@@ -15,7 +15,7 @@
</t>
<t t-name="mail.MessagingMenu.content">
<t t-set="failures" t-value="store.NotificationGroup.records"/>
<t t-set="failures" t-value="store.discuss.notificationGroups"/>
<div class="o-mail-MessagingMenu d-flex flex-column flex-grow-1" t-att-class="{ 'overflow-auto w-100 mh-100': ui.isSmall }">
<div class="o-mail-MessagingMenu-header" t-att-class="{'flex-row-reverse text-uppercase': ui.isSmall, 'border-bottom d-flex flex-shrink-0 p-2': !env.inDiscussApp }">
<t t-if="!ui.isSmall">
@@ -168,9 +168,9 @@ patch(ThreadService.prototype, {
thread.suggestedRecipients = recipients;
},
async leaveChannel(channel) {
const chatWindow = this.store.ChatWindow.records.find((c) => c.thread?.eq(channel));
const chatWindow = this.store.discuss.chatWindows.find((c) => c.thread?.eq(channel));
if (chatWindow) {
this.chatWindowService.close(chatWindow);
await this.chatWindowService.close(chatWindow);
}
super.leaveChannel(...arguments);
},
@@ -239,10 +239,10 @@ patch(ThreadService.prototype, {
]);
follower.delete();
},
unpin(thread) {
const chatWindow = this.store.ChatWindow.records.find((c) => c.thread?.eq(thread));
async unpin(thread) {
const chatWindow = this.store.discuss.chatWindows.find((c) => c.thread?.eq(thread));
if (chatWindow) {
this.chatWindowService.close(chatWindow);
await this.chatWindowService.close(chatWindow);
}
super.unpin(...arguments);
},
@@ -5,8 +5,8 @@ import { ChatWindowService } from "@mail/core/common/chat_window_service";
import { patch } from "@web/core/utils/patch";
patch(ChatWindowService.prototype, {
close(chatWindow) {
super.close(...arguments);
async _onClose(chatWindow) {
await super._onClose(...arguments);
this.notifyState(chatWindow);
},
hide(chatWindow) {
@@ -90,11 +90,11 @@ export class DiscussCoreWeb {
if (data.serverFoldState && thread && data.serverFoldState !== thread.state) {
thread.state = data.serverFoldState;
if (thread.state === "closed") {
const chatWindow = this.store.ChatWindow.records.find((chatWindow) =>
const chatWindow = this.store.discuss.chatWindows.find((chatWindow) =>
chatWindow.thread?.eq(thread)
);
if (chatWindow) {
this.chatWindowService.close(chatWindow);
await this.chatWindowService.close(chatWindow);
}
} else {
this.store.ChatWindow.insert({