[REF] mail: support passing commands as data of relational field

This significantly eases processing command data on relational field
received from server.

closes odoo/odoo#137341

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Alexandre Kühn
2023-10-03 20:17:47 +00:00
parent 3b251d6d66
commit 9a70545913
9 changed files with 77 additions and 133 deletions
@@ -22,42 +22,30 @@ export class ChannelMember extends Record {
* @returns {import("models").ChannelMember}
*/
static insert(data) {
const memberData = Array.isArray(data) ? data[1] : data;
/** @type {import("models").ChannelMember} */
const member = this.preinsert(memberData);
const member = this.preinsert(data);
member.update(data);
return member;
}
update(data) {
const [command, memberData] = Array.isArray(data) ? data : ["ADD", data];
this.id = memberData.id;
if ("persona" in memberData) {
this.id = data.id;
if ("persona" in data) {
this.persona = {
...(memberData.persona.partner ?? memberData.persona.guest),
type: memberData.persona.guest ? "guest" : "partner",
country: memberData.persona.partner?.country,
channelId: memberData.persona.guest ? memberData.channel.id : null,
...(data.persona.partner ?? data.persona.guest),
type: data.persona.guest ? "guest" : "partner",
country: data.persona.partner?.country,
channelId: data.persona.guest ? data.channel.id : null,
};
}
let thread = memberData.thread ?? this.thread;
if (!thread && memberData.channel?.id) {
let thread = data.thread ?? this.thread;
if (!thread && data.channel?.id) {
thread = {
id: memberData.channel.id,
id: data.channel.id,
model: "discuss.channel",
};
}
if (thread && !this.thread) {
this.thread = thread;
}
switch (command) {
case "ADD":
this.thread?.channelMembers.add(this);
break;
case "DELETE":
this.delete();
break;
}
this.thread ??= thread;
}
/** @type {number} */
@@ -103,31 +103,8 @@ export class Message extends Record {
partner: this._store.self,
};
}
if (data.messageReactionGroups) {
const reactionContentToUnlink = new Set();
const reactionsToInsert = [];
for (const rawReaction of data.messageReactionGroups) {
const [command, reactionData] = Array.isArray(rawReaction)
? rawReaction
: ["ADD", rawReaction];
const reaction = this._store.MessageReactions.insert(reactionData);
if (command === "ADD") {
reactionsToInsert.push(reaction);
} else {
reactionContentToUnlink.add(reaction.content);
}
}
this.reactions = this.reactions.filter(
({ content }) => !reactionContentToUnlink.has(content)
);
reactionsToInsert.forEach((reaction) => {
const idx = this.reactions.findIndex(({ content }) => reaction.content === content);
if (idx !== -1) {
this.reactions[idx] = reaction;
} else {
this.reactions.push(reaction);
}
});
if ("messageReactionGroups" in data) {
this.reactions = data.messageReactionGroups;
}
if (this.isNotification && !this.notificationType) {
const parser = new DOMParser();
@@ -46,19 +46,9 @@ export class NotificationGroup extends Record {
type: data.type ?? this.type,
status: data.status ?? this.status,
});
const notifications = data.notifications ?? [];
const alreadyKnownNotifications = new Set(this.notifications.map(({ id }) => id));
const notificationIdsToRemove = new Set();
for (const [commandName, notification] of notifications) {
if (commandName === "ADD" && !alreadyKnownNotifications.has(notification.id)) {
this.notifications.push(notification);
} else if (commandName === "DELETE") {
notificationIdsToRemove.add(notification.id);
}
if ("notifications" in data) {
this.notifications = data.notifications;
}
this.notifications = this.notifications.filter(
({ id }) => !notificationIdsToRemove.has(id)
);
this.lastMessage = this.notifications[0]?.message;
for (const notification of this.notifications) {
if (this.lastMessage?.id < notification.message?.id) {
+19 -6
View File
@@ -130,6 +130,8 @@ export class RecordList extends Array {
if (r2 && r2.notEq(r3)) {
receiver.__deleteInverse__(r2);
}
const { onDelete } = receiver.owner.Model.__rels__.get(receiver.name);
onDelete?.call(receiver.owner, r2);
receiver.__list__[index] = r3?.localId;
if (r3) {
receiver.__addInverse__(r3);
@@ -178,6 +180,8 @@ export class RecordList extends Array {
if (r2) {
this.__deleteInverse__(r2);
}
const { onDelete } = this.owner.Model.__rels__.get(this.name);
onDelete?.call(this.owner, r2);
return r2;
}
/** @returns {R} */
@@ -186,6 +190,8 @@ export class RecordList extends Array {
if (r2) {
this.__deleteInverse__(r2);
}
const { onDelete } = this.owner.Model.__rels__.get(this.name);
onDelete?.call(this.owner, r2);
return r2;
}
/** @param {R[]} records */
@@ -265,6 +271,8 @@ export class RecordList extends Array {
this.__list__.splice(start, deleteCount, ...newRecords.map((r) => r.localId));
for (const r of oldRecords) {
this.__deleteInverse__(r);
const { onDelete } = this.owner.Model.__rels__.get(this.name);
onDelete?.call(this.owner, r);
}
for (const r of newRecords) {
this.__addInverse__(r);
@@ -419,19 +427,21 @@ export class Record {
}
/**
* @template {keyof import("model ").Models} M
* @param {M} modelName
* @param {M} targetModel
* @returns {import("models").Models[M]}
*/
static one(modelName) {
return [ONE_SYM, modelName];
static one(targetModel) {
return [ONE_SYM, { targetModel }];
}
/**
* @template {keyof import("model").Models} M
* @param {M} modelName
* @param {M} targetModel
* @param {(r) => void} [onDelete] function that is called when a record is removed
* from the relation.
* @returns {import("models").Models[M][]}
*/
static many(modelName) {
return [MANY_SYM, modelName];
static many(targetModel, { onDelete } = {}) {
return [MANY_SYM, { targetModel, onDelete }];
}
/**
* @param {Object} data
@@ -449,6 +459,9 @@ export class Record {
static preinsert(data) {
return this.get(data) ?? this.new(data);
}
static isCommand(data) {
return ["ADD", "DELETE"].includes(data?.[0]?.[0]);
}
/**
* Raw relational values of the record, each of which contains object id(s)
@@ -209,6 +209,26 @@ export const storeService = {
const r1 = receiver;
/** @type {RecordList<Record>} */
const l1 = r1.__rels__.get(name);
if (Record.isCommand(val)) {
for (const [cmd, cmdData] of val) {
if (Array.isArray(cmdData)) {
for (const item of cmdData) {
if (cmd === "ADD") {
l1.add(item);
} else {
l1.delete(item);
}
}
} else {
if (cmd === "ADD") {
l1.add(cmdData);
} else {
l1.delete(cmdData);
}
}
}
return true;
}
/** @type {Record[]|Set<Record>|RecordList<Record>} */
const collection = val;
const oldRecords = l1.slice();
@@ -233,6 +253,10 @@ export const storeService = {
if (r2) {
r2.__invs__.delete(r1.localId, name);
}
if (Record.isCommand(val)) {
const [cmd, cmdData] = val.at(-1);
val = cmd === "ADD" ? cmdData : null;
}
if ([null, false, undefined].includes(val)) {
delete receiver[name];
return true;
@@ -260,7 +284,7 @@ export const storeService = {
if (![Record.one()[0], Record.many()[0]].includes(val?.[0])) {
continue;
}
Model.__rels__.set(name, { targetModel: val[1] });
Model.__rels__.set(name, val[1]);
}
}
// Make true store (as a model)
@@ -131,16 +131,12 @@ export class Thread extends Record {
this.customName = serverData.channel.custom_channel_name;
}
if (serverData.channel?.channelMembers) {
for (const [command, membersData] of serverData.channel.channelMembers) {
const members = Array.isArray(membersData) ? membersData : [membersData];
for (const memberData of members) {
const member = this._store.ChannelMember.insert([command, memberData]);
if (this.type !== "chat") {
continue;
}
this.channelMembers = serverData.channel.channelMembers;
if (this.type === "chat") {
for (const member of this.channelMembers) {
if (
member.persona.notEq(this._store.user) ||
(serverData.channel.channelMembers[0][1].length === 1 &&
(this.channelMembers.length === 1 &&
member.persona?.eq(this._store.user))
) {
this.chatPartner = member.persona;
@@ -149,26 +145,7 @@ export class Thread extends Record {
}
}
if ("invitedMembers" in serverData) {
if (!serverData.invitedMembers) {
this.invitedMembers = [];
return;
}
const command = serverData.invitedMembers[0][0];
const members = serverData.invitedMembers[0][1];
switch (command) {
case "ADD":
if (members) {
for (const member of members) {
this.invitedMembers.add(member);
}
}
break;
case "DELETE":
for (const member of members) {
this.invitedMembers.delete(member);
}
break;
}
this.invitedMembers = serverData.invitedMembers;
}
if ("seen_partners_info" in serverData) {
this.seenInfos = serverData.seen_partners_info.map(
@@ -206,8 +183,13 @@ export class Thread extends Record {
activeRtcSession = Record.one("RtcSession");
/** @type {object|undefined} */
channel;
channelMembers = Record.many("ChannelMember");
rtcSessions = Record.many("RtcSession");
channelMembers = Record.many("ChannelMember", { onDelete: (r) => r.delete() });
rtcSessions = Record.many("RtcSession", {
/** @this {import("models").Thread} */
onDelete(r) {
this._store.env.services["discuss.rtc"].deleteSession(r.id);
},
});
rtcInvitingSession = Record.one("RtcSession");
invitedMembers = Record.many("ChannelMember");
chatPartner = Record.one("Persona");
@@ -73,7 +73,7 @@ export class ThreadService {
thread.memberCount = results["memberCount"];
for (const channelMember of channelMembers) {
if (channelMember.persona || channelMember.partner) {
this.store.ChannelMember.insert({ ...channelMember, thread });
thread.channelMembers.add({ ...channelMember, thread });
}
}
}
@@ -1522,24 +1522,13 @@ export class Rtc {
}
}
updateRtcSessions(channelId, sessionsData, command) {
updateRtcSessions(channelId, sessionsData) {
const channel = this.store.Thread.get({ model: "discuss.channel", id: channelId });
if (!channel) {
return;
}
const oldCount = Object.keys(channel.rtcSessions).length;
switch (command) {
case "DELETE":
for (const sessionData of sessionsData) {
this.deleteSession(sessionData.id);
}
break;
case "ADD":
for (const sessionData of sessionsData) {
channel.rtcSessions.add(sessionData);
}
break;
}
channel.rtcSessions = sessionsData;
if (channel.rtcSessions.length > oldCount) {
this.soundEffectsService.play("channel-join");
} else if (channel.rtcSessions.length < oldCount) {
@@ -1582,16 +1571,11 @@ export const rtcService = {
services["bus_service"].subscribe(
"discuss.channel/rtc_sessions_update",
({ id, rtcSessions }) => {
const sessionsData = rtcSessions[0][1];
const command = rtcSessions[0][0];
rtc.updateRtcSessions(id, sessionsData, command);
rtc.updateRtcSessions(id, rtcSessions);
}
);
services["bus_service"].subscribe("discuss.channel/joined", ({ channel }) => {
const rtcSessions = channel.rtcSessions;
const sessionsData = rtcSessions[0][1];
const command = rtcSessions[0][0];
rtc.updateRtcSessions(channel.id, sessionsData, command);
rtc.updateRtcSessions(channel.id, channel.rtcSessions);
});
services["bus_service"].subscribe("mail.record/insert", (payload) => {
if (payload.RtcSession) {
@@ -25,21 +25,7 @@ patch(Thread.prototype, {
}
}
if (rtcContinue && "rtcSessions" in data) {
for (const command of data.rtcSessions) {
const sessionsData = command[1];
switch (command[0]) {
case "DELETE":
for (const rtcSessionData of sessionsData) {
this.Model.env.services["discuss.rtc"].deleteSession(rtcSessionData.id);
}
break;
case "ADD":
for (const rtcSessionData of sessionsData) {
this.rtcSessions.add(rtcSessionData);
}
break;
}
}
this.rtcSessions = data.rtcSessions;
}
},
});