[IMP] mail: introduce Channel and ChannelMember model
task-2832456 closes odoo/odoo#91918 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
committed by
Alexandre Kühn
parent
4c190bce3a
commit
d9c25be92f
@@ -1169,12 +1169,24 @@ class Channel(models.Model):
|
||||
|
||||
def load_more_members(self, known_member_ids):
|
||||
self.ensure_one()
|
||||
partners = self.env['res.partner'].with_context(active_test=False).search_read(
|
||||
domain=[('id', 'not in', known_member_ids), ('channel_ids', 'in', self.id)],
|
||||
fields=['id', 'name', 'im_status'],
|
||||
limit=30
|
||||
channel_partners = self.env['mail.channel.partner'].search(
|
||||
domain=[('id', 'not in', known_member_ids), ('channel_id', '=', self.id)],
|
||||
limit=100
|
||||
)
|
||||
return [('insert', partners)]
|
||||
count = self.env['mail.channel.partner'].search_count(
|
||||
domain=[('channel_id', '=', self.id)],
|
||||
)
|
||||
return {
|
||||
'channelMembers': [('insert', [{
|
||||
'id': member.id,
|
||||
'partner': [('insert', {
|
||||
'id': member.partner_id.id,
|
||||
'name': member.partner_id.name,
|
||||
'im_status': member.partner_id.im_status,
|
||||
})]
|
||||
} for member in channel_partners])],
|
||||
'memberCount': count,
|
||||
}
|
||||
|
||||
def _get_avatar_cache_key(self):
|
||||
if not self.avatar_128:
|
||||
|
||||
@@ -6,17 +6,10 @@ const { Component } = owl;
|
||||
|
||||
export class ChannelMember extends Component {
|
||||
|
||||
/**
|
||||
* @returns {Thread}
|
||||
*/
|
||||
get channel() {
|
||||
return this.props.channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ChannelMemberView}
|
||||
*/
|
||||
get record() {
|
||||
get channelMemberView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
@@ -24,7 +17,6 @@ export class ChannelMember extends Component {
|
||||
|
||||
Object.assign(ChannelMember, {
|
||||
props: {
|
||||
channel: Object,
|
||||
record: Object,
|
||||
},
|
||||
template: 'mail.ChannelMember',
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ChannelMember" owl="1">
|
||||
<div class="o_ChannelMember d-flex align-items-center mx-3 my-1" t-attf-class="{{ className }}" t-att-data-partner-local-id="record.partner.localId" t-ref="root">
|
||||
<div class="o_ChannelMember_avatarContainer position-relative flex-shrink-0 o_cursor_pointer" t-on-click="() => channel.onClickMemberAvatar(record.partner)">
|
||||
<img class="o_ChannelMember_avatar rounded-circle w-100 h-100" t-attf-src="/mail/channel/{{ channel.id }}/partner/{{ record.partner.id }}/avatar_128" alt="Avatar"/>
|
||||
<div class="o_ChannelMember d-flex align-items-center mx-3 my-1" t-attf-class="{{ className }}" t-att-data-partner-local-id="channelMemberView.channelMember.partner.localId" t-ref="root">
|
||||
<div class="o_ChannelMember_avatarContainer position-relative flex-shrink-0 o_cursor_pointer" t-on-click="channelMemberView.onClickMemberAvatar">
|
||||
<img class="o_ChannelMember_avatar rounded-circle w-100 h-100" t-attf-src="/mail/channel/{{ channelMemberView.channelMember.channel.id }}/partner/{{ channelMemberView.channelMember.partner.id }}/avatar_128" alt="Avatar"/>
|
||||
|
||||
<t t-if="record.partner.im_status and record.partner.im_status !== 'im_partner'">
|
||||
<t t-if="channelMemberView.channelMember.partner.im_status and channelMemberView.channelMember.partner.im_status !== 'im_partner'">
|
||||
<PartnerImStatusIcon
|
||||
className="'o_ChannelMember_partnerImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center text-light'"
|
||||
classNameObj="{
|
||||
'o-isDeviceSmall': messaging.device.isSmall,
|
||||
'small': !messaging.device.isSmall,
|
||||
}"
|
||||
partner="record.partner"
|
||||
partner="channelMemberView.channelMember.partner"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<span class="o_ChannelMember_name ml-2 flex-column-1 text-truncate o_cursor_pointer" t-on-click="() => channel.onClickMemberName(record.partner)">
|
||||
<t t-esc="record.partner.nameOrDisplayName"/>
|
||||
<span class="o_ChannelMember_name ml-2 flex-column-1 text-truncate o_cursor_pointer" t-on-click="channelMemberView.onClickMemberName">
|
||||
<t t-esc="channelMemberView.channelMember.partner.nameOrDisplayName"/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -9,7 +9,7 @@ export class ChannelMemberList extends Component {
|
||||
/**
|
||||
* @returns {ChannelMemberListView}
|
||||
*/
|
||||
get channelMemberListView() {
|
||||
get channelMemberListView() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
<t t-if="channelMemberListView.channel.unknownMemberCount > 1">
|
||||
<span class="mx-2 mt-2">And <t t-esc="channelMemberListView.channel.unknownMemberCount"/> other members.</span>
|
||||
</t>
|
||||
<t t-if="channelMemberListView.channel.unknownMemberCount > 0">
|
||||
<t t-if="!channelMemberListView.channel.areAllMembersLoaded">
|
||||
<div class="mx-2 my-1">
|
||||
<button class="btn btn-secondary" t-on-click="channelMemberListView.channel.onClickLoadMoreMembers">Load more</button>
|
||||
<button class="o_ChannelMemberList_loadMoreButton btn btn-secondary" t-on-click="channelMemberListView.onClickLoadMoreMembers">Load more</button>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<t t-name="mail.ChannelMemberListCategory" owl="1">
|
||||
<h6 class="m-2" t-esc="record.title"/>
|
||||
<t t-foreach="record.channelMemberViews" t-as="channelMemberView" t-key="channelMemberView.localId">
|
||||
<ChannelMember channel="record.channel" record="channelMemberView"/>
|
||||
<ChannelMember record="channelMemberView"/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { decrement, increment } from '@mail/model/model_field_command';
|
||||
import { Listener } from '@mail/model/model_listener';
|
||||
import { followRelations } from '@mail/model/model_utils';
|
||||
import { cleanSearchTerm } from '@mail/utils/utils';
|
||||
|
||||
/**
|
||||
* Defines a set containing the relation records of the given field on the given
|
||||
@@ -79,11 +80,14 @@ export class RelationSet {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'case-insensitive-asc':
|
||||
if (valA.toLowerCase() === valB.toLowerCase()) {
|
||||
case 'case-insensitive-asc': {
|
||||
const cleanedValA = cleanSearchTerm(valA);
|
||||
const cleanedValB = cleanSearchTerm(valB);
|
||||
if (cleanedValA === cleanedValB) {
|
||||
break;
|
||||
}
|
||||
return valA.toLowerCase() < valB.toLowerCase() ? -1 : 1;
|
||||
return cleanedValA < cleanedValB ? -1 : 1;
|
||||
}
|
||||
case 'smaller-first':
|
||||
if (valA === valB) {
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one, many } from '@mail/model/model_field';
|
||||
import { insertAndReplace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'Channel',
|
||||
identifyingFields: ['id'],
|
||||
recordMethods: {
|
||||
async fetchChannelMembers() {
|
||||
const channelData = await this.messaging.rpc({
|
||||
model: 'mail.channel',
|
||||
method: 'load_more_members',
|
||||
args: [[this.id]],
|
||||
kwargs: {
|
||||
known_member_ids: this.channelMembers.map(channelMember => channelMember.id),
|
||||
},
|
||||
});
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
this.update(channelData);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeAreAllMembersLoaded() {
|
||||
return this.memberCount === this.channelMembers.length;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeThread() {
|
||||
return insertAndReplace({
|
||||
id: this.id,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {integer}
|
||||
*/
|
||||
_computeUnknownMemberCount() {
|
||||
return this.memberCount - this.channelMembers.length;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Array[]}
|
||||
*/
|
||||
_sortMembers() {
|
||||
return [
|
||||
['defined-first', 'name'],
|
||||
['case-insensitive-asc', 'name'],
|
||||
];
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
areAllMembersLoaded: attr({
|
||||
compute: '_computeAreAllMembersLoaded',
|
||||
}),
|
||||
channelMembers: many('ChannelMember', {
|
||||
inverse: 'channel',
|
||||
isCausal: true,
|
||||
}),
|
||||
id: attr({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
memberCount: attr({
|
||||
related: 'thread.memberCount',
|
||||
}),
|
||||
orderedOfflineMembers: many('ChannelMember', {
|
||||
inverse: 'channelAsOfflineMember',
|
||||
sort: '_sortMembers',
|
||||
}),
|
||||
orderedOnlineMembers: many('ChannelMember', {
|
||||
inverse: 'channelAsOnlineMember',
|
||||
sort: '_sortMembers',
|
||||
}),
|
||||
thread: one('Thread', {
|
||||
compute: '_computeThread',
|
||||
inverse: 'channel',
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
/**
|
||||
* States how many members are currently unknown on the client side.
|
||||
* This is the difference between the total number of members of the
|
||||
* channel as reported in memberCount and those actually in members.
|
||||
*/
|
||||
unknownMemberCount: attr({
|
||||
compute: '_computeUnknownMemberCount',
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one, many } from '@mail/model/model_field';
|
||||
import { clear, replace } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'ChannelMember',
|
||||
identifyingFields: ['id'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeChannelAsOfflineMember() {
|
||||
return this.partner && !this.partner.isOnline ? replace(this.channel) : clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeChannelAsOnlineMember() {
|
||||
return this.partner && this.partner.isOnline ? replace(this.channel) : clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeName() {
|
||||
return this.partner.nameOrDisplayName;
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
channel: one('Channel', {
|
||||
inverse: 'channelMembers',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
channelAsOfflineMember: one('Channel', {
|
||||
compute: '_computeChannelAsOfflineMember',
|
||||
inverse: 'orderedOfflineMembers',
|
||||
}),
|
||||
channelAsOnlineMember: one('Channel', {
|
||||
compute: '_computeChannelAsOnlineMember',
|
||||
inverse: 'orderedOnlineMembers',
|
||||
}),
|
||||
channelMemberViews: many('ChannelMemberView', {
|
||||
inverse: 'channelMember',
|
||||
isCausal: true,
|
||||
}),
|
||||
id: attr({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
name: attr({
|
||||
compute: '_computeName',
|
||||
}),
|
||||
partner: one('Partner', {
|
||||
inverse: 'channelMembers',
|
||||
readonly: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -26,12 +26,12 @@ registerModel({
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeChannelMemberViews() {
|
||||
_computeChannelMemberViews() {
|
||||
if (this.members.length === 0) {
|
||||
return clear();
|
||||
}
|
||||
return insertAndReplace(
|
||||
this.members.map(member => ({ partner: replace(member) })),
|
||||
this.members.map(member => ({ channelMember: replace(member) })),
|
||||
);
|
||||
},
|
||||
/**
|
||||
@@ -72,7 +72,7 @@ registerModel({
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
channel: one('Thread', {
|
||||
channel: one('Channel', {
|
||||
compute: '_computeChannel',
|
||||
required: true,
|
||||
}),
|
||||
@@ -89,7 +89,7 @@ registerModel({
|
||||
inverse: 'channelMemberListCategoryViewOwner',
|
||||
isCausal: true,
|
||||
}),
|
||||
members: many('Partner', {
|
||||
members: many('ChannelMember', {
|
||||
compute: '_computeMembers',
|
||||
}),
|
||||
title: attr({
|
||||
|
||||
@@ -7,17 +7,28 @@ import { clear, insertAndReplace, replace } from '@mail/model/model_field_comman
|
||||
registerModel({
|
||||
name: 'ChannelMemberListView',
|
||||
identifyingFields: [['chatWindowOwner', 'threadViewOwner']],
|
||||
lifecycleHooks: {
|
||||
_created() {
|
||||
this.channel.fetchChannelMembers();
|
||||
},
|
||||
},
|
||||
recordMethods: {
|
||||
/**
|
||||
* Handles click on the "load more members" button.
|
||||
*/
|
||||
async onClickLoadMoreMembers() {
|
||||
this.channel.fetchChannelMembers();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeChannel() {
|
||||
if (this.chatWindowOwner) {
|
||||
return replace(this.chatWindowOwner.thread);
|
||||
return replace(this.chatWindowOwner.thread.channel);
|
||||
}
|
||||
if (this.threadViewOwner) {
|
||||
return replace(this.threadViewOwner.thread);
|
||||
return replace(this.threadViewOwner.thread.channel);
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
@@ -43,7 +54,7 @@ registerModel({
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
channel: one('Thread', {
|
||||
channel: one('Channel', {
|
||||
compute: '_computeChannel',
|
||||
readonly: true,
|
||||
}),
|
||||
|
||||
@@ -5,14 +5,36 @@ import { one } from '@mail/model/model_field';
|
||||
|
||||
registerModel({
|
||||
name: 'ChannelMemberView',
|
||||
identifyingFields: ['channelMemberListCategoryViewOwner', 'partner'],
|
||||
identifyingFields: ['channelMemberListCategoryViewOwner', 'channelMember'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* Handles click on the avatar of the channel member in the member list
|
||||
* of this channel.
|
||||
*/
|
||||
onClickMemberAvatar() {
|
||||
if (!this.channelMember.partner) {
|
||||
return;
|
||||
}
|
||||
this.channelMember.partner.openChat();
|
||||
},
|
||||
/**
|
||||
* Handles click on the name of the channel member in the member list of
|
||||
* this channel.
|
||||
*/
|
||||
onClickMemberName() {
|
||||
if (!this.channelMember.partner) {
|
||||
return;
|
||||
}
|
||||
this.channelMember.partner.openProfile();
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
channelMemberListCategoryViewOwner: one('ChannelMemberListCategoryView', {
|
||||
inverse: 'channelMemberViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
partner: one('Partner', {
|
||||
channelMember: one('ChannelMember', {
|
||||
inverse: 'channelMemberViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
|
||||
@@ -376,7 +376,7 @@ registerModel({
|
||||
inverse: 'partner',
|
||||
isCausal: true,
|
||||
}),
|
||||
channelMemberViews: many('ChannelMemberView', {
|
||||
channelMembers: many('ChannelMember', {
|
||||
inverse: 'partner',
|
||||
isCausal: true,
|
||||
}),
|
||||
|
||||
@@ -797,24 +797,6 @@ registerModel({
|
||||
pinned: this.isPendingPinned,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Handles click on the avatar of the given member in the member list of
|
||||
* this channel.
|
||||
*
|
||||
* @param {Partner} member
|
||||
*/
|
||||
onClickMemberAvatar(member) {
|
||||
member.openChat();
|
||||
},
|
||||
/**
|
||||
* Handles click on the name of the given member in the member list of
|
||||
* this channel.
|
||||
*
|
||||
* @param {Partner} member
|
||||
*/
|
||||
onClickMemberName(member) {
|
||||
member.openProfile();
|
||||
},
|
||||
/**
|
||||
* Opens this thread either as form view, in discuss app, or as a chat
|
||||
* window. The thread will be opened in an "active" matter, which will
|
||||
@@ -1053,6 +1035,13 @@ registerModel({
|
||||
});
|
||||
return replace(allAttachments);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeChannel() {
|
||||
return this.model === 'mail.channel' ? insertAndReplace({ id: this.id }) : clear();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
@@ -1468,20 +1457,6 @@ registerModel({
|
||||
}
|
||||
return replace(message);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Partner[]}
|
||||
*/
|
||||
_computeOrderedOfflineMembers() {
|
||||
return replace(this._sortMembers(this.members.filter(member => !member.isOnline)));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Partner[]}
|
||||
*/
|
||||
_computeOrderedOnlineMembers() {
|
||||
return replace(this._sortMembers(this.members.filter(member => member.isOnline)));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Message[]}
|
||||
@@ -1565,13 +1540,6 @@ registerModel({
|
||||
this.getMemberName(this.orderedOtherTypingMembers[1])
|
||||
);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {integer}
|
||||
*/
|
||||
_computeUnknownMemberCount() {
|
||||
return this.memberCount - this.members.length;
|
||||
},
|
||||
/**
|
||||
* Compute an url string that can be used inside a href attribute
|
||||
*
|
||||
@@ -1701,44 +1669,12 @@ registerModel({
|
||||
['case-insensitive-asc', 'nameOrDisplayName'],
|
||||
];
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Partner[]} members
|
||||
* @returns {Partner[]}
|
||||
*/
|
||||
_sortMembers(members) {
|
||||
return [...members].sort((a, b) => {
|
||||
const cleanedAName = cleanSearchTerm(a.nameOrDisplayName || '');
|
||||
const cleanedBName = cleanSearchTerm(b.nameOrDisplayName || '');
|
||||
if (cleanedAName < cleanedBName) {
|
||||
return -1;
|
||||
}
|
||||
if (cleanedAName > cleanedBName) {
|
||||
return 1;
|
||||
}
|
||||
return a.id - b.id;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Event handler for clicking thread in discuss app.
|
||||
*/
|
||||
async onClick() {
|
||||
await this.open();
|
||||
},
|
||||
/**
|
||||
* Handles click on the "load more members" button.
|
||||
*/
|
||||
async onClickLoadMoreMembers() {
|
||||
const members = await this.messaging.rpc({
|
||||
model: 'mail.channel',
|
||||
method: 'load_more_members',
|
||||
args: [[this.id]],
|
||||
kwargs: {
|
||||
'known_member_ids': this.members.map(partner => partner.id),
|
||||
},
|
||||
});
|
||||
this.update({ members });
|
||||
},
|
||||
onCurrentPartnerInactiveTypingTimeout() {
|
||||
this.unregisterCurrentPartnerIsTyping();
|
||||
},
|
||||
@@ -1785,6 +1721,12 @@ registerModel({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
channel: one('Channel', {
|
||||
compute: '_computeChannel',
|
||||
inverse: 'thread',
|
||||
isCausal: true,
|
||||
readonly: true,
|
||||
}),
|
||||
channel_type: attr(),
|
||||
/**
|
||||
* States the chat window related to this thread (if any).
|
||||
@@ -2174,18 +2116,6 @@ registerModel({
|
||||
needactionMessagesAsOriginThread: many('Message', {
|
||||
compute: '_computeNeedactionMessagesAsOriginThread',
|
||||
}),
|
||||
/**
|
||||
* All offline members ordered like they are displayed.
|
||||
*/
|
||||
orderedOfflineMembers: many('Partner', {
|
||||
compute: '_computeOrderedOfflineMembers',
|
||||
}),
|
||||
/**
|
||||
* All online members ordered like they are displayed.
|
||||
*/
|
||||
orderedOnlineMembers: many('Partner', {
|
||||
compute: '_computeOrderedOnlineMembers',
|
||||
}),
|
||||
/**
|
||||
* All messages ordered like they are displayed.
|
||||
*/
|
||||
@@ -2385,14 +2315,6 @@ registerModel({
|
||||
compute: '_computeTypingStatusText',
|
||||
default: '',
|
||||
}),
|
||||
/**
|
||||
* States how many members are currently unknown on the client side.
|
||||
* This is the difference between the total number of members of the
|
||||
* channel as reported in memberCount and those actually in members.
|
||||
*/
|
||||
unknownMemberCount: attr({
|
||||
compute: '_computeUnknownMemberCount',
|
||||
}),
|
||||
/**
|
||||
* URL to access to the conversation.
|
||||
*/
|
||||
|
||||
@@ -219,6 +219,11 @@ patch(MockServer.prototype, 'mail', {
|
||||
const ids = args.args[0];
|
||||
return this._mockMailChannelWriteImage128(ids[0]);
|
||||
}
|
||||
if (args.model === 'mail.channel' && args.method === 'load_more_members') {
|
||||
const [channel_ids] = args.args;
|
||||
const { known_member_ids } = args.kwargs;
|
||||
return this._mockMailChannelLoadMoreMembers(channel_ids, known_member_ids);
|
||||
}
|
||||
// mail.message methods
|
||||
if (args.model === 'mail.message' && args.method === 'mark_all_as_read') {
|
||||
const domain = args.args[0] || args.kwargs.domain;
|
||||
@@ -1286,6 +1291,35 @@ patch(MockServer.prototype, 'mail', {
|
||||
}
|
||||
return messageData;
|
||||
},
|
||||
/**
|
||||
* Simulates `load_more_members` on `mail.channel`.
|
||||
*
|
||||
* @private
|
||||
* @param {integer[]} channel_ids
|
||||
* @param {integer[]} known_member_ids
|
||||
*/
|
||||
_mockMailChannelLoadMoreMembers(channel_ids, known_member_ids) {
|
||||
const channelPartners = this.pyEnv['mail.channel.partner'].searchRead([
|
||||
['id', 'not in', known_member_ids],
|
||||
['channel_id', 'in', channel_ids],
|
||||
], { limit: 100 });
|
||||
const memberCount = this.pyEnv['mail.channel.partner'].searchCount([
|
||||
['channel_id', 'in', channel_ids],
|
||||
]);
|
||||
return {
|
||||
channelMembers: [['insert', channelPartners.map(channelPartner => {
|
||||
const [partner] = this.pyEnv['res.partner'].searchRead(
|
||||
[['id', '=', channelPartner.partner_id[0]]],
|
||||
{ fields: ['id', 'name', 'im_status'] }
|
||||
);
|
||||
return {
|
||||
id: channelPartner.id,
|
||||
partner: [['insert', partner]],
|
||||
};
|
||||
})]],
|
||||
memberCount,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Simulates `notify_typing` on `mail.channel`.
|
||||
*
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_tests.js', {
|
||||
QUnit.module('channel_member_list_tests.js', {
|
||||
beforeEach() {
|
||||
this.start = async params => {
|
||||
return start(Object.assign({}, params, {
|
||||
@@ -113,7 +113,7 @@ QUnit.test('should have correct members in member list', async function (assert)
|
||||
}"]`,
|
||||
"should have 'Demo' in member list ('Demo' is a member)",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('there should be a button to hide member list in the thread view topbar when the member list is visible', async function (assert) {
|
||||
assert.expect(1);
|
||||
@@ -143,5 +143,66 @@ QUnit.test('there should be a button to hide member list in the thread view topb
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('should show a button to load more members if they are not all loaded', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const channel_last_seen_partner_ids = [[0, 0, { partner_id: pyEnv.currentPartnerId }]];
|
||||
for (let i = 0; i < 101; i++) {
|
||||
const resPartnerId = pyEnv['res.partner'].create({ name: "name" + i });
|
||||
channel_last_seen_partner_ids.push([0, 0, { partner_id: resPartnerId }]);
|
||||
}
|
||||
const mailChannelId = pyEnv['mail.channel'].create({
|
||||
channel_last_seen_partner_ids,
|
||||
channel_type: 'group',
|
||||
public: 'private',
|
||||
});
|
||||
const { click } = await this.start({
|
||||
discuss: {
|
||||
context: {
|
||||
active_id: `mail.channel_${mailChannelId}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await click('.o_ThreadViewTopbar_showMemberListButton');
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ChannelMemberList_loadMoreButton',
|
||||
"should have a load more button because 100 members were fetched initially and there are 102 members in total",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('Load more button should load more members', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const channel_last_seen_partner_ids = [[0, 0, { partner_id: pyEnv.currentPartnerId }]];
|
||||
for (let i = 0; i < 101; i++) {
|
||||
const resPartnerId = pyEnv['res.partner'].create({ name: "name" + i });
|
||||
channel_last_seen_partner_ids.push([0, 0, { partner_id: resPartnerId }]);
|
||||
}
|
||||
const mailChannelId = pyEnv['mail.channel'].create({
|
||||
channel_last_seen_partner_ids,
|
||||
channel_type: 'group',
|
||||
public: 'private',
|
||||
});
|
||||
const { click } = await this.start({
|
||||
discuss: {
|
||||
context: {
|
||||
active_id: `mail.channel_${mailChannelId}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await click('.o_ThreadViewTopbar_showMemberListButton');
|
||||
await click('.o_ChannelMemberList_loadMoreButton');
|
||||
assert.containsN(
|
||||
document.body,
|
||||
'.o_ChannelMember',
|
||||
102,
|
||||
"should load all the members because 100 members were fetched initially, and load more fetched the remaining 2 members",
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user