[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
This commit is contained in:
Nikunj Ladava
2019-08-05 11:50:19 +00:00
committed by Xavier-Do
parent 634bdbfa3a
commit 0c37b9fb39
15 changed files with 511 additions and 94 deletions
+12
View File
@@ -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>
+31 -52
View File
@@ -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()
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})
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')
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
+25
View File
@@ -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()
+4 -4
View File
@@ -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');
});
},
@@ -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
@@ -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 {
@@ -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) {
@@ -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({
@@ -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);
}
});
+6
View File
@@ -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 {
+7
View File
@@ -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">
+5 -1
View File
@@ -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>
+384 -29
View File
@@ -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 === 'message_post') {
return messagePostPromise;
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 === '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;
// Simulate server response
messagePostPromise.resolve();
// 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.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();
});
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");
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.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();
});
});
});
+1 -1
View File
@@ -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)
+4 -4
View File
@@ -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")