[REF] mail: support multi-data insertion in discuss model

Before this commit, when having an array of data to insert
in a model, we had to iterate and insert data item on by one:
```js
messageDataList.forEach(data => this.store.Message.insert(data));
```

With this commit, we can simply insert the array of data to
insert:
```js
this.store.Message.insert(messageDataList);
```

This helps simplifying the business code further, less LOCs is
usually better!

Part-of: odoo/odoo#139501
This commit is contained in:
Alexandre Kühn
2023-10-26 13:41:16 +00:00
parent c0da919697
commit f97012e0ed
32 changed files with 73 additions and 134 deletions
@@ -14,11 +14,8 @@ export class Attachment extends FileModelMixin(Record) {
static get(data) {
return super.get(data);
}
/** @returns {import("models").Attachment} */
/** @returns {import("models").Attachment|import("models").Attachment[]} */
static insert(data) {
if (!("id" in data)) {
throw new Error("Cannot insert attachment: id is missing in data");
}
return super.insert(...arguments);
}
static new(data) {
@@ -10,7 +10,7 @@ export class CannedResponse extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").CannedResponse} */
/** @returns {import("models").CannedResponse|import("models").CannedResponse[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -17,7 +17,7 @@ export class ChannelMember extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").ChannelMember} */
/** @returns {import("models").ChannelMember|import("models").ChannelMember[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -15,7 +15,7 @@ export class ChatWindow extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").ChatWindow} */
/** @returns {import("models").ChatWindow|import("models").ChatWindow[]} */
static insert() {
return super.insert(...arguments);
}
@@ -8,12 +8,8 @@ export class Composer extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").Composer} */
/** @returns {import("models").Composer|import("models").Composer[]} */
static insert(data) {
const { message, thread } = data;
if (Boolean(message) === Boolean(thread)) {
throw new Error("Composer shall have a thread xor a message.");
}
return super.insert(...arguments);
}
@@ -8,7 +8,7 @@ export class DiscussAppCategory extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").DiscussAppCategory} */
/** @returns {import("models").DiscussAppCategory|import("models").DiscussAppCategory[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -37,7 +37,7 @@ export class DiscussApp extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").DiscussApp} */
/** @returns {import("models").DiscussApp|import("models").DiscussApp[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -25,7 +25,7 @@ export class Failure extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").Failure} */
/** @returns {import("models").Failure|import("models").Failure[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -10,7 +10,7 @@ export class Follower extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").Follower} */
/** @returns {import("models").Follower|import("models").Follower[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -8,7 +8,7 @@ export class LinkPreview extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").LinkPreview} */
/** @returns {import("models").LinkPreview|import("models").LinkPreview[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -26,7 +26,7 @@ export class MailCoreCommon {
this.busService.subscribe("ir.attachment/delete", (payload) => {
const { id: attachmentId, message: messageData } = payload;
if (messageData) {
this.store.Message.insert({ ...messageData });
this.store.Message.insert(messageData);
}
const attachment = this.store.Attachment.get(attachmentId);
if (attachment) {
@@ -54,18 +54,13 @@ export class MailCoreCommon {
}
});
this.busService.subscribe("mail.message/notification_update", (payload) => {
payload.elements.map((message) => {
this.store.Message.insert(
{
...message,
// implicit: failures are sent by the server at
// initialization only if the current partner is
// author of the message
author: this.store.self,
},
{ html: true }
);
});
// implicit: failures are sent by the server at
// initialization only if the current partner is
// author of the message
this.store.Message.insert(
payload.elements.map((data) => ({ ...data, author: this.store.self })),
{ html: true }
);
});
this.busService.subscribe("mail.message/toggle_star", (payload) => {
const { message_ids: messageIds, starred } = payload;
@@ -88,26 +83,8 @@ export class MailCoreCommon {
}
});
this.busService.subscribe("mail.record/insert", (payload) => {
if (payload.Thread) {
this.store.Thread.insert(payload.Thread);
}
if (payload.Persona) {
const personas = Array.isArray(payload.Persona)
? payload.Persona
: [payload.Persona];
for (const persona of personas) {
this.store.Persona.insert(persona);
}
}
const { LinkPreview: linkPreviews } = payload;
if (linkPreviews) {
for (const linkPreview of linkPreviews) {
this.store.LinkPreview.insert(linkPreview);
}
}
const { Message: messageData } = payload;
if (messageData) {
this.store.Message.insert(messageData, { html: true });
for (const Model in payload) {
this.store[Model].insert(payload[Model], { html: true });
}
});
});
@@ -33,7 +33,7 @@ export class Message extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").Message} */
/** @returns {import("models").Message|import("models").Message[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -8,7 +8,7 @@ export class MessageReactions extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").MessageReactions} */
/** @returns {import("models").MessageReactions|import("models").MessageReactions[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -84,9 +84,7 @@ export class Messaging {
this.store.discuss.isActive =
data.menu_id === this.router.current.hash?.menu_id ||
this.router.hash?.action === "mail.action_discuss";
(data.shortcodes ?? []).forEach((code) => {
this.store.CannedResponse.insert(code);
});
this.store.CannedResponse.insert(data.shortcodes ?? []);
this.store.hasLinkPreviewFeature = data.hasLinkPreviewFeature;
this.store.initBusId = data.initBusId;
this.store.odoobotOnboarding = data.odoobotOnboarding;
@@ -114,7 +112,7 @@ export class Messaging {
* @return {import("models").Persona[]}
*/
async searchPartners(searchStr = "", limit = 10) {
let partners = [];
const partners = [];
const searchTerm = cleanTerm(searchStr);
for (const localId in this.store.Persona.records) {
const persona = this.store.Persona.records[localId];
@@ -136,9 +134,7 @@ export class Messaging {
searchTerm,
limit,
]);
partners = partnersData.map((data) =>
this.store.Persona.insert({ ...data, type: "partner" })
);
this.store.Persona.insert(partnersData);
}
return partners;
}
@@ -12,7 +12,7 @@ export class Notification extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").Notification} */
/** @returns {import("models").Notification|import("models").Notification[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -20,7 +20,7 @@ export class Persona extends Record {
static get(data) {
return super.get(data);
}
/** @returns {import("models").Persona} */
/** @returns {import("models").Persona|import("models").Persona[]} */
static insert(data) {
return super.insert(...arguments);
}
+11 -4
View File
@@ -680,12 +680,19 @@ export class Record {
static attr(def, { html } = {}) {
return [ATTR_SYM, { default: def, html }];
}
/** @returns {Record} */
static insert(data, { html } = {}) {
/** @returns {Record|Record[]} */
static insert(data, options = {}) {
const isMulti = Array.isArray(data);
if (!isMulti) {
data = [data];
}
const oldTrusted = Record.trusted;
Record.trusted = html ?? Record.trusted;
const res = this._insert(...arguments);
Record.trusted = options.html ?? Record.trusted;
const res = data.map((d) => this._insert(d, options));
Record.trusted = oldTrusted;
if (!isMulti) {
return res[0];
}
return res;
}
/** @returns {Record} */
@@ -29,7 +29,7 @@ export class BaseStore extends Record {
}
export class Store extends BaseStore {
/** @returns {import("models").Store} */
/** @returns {import("models").Store|import("models").Store[]} */
static insert() {
return super.insert(...arguments);
}
@@ -9,7 +9,6 @@ export function useSuggestion() {
const comp = useComponent();
const sequential = useSequential();
const suggestionService = useService("mail.suggestion");
const store = useState(useService("mail.store"));
const self = {
clearRawMentions() {
comp.props.composer.mentionedChannels.length = 0;
@@ -110,9 +109,7 @@ export function useSuggestion() {
});
}
if (option.cannedResponse) {
comp.props.composer.cannedResponses.push(
store.CannedResponse.insert(option.cannedResponse)
);
comp.props.composer.cannedResponses.push(option.cannedResponse);
}
self.clearSearch();
comp.props.composer.textInputContent = textLeft + recordReplacement + " " + textRight;
@@ -14,7 +14,7 @@ export class SuggestionService {
this.env = env;
this.orm = services.orm;
this.store = services["mail.store"];
this.personaService = services['mail.persona'];
this.personaService = services["mail.persona"];
}
getSupportedDelimiters(thread) {
@@ -51,7 +51,7 @@ export class SuggestionService {
[],
kwargs
);
suggestedPartners.forEach((data) => this.store.Persona.insert(data));
this.store.Persona.insert(suggestedPartners);
}
/**
@@ -64,7 +64,7 @@ export class SuggestionService {
[],
{ search: term }
);
suggestedThreads.forEach((data) => this.store.Thread.insert(data));
this.store.Thread.insert(suggestedThreads);
}
/**
@@ -53,14 +53,8 @@ export class Thread extends Record {
// Transform "Thread,<model> AND <id>" to "<model>_<id>""
return localId.split(",").slice(1).join("_").replace(" AND ", "_");
}
/** @returns {import("models").Thread} */
/** @returns {import("models").Thread|import("models").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");
}
return super.insert(...arguments);
}
@@ -191,9 +191,7 @@ export class ThreadService {
after,
before,
});
const messages = rawMessages
.reverse()
.map((data) => this.store.Message.insert(data, { html: true }));
const messages = this.store.Message.insert(rawMessages.reverse(), { html: true });
thread.isLoaded = true;
return messages;
} catch (e) {
@@ -292,9 +290,7 @@ export class ThreadService {
...this.getFetchParams(thread),
around: messageId,
});
thread.messages = messages
.reverse()
.map((message) => this.store.Message.insert(message, { html: true }));
thread.messages = this.store.Message.insert(messages.reverse(), { html: true });
thread.loadNewer = messageId ? true : false;
thread.loadOlder = true;
if (messages.length < FETCH_LIMIT) {
@@ -968,7 +964,7 @@ export class ThreadService {
return {
count,
loadMore: messages.length === FETCH_LIMIT,
messages: messages.map((message) => this.store.Message.insert(message, { html: true })),
messages: this.store.Message.insert(messages, { html: true }),
};
}
}
@@ -87,8 +87,6 @@ export class ActivityListPopover extends Component {
context: this.user.user_context,
}
);
for (const activityData of activitiesData) {
this.store.Activity.insert(activityData);
}
this.store.Activity.insert(activitiesData, { html: true });
}
}
@@ -46,7 +46,7 @@ export class Activity extends Record {
* @param {Data} data
* @param {Object} [param1]
* @param {boolean} param1.broadcast
* @returns {import("models").Activity}
* @returns {import("models").Activity|import("models").Activity[]}
*/
static insert(data, { broadcast = true } = {}) {
return super.insert(...arguments);
@@ -52,7 +52,7 @@ export class ActivityService {
return action;
}
async edit(activityId){
async edit(activityId) {
return new Promise((resolve) =>
this.env.services.action.doAction(
{
@@ -74,16 +74,20 @@ export class ActivityService {
active_model: resModel,
active_ids: resIds,
active_id: resIds[0],
...((defaultActivityTypeId !== undefined) ? {
default_activity_type_id: defaultActivityTypeId,
} : {}),
...(defaultActivityTypeId !== undefined
? {
default_activity_type_id: defaultActivityTypeId,
}
: {}),
};
return new Promise((resolve) =>
this.env.services.action.doAction(
{
type: "ir.actions.act_window",
name: (resIds && resIds.length > 1) ? _t("Schedule Activity On Selected Records") :
_t("Schedule Activity"),
name:
resIds && resIds.length > 1
? _t("Schedule Activity On Selected Records")
: _t("Schedule Activity"),
res_model: "mail.activity.schedule",
view_mode: "form",
views: [[false, "form"]],
@@ -105,7 +109,7 @@ export class ActivityService {
_onBroadcastChannelMessage({ data }) {
switch (data.type) {
case "INSERT":
this.store.Activity.insert(data.payload, { broadcast: false });
this.store.Activity.insert(data.payload, { broadcast: false, html: true });
break;
case "DELETE": {
const activity = this.store.Activity.insert(data.payload, { broadcast: false });
@@ -23,17 +23,12 @@ export class MailCoreWeb {
setup() {
this.messagingService.isReady.then(() => {
this.rpc("/mail/load_message_failures", {}, { silent: true }).then((messages) => {
messages.map((messageData) =>
this.store.Message.insert(
{
...messageData,
// implicit: failures are sent by the server at
// initialization only if the current partner is
// author of the message
author: this.store.user,
},
{ html: true }
)
// implicit: failures are sent by the server at
// initialization only if the current partner is
// author of the message
this.store.Message.insert(
messages.map((m) => ({ ...m, author: this.store.user })),
{ html: true }
);
this.store.failures.sort((f1, f2) => f2.lastMessage?.id - f1.lastMessage?.id);
});
@@ -1581,11 +1581,6 @@ export const rtcService = {
services["mail.user_settings"].setVolumes(payload);
}
});
services["bus_service"].subscribe("mail.record/insert", (payload) => {
if (payload.RtcSession) {
rtc.store.RtcSession.insert(payload.RtcSession);
}
});
return rtc;
},
};
@@ -10,7 +10,7 @@ export class RtcSession extends Record {
static get(data) {
return super.get(data);
}
/** @returns {number, import("models").RtcSession} */
/** @returns {import("models").RtcSession|import("models").RtcSession[]} */
static insert(data) {
return super.insert(...arguments);
}
@@ -53,15 +53,7 @@ export class ChannelInvitation extends Component {
if (!results) {
return;
}
const Partners = results["partners"];
const selectablePartners = [];
for (const selectablePartner of Partners) {
const newPartner = this.store.Persona.insert({
type: "partner",
...selectablePartner,
});
selectablePartners.push(newPartner);
}
const selectablePartners = this.store.Persona.insert(results.partners);
this.state.selectablePartners = this.suggestionService.sortPartnerSuggestions(
selectablePartners,
this.searchStr,
@@ -39,7 +39,7 @@ patch(ThreadService.prototype, {
channel_id: thread.id,
limit,
});
const attachments = rawAttachments.map((a) => this.store.Attachment.insert(a));
const attachments = this.store.Attachment.insert(rawAttachments);
if (attachments.length < limit) {
thread.areAttachmentsLoaded = true;
}
@@ -80,9 +80,7 @@ export class MessagePin {
const messagesData = await this.rpcService("/discuss/channel/pinned_messages", {
channel_id: channel.id,
});
messagesData.forEach((messageData) =>
this.store.Message.insert(messageData, { html: true })
);
this.store.Message.insert(messagesData, { html: true });
channel.pinnedMessagesState = "loaded";
} catch (e) {
channel.pinnedMessagesState = "error";
@@ -74,10 +74,8 @@ QUnit.test("Assign & Delete on fields with inverses", async (assert) => {
}).register();
const store = await start();
const thread = store.Thread.insert("General");
const john = store.Member.insert("John");
const marc = store.Member.insert("Marc");
const hello = store.Message.insert("hello");
const world = store.Message.insert("world");
const [john, marc] = store.Member.insert(["John", "Marc"]);
const [hello, world] = store.Message.insert(["hello", "world"]);
// Assign on fields should adapt inverses
Object.assign(thread, { composer: {}, members: [["ADD", john]], messages: [hello, world] });
assert.ok(thread.composer);
@@ -132,8 +130,7 @@ QUnit.test("Computed relational field", async (assert) => {
}).register();
const store = await start();
const thread = store.Thread.insert("General");
const john = store.Persona.insert("John");
const marc = store.Persona.insert("Marc");
const [john, marc] = store.Persona.insert(["John", "Marc"]);
Object.assign(thread, { members: [john, marc] });
assert.ok(thread.admin.eq(john));
thread.members.delete(john);