[IMP] mail, sms, snailmail: remove default date on message

We should not assume that the default date of the message is "now". A message
should only have a date when it's coming from the database that supply it.

closes odoo/odoo#70069

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
Didier (did)
2021-06-08 13:25:25 +00:00
parent e592314964
commit 40f2c764b0
14 changed files with 125 additions and 62 deletions
@@ -37,9 +37,11 @@
</div>
</t>
<t t-else="">
<div class="o_Message_date o_Message_sidebarItem o-message-squashed" t-att-class="{ 'o-message-selected': isSelected }">
<t t-esc="shortTime"/>
</div>
<t t-if="message.date">
<div class="o_Message_date o_Message_sidebarItem o-message-squashed" t-att-class="{ 'o-message-selected': isSelected }">
<t t-esc="shortTime"/>
</div>
</t>
<div class="o_Message_commands o_Message_sidebarCommands o_Message_sidebarItem o-message-squashed" t-att-class="{ 'o-message-selected': isSelected, 'o-mobile': env.messaging.device.isMobile }">
<t t-if="message.message_type !== 'notification'">
<div class="o_Message_command o_Message_commandStar fa"
@@ -76,9 +78,11 @@
Anonymous
</div>
</t>
<div class="o_Message_date o_Message_headerDate" t-att-class="{ 'o-message-selected': isSelected }" t-att-title="datetime">
- <t t-esc="message.dateFromNow"/>
</div>
<t t-if="message.date">
<div class="o_Message_date o_Message_headerDate" t-att-class="{ 'o-message-selected': isSelected }" t-att-title="datetime">
- <t t-esc="message.dateFromNow"/>
</div>
</t>
<t t-if="message.isCurrentPartnerAuthor and threadView and threadView.thread and threadView.thread.hasSeenIndicators">
<MessageSeenIndicator class="o_Message_seenIndicator" messageLocalId="message.localId" threadLocalId="threadView.thread.localId"/>
</t>
@@ -56,6 +56,7 @@ QUnit.test('basic rendering', async function (assert) {
const message = this.env.models['mail.message'].create({
author: insert({ id: 7, display_name: "Demo User" }),
body: "<p>Test</p>",
date: moment(),
id: 100,
});
await this.createMessageComponent(message);
@@ -172,6 +172,11 @@ class MessageList extends Component {
* @returns {string}
*/
getDateDay(message) {
if (!message.date) {
// Without a date, we assume that it's a today message. This is
// mainly done to avoid flicker inside the UI.
return this.env._t("Today");
}
const date = message.date.format('YYYY-MM-DD');
if (date === moment().format('YYYY-MM-DD')) {
return this.env._t("Today");
@@ -293,7 +298,10 @@ class MessageList extends Component {
if (!this.props.hasSquashCloseMessages) {
return false;
}
if (Math.abs(message.date.diff(prevMessage.date)) > 60000) {
if (!prevMessage.date && message.date) {
return false;
}
if (message.date && prevMessage.date && Math.abs(message.date.diff(prevMessage.date)) > 60000) {
// more than 1 min. elasped
return false;
}
@@ -18,9 +18,11 @@
(<t t-esc="group.notifications.length"/>)
</span>
<span class="o-autogrow"/>
<span class="o_NotificationGroup_date">
<t t-esc="group.date.fromNow()"/>
</span>
<t t-if="group.date">
<span class="o_NotificationGroup_date">
<t t-esc="group.date.fromNow()"/>
</span>
</t>
</div>
<div class="o_NotificationGroup_core">
<span class="o_NotificationGroup_coreItem o_NotificationGroup_inlineText">
@@ -84,7 +84,7 @@ class NotificationList extends Component {
return 1;
}
if (t1.lastNeedactionMessageAsOriginThread && t2.lastNeedactionMessageAsOriginThread) {
return t1.lastNeedactionMessageAsOriginThread.date.isBefore(t2.lastNeedactionMessageAsOriginThread.date) ? 1 : -1;
return t1.lastNeedactionMessageAsOriginThread.id < t2.lastNeedactionMessageAsOriginThread.id ? 1 : -1;
}
if (t1.lastNeedactionMessageAsOriginThread) {
return -1;
@@ -112,7 +112,7 @@ class NotificationList extends Component {
return 1;
}
if (t1.lastMessage && t2.lastMessage) {
return t1.lastMessage.date.isBefore(t2.lastMessage.date) ? 1 : -1;
return t1.lastMessage.id < t2.lastMessage.id ? 1 : -1;
}
if (t1.lastMessage) {
return -1;
@@ -133,9 +133,8 @@ class NotificationList extends Component {
if (props.filter === 'all') {
const notificationGroups = this.env.messaging.notificationGroupManager.groups;
notifications = Object.values(notificationGroups)
.sort((group1, group2) =>
group1.date.isAfter(group2.date) ? -1 : 1
).map(notificationGroup => {
.sort((group1, group2) => group1.sequence - group2.sequence)
.map(notificationGroup => {
return {
notificationGroup,
uniqueId: notificationGroup.localId,
@@ -419,13 +419,12 @@ QUnit.test('different mail.channel are not grouped', async function (assert) {
);
});
QUnit.test('multiple grouped notifications by document model, sorted by date desc', async function (assert) {
QUnit.test('multiple grouped notifications by document model, sorted by the most recent message of each group', async function (assert) {
assert.expect(9);
this.data['mail.message'].records.push(
// first message that is expected to have a failure
{
date: moment.utc().format("YYYY-MM-DD HH:mm:ss"), // random date
id: 11, // random unique id, will be used to link failure to message
message_type: 'email', // message must be email (goal of the test)
model: 'res.partner', // different model from second message
@@ -434,8 +433,6 @@ QUnit.test('multiple grouped notifications by document model, sorted by date des
},
// second message that is expected to have a failure
{
// random date, later than first message
date: moment.utc().add(1, 'days').format("YYYY-MM-DD HH:mm:ss"),
id: 12, // random unique id, will be used to link failure to message
message_type: 'email', // message must be email (goal of the test)
model: 'res.company', // different model from first message
@@ -31,7 +31,7 @@
(<t t-esc="thread.needactionMessagesAsOriginThread.length"/>)
</span>
<span class="o-autogrow"/>
<t t-if="thread.lastNeedactionMessageAsOriginThread">
<t t-if="thread.lastNeedactionMessageAsOriginThread and thread.lastNeedactionMessageAsOriginThread.date">
<span class="o_ThreadNeedactionPreview_date">
<t t-esc="thread.lastNeedactionMessageAsOriginThread.date.fromNow()"/>
</span>
@@ -34,7 +34,7 @@
</span>
</t>
<span class="o-autogrow"/>
<t t-if="thread.lastMessage">
<t t-if="thread.lastMessage and thread.lastMessage.date">
<span class="o_ThreadPreview_date" t-att-class="{ 'o-muted': thread.localMessageUnreadCounter === 0 }">
<t t-esc="thread.lastMessage.date.fromNow()"/>
</span>
@@ -586,9 +586,10 @@ function factory(dependencies) {
inverse: 'checkedMessages',
readonly: true,
}),
date: attr({
default: moment(),
}),
/**
* Determines the date of the message as a moment object.
*/
date: attr(),
/**
* States the time elapsed since date up to now.
*/
@@ -2,7 +2,7 @@
import { registerNewModel } from '@mail/model/model_core';
import { attr, many2one, one2many } from '@mail/model/model_field';
import { insert, unlink } from '@mail/model/model_field_command';
import { clear, insert, unlink } from '@mail/model/model_field_command';
function factory(dependencies) {
@@ -51,13 +51,17 @@ function factory(dependencies) {
* @returns {mail.thread|undefined}
*/
_computeThread() {
if (this.res_id) {
return insert({
id: this.res_id,
model: this.res_model,
});
const notificationsThreadIds = this.notifications
.filter(notification => notification.message && notification.message.originThread)
.map(notification => notification.message.originThread.id);
const threadIds = new Set(notificationsThreadIds);
if (threadIds.size !== 1) {
return unlink();
}
return unlink();
return insert({
id: notificationsThreadIds[0],
model: this.res_model,
});
}
/**
@@ -67,6 +71,32 @@ function factory(dependencies) {
return `${this.modelName}_${data.id}`;
}
/**
* Compute the most recent date inside the notification messages.
*
* @private
* @returns {moment|undefined}
*/
_computeDate() {
const dates = this.notifications
.filter(notification => notification.message && notification.message.date)
.map(notification => notification.message.date);
if (dates.length === 0) {
return clear();
}
return moment.max(dates);
}
/**
* Compute the position of the group inside the notification list.
*
* @private
* @returns {number}
*/
_computeSequence() {
return -Math.max(...this.notifications.map(notification => notification.message.id));
}
/**
* Opens the view that displays all the records of the group.
*
@@ -97,23 +127,63 @@ function factory(dependencies) {
}
NotificationGroup.fields = {
date: attr(),
/**
* States the most recent date of all the notification message.
*/
date: attr({
compute: '_computeDate',
dependencies: [
'notifications',
'notificationsMessage',
'notificationsMessageDate',
],
}),
id: attr({
required: true,
}),
notification_type: attr(),
notifications: one2many('mail.notification'),
res_id: attr(),
/**
* Serves as compute dependency.
*/
notificationsMessage: one2many('mail.message', {
related: 'notifications.message',
}),
/**
* Serves as compute dependency.
*/
notificationsMessageDate: attr({
related: 'notificationsMessage.date',
}),
/**
* Serves as compute dependency.
*/
notificationsMessageOriginThread: one2many('mail.thread', {
related: 'notificationsMessage.originThread',
}),
res_model: attr(),
res_model_name: attr(),
/**
* States the position of the group inside the notification list.
*/
sequence: attr({
compute: '_computeSequence',
default: 0,
dependencies: [
'notifications',
'notificationsMessage',
],
}),
/**
* Related thread when the notification group concerns a single thread.
*/
thread: many2one('mail.thread', {
compute: '_computeThread',
dependencies: [
'res_id',
'res_model',
'notifications',
'notificationsMessage',
'notificationsMessageOriginThread',
],
})
};
@@ -33,25 +33,6 @@ function factory(dependencies) {
res_model_name: thread.model_name,
});
group.update({ notifications: link(notification) });
// keep res_id only if all notifications are for the same record
// set null if multiple records are present in the group
let res_id = group.res_id;
if (group.res_id === undefined) {
res_id = thread.id;
} else if (group.res_id !== thread.id) {
res_id = null;
}
// keep only the most recent date from all notification messages
let date = group.date;
if (!date) {
date = notification.message.date;
} else {
date = moment.max(group.date, notification.message.date);
}
group.update({
date,
res_id,
});
// avoid linking the same group twice when adding a notification
// to an existing group
if (!groups.includes(group)) {
@@ -124,7 +124,7 @@ class MockModels {
attachment_ids: { string: "Attachments", type: 'many2many', relation: 'ir.attachment', default: [] },
author_id: { string: "Author", type: 'many2one', relation: 'res.partner', default() { return this.currentPartnerId; } },
body: { string: "Contents", type: 'html', default: "<p></p>" },
date: { string: "Date", type: 'datetime' },
date: { string: "Date", type: 'datetime', default() { return moment.utc().format("YYYY-MM-DD HH:mm:ss"); } },
email_from: { string: "From", type: 'char' },
history_partner_ids: { string: "Partners with History", type: 'many2many', relation: 'res.partner' },
id: { string: "Id", type: 'integer' },
@@ -108,7 +108,7 @@ QUnit.test('notifications grouped by notification_type', async function (assert)
// first message that is expected to have a failure
{
id: 11, // random unique id, will be used to link failure to message
message_type: 'email', // different type from second message
message_type: 'sms', // different type from second message
model: 'res.partner', // same model as second message (and not `mail.channel`)
res_id: 31, // same res_id as second message
res_model_name: "Partner", // random related model name
@@ -116,7 +116,7 @@ QUnit.test('notifications grouped by notification_type', async function (assert)
// second message that is expected to have a failure
{
id: 12, // random unique id, will be used to link failure to message
message_type: 'sms', // different type from first message
message_type: 'email', // different type from first message
model: 'res.partner', // same model as first message (and not `mail.channel`)
res_id: 31, // same res_id as first message
res_model_name: "Partner", // same related model name for consistency
@@ -127,13 +127,13 @@ QUnit.test('notifications grouped by notification_type', async function (assert)
{
mail_message_id: 11, // id of the related first message
notification_status: 'exception', // necessary value to have a failure
notification_type: 'email', // different type from second failure
notification_type: 'sms', // different type from second failure
},
// second failure that is expected to be used in the test
{
mail_message_id: 12, // id of the related second message
notification_status: 'exception', // necessary value to have a failure
notification_type: 'sms', // different type from first failure
notification_type: 'email', // different type from first failure
}
);
await this.start();
@@ -103,7 +103,7 @@ QUnit.test('notifications grouped by notification_type', async function (assert)
// first message that is expected to have a failure
{
id: 11, // random unique id, will be used to link failure to message
message_type: 'email', // different type from second message
message_type: 'snailmail', // different type from second message
model: 'res.partner', // same model as second message (and not `mail.channel`)
res_id: 31, // same res_id as second message
res_model_name: "Partner", // random related model name
@@ -111,7 +111,7 @@ QUnit.test('notifications grouped by notification_type', async function (assert)
// second message that is expected to have a failure
{
id: 12, // random unique id, will be used to link failure to message
message_type: 'snailmail', // different type from first message
message_type: 'email', // different type from first message
model: 'res.partner', // same model as first message (and not `mail.channel`)
res_id: 31, // same res_id as first message
res_model_name: "Partner", // same related model name for consistency
@@ -122,13 +122,13 @@ QUnit.test('notifications grouped by notification_type', async function (assert)
{
mail_message_id: 11, // id of the related first message
notification_status: 'exception', // necessary value to have a failure
notification_type: 'email', // different type from second failure
notification_type: 'snail', // different type from second failure
},
// second failure that is expected to be used in the test
{
mail_message_id: 12, // id of the related second message
notification_status: 'exception', // necessary value to have a failure
notification_type: 'snail', // different type from first failure
notification_type: 'email', // different type from first failure
}
);
await this.start();