[IMP] mail: introduce MessageSeenIndicatorView

This commit introduce the MessageSeenIndicatorView model,
in order to move business code from MessageSeenIndicator
component to MessageSeenIndicatorViews.

Code that are not in modelling are hacky, hard to maintain,
and very prone to bug. That's why it's important to move most
code in components to models.

Task-2793280

closes odoo/odoo#88950

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2022-04-15 20:06:30 +02:00
parent d10e32e141
commit 85ec1d37c4
6 changed files with 213 additions and 148 deletions
@@ -49,8 +49,8 @@
<t t-esc="messageView.message.shortTime"/>
</div>
</t>
<t t-if="!messageView.isActive and messageView.message.isCurrentUserOrGuestAuthor and messageView.threadView and messageView.threadView.thread and messageView.threadView.thread.hasSeenIndicators">
<MessageSeenIndicator className="'o_Message_seenIndicator'" messageLocalId="messageView.message.localId" threadLocalId="messageView.threadView.thread.localId"/>
<t t-if="!messageView.isActive and messageView.messageSeenIndicatorView">
<MessageSeenIndicator className="'o_Message_seenIndicator'" localId="messageView.messageSeenIndicatorView.localId"/>
</t>
</t>
</div>
@@ -87,8 +87,8 @@
- <t t-esc="messageView.message.dateFromNow"/>
</div>
</t>
<t t-if="messageView.message.isCurrentUserOrGuestAuthor and messageView.threadView and messageView.threadView.thread and messageView.threadView.thread.hasSeenIndicators">
<MessageSeenIndicator className="'o_Message_seenIndicator'" messageLocalId="messageView.message.localId" threadLocalId="messageView.threadView.thread.localId"/>
<t t-if="messageView.messageSeenIndicatorView">
<MessageSeenIndicator className="'o_Message_seenIndicator'" localId="messageView.messageSeenIndicatorView.localId"/>
</t>
<t t-if="messageView.threadView and messageView.message.originThread and messageView.message.originThread !== messageView.threadView.thread">
<div class="o_Message_originThread" t-att-class="{ 'o-message-selected': messageView.isSelected }">
@@ -10,39 +10,16 @@ export class MessageSeenIndicator extends Component {
// Public
//--------------------------------------------------------------------------
/**
* @returns {Message}
*/
get message() {
return this.messaging && this.messaging.models['Message'].get(this.props.messageLocalId);
}
/**
* @returns {MessageSeenIndicator}
*/
get messageSeenIndicator() {
if (!this.thread || this.thread.model !== 'mail.channel') {
return undefined;
}
return this.messaging.models['MessageSeenIndicator'].findFromIdentifyingData({
message: this.message,
thread: this.thread,
});
}
/**
* @returns {Thread}
*/
get thread() {
return this.messaging && this.messaging.models['Thread'].get(this.props.threadLocalId);
get messageSeenIndicatorView() {
return this.messaging && this.messaging.models['MessageSeenIndicatorView'].get(this.props.localId);
}
}
Object.assign(MessageSeenIndicator, {
props: {
messageLocalId: String,
threadLocalId: String,
},
props: { localId: String },
template: 'mail.MessageSeenIndicator',
});
@@ -2,13 +2,13 @@
<templates xml:space="preserve">
<t t-name="mail.MessageSeenIndicator" owl="1">
<t t-if="messageSeenIndicator">
<span class="o_MessageSeenIndicator" t-att-class="{ 'o-all-seen': messageSeenIndicator.hasEveryoneSeen }" t-attf-class="{{ className }}" t-att-title="messageSeenIndicator.text" t-ref="root">
<t t-if="!messageSeenIndicator.isMessagePreviousToLastCurrentPartnerMessageSeenByEveryone">
<t t-if="messageSeenIndicator.hasSomeoneFetched or messageSeenIndicator.hasSomeoneSeen">
<t t-if="messageSeenIndicatorView">
<span class="o_MessageSeenIndicator" t-att-class="{ 'o-all-seen': messageSeenIndicatorView.messageSeenIndicator.hasEveryoneSeen }" t-attf-class="{{ className }}" t-att-title="messageSeenIndicatorView.messageSeenIndicator.text" t-ref="root">
<t t-if="!messageSeenIndicatorView.messageSeenIndicator.isMessagePreviousToLastCurrentPartnerMessageSeenByEveryone">
<t t-if="messageSeenIndicatorView.messageSeenIndicator.hasSomeoneFetched or messageSeenIndicatorView.messageSeenIndicator.hasSomeoneSeen">
<i class="o_MessageSeenIndicator_icon o-first fa fa-check"/>
</t>
<t t-if="messageSeenIndicator.hasSomeoneSeen">
<t t-if="messageSeenIndicatorView.messageSeenIndicator.hasSomeoneSeen">
<i class="o_MessageSeenIndicator_icon o-second fa fa-check"/>
</t>
</t>
@@ -0,0 +1,35 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'MessageSeenIndicatorView',
identifyingFields: ['messageViewOwner'],
recordMethods: {
/**
* @private
* @returns {FieldCommand}
*/
_computeMessageSeenIndicator() {
if (this.messageViewOwner.threadView && this.messageViewOwner.threadView.thread) {
return insertAndReplace({
message: replace(this.messageViewOwner.message),
thread: replace(this.messageViewOwner.threadView.thread),
});
}
return clear();
},
},
fields: {
messageViewOwner: one('MessageView', {
inverse: 'messageSeenIndicatorView',
readonly: true,
required: true,
}),
messageSeenIndicator: one('MessageSeenIndicator', {
compute: '_computeMessageSeenIndicator',
}),
},
});
@@ -258,6 +258,21 @@ registerModel({
this.message.parentMessage
) ? insertAndReplace() : clear();
},
/**
* @private
* @retuns {FieldCommand}
*/
_computeMessageSeenIndicatorView() {
if (
this.message.isCurrentUserOrGuestAuthor &&
this.threadView &&
this.threadView.thread &&
this.threadView.thread.hasSeenIndicators
) {
return insertAndReplace();
}
return clear();
},
},
fields: {
/**
@@ -384,6 +399,11 @@ registerModel({
isCausal: true,
readonly: true,
}),
messageSeenIndicatorView: one('MessageSeenIndicatorView', {
compute: '_computeMessageSeenIndicatorView',
inverse: 'messageViewOwner',
isCausal: true,
}),
/**
* States the thread view that is displaying this messages (if any).
*/
@@ -1,9 +1,9 @@
/** @odoo-module **/
import { insert, insertAndReplace, link } from '@mail/model/model_field_command';
import {
createRootMessagingComponent,
start,
startServer,
} from '@mail/../tests/helpers/test_utils';
QUnit.module('mail', {}, function () {
@@ -26,30 +26,38 @@ QUnit.module('message_seen_indicator_tests.js', {
QUnit.test('rendering when just one has received the message', async function (assert) {
assert.expect(3);
const { messaging, widget } = await start();
const thread = messaging.models['Thread'].create({
id: 1000,
model: 'mail.channel',
partnerSeenInfos: insertAndReplace([
{
lastFetchedMessage: insert({ id: 100 }),
partner: insertAndReplace({ id: 10 }),
},
{
partner: insertAndReplace({ id: 100 }),
},
]),
messageSeenIndicators: insertAndReplace({
message: insertAndReplace({ id: 100 }),
}),
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({ name: "Demo User" });
const resPartnerId2 = pyEnv['res.partner'].create({ name: "Other User" });
const mailChannelId = pyEnv['mail.channel'].create({
channel_last_seen_partner_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
[0, 0, { partner_id: resPartnerId2 }],
],
channel_type: 'chat', // only chat channel have seen notification
});
const message = messaging.models['Message'].insert({
author: insert({ id: messaging.currentPartner.id, display_name: "Demo User" }),
const mailMessageId = pyEnv['mail.message'].create({
author_id: pyEnv.currentPartnerId,
body: "<p>Test</p>",
id: 100,
originThread: link(thread),
model: 'mail.channel',
res_id: mailChannelId,
});
await this.createMessageSeenIndicatorComponent({ message, target: widget.el, thread });
const [cpId1] = pyEnv['mail.channel.partner'].search([['channel_id', '=', mailChannelId], ['partner_id', '=', resPartnerId1]]);
pyEnv['mail.channel.partner'].write([cpId1], {
fetched_message_id: mailMessageId,
seen_message_id: false,
});
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId}`,
},
},
hasDiscuss: true,
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_MessageSeenIndicator',
@@ -70,31 +78,37 @@ QUnit.test('rendering when just one has received the message', async function (a
QUnit.test('rendering when everyone have received the message', async function (assert) {
assert.expect(3);
const { messaging, widget } = await start();
const thread = messaging.models['Thread'].create({
id: 1000,
model: 'mail.channel',
partnerSeenInfos: insertAndReplace([
{
lastFetchedMessage: insert({ id: 100 }),
partner: insertAndReplace({ id: 10 }),
},
{
lastFetchedMessage: insert({ id: 99 }),
partner: insertAndReplace({ id: 100 }),
},
]),
messageSeenIndicators: insertAndReplace({
message: insertAndReplace({ id: 100 }),
}),
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({ name: "Demo User" });
const resPartnerId2 = pyEnv['res.partner'].create({ name: "Other User" });
const mailChannelId = pyEnv['mail.channel'].create({
channel_last_seen_partner_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
[0, 0, { partner_id: resPartnerId2 }],
],
channel_type: 'chat',
});
const message = messaging.models['Message'].insert({
author: insert({ id: messaging.currentPartner.id, display_name: "Demo User" }),
const mailMessageId = pyEnv['mail.message'].create({
author_id: pyEnv.currentPartnerId,
body: "<p>Test</p>",
id: 100,
originThread: link(thread),
model: 'mail.channel',
res_id: mailChannelId,
});
await this.createMessageSeenIndicatorComponent({ message, target: widget.el, thread });
const cpIds = pyEnv['mail.channel.partner'].search([['channel_id', '=', mailChannelId]]);
pyEnv['mail.channel.partner'].write(cpIds, {
fetched_message_id: mailMessageId,
seen_message_id: false,
});
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId}`,
},
},
hasDiscuss: true,
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_MessageSeenIndicator',
@@ -115,32 +129,41 @@ QUnit.test('rendering when everyone have received the message', async function (
QUnit.test('rendering when just one has seen the message', async function (assert) {
assert.expect(3);
const { messaging, widget } = await start();
const thread = messaging.models['Thread'].create({
id: 1000,
model: 'mail.channel',
partnerSeenInfos: insertAndReplace([
{
lastFetchedMessage: insert({ id: 100 }),
lastSeenMessage: insert({ id: 100 }),
partner: insertAndReplace({ id: 10 }),
},
{
lastFetchedMessage: insert({ id: 99 }),
partner: insertAndReplace({ id: 100 }),
},
]),
messageSeenIndicators: insertAndReplace({
message: insertAndReplace({ id: 100 }),
}),
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({ name: "Demo User" });
const resPartnerId2 = pyEnv['res.partner'].create({ name: "Other User" });
const mailChannelId = pyEnv['mail.channel'].create({
channel_last_seen_partner_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
[0, 0, { partner_id: resPartnerId2 }],
],
channel_type: 'chat',
});
const message = messaging.models['Message'].insert({
author: insert({ id: messaging.currentPartner.id, display_name: "Demo User" }),
const mailMessageId = pyEnv['mail.message'].create({
author_id: pyEnv.currentPartnerId,
body: "<p>Test</p>",
id: 100,
originThread: link(thread),
model: 'mail.channel',
res_id: mailChannelId,
});
await this.createMessageSeenIndicatorComponent({ message, target: widget.el, thread });
const cpIds = pyEnv['mail.channel.partner'].search([['channel_id', '=', mailChannelId]]);
pyEnv['mail.channel.partner'].write(cpIds, {
fetched_message_id: mailMessageId,
seen_message_id: false,
});
const [seenCpId] = pyEnv['mail.channel.partner'].search([['channel_id', '=', mailChannelId], ['partner_id', '=', resPartnerId1]]);
pyEnv['mail.channel.partner'].write([seenCpId], {
seen_message_id: mailMessageId,
});
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId}`,
},
},
hasDiscuss: true,
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_MessageSeenIndicator',
@@ -162,31 +185,37 @@ QUnit.test('rendering when just one has seen the message', async function (asser
QUnit.test('rendering when just one has seen & received the message', async function (assert) {
assert.expect(3);
const { messaging, widget } = await start();
const thread = messaging.models['Thread'].create({
id: 1000,
model: 'mail.channel',
partnerSeenInfos: insertAndReplace([
{
lastFetchedMessage: insert({ id: 100 }),
lastSeenMessage: insert({ id: 100 }),
partner: insertAndReplace({ id: 10 }),
},
{
partner: insertAndReplace({ id: 100 }),
},
]),
messageSeenIndicators: insertAndReplace({
message: insertAndReplace({ id: 100 }),
}),
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({ name: "Demo User" });
const resPartnerId2 = pyEnv['res.partner'].create({ name: "Other User" });
const mailChannelId = pyEnv['mail.channel'].create({
channel_last_seen_partner_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
[0, 0, { partner_id: resPartnerId2 }],
],
channel_type: 'chat',
});
const message = messaging.models['Message'].insert({
author: insert({ id: messaging.currentPartner.id, display_name: "Demo User" }),
const mailMessageId = pyEnv['mail.message'].create({
author_id: pyEnv.currentPartnerId,
body: "<p>Test</p>",
id: 100,
originThread: link(thread),
model: 'mail.channel',
res_id: mailChannelId,
});
await this.createMessageSeenIndicatorComponent({ message, target: widget.el, thread });
const [seenCpId] = pyEnv['mail.channel.partner'].search([['channel_id', '=', mailChannelId], ['partner_id', '=', resPartnerId1]]);
pyEnv['mail.channel.partner'].write([seenCpId], {
seen_message_id: mailMessageId,
fetched_message_id: mailMessageId,
});
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId}`,
},
},
hasDiscuss: true,
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_MessageSeenIndicator',
@@ -208,33 +237,37 @@ QUnit.test('rendering when just one has seen & received the message', async func
QUnit.test('rendering when just everyone has seen the message', async function (assert) {
assert.expect(3);
const { messaging, widget } = await start();
const thread = messaging.models['Thread'].create({
id: 1000,
model: 'mail.channel',
partnerSeenInfos: insertAndReplace([
{
lastFetchedMessage: insert({ id: 100 }),
lastSeenMessage: insert({ id: 100 }),
partner: insertAndReplace({ id: 10 }),
},
{
lastFetchedMessage: insert({ id: 100 }),
lastSeenMessage: insert({ id: 100 }),
partner: insertAndReplace({ id: 100 }),
},
]),
messageSeenIndicators: insertAndReplace({
message: insertAndReplace({ id: 100 }),
}),
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({ name: "Demo User" });
const resPartnerId2 = pyEnv['res.partner'].create({ name: "Other User" });
const mailChannelId = pyEnv['mail.channel'].create({
channel_last_seen_partner_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: resPartnerId1 }],
[0, 0, { partner_id: resPartnerId2 }],
],
channel_type: 'chat',
});
const message = messaging.models['Message'].insert({
author: insert({ id: messaging.currentPartner.id, display_name: "Demo User" }),
const mailMessageId = pyEnv['mail.message'].create({
author_id: pyEnv.currentPartnerId,
body: "<p>Test</p>",
id: 100,
originThread: link(thread),
model: 'mail.channel',
res_id: mailChannelId,
});
await this.createMessageSeenIndicatorComponent({ message, target: widget.el, thread });
const cpIds = pyEnv['mail.channel.partner'].search([['channel_id', '=', mailChannelId]]);
pyEnv['mail.channel.partner'].write(cpIds, {
fetched_message_id: mailMessageId,
seen_message_id: mailMessageId,
});
const { openDiscuss } = await start({
discuss: {
params: {
default_active_id: `mail.channel_${mailChannelId}`,
},
},
hasDiscuss: true,
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_MessageSeenIndicator',