summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikunj Ladava <nla@odoo.com>2018-02-02 06:21:44 +0000
committerXavier-Do <xdo@odoo.com>2019-08-05 11:50:19 +0000
commit0c37b9fb39005eab70ee8b39da84f79c981b2695 (patch)
treef365ab14cca2a776cb0c91587f841ba95257f8de
parent634bdbfa3a316fcaa9323f97bb59f198cd771b27 (diff)
[IMP] mail, test_mail: new 'History' mailbox in Discuss
This commit adds a new mailbox in the Discuss app called 'History'. Messages that have been marked as read are moved to 'History'. Notifications are required to link history messages to users. So notifications are now deleted after they are more tha 6 months old. This means History mailbox keeps less than 6 months old messages. Note that this feature only works when user notifications are handled in Odoo. This can be set in the user preferences, under the "Notification Management" section. Task-ID 402597
-rw-r--r--addons/mail/data/ir_cron_data.xml12
-rw-r--r--addons/mail/models/mail_message.py83
-rw-r--r--addons/mail/models/mail_notification.py25
-rw-r--r--addons/mail/static/src/js/discuss_mobile.js8
-rw-r--r--addons/mail/static/src/js/models/messages/message.js21
-rw-r--r--addons/mail/static/src/js/models/threads/mailbox.js3
-rw-r--r--addons/mail/static/src/js/models/threads/searchable_thread.js3
-rw-r--r--addons/mail/static/src/js/services/mail_manager.js6
-rw-r--r--addons/mail/static/src/js/services/mail_notification_manager.js2
-rw-r--r--addons/mail/static/src/scss/thread.scss6
-rw-r--r--addons/mail/static/src/xml/discuss.xml7
-rw-r--r--addons/mail/static/src/xml/thread.xml6
-rw-r--r--addons/mail/static/tests/discuss_tests.js411
-rw-r--r--addons/test_mail/tests/common.py2
-rw-r--r--addons/test_mail/tests/test_mail_message.py8
15 files changed, 510 insertions, 93 deletions
diff --git a/addons/mail/data/ir_cron_data.xml b/addons/mail/data/ir_cron_data.xml
index 27ae8795804..10073110a3a 100644
--- a/addons/mail/data/ir_cron_data.xml
+++ b/addons/mail/data/ir_cron_data.xml
@@ -43,5 +43,17 @@
<field eval="False" name="doall" />
<field name="priority">1000</field>
</record>
+
+ <record id="ir_cron_delete_notification" model="ir.cron">
+ <field name="name">Notification: Delete Notifications older than 6 Month</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">days</field>
+ <field name="numbercall">-1</field>
+ <field name="doall" eval="False"/>
+ <field name="model_id" ref="model_mail_notification"/>
+ <field name="code">model._gc_notifications(max_age_days=180)</field>
+ <field name="state">code</field>
+ </record>
+
</data>
</odoo>
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 38426d629bd..a7061c77d34 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -190,53 +190,28 @@ class Message(models.Model):
#------------------------------------------------------
@api.model
- def mark_all_as_read(self, channel_ids=None, domain=None):
- """ Remove all needactions of the current partner. If channel_ids is
- given, restrict to messages written in one of those channels. """
+ def mark_all_as_read(self, domain=None):
+ # not really efficient method: it does one db request for the
+ # search, and one for each message in the result set is_read to True in the
+ # current notifications from the relation.
partner_id = self.env.user.partner_id.id
- delete_mode = not self.env.user.share # delete employee notifs, keep customer ones
- if not domain and delete_mode:
- query = "DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s"
- args = [(partner_id,)]
- if channel_ids:
- query += """
- AND mail_message_id in
- (SELECT mail_message_id
- FROM mail_message_mail_channel_rel
- WHERE mail_channel_id in %s)"""
- args += [tuple(channel_ids)]
- query += " RETURNING mail_message_id as id"
- self._cr.execute(query, args)
- self.invalidate_cache()
-
- ids = [m['id'] for m in self._cr.dictfetchall()]
- else:
- # not really efficient method: it does one db request for the
- # search, and one for each message in the result set to remove the
- # current user from the relation.
- msg_domain = [('needaction_partner_ids', 'in', partner_id)]
- if channel_ids:
- msg_domain += [('channel_ids', 'in', channel_ids)]
- unread_messages = self.search(expression.AND([msg_domain, domain]))
- notifications = self.env['mail.notification'].sudo().search([
- ('mail_message_id', 'in', unread_messages.ids),
- ('res_partner_id', '=', self.env.user.partner_id.id),
- ('is_read', '=', False)])
- if delete_mode:
- notifications.unlink()
- else:
- notifications.write({'is_read': True})
- ids = unread_messages.mapped('id')
+ msg_domain = [('needaction_partner_ids', 'in', partner_id)]
+ unread_messages = self.search(expression.AND([msg_domain, domain]))
+ ids = unread_messages.ids
+ notifications = self.env['mail.notification'].sudo().search([
+ ('mail_message_id', 'in', ids),
+ ('res_partner_id', '=', partner_id),
+ ('is_read', '=', False)])
+ notifications.write({'is_read': True})
- notification = {'type': 'mark_as_read', 'message_ids': ids, 'channel_ids': channel_ids}
- self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification)
+ notification = {'type': 'mark_as_read', 'message_ids': ids}
+ self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', partner_id), notification)
return ids
def set_message_done(self):
""" Remove the needaction from messages for the current partner. """
partner_id = self.env.user.partner_id
- delete_mode = not self.env.user.share # delete employee notifs, keep customer ones
notifications = self.env['mail.notification'].sudo().search([
('mail_message_id', 'in', self.ids),
@@ -264,10 +239,7 @@ class Message(models.Model):
current_group = [record.id]
current_channel_ids = record.channel_ids
- if delete_mode:
- notifications.unlink()
- else:
- notifications.write({'is_read': True})
+ notifications.write({'is_read': True})
for (msg_ids, channel_ids) in groups:
notification = {'type': 'mark_as_read', 'message_ids': msg_ids, 'channel_ids': [c.id for c in channel_ids]}
@@ -501,20 +473,27 @@ class Message(models.Model):
note_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
# fetch notification status
- notif_dict = {}
- notifs = self.env['mail.notification'].sudo().search([('mail_message_id', 'in', list(mid for mid in message_tree)), ('res_partner_id', '!=', False), ('is_read', '=', False)])
+
+ notif_dict = defaultdict(lambda: defaultdict(list))
+ notifs = self.env['mail.notification'].sudo().search([('mail_message_id', 'in', list(mid for mid in message_tree)), ('res_partner_id', '!=', False)])
+
for notif in notifs:
mid = notif.mail_message_id.id
- if not notif_dict.get(mid):
- notif_dict[mid] = {'partner_id': list()}
- notif_dict[mid]['partner_id'].append(notif.res_partner_id.id)
+ if notif.is_read:
+ notif_dict[mid]['history_partner_ids'].append(notif.res_partner_id.id)
+ else:
+ notif_dict[mid]['needaction_partner_ids'].append(notif.res_partner_id.id)
for message in message_values:
- message['needaction_partner_ids'] = notif_dict.get(message['id'], dict()).get('partner_id', [])
- message['is_note'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == note_id
- message['is_discussion'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == com_id
+ message.update({
+ 'needaction_partner_ids': notif_dict[message['id']]['needaction_partner_ids'],
+ 'history_partner_ids': notif_dict[message['id']]['history_partner_ids'],
+ 'is_note': message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == note_id,
+ 'is_discussion': message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == com_id,
+ 'subtype_description': message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description']
+ })
message['is_notification'] = message['message_type'] == 'user_notification'
- message['subtype_description'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description']
+
if message['model'] and self.env[message['model']]._original_module:
message['module_icon'] = modules.module.get_module_icon(self.env[message['model']]._original_module)
return message_values
diff --git a/addons/mail/models/mail_notification.py b/addons/mail/models/mail_notification.py
index c89ce0fd57d..e47f337e491 100644
--- a/addons/mail/models/mail_notification.py
+++ b/addons/mail/models/mail_notification.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
+from dateutil.relativedelta import relativedelta
+
from odoo import api, fields, models
from odoo.tools.translate import _
@@ -38,6 +40,7 @@ class Notification(models.Model):
("UNKNOWN", "Unknown error"),
], string='Failure type')
failure_reason = fields.Text('Failure reason', copy=False)
+ read_date = fields.Datetime('Read Date', copy=False)
_sql_constraints = [
# email notification;: partner is required
@@ -57,3 +60,25 @@ class Notification(models.Model):
return dict(type(self).failure_type.selection).get(self.failure_type, _('No Error'))
else:
return _("Unknown error") + ": %s" % (self.failure_reason or '')
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ for vals in vals_list:
+ if vals.get('is_read'):
+ vals['read_date'] = fields.Datetime.now()
+ return super(Notification, self).create(vals_list)
+
+ def write(self, vals):
+ if vals.get('is_read'):
+ vals['read_date'] = fields.Datetime.now()
+ return super(Notification, self).write(vals)
+
+ @api.model
+ def _gc_notifications(self, max_age_days=180):
+ domain = [
+ ('is_read', '=', True),
+ ('read_date', '<', fields.Datetime.now() - relativedelta(days=max_age_days)),
+ ('res_partner_id.partner_share', '=', False),
+ ('notification_type', '=', 'email')
+ ]
+ return self.search(domain).unlink()
diff --git a/addons/mail/static/src/js/discuss_mobile.js b/addons/mail/static/src/js/discuss_mobile.js
index 617700c20c1..f90491636a7 100644
--- a/addons/mail/static/src/js/discuss_mobile.js
+++ b/addons/mail/static/src/js/discuss_mobile.js
@@ -71,7 +71,7 @@ Discuss.include({
* @returns {Boolean} true iff we currently are in the Inbox tab
*/
_isInInboxTab: function () {
- return _.contains(['mailbox_inbox', 'mailbox_starred'], this._currentState);
+ return _.contains(['mailbox_inbox', 'mailbox_starred', 'mailbox_history'], this._currentState);
},
/**
* @override
@@ -160,7 +160,7 @@ Discuss.include({
*/
_updateContent: function (type) {
var self = this;
- var inMailbox = type === 'mailbox_inbox' || type === 'mailbox_starred';
+ var inMailbox = _.contains(['mailbox_inbox', 'mailbox_starred', 'mailbox_history'], type);
if (!inMailbox && this._isInInboxTab()) {
// we're leaving the inbox, so store the thread scrolltop
this._storeThreadState();
@@ -230,8 +230,8 @@ Discuss.include({
// update bottom buttons
self.$('.o_mail_mobile_tab').removeClass('active');
- // mailbox_inbox and mailbox_starred share the same tab
- type = type === 'mailbox_starred' ? 'mailbox_inbox' : type;
+ // mailbox_inbox, mailbox_starred and mailbox_history share the same tab
+ type = _.contains(['mailbox_inbox', 'mailbox_starred', 'mailbox_history'], type) ? 'mailbox_inbox' : type;
self.$('.o_mail_mobile_tab[data-type=' + type + ']').addClass('active');
});
},
diff --git a/addons/mail/static/src/js/models/messages/message.js b/addons/mail/static/src/js/models/messages/message.js
index d22569461e5..e5470d219a2 100644
--- a/addons/mail/static/src/js/models/messages/message.js
+++ b/addons/mail/static/src/js/models/messages/message.js
@@ -32,6 +32,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @param {string} [data.moderation_status='accepted']
* @param {string} [data.module_icon]
* @param {Array} [data.needaction_partner_ids = []]
+ * @param {Array} [data.history_partner_ids = []]
* @param {string} [data.record_name]
* @param {integer} [data.res_id]
* @param {Array} [data.starred_partner_ids = []]
@@ -648,6 +649,9 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
if (_.contains(this._starredPartnerIDs, session.partner_id)) {
this.setStarred(true);
}
+ if (_.contains(this._historyPartnerIDs, session.partner_id)) {
+ this._setHistory(true);
+ }
if (
this.originatesFromChannel() &&
_.contains(
@@ -739,6 +743,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
* @param {string} [data.moderation_status='accepted']
* @param {string} [data.module_icon]
* @param {Array} [data.needaction_partner_ids = []]
+ * @param {Array} [data.history_partner_ids = []]
* @param {string} [data.record_name]
* @param {integer} [data.res_id]
* @param {Array} [data.starred_partner_ids = []]
@@ -757,6 +762,7 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
this._moduleIcon = data.module_icon;
this._needactionPartnerIDs = data.needaction_partner_ids || [];
this._starredPartnerIDs = data.starred_partner_ids || [];
+ this._historyPartnerIDs = data.history_partner_ids || [];
this._subject = data.subject;
this._subtypeDescription = data.subtype_description;
this._threadIDs = data.channel_ids || [];
@@ -764,6 +770,21 @@ var Message = AbstractMessage.extend(Mixins.EventDispatcherMixin, ServicesMixin
this._moderationStatus = data.moderation_status || 'accepted';
},
+ /*
+ * Set whether the message is history or not.
+ * If it is history, the message is moved to the "History" mailbox.
+ * Note that this function only applies it locally, the server is not aware
+ *
+ * @private
+ * @param {boolean} history if set, the message is history
+ */
+ _setHistory: function (history) {
+ if (history) {
+ this._addThread('mailbox_history');
+ } else {
+ this.removeThread('mailbox_history');
+ }
+ },
/**
* Set whether the message is moderated by current user or not.
* If it is moderated by the current user, the message is moved to the
diff --git a/addons/mail/static/src/js/models/threads/mailbox.js b/addons/mail/static/src/js/models/threads/mailbox.js
index bedd6af27e1..236d960c02d 100644
--- a/addons/mail/static/src/js/models/threads/mailbox.js
+++ b/addons/mail/static/src/js/models/threads/mailbox.js
@@ -128,7 +128,6 @@ var Mailbox = SearchableThread.extend({
model: 'mail.message',
method: 'mark_all_as_read',
kwargs: {
- channel_ids: [],
domain: domain,
},
});
@@ -183,6 +182,8 @@ var Mailbox = SearchableThread.extend({
return [['needaction', '=', true]];
} else if (this._id === 'mailbox_starred') {
return [['starred', '=', true]];
+ } else if (this._id === 'mailbox_history') {
+ return [['needaction', '=', false]];
} else if (this._id === 'mailbox_moderation') {
return [['need_moderation', '=', true]];
} else {
diff --git a/addons/mail/static/src/js/models/threads/searchable_thread.js b/addons/mail/static/src/js/models/threads/searchable_thread.js
index 511bc6348c8..74018af1bc0 100644
--- a/addons/mail/static/src/js/models/threads/searchable_thread.js
+++ b/addons/mail/static/src/js/models/threads/searchable_thread.js
@@ -135,11 +135,12 @@ var SearchableThread = Thread.extend({
* @override
* @private
* @param {mail.model.Message} message
- * @param {Object} options
+ * @param {Object} [options={}]
* @param {Array} [options.domain=[]]
* @param {boolean} [options.incrementUnread=false]
*/
_addMessage: function (message, options) {
+ options = options || {};
this._super.apply(this, arguments);
var cache = this._getCache(options.domain || []);
var index = _.sortedIndex(cache.messages, message, function (msg) {
diff --git a/addons/mail/static/src/js/services/mail_manager.js b/addons/mail/static/src/js/services/mail_manager.js
index 6c7cefe42e3..2aa083a56d2 100644
--- a/addons/mail/static/src/js/services/mail_manager.js
+++ b/addons/mail/static/src/js/services/mail_manager.js
@@ -1257,7 +1257,7 @@ var MailManager = AbstractService.extend({
},
/**
* Update the mailboxes with mail data fetched from server, namely 'Inbox',
- * 'Starred', and 'Moderation Queue' if the user is a moderator of a channel
+ * 'Starred', 'History', and 'Moderation Queue' if the user is a moderator of a channel
*
* @private
* @param {Object} data
@@ -1281,6 +1281,10 @@ var MailManager = AbstractService.extend({
name: _t("Starred"),
mailboxCounter: data.starred_counter || 0,
});
+ this._addMailbox({
+ id: 'history',
+ name: _t("History"),
+ });
if (data.is_moderator) {
this._addMailbox({
diff --git a/addons/mail/static/src/js/services/mail_notification_manager.js b/addons/mail/static/src/js/services/mail_notification_manager.js
index c22276867b1..704e114d8bd 100644
--- a/addons/mail/static/src/js/services/mail_notification_manager.js
+++ b/addons/mail/static/src/js/services/mail_notification_manager.js
@@ -319,12 +319,14 @@ MailManager.include({
*/
_handlePartnerMarkAsReadNotification: function (data) {
var self = this;
+ var history = this.getMailbox('history');
_.each(data.message_ids, function (messageID) {
var message = _.find(self._messages, function (msg) {
return msg.getID() === messageID;
});
if (message) {
self._removeMessageFromThread('mailbox_inbox', message);
+ history.addMessage(message);
self._mailBus.trigger('update_message', message, data.type);
}
});
diff --git a/addons/mail/static/src/scss/thread.scss b/addons/mail/static/src/scss/thread.scss
index 89e5f220ea2..5654020c783 100644
--- a/addons/mail/static/src/scss/thread.scss
+++ b/addons/mail/static/src/scss/thread.scss
@@ -229,6 +229,12 @@
margin-bottom: 20px;
font-weight: bold;
font-size: 125%;
+
+ &.o_neutral_face_icon:before {
+ @extend %o-nocontent-init-image;
+ @include size(120px, 140px);
+ background: transparent url(/web/static/src/img/neutral_face.svg) no-repeat center;
+ }
}
.o_mail_no_content {
diff --git a/addons/mail/static/src/xml/discuss.xml b/addons/mail/static/src/xml/discuss.xml
index afee3a84da2..71138ae1f24 100644
--- a/addons/mail/static/src/xml/discuss.xml
+++ b/addons/mail/static/src/xml/discuss.xml
@@ -60,6 +60,10 @@
<t t-set="counter" t-value="starred.getMailboxCounter()"/>
<t t-call="mail.discuss.SidebarCounter"/>
</div>
+ <div t-attf-class="o_mail_discuss_title_main o_mail_discuss_item #{(activeThreadID === 'mailbox_history') ? 'o_active': ''}"
+ data-thread-id="mailbox_history">
+ <span class="o_thread_name"><i class="fa fa-history mr8"/>History</span>
+ </div>
<div t-if="isMyselfModerator" t-attf-class="o_mail_discuss_title_main o_mail_discuss_item #{(activeThreadID == 'mailbox_moderation') ? 'o_active': ''}"
data-thread-id="mailbox_moderation">
<span class="o_thread_name"> <i class="fa fa-envelope mr8"/>Moderation Queue</span>
@@ -242,6 +246,9 @@
<button type="button" class="btn btn-secondary d-inline d-md-none o_mailbox_inbox_item" title="Starred" data-type="mailbox_starred">
Starred
</button>
+ <button type="button" class="btn btn-secondary d-inline d-md-none o_mailbox_inbox_item" title="History" data-type="mailbox_history">
+ History
+ </button>
</div>
<div class="o_mail_discuss_content"/>
<div class="o_mail_mobile_tabs">
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml
index dada8901491..ef21b2c5ede 100644
--- a/addons/mail/static/src/xml/thread.xml
+++ b/addons/mail/static/src/xml/thread.xml
@@ -128,9 +128,13 @@
<div>New messages appear here.</div>
</t>
<t t-if="thread.getID() === 'mailbox_starred'">
- <div class="o_thread_title">No starred message</div>
+ <div class="o_thread_title">No starred messages</div>
<div>You can mark any message as 'starred', and it shows up in this mailbox.</div>
</t>
+ <t t-if="thread.getID() === 'mailbox_history'">
+ <div class="o_thread_title o_neutral_face_icon">No history messages</div>
+ <div>Messages marked as read will appear in the history.</div>
+ </t>
<t t-if="thread.getID() === 'mailbox_moderation'">
<div class="o_thread_title">You have no message to moderate</div>
<div>Pending moderation messages appear here.</div>
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 8c65266a392..7473194d2b5 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -50,6 +50,11 @@ QUnit.module('Discuss', {
type: 'many2many',
relation: 'res.partner',
},
+ history_partner_ids: {
+ string: "Partners with History",
+ type: 'many2many',
+ relation: 'res.partner',
+ },
model: {
string: "Related Document model",
type: 'char',
@@ -72,6 +77,24 @@ QUnit.module('Discuss', {
im_status: 'online',
}]
},
+ 'mail.notification': {
+ fields: {
+ is_read: {
+ string: "Is Read",
+ type: 'boolean',
+ },
+ mail_message_id: {
+ string: "Message",
+ type: 'many2one',
+ relation: 'mail.message',
+ },
+ res_partner_id: {
+ string: "Needaction Recipient",
+ type: 'many2one',
+ relation: 'res.partner',
+ },
+ },
+ },
};
this.services = mailTestUtils.getMailServices();
},
@@ -83,7 +106,7 @@ QUnit.module('Discuss', {
});
QUnit.test('basic rendering', async function (assert) {
- assert.expect(5);
+ assert.expect(6);
var discuss = await createDiscuss({
id: 1,
@@ -105,11 +128,15 @@ QUnit.test('basic rendering', async function (assert) {
var $inbox = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_inbox]');
assert.strictEqual($inbox.length, 1,
- "should have the channel item 'mailbox_inbox' in the sidebar");
+ "should have the mailbox item 'mailbox_inbox' in the sidebar");
var $starred = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_starred]');
assert.strictEqual($starred.length, 1,
- "should have the channel item 'mailbox_starred' in the sidebar");
+ "should have the mailbox item 'mailbox_starred' in the sidebar");
+
+ var $history = $sidebar.find('.o_mail_discuss_item[data-thread-id=mailbox_history]');
+ assert.strictEqual($history.length, 1,
+ "should have the mailbox item 'mailbox_history' in the sidebar");
discuss.destroy();
});
@@ -1490,54 +1517,382 @@ QUnit.test('custom-named DM conversation', async function (assert) {
discuss.destroy();
});
-QUnit.test('input not cleared on unresolved message_post rpc', async function (assert) {
- assert.expect(2);
- // Promise to simulate late server response on message post
- var messagePostPromise = testUtils.makeTestPromise();
+QUnit.test('messages marked as read move to "History" mailbox', async function (assert) {
+ assert.expect(3);
+
+ this.data['mail.message'].records = [{
+ author_id: [5, 'Demo User'],
+ body: '<p>test 1</p>',
+ id: 1,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }, {
+ author_id: [6, 'Test User'],
+ body: '<p>test 2</p>',
+ id: 2,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }];
+ this.data['mail.notification'].records = [{
+ id: 50,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }, {
+ id: 51,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }];
this.data.initMessaging = {
- channel_slots: {
- channel_channel: [{
- id: 1,
- channel_type: "channel",
- name: "general",
- }],
+ needaction_inbox_counter: 2,
+ };
+
+ var markAllReadDef = testUtils.makeTestPromise();
+ var objectDiscuss;
+
+ var discuss = await createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ session: { partner_id: 3 },
+ mockRPC: function (route, args) {
+ if (args.method === 'mark_all_as_read') {
+ _.each(this.data['mail.message'].records, function (message) {
+ message.history_partner_ids = [3];
+ message.needaction_partner_ids = [];
+ });
+ var notificationData = {
+ type: 'mark_as_read',
+ message_ids: [1, 2],
+ };
+ var notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ return this._super.apply(this, arguments);
},
+ })
+ objectDiscuss = discuss;
+
+ var $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
+ var $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+
+ await testUtils.dom.click($history);
+ assert.containsOnce(discuss, '.o_mail_no_content',
+ "should display no content message");
+
+ await testUtils.dom.click($inbox);
+
+ var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
+ testUtils.dom.click($markAllReadButton);
+
+ await markAllReadDef;
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+
+ assert.containsN(discuss, '.o_thread_message', 2,
+ "there should be two messages in History");
+
+ discuss.destroy();
+});
+
+QUnit.test('all messages in "Inbox" in "History" after marked all as read', async function (assert) {
+ assert.expect(10);
+
+ var messagesData = [];
+
+ for (var i = 0; i < 40; i++) {
+ messagesData.push({
+ author_id: [i, 'User ' + i],
+ body: '<p>test ' + i + '</p>',
+ id: i,
+ needaction: true,
+ needaction_partner_ids: [3],
+ });
+ }
+
+ this.data['mail.message'].records = messagesData;
+ this.data.initMessaging = {
+ needaction_inbox_counter: 2,
};
+ var messageFetchCount = 0;
+ var loadMoreDef = testUtils.makeTestPromise();
+ var markAllReadDef = testUtils.makeTestPromise();
+ var objectDiscuss;
+
var discuss = await createDiscuss({
id: 1,
context: {},
params: {},
data: this.data,
services: this.services,
+ session: { partner_id: 3 },
mockRPC: function (route, args) {
- if (args.method === 'message_post') {
- return messagePostPromise;
+ if (args.method === 'mark_all_as_read') {
+ var messageIDs = [];
+ for (var i = 0; i < messagesData.length; i++) {
+ this.data['mail.message'].records[i].history_partner_ids = [3];
+ this.data['mail.message'].records[i].needaction_partner_ids = [];
+ this.data['mail.message'].records[i].needaction = false;
+ messageIDs.push(i);
+ }
+ var notificationData = {
+ type: 'mark_as_read',
+ message_ids: messageIDs,
+ };
+ var notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ if (args.method === 'message_fetch') {
+ // 1st message_fetch: 'Inbox' initially
+ // 2nd message_fetch: 'History' initially
+ // 3rd message_fetch: 'History' load more
+ assert.step(args.method);
+
+ messageFetchCount++;
+ if (messageFetchCount === 3) {
+ loadMoreDef.resolve();
+ }
}
return this._super.apply(this, arguments);
},
});
+ objectDiscuss = discuss;
- // Click on channel 'general'
- var $general = discuss.$('.o_mail_discuss_sidebar').find('.o_mail_discuss_item[data-thread-id=1]');
- await testUtils.dom.click($general);
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for needaction messages (Inbox)");
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages that are loaded in Inbox");
- // Type message
- var $input = discuss.$('textarea.o_composer_text_field').first();
- $input.focus();
- $input.val('test message');
+ var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
- // Send message
- await testUtils.fields.triggerKeydown($input, 'enter');
- assert.strictEqual($input.val(), 'test message', "composer should not be cleared on send without server response");
+ await testUtils.dom.click($markAllReadButton);
+ await markAllReadDef;
+
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ var $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+
+ await testUtils.dom.click($history);
+
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for history");
+
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages in History");
+
+ // simulate a scroll to top to load more messages
+ discuss.$('.o_mail_thread').scrollTop(0);
+
+ await loadMoreDef;
+ await testUtils.nextTick();
+
+ assert.verifySteps(['message_fetch'],
+ "should fetch more messages in history for loadMore");
+ assert.containsN(discuss, '.o_thread_message', 40,
+ "there should be 40 messages in History");
+
+ discuss.destroy();
+});
+
+QUnit.test('messages marked as read move to "History" mailbox', async function (assert) {
+ assert.expect(3);
+
+ this.data['mail.message'].records = [{
+ author_id: [5, 'Demo User'],
+ body: '<p>test 1</p>',
+ id: 1,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }, {
+ author_id: [6, 'Test User'],
+ body: '<p>test 2</p>',
+ id: 2,
+ needaction: true,
+ needaction_partner_ids: [3],
+ }];
+ this.data['mail.notification'].records = [{
+ id: 50,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }, {
+ id: 51,
+ is_read: false,
+ mail_message_id: 1,
+ res_partner_id: 3,
+ }];
+
+ this.data.initMessaging = {
+ needaction_inbox_counter: 2,
+ };
+
+ const markAllReadDef = testUtils.makeTestPromise();
+ let objectDiscuss;
+
+ const discuss = await createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ session: { partner_id: 3 },
+ mockRPC(route, args) {
+ if (args.method === 'mark_all_as_read') {
+ for (const message of (this.data['mail.message'].records)) {
+ message.history_partner_ids = [3];
+ message.needaction_partner_ids = [];
+ }
+ const notificationData = {
+ type: 'mark_as_read',
+ message_ids: [1, 2],
+ };
+ const notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ return this._super.apply(this, arguments);
+ },
+ })
+ objectDiscuss = discuss;
+
+ const $inbox = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_inbox"]');
+ let $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+ assert.containsOnce(discuss, '.o_mail_no_content',
+ "should display no content message");
+
+ await testUtils.dom.click($inbox);
+ var $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
+ testUtils.dom.click($markAllReadButton);
+ await markAllReadDef;
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+ assert.containsN(discuss, '.o_thread_message', 2,
+ "there should be two messages in History");
+
+ discuss.destroy();
+});
+
+QUnit.test('all messages in "Inbox" in "History" after marked all as read', async function (assert) {
+ assert.expect(10);
+
+ const messagesData = [];
+ for (let i = 0; i < 40; i++) {
+ messagesData.push({
+ author_id: [i, 'User ' + i],
+ body: '<p>test ' + i + '</p>',
+ id: i,
+ needaction: true,
+ needaction_partner_ids: [3],
+ });
+ }
+
+ this.data['mail.message'].records = messagesData;
+ this.data.initMessaging = {
+ needaction_inbox_counter: 2,
+ };
+
+ let messageFetchCount = 0;
+ const loadMoreDef = testUtils.makeTestPromise();
+ const markAllReadDef = testUtils.makeTestPromise();
+ let objectDiscuss;
+
+ const discuss = await createDiscuss({
+ id: 1,
+ context: {},
+ params: {},
+ data: this.data,
+ services: this.services,
+ session: { partner_id: 3 },
+ mockRPC(route, args) {
+ if (args.method === 'mark_all_as_read') {
+ const messageIDs = [];
+ for (let i = 0; i < messagesData.length; i++) {
+ this.data['mail.message'].records[i].history_partner_ids = [3];
+ this.data['mail.message'].records[i].needaction_partner_ids = [];
+ this.data['mail.message'].records[i].needaction = false;
+ messageIDs.push(i);
+ }
+ const notificationData = {
+ type: 'mark_as_read',
+ message_ids: messageIDs,
+ };
+ const notification = [[false, 'res.partner', 3], notificationData];
+ objectDiscuss.call('bus_service', 'trigger', 'notification', [notification]);
+ markAllReadDef.resolve();
+ return Promise.resolve(3);
+ }
+ if (args.method === 'message_fetch') {
+ // 1st message_fetch: 'Inbox' initially
+ // 2nd message_fetch: 'History' initially
+ // 3rd message_fetch: 'History' load more
+ assert.step(args.method);
+
+ messageFetchCount++;
+ if (messageFetchCount === 3) {
+ loadMoreDef.resolve();
+ }
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ objectDiscuss = discuss;
+
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for needaction messages (Inbox)");
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages that are loaded in Inbox");
- // Simulate server response
- messagePostPromise.resolve();
+ const $markAllReadButton = $('.o_mail_discuss_button_mark_all_read');
+ await testUtils.dom.click($markAllReadButton);
+ await markAllReadDef;
+ // immediately jump to end of the fadeout animation on messages
+ discuss.$('.o_thread_message').stop(false, true);
+ assert.containsNone(discuss, '.o_thread_message',
+ "there should no message in inbox anymore");
+
+ const $history = discuss.$('.o_mail_discuss_item[data-thread-id="mailbox_history"]');
+ await testUtils.dom.click($history);
+ assert.verifySteps(['message_fetch'],
+ "should fetch messages once for history");
+ assert.containsN(discuss, '.o_thread_message', 30,
+ "there should be 30 messages in History");
+
+ // simulate a scroll to top to load more messages
+ discuss.$('.o_mail_thread').scrollTop(0);
+ await loadMoreDef;
await testUtils.nextTick();
- assert.strictEqual($input.val(), '', "composer should be cleared on send after server response");
+ assert.verifySteps(['message_fetch'],
+ "should fetch more messages in history for loadMore");
+ assert.containsN(discuss, '.o_thread_message', 40,
+ "there should be 40 messages in History");
+
discuss.destroy();
});
+
});
});
diff --git a/addons/test_mail/tests/common.py b/addons/test_mail/tests/common.py
index 752c489cadb..d3c41a16ab1 100644
--- a/addons/test_mail/tests/common.py
+++ b/addons/test_mail/tests/common.py
@@ -73,7 +73,7 @@ class BaseFunctionalTest(common.SavepointCase):
for partner_attribute in counters.keys():
counter, notif_type, notif_read = counters[partner_attribute]
partner = getattr(self, partner_attribute)
- partner_notif = new_notifications.filtered(lambda n: n.res_partner_id == partner)
+ partner_notif = new_notifications.filtered(lambda n: n.res_partner_id == partner and (n.is_read == (notif_read not in ['unread', ''])))
self.assertEqual(len(partner_notif), counter)
diff --git a/addons/test_mail/tests/test_mail_message.py b/addons/test_mail/tests/test_mail_message.py
index 07116885592..d4181f5b334 100644
--- a/addons/test_mail/tests/test_mail_message.py
+++ b/addons/test_mail/tests/test_mail_message.py
@@ -336,7 +336,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
# mark all as read clear needactions
group_private.message_post(body='Test', message_type='comment', subtype='mail.mt_comment', partner_ids=[emp_partner.id])
- emp_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ emp_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = emp_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all as read should conclude all needactions")
@@ -353,7 +353,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
na_count = emp_partner.get_needaction_count()
self.assertEqual(na_count, 1, "message not accessible is currently still counted")
- emp_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ emp_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = emp_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones")
@@ -364,7 +364,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
# mark all as read clear needactions
self.group_pigs.message_post(body='Test', message_type='comment', subtype='mail.mt_comment', partner_ids=[portal_partner.id])
- portal_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ portal_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = portal_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all as read should conclude all needactions")
@@ -380,7 +380,7 @@ class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
na_count = portal_partner.get_needaction_count()
self.assertEqual(na_count, 1, "message not accessible is currently still counted")
- portal_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[])
+ portal_partner.env['mail.message'].mark_all_as_read(domain=[])
na_count = portal_partner.get_needaction_count()
self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones")