diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index 67c721d2932..f5b04c83bb6 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -2,7 +2,7 @@ { 'name': 'Discuss', - 'version': '1.4', + 'version': '1.5', 'category': 'Productivity/Discuss', 'sequence': 145, 'summary': 'Chat, mail gateway and private channels', @@ -22,7 +22,6 @@ 'views/mail_message_views.xml', 'views/mail_mail_views.xml', 'views/mail_followers_views.xml', - 'views/mail_moderation_views.xml', 'views/mail_channel_partner_views.xml', 'views/mail_channel_views.xml', 'views/mail_shortcode_views.xml', @@ -139,9 +138,6 @@ 'mail/static/src/components/message_seen_indicator/message_seen_indicator.js', 'mail/static/src/components/messaging_menu/messaging_menu.js', 'mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.js', - 'mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js', - 'mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js', - 'mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js', 'mail/static/src/components/notification_alert/notification_alert.js', 'mail/static/src/components/notification_group/notification_group.js', 'mail/static/src/components/notification_list/notification_list.js', @@ -301,7 +297,6 @@ 'mail/static/src/components/dialog_manager/dialog_manager_tests.js', 'mail/static/src/components/discuss/tests/discuss_domain_tests.js', 'mail/static/src/components/discuss/tests/discuss_inbox_tests.js', - 'mail/static/src/components/discuss/tests/discuss_moderation_tests.js', 'mail/static/src/components/discuss/tests/discuss_pinned_tests.js', 'mail/static/src/components/discuss/tests/discuss_sidebar_tests.js', 'mail/static/src/components/discuss/tests/discuss_tests.js', @@ -396,9 +391,6 @@ 'mail/static/src/components/message_seen_indicator/message_seen_indicator.xml', 'mail/static/src/components/messaging_menu/messaging_menu.xml', 'mail/static/src/components/mobile_messaging_navbar/mobile_messaging_navbar.xml', - 'mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.xml', - 'mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.xml', - 'mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.xml', 'mail/static/src/components/notification_alert/notification_alert.xml', 'mail/static/src/components/notification_group/notification_group.xml', 'mail/static/src/components/notification_list/notification_list.xml', diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index a330f02a738..cad07d1e3ff 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -264,8 +264,6 @@ class MailController(http.Controller): 'mention_partner_suggestions': request.env['res.partner'].get_static_mention_suggestions(), 'shortcodes': request.env['mail.shortcode'].sudo().search_read([], ['source', 'substitution', 'description']), 'menu_id': request.env['ir.model.data'].xmlid_to_res_id('mail.menu_root_discuss'), - 'moderation_counter': request.env.user.moderation_counter, - 'moderation_channel_ids': request.env.user.moderation_channel_ids.ids, 'partner_root': request.env.ref('base.partner_root').sudo().mail_partner_format(), 'public_partners': [partner.mail_partner_format() for partner in request.env.ref('base.group_public').sudo().with_context(active_test=False).users.partner_id], 'current_partner': request.env.user.partner_id.mail_partner_format(), diff --git a/addons/mail/data/ir_cron_data.xml b/addons/mail/data/ir_cron_data.xml index 10073110a3a..2945e687420 100644 --- a/addons/mail/data/ir_cron_data.xml +++ b/addons/mail/data/ir_cron_data.xml @@ -31,19 +31,6 @@ - - Mail: Notify channel moderators - - code - model._notify_moderators - - 1 - days - -1 - - 1000 - - Notification: Delete Notifications older than 6 Month 1 diff --git a/addons/mail/data/mail_templates.xml b/addons/mail/data/mail_templates.xml index 3071cb5bc0c..5e8c59c30ee 100644 --- a/addons/mail/data/mail_templates.xml +++ b/addons/mail/data/mail_templates.xml @@ -341,29 +341,5 @@
- - - - - diff --git a/addons/mail/models/__init__.py b/addons/mail/models/__init__.py index 97eaa360340..8065fa6b716 100644 --- a/addons/mail/models/__init__.py +++ b/addons/mail/models/__init__.py @@ -17,7 +17,6 @@ from . import mail_thread from . import mail_thread_blacklist from . import mail_thread_cc from . import mail_blacklist -from . import mail_moderation from . import mail_channel_partner from . import mail_channel from . import mail_template diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index b05fc2055be..83a7691475e 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -11,7 +11,6 @@ from odoo.exceptions import UserError, ValidationError from odoo.osv import expression from odoo.tools import ormcache, formataddr -MODERATION_FIELDS = ['moderation', 'moderator_ids', 'moderation_ids', 'moderation_notify', 'moderation_notify_msg', 'moderation_guidelines', 'moderation_guidelines_msg'] _logger = logging.getLogger(__name__) @@ -48,7 +47,6 @@ class Channel(models.Model): description = fields.Text('Description') email_send = fields.Boolean('Send messages by email', default=False) image_128 = fields.Image("Image", max_width=128, max_height=128, default=_get_default_image) - # members (depends=['...'] is for `test_mail/tests/common.py`, class Moderation, `setUpClass`) channel_partner_ids = fields.Many2many( 'res.partner', string='Members', compute='_compute_channel_partner_ids', inverse='_inverse_channel_partner_ids', @@ -73,21 +71,6 @@ class Channel(models.Model): help='This group is visible by non members. Invisible groups can add members through the invite button.') group_public_id = fields.Many2one('res.groups', string='Authorized Group', default=lambda self: self.env.ref('base.group_user')) - # moderation - moderation = fields.Boolean(string='Moderate this channel') - moderator_ids = fields.Many2many('res.users', 'mail_channel_moderator_rel', string='Moderators') - is_moderator = fields.Boolean(help="Current user is a moderator of the channel", string='Moderator', compute="_compute_is_moderator") - moderation_ids = fields.One2many( - 'mail.moderation', 'channel_id', string='Moderated Emails', - groups="base.group_user") - moderation_count = fields.Integer( - string='Moderated emails count', compute='_compute_moderation_count', - groups="base.group_user") - moderation_notify = fields.Boolean(string="Automatic notification", help="People receive an automatic notification about their message being waiting for moderation.") - moderation_notify_msg = fields.Text(string="Notification message") - moderation_guidelines = fields.Boolean(string="Send guidelines to new subscribers", help="Newcomers on this moderated channel will automatically receive the guidelines.") - moderation_guidelines_msg = fields.Text(string="Guidelines") - # COMPUTE / INVERSE @api.depends('channel_type') @@ -133,46 +116,6 @@ class Channel(models.Model): for channel in self: channel.is_member = self.env.user.partner_id in channel.channel_partner_ids - @api.depends('moderator_ids') - @api.depends_context('uid') - def _compute_is_moderator(self): - for channel in self: - channel.is_moderator = self.env.user in channel.moderator_ids - - @api.depends('moderation_ids') - def _compute_moderation_count(self): - read_group_res = self.env['mail.moderation'].read_group([('channel_id', 'in', self.ids)], ['channel_id'], 'channel_id') - data = dict((res['channel_id'][0], res['channel_id_count']) for res in read_group_res) - for channel in self: - channel.moderation_count = data.get(channel.id, 0) - - # CONSTRAINTS - - @api.constrains('moderator_ids') - def _check_moderator_email(self): - if any(not moderator.email for channel in self for moderator in channel.moderator_ids): - raise ValidationError(_("Moderators must have an email address.")) - - @api.constrains('moderator_ids', 'channel_partner_ids') - def _check_moderator_is_member(self): - for channel in self: - if channel.moderator_ids.partner_id > channel.channel_partner_ids: - missing = channel.mapped('moderator_ids.partner_id') - channel.channel_partner_ids - raise ValidationError( - _("Moderators should be members of the channel they moderate (missing %(user_names)s).", - user_names=', '.join(missing.mapped('name'))) - ) - - @api.constrains('moderation', 'email_send') - def _check_moderation_parameters(self): - if any(not channel.email_send and channel.moderation for channel in self): - raise ValidationError(_('Only mailing lists can be moderated.')) - - @api.constrains('moderator_ids') - def _check_moderator_existence(self): - if any(not channel.moderator_ids for channel in self if channel.moderation): - raise ValidationError(_('Moderated channels must have moderators.')) - # ONCHANGE @api.onchange('public') @@ -180,29 +123,6 @@ class Channel(models.Model): if self.public != 'public' and self.alias_contact == 'everyone': self.alias_contact = 'followers' - @api.onchange('moderator_ids') - def _onchange_moderator_ids(self): - missing_partner_ids = set(self.mapped('moderator_ids.partner_id').ids) - set(self.mapped('channel_last_seen_partner_ids.partner_id').ids) - if missing_partner_ids: - self.channel_last_seen_partner_ids = [ - Command.create({'partner_id': partner_id}) - for partner_id in missing_partner_ids - ] - - @api.onchange('email_send') - def _onchange_email_send(self): - if not self.email_send: - self.moderation = False - - @api.onchange('moderation') - def _onchange_moderation(self): - if not self.moderation: - self.moderation_notify = False - self.moderation_guidelines = False - self.moderator_ids = False - else: - self.moderator_ids |= self.env.user - # ------------------------------------------------------------ # CRUD # ------------------------------------------------------------ @@ -271,27 +191,11 @@ class Channel(models.Model): raise UserError(_('You cannot delete those groups, as the Whole Company group is required by other modules.')) def write(self, vals): - # First checks if user tries to modify moderation fields and has not the right to do it. - if any(key for key in MODERATION_FIELDS if vals.get(key)) and not self.env.user.has_group('base.group_system'): - not_moderator = self.filtered(lambda channel: channel.moderation and self.env.user not in channel.moderator_ids) - if not_moderator: - raise UserError(_("You do not have rights to modify moderation configuration on %(channel_names)s", - channel_names=', '.join(not_moderator.mapped('name')) - )) - result = super(Channel, self).write(vals) if vals.get('group_ids'): self._subscribe_users_automatically() - # avoid keeping messages to moderate and accept them - if vals.get('moderation') is False: - self.env['mail.message'].search([ - ('moderation_status', '=', 'pending_moderation'), - ('model', '=', 'mail.channel'), - ('res_id', 'in', self.ids) - ])._moderate_accept() - return result def init(self): @@ -568,19 +472,12 @@ class Channel(models.Model): return super(Channel, self)._notify_email_recipient_values(whitelist.ids) def _notify_thread(self, message, msg_vals=False, **kwargs): - # When posting a message on a mail channel, manage moderation and postpone notify users - if not msg_vals or msg_vals.get('moderation_status') != 'pending_moderation': - # link message to channel - rdata = super(Channel, self)._notify_thread(message, msg_vals=msg_vals, **kwargs) - - message_format_values = message.message_format()[0] - bus_notifications = self._channel_message_notifications(message, message_format_values) - self.env['bus.bus'].sudo().sendmany(bus_notifications) - - else: - message._notify_pending_by_chat() - rdata = False + # link message to channel + rdata = super(Channel, self)._notify_thread(message, msg_vals=msg_vals, **kwargs) + message_format_values = message.message_format()[0] + bus_notifications = self._channel_message_notifications(message, message_format_values) + self.env['bus.bus'].sudo().sendmany(bus_notifications) return rdata def _message_receive_bounce(self, email, partner): @@ -592,10 +489,6 @@ class Channel(models.Model): @api.returns('mail.message', lambda value: value.id) def message_post(self, *, message_type='notification', **kwargs): - moderation_status, email = self._extract_moderation_values(message_type, **kwargs) - if moderation_status == 'rejected': - return self.env['mail.message'] - self.filtered(lambda channel: channel.is_chat).mapped('channel_last_seen_partner_ids').sudo().write({'is_pinned': True}) # mail_post_autofollow=False is necessary to prevent adding followers @@ -605,21 +498,7 @@ class Channel(models.Model): # The current client code might be setting the key to True on sending # message but it is only useful when targeting customers in chatter. # This value should simply be set to False in channels no matter what. - message = super(Channel, self.with_context(mail_create_nosubscribe=True, mail_post_autofollow=False)).message_post(message_type=message_type, moderation_status=moderation_status, **kwargs) - - # Notifies the message author when his message is pending moderation if required on channel. - # The fields "email_from" and "reply_to" are filled in automatically by method create in model mail.message. - if self.moderation_notify and self.moderation_notify_msg and message_type in ['email','comment'] and moderation_status == 'pending_moderation': - self.env['mail.mail'].sudo().create({ - 'author_id': self.env.user.partner_id.id, - 'email_from': self.env.user.company_id.catchall_formatted or self.env.user.company_id.email_formatted, - 'body_html': self.moderation_notify_msg, - 'subject': 'Re: %s' % (kwargs.get('subject', '')), - 'email_to': email, - 'auto_delete': True, - 'state': 'outgoing' - }) - return message + return super(Channel, self.with_context(mail_create_nosubscribe=True, mail_post_autofollow=False)).message_post(message_type=message_type, **kwargs) def _message_post_after_hook(self, message, msg_vals): """ @@ -628,92 +507,11 @@ class Channel(models.Model): self._set_last_seen_message(message) return super()._message_post_after_hook(message=message, msg_vals=msg_vals) - def _extract_moderation_values(self, message_type, **kwargs): - """ This method is used to compute moderation status before the creation - of a message. For this operation the message's author email address is required. - This address is returned with status for other computations. """ - moderation_status = 'accepted' - email = '' - if self.moderation and message_type in ['email', 'comment']: - author_id = kwargs.get('author_id') - if author_id and isinstance(author_id, int): - email = self.env['res.partner'].browse([author_id]).email - elif author_id: - email = author_id.email - elif kwargs.get('email_from'): - email = tools.email_split(kwargs['email_from'])[0] - else: - email = self.env.user.email - if email in self.mapped('moderator_ids.email'): - return moderation_status, email - status = self.env['mail.moderation'].sudo().search([('email', '=', email), ('channel_id', 'in', self.ids)]).mapped('status') - if status and status[0] == 'allow': - moderation_status = 'accepted' - elif status and status[0] == 'ban': - moderation_status = 'rejected' - else: - moderation_status = 'pending_moderation' - return moderation_status, email - def _message_subscribe(self, partner_ids=None, subtype_ids=None, customer_ids=None): """ Do not allow follower subscription on channels. Only members are considered. """ raise UserError(_('Adding followers on channels is not possible. Consider adding members instead.')) - # ------------------------------------------------------------ - # MODERATION - # ------------------------------------------------------------ - - def send_guidelines(self): - """ Send guidelines to all channel members. """ - if self.env.user in self.moderator_ids or self.env.user.has_group('base.group_system'): - success = self._send_guidelines(self.channel_partner_ids) - if not success: - raise UserError(_('View "mail.mail_channel_send_guidelines" was not found. No email has been sent. Please contact an administrator to fix this issue.')) - else: - raise UserError(_("Only an administrator or a moderator can send guidelines to channel members!")) - - def _send_guidelines(self, partners): - """ Send guidelines of a given channel. Returns False if template used for guidelines - not found. Caller may have to handle this return value. """ - self.ensure_one() - view = self.env.ref('mail.mail_channel_send_guidelines', raise_if_not_found=False) - if not view: - _logger.warning('View "mail.mail_channel_send_guidelines" was not found.') - return False - banned_emails = self.env['mail.moderation'].sudo().search([ - ('status', '=', 'ban'), - ('channel_id', 'in', self.ids) - ]).mapped('email') - for partner in partners.filtered(lambda p: p.email and not (p.email in banned_emails)): - company = partner.company_id or self.env.company - create_values = { - 'email_from': company.catchall_formatted or company.email_formatted, - 'author_id': self.env.user.partner_id.id, - 'body_html': view._render({'channel': self, 'partner': partner}, engine='ir.qweb', minimal_qcontext=True), - 'subject': _("Guidelines of channel %s", self.name), - 'recipient_ids': [Command.link(partner.id)] - } - mail = self.env['mail.mail'].sudo().create(create_values) - return True - - def _update_moderation_email(self, emails, status): - """ This method adds emails into either white or black of the channel list of emails - according to status. If an email in emails is already moderated, the method updates the email status. - :param emails: list of email addresses to put in white or black list of channel. - :param status: value is 'allow' or 'ban'. Emails are put in white list if 'allow', in black list if 'ban'. - """ - self.ensure_one() - splitted_emails = [tools.email_split(email)[0] for email in emails if tools.email_split(email)] - moderated = self.env['mail.moderation'].sudo().search([ - ('email', 'in', splitted_emails), - ('channel_id', 'in', self.ids) - ]) - cmds = [Command.update(record.id, {'status': status}) for record in moderated] - not_moderated = [email for email in splitted_emails if email not in moderated.mapped('email')] - cmds += [Command.create({'email': email, 'status': status}) for email in not_moderated] - return self.write({'moderation_ids': cmds}) - # ------------------------------------------------------------ # BROADCAST # ------------------------------------------------------------ @@ -813,8 +611,6 @@ class Channel(models.Model): 'channel_type': channel.channel_type, 'public': channel.public, 'mass_mailing': channel.email_send, - 'moderation': channel.moderation, - 'is_moderator': self.env.uid in channel.moderator_ids.ids, 'group_based_subscription': bool(channel.group_ids), 'create_uid': channel.create_uid.id, } @@ -1126,9 +922,6 @@ class Channel(models.Model): notification = _('
joined #%s
', self.id, self.name) self.message_post(body=notification, message_type="notification", subtype_xmlid="mail.mt_comment") - if added and self.moderation_guidelines: - self._send_guidelines(self.env.user.partner_id) - channel_info = self.channel_info('join')[0] self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), channel_info) return channel_info diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index e0f7f9d5af1..4817acb4200 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -162,13 +162,6 @@ class Message(models.Model): message_id = fields.Char('Message-Id', help='Message unique identifier', index=True, readonly=1, copy=False) reply_to = fields.Char('Reply-To', help='Reply email address. Setting the reply_to bypasses the automatic thread creation.') mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server') - # moderation - moderation_status = fields.Selection([ - ('pending_moderation', 'Pending Moderation'), - ('accepted', 'Accepted'), - ('rejected', 'Rejected')], string="Moderation Status", index=True) - moderator_id = fields.Many2one('res.users', string="Moderated By", index=True) - need_moderation = fields.Boolean('Need moderation', compute='_compute_need_moderation', search='_search_need_moderation') # keep notification layout informations to be able to generate mail again email_layout_xmlid = fields.Char('Layout', copy=False) # xml id of layout add_sign = fields.Boolean(default=True) @@ -232,21 +225,6 @@ class Message(models.Model): return [('starred_partner_ids', 'in', [self.env.user.partner_id.id])] return [('starred_partner_ids', 'not in', [self.env.user.partner_id.id])] - def _compute_need_moderation(self): - for message in self: - message.need_moderation = False - - @api.model - def _search_need_moderation(self, operator, operand): - if operator == '=' and operand is True: - return ['&', '&', - ('moderation_status', '=', 'pending_moderation'), - ('model', '=', 'mail.channel'), - ('res_id', 'in', self.env.user.moderation_channel_ids.ids)] - - # no support for other operators - raise UserError(_('Unsupported search filter on moderation status')) - # ------------------------------------------------------ # CRUD / ORM # ------------------------------------------------------ @@ -360,12 +338,10 @@ class Message(models.Model): - write: if - author_id == pid, uid is the author, OR - uid is in the recipients (partner_ids) OR - - uid is moderator of the channel and moderation_status is pending_moderation OR - - uid has write or create access on the related document if model, res_id and moderation_status is not pending_moderation + - uid has write or create access on the related document if model, res_id - otherwise: raise - unlink: if - - uid is moderator of the channel and moderation_status is pending_moderation OR - - uid has write or create access on the related document if model, res_id and moderation_status is not pending_moderation + - uid has write or create access on the related document - otherwise: raise Specific case: non employee users see only messages with subtype (aka do @@ -402,16 +378,13 @@ class Message(models.Model): # Read mail_message.ids to have their values message_values = dict((message_id, {}) for message_id in self.ids) - self.flush(['model', 'res_id', 'author_id', 'parent_id', 'moderation_status', 'message_type', 'partner_ids']) + self.flush(['model', 'res_id', 'author_id', 'parent_id', 'message_type', 'partner_ids']) self.env['mail.notification'].flush(['mail_message_id', 'res_partner_id']) - self.env['mail.channel'].flush(['moderator_ids']) - self.env['res.users'].flush(['moderation_channel_ids']) if operation == 'read': self._cr.execute(""" SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id), - m.moderation_status, m.message_type as message_type FROM "%s" m LEFT JOIN "mail_message_res_partner_rel" partner_rel @@ -419,74 +392,47 @@ class Message(models.Model): LEFT JOIN "mail_notification" needaction_rel ON needaction_rel.mail_message_id = m.id AND needaction_rel.res_partner_id = %%(pid)s WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=self.env.user.partner_id.id, ids=self.ids)) - for mid, rmod, rid, author_id, parent_id, partner_id, moderation_status, message_type in self._cr.fetchall(): + for mid, rmod, rid, author_id, parent_id, partner_id, message_type in self._cr.fetchall(): message_values[mid] = { 'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id, - 'moderation_status': moderation_status, - 'moderator_id': False, 'notified': any((message_values[mid].get('notified'), partner_id)), 'message_type': message_type, } elif operation == 'write': self._cr.execute(""" - SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status, + SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id), - channel_moderator_rel.res_users_id as moderator_id, m.message_type as message_type FROM "%s" m LEFT JOIN "mail_message_res_partner_rel" partner_rel ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = %%(pid)s LEFT JOIN "mail_notification" needaction_rel ON needaction_rel.mail_message_id = m.id AND needaction_rel.res_partner_id = %%(pid)s - LEFT JOIN "mail_channel" moderated_channel - ON m.moderation_status = 'pending_moderation' AND m.res_id = moderated_channel.id - LEFT JOIN "mail_channel_moderator_rel" channel_moderator_rel - ON channel_moderator_rel.mail_channel_id = moderated_channel.id AND channel_moderator_rel.res_users_id = %%(uid)s WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=self.env.user.partner_id.id, uid=self.env.user.id, ids=self.ids)) - for mid, rmod, rid, author_id, parent_id, moderation_status, partner_id, moderator_id, message_type in self._cr.fetchall(): + for mid, rmod, rid, author_id, parent_id, partner_id, message_type in self._cr.fetchall(): message_values[mid] = { 'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id, - 'moderation_status': moderation_status, - 'moderator_id': moderator_id, 'notified': any((message_values[mid].get('notified'), partner_id)), 'message_type': message_type, } - elif operation == 'create': - self._cr.execute("""SELECT DISTINCT id, model, res_id, author_id, parent_id, moderation_status, message_type FROM "%s" WHERE id = ANY (%%s)""" % self._table, (self.ids,)) - for mid, rmod, rid, author_id, parent_id, moderation_status, message_type in self._cr.fetchall(): + elif operation in ('create', 'unlink'): + self._cr.execute("""SELECT DISTINCT id, model, res_id, author_id, parent_id, message_type FROM "%s" WHERE id = ANY (%%s)""" % self._table, (self.ids,)) + for mid, rmod, rid, author_id, parent_id, message_type in self._cr.fetchall(): message_values[mid] = { 'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id, - 'moderation_status': moderation_status, - 'moderator_id': False, - 'message_type': message_type, - } - else: # unlink - self._cr.execute("""SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, m.moderation_status, channel_moderator_rel.res_users_id as moderator_id, m.message_type as message_type - FROM "%s" m - LEFT JOIN "mail_channel" moderated_channel - ON m.moderation_status = 'pending_moderation' AND m.res_id = moderated_channel.id - LEFT JOIN "mail_channel_moderator_rel" channel_moderator_rel - ON channel_moderator_rel.mail_channel_id = moderated_channel.id AND channel_moderator_rel.res_users_id = (%%s) - WHERE m.id = ANY (%%s)""" % self._table, (self.env.user.id, self.ids,)) - for mid, rmod, rid, author_id, parent_id, moderation_status, moderator_id, message_type in self._cr.fetchall(): - message_values[mid] = { - 'model': rmod, - 'res_id': rid, - 'author_id': author_id, - 'parent_id': parent_id, - 'moderation_status': moderation_status, - 'moderator_id': moderator_id, 'message_type': message_type, } + else: + raise ValueError(_('Wrong operation name (%s)', operation)) # Author condition (READ, WRITE, CREATE (private)) author_ids = [] @@ -494,18 +440,13 @@ class Message(models.Model): author_ids = [mid for mid, message in message_values.items() if message.get('author_id') and message.get('author_id') == self.env.user.partner_id.id] elif operation == 'write': - author_ids = [mid for mid, message in message_values.items() - if message.get('moderation_status') != 'pending_moderation' and message.get('author_id') == self.env.user.partner_id.id] + author_ids = [mid for mid, message in message_values.items() if message.get('author_id') == self.env.user.partner_id.id] elif operation == 'create': author_ids = [mid for mid, message in message_values.items() if not self.is_thread_message(message)] - # Moderator condition: allow to WRITE, UNLINK if moderator of a pending message - moderator_ids = [] - if operation in ['write', 'unlink']: - moderator_ids = [mid for mid, message in message_values.items() if message.get('moderator_id')] messages_to_check = self.ids - messages_to_check = set(messages_to_check).difference(set(author_ids), set(moderator_ids)) + messages_to_check = set(messages_to_check).difference(set(author_ids)) if not messages_to_check: return @@ -522,10 +463,11 @@ class Message(models.Model): # CRUD: Access rights related to the document document_related_ids = [] - document_related_candidate_ids = [mid for mid, message in message_values.items() - if (message.get('model') and message.get('res_id') and - message.get('message_type') != 'user_notification' and - (message.get('moderation_status') != 'pending_moderation' or operation not in ['write', 'unlink']))] + document_related_candidate_ids = [ + mid for mid, message in message_values.items() + if (message.get('model') and message.get('res_id') and + message.get('message_type') != 'user_notification') + ] model_record_ids = _generate_model_record_ids(message_values, document_related_candidate_ids) for model, doc_ids in model_record_ids.items(): DocumentModel = self.env[model] @@ -538,11 +480,12 @@ class Message(models.Model): mids = records.browse(doc_ids)._filter_access_rules(check_operation) document_related_ids += [ mid for mid, message in message_values.items() - if (message.get('model') == model and + if ( + message.get('model') == model and message.get('res_id') in mids.ids and - message.get('message_type') != 'user_notification' and - (message.get('moderation_status') != 'pending_moderation' or - operation not in ['write', 'unlink']))] + message.get('message_type') != 'user_notification' + ) + ] messages_to_check = messages_to_check.difference(set(document_related_ids)) @@ -794,163 +737,6 @@ class Message(models.Model): notification = {'type': 'toggle_star', 'message_ids': [self.id], 'starred': starred} self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification) - # -------------------------------------------------- - # MODERATION API - # -------------------------------------------------- - - def moderate(self, decision, **kwargs): - """ Moderate messages. A check is done on moderation status of the - current user to ensure we only moderate valid messages. """ - moderated_channels = self.env.user.moderation_channel_ids - to_moderate = [message.id for message in self - if message.model == 'mail.channel' and - message.res_id in moderated_channels.ids and - message.moderation_status == 'pending_moderation'] - if to_moderate: - self.browse(to_moderate)._moderate(decision, **kwargs) - - def _moderate(self, decision, **kwargs): - """ :param decision - * accept - moderate message and broadcast that message to followers of relevant channels. - * reject - message will be deleted from the database without broadcast - an email sent to the author with an explanation that the moderators can edit. - * discard - message will be deleted from the database without broadcast. - * allow - add email address to white list people of specific channel, - so that next time if a message come from same email address on same channel, - it will be automatically broadcasted to relevant channels without any approval from moderator. - * ban - add email address to black list of emails for the specific channel. - From next time, a person sending a message using that email address will not need moderation. - message_post will not create messages with the corresponding expeditor. - """ - if decision == 'accept': - self._moderate_accept() - elif decision == 'reject': - self._moderate_send_reject_email(kwargs.get('title'), kwargs.get('comment')) - self._moderate_discard() - elif decision == 'discard': - self._moderate_discard() - elif decision == 'allow': - channels = self.env['mail.channel'].browse(self.mapped('res_id')) - for channel in channels: - channel._update_moderation_email( - list({message.email_from for message in self if message.res_id == channel.id}), - 'allow' - ) - self._search_from_same_authors()._moderate_accept() - elif decision == 'ban': - channels = self.env['mail.channel'].browse(self.mapped('res_id')) - for channel in channels: - channel._update_moderation_email( - list({message.email_from for message in self if message.res_id == channel.id}), - 'ban' - ) - self._search_from_same_authors()._moderate_discard() - - def _moderate_accept(self): - self.write({ - 'moderation_status': 'accepted', - 'moderator_id': self.env.uid - }) - # proceed with notification process to send notification emails and Inbox messages - for message in self: - if message.is_thread_message(): # note, since we will only intercept _notify_thread for message posted on channel, - # message will always be a thread_message. This check should always be true. - self.env[message.model].browse(message.res_id)._notify_thread(message) - - def _moderate_send_reject_email(self, subject, comment): - for msg in self: - if not msg.email_from: - continue - body_html = tools.append_content_to_html('
%s
' % tools.ustr(comment), msg.body, plaintext=False) - vals = { - 'subject': subject, - 'body_html': body_html, - 'author_id': self.env.user.partner_id.id, - 'email_from': self.env.user.email_formatted or self.env.company.catchall_formatted, - 'email_to': msg.email_from, - 'auto_delete': True, - 'state': 'outgoing' - } - self.env['mail.mail'].sudo().create(vals) - - def _search_from_same_authors(self): - """ Returns all pending moderation messages that have same email_from and - same res_id as given recordset. """ - messages = self.env['mail.message'].sudo() - for message in self: - messages |= messages.search([ - ('moderation_status', '=', 'pending_moderation'), - ('email_from', '=', message.email_from), - ('model', '=', 'mail.channel'), - ('res_id', '=', message.res_id) - ]) - return messages - - def _moderate_discard(self): - """ Notify deletion of messages to their moderators and authors and then delete them. - """ - channel_ids = self.mapped('res_id') - moderators = self.env['mail.channel'].browse(channel_ids).mapped('moderator_ids') - authors = self.mapped('author_id') - partner_to_pid = {} - for moderator in moderators: - partner_to_pid.setdefault(moderator.partner_id.id, set()) - partner_to_pid[moderator.partner_id.id] |= set([message.id for message in self if message.res_id in moderator.moderation_channel_ids.ids]) - for author in authors: - partner_to_pid.setdefault(author.id, set()) - partner_to_pid[author.id] |= set([message.id for message in self if message.author_id == author]) - - notifications = [] - for partner_id, message_ids in partner_to_pid.items(): - notifications.append([ - (self._cr.dbname, 'res.partner', partner_id), - {'type': 'deletion', 'message_ids': sorted(list(message_ids))} # sorted to make deterministic for tests - ]) - self.env['bus.bus'].sendmany(notifications) - self.unlink() - - def _notify_pending_by_chat(self): - """ Generate the bus notifications for the given message and send them - to the appropriate moderators and the author (if the author has not been - elected moderator meanwhile). The author notification can be considered - as a feedback to the author. - """ - self.ensure_one() - message = self.message_format()[0] - partners = self.env['mail.channel'].browse(self.res_id).mapped('moderator_ids.partner_id') - notifications = [] - for partner in partners: - notifications.append([ - (self._cr.dbname, 'res.partner', partner.id), - {'type': 'moderator', 'message': message} - ]) - if self.author_id not in partners: - notifications.append([ - (self._cr.dbname, 'res.partner', self.author_id.id), - {'type': 'author', 'message': message} - ]) - self.env['bus.bus'].sendmany(notifications) - - @api.model - def _notify_moderators(self): - """ Push a notification (Inbox/email) to moderators having messages - waiting for moderation. This method is called once a day by a cron. - """ - channels = self.env['mail.channel'].browse(self.search([('moderation_status', '=', 'pending_moderation')]).mapped('res_id')) - moderators_to_notify = channels.mapped('moderator_ids') - template = self.env.ref('mail.mail_channel_notify_moderation', raise_if_not_found=False) - if not template: - _logger.warning('Template "mail.mail_channel_notify_moderation" was not found. Cannot send reminder notifications.') - return - MailThread = self.env['mail.thread'].with_context(mail_notify_author=True) - for moderator in moderators_to_notify: - MailThread.message_notify( - partner_ids=moderator.partner_id.ids, - subject=_('Message are pending moderation'), # tocheck: target language - body=template._render({'record': moderator.partner_id}, engine='ir.qweb', minimal_qcontext=True), - email_from=moderator.company_id.catchall_formatted or moderator.company_id.email_formatted, - ) - # ------------------------------------------------------ # MESSAGE READ / FETCH / FAILURE API # ------------------------------------------------------ @@ -1027,32 +813,13 @@ class Message(models.Model): return messages._message_notification_format() @api.model - def message_fetch(self, domain, limit=20, moderated_channel_ids=None): + def message_fetch(self, domain, limit=20): """ Get a limited amount of formatted messages with provided domain. :param domain: the domain to filter messages; :param limit: the maximum amount of messages to get; - :param list(int) moderated_channel_ids: if set, it contains the ID - of a moderated channel. Fetched messages should include pending - moderation messages for moderators. If the current user is not - moderator, it should still get self-authored messages that are - pending moderation; :returns list(dict). """ - messages = self.search(domain, limit=limit) - if moderated_channel_ids: - # Split load moderated and regular messages, as the ORed domain can - # cause performance issues on large databases. - moderated_messages_dom = [ - ('model', '=', 'mail.channel'), - ('res_id', 'in', moderated_channel_ids), - '|', - ('author_id', '=', self.env.user.partner_id.id), - ('moderation_status', '=', 'pending_moderation'), - ] - messages |= self.search(moderated_messages_dom, limit=limit) - # Truncate the results to `limit` - messages = messages.sorted(key='id', reverse=True)[:limit] - return messages.message_format() + return self.search(domain, limit=limit).message_format() def message_format(self): """ Get the message values in the format for web client. Since message values can be broadcasted, @@ -1092,7 +859,6 @@ class Message(models.Model): 'is_note': True # only if the message is a note (subtype == note) 'is_discussion': False # only if the message is a discussion (subtype == discussion) 'is_notification': False # only if the message is a note but is a notification aka not linked to a document like assignation - 'moderation_status': 'pending_moderation' } """ vals_list = self._message_format(self._get_message_format_fields()) @@ -1122,7 +888,6 @@ class Message(models.Model): 'model', 'res_id', 'record_name', # document related 'partner_ids', # recipients 'starred_partner_ids', # list of partner ids for whom the message is starred - 'moderation_status', ] def _message_notification_format(self): diff --git a/addons/mail/models/mail_moderation.py b/addons/mail/models/mail_moderation.py deleted file mode 100644 index 7e4982a946a..00000000000 --- a/addons/mail/models/mail_moderation.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import fields, models - - -class Moderation(models.Model): - _name = 'mail.moderation' - _description = 'Channel black/white list' - - email = fields.Char(string="Email", index=True, required=True) - status = fields.Selection([ - ('allow', 'Always Allow'), - ('ban', 'Permanent Ban')], - string="Status", required=True) - channel_id = fields.Many2one('mail.channel', string="Channel", index=True, required=True) - - _sql_constraints = [ - ('channel_email_uniq', 'unique (email,channel_id)', 'The email address must be unique per channel !') - ] diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 315b0f0d568..d6b15cbdb37 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2106,9 +2106,6 @@ class MailThread(models.AbstractModel): * performs the notification process by calling the various notification methods implemented; - This method cnn be overridden to intercept and postpone notification - mechanism like mail.channel moderation. - :param message: mail.message record to notify; :param msg_vals: dictionary of values used to create the message. If given it is used instead of accessing ``self`` to lessen query count in some diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py index c038b97bfe9..4c62a77f55d 100644 --- a/addons/mail/models/res_users.py +++ b/addons/mail/models/res_users.py @@ -25,37 +25,6 @@ class Users(models.Model): help="Policy on how to handle Chatter notifications:\n" "- Handle by Emails: notifications are sent to your email address\n" "- Handle in Odoo: notifications appear in your Odoo Inbox") - # channel-specific: moderation - is_moderator = fields.Boolean(string='Is moderator', compute='_compute_is_moderator') - moderation_counter = fields.Integer(string='Moderation count', compute='_compute_moderation_counter') - moderation_channel_ids = fields.Many2many( - 'mail.channel', 'mail_channel_moderator_rel', - string='Moderated channels') - - @api.depends('moderation_channel_ids.moderation', 'moderation_channel_ids.moderator_ids') - def _compute_is_moderator(self): - moderated = self.env['mail.channel'].search([ - ('id', 'in', self.mapped('moderation_channel_ids').ids), - ('moderation', '=', True), - ('moderator_ids', 'in', self.ids) - ]) - user_ids = moderated.mapped('moderator_ids') - for user in self: - user.is_moderator = user in user_ids - - def _compute_moderation_counter(self): - self._cr.execute(""" -SELECT channel_moderator.res_users_id, COUNT(msg.id) -FROM "mail_channel_moderator_rel" AS channel_moderator -JOIN "mail_message" AS msg -ON channel_moderator.mail_channel_id = msg.res_id - AND channel_moderator.res_users_id IN %s - AND msg.model = 'mail.channel' - AND msg.moderation_status = 'pending_moderation' -GROUP BY channel_moderator.res_users_id""", [tuple(self.ids)]) - result = dict(self._cr.fetchall()) - for user in self: - user.moderation_counter = result.get(user.id, 0) @property def SELF_READABLE_FIELDS(self): diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index 5090cf7fd42..b357cd858c4 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -17,7 +17,6 @@ access_mail_channel_user,mail.group.user,model_mail_channel,base.group_user,1,1, access_mail_channel_partner_public,mail.channel.partner.public,model_mail_channel_partner,base.group_public,1,0,0,0 access_mail_channel_partner_portal,mail.channel.partner.portal,model_mail_channel_partner,base.group_portal,1,1,1,1 access_mail_channel_partner_user,mail.channel.partner.user,model_mail_channel_partner,base.group_user,1,1,1,1 -access_mail_moderation_user,mail.moderation.user,model_mail_moderation,base.group_user,1,1,1,1 access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0 access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,0,0,0 access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1 diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index d38c1cccae3..cdbfe1230a8 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -55,12 +55,6 @@
- - White/Black List: moderators: moderated channels only - - [('channel_id.moderator_ids', 'in', user.id)] - - Mail Compose Message Rule diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js index f3c5e107d1b..fdaf4f01bc3 100644 --- a/addons/mail/static/src/components/discuss/discuss.js +++ b/addons/mail/static/src/components/discuss/discuss.js @@ -7,8 +7,6 @@ import { Composer } from '@mail/components/composer/composer'; import { DiscussMobileMailboxSelection } from '@mail/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection'; import { DiscussSidebar } from '@mail/components/discuss_sidebar/discuss_sidebar'; import { MobileMessagingNavbar } from '@mail/components/mobile_messaging_navbar/mobile_messaging_navbar'; -import { ModerationDiscardDialog } from '@mail/components/moderation_discard_dialog/moderation_discard_dialog'; -import { ModerationRejectDialog } from '@mail/components/moderation_reject_dialog/moderation_reject_dialog'; import { NotificationList } from '@mail/components/notification_list/notification_list'; import { ThreadView } from '@mail/components/thread_view/thread_view'; @@ -21,8 +19,6 @@ const components = { DiscussMobileMailboxSelection, DiscussSidebar, MobileMessagingNavbar, - ModerationDiscardDialog, - ModerationRejectDialog, NotificationList, ThreadView, }; @@ -34,12 +30,7 @@ export class Discuss extends Component { constructor(...args) { super(...args); useShouldUpdateBasedOnProps(); - useStore((...args) => this._useStoreSelector(...args), { - compareDepth: { - checkedMessages: 1, - uncheckedMessages: 1, - }, - }); + useStore((...args) => this._useStoreSelector(...args), {}); this._updateLocalStoreProps(); /** * Reference of the composer. Useful to focus it. @@ -173,15 +164,10 @@ export class Discuss extends Component { const threadView = discuss && discuss.threadView; const replyingToMessage = discuss && discuss.replyingToMessage; const replyingToMessageOriginThread = replyingToMessage && replyingToMessage.originThread; - const checkedMessages = threadView ? threadView.checkedMessages : []; return { - checkedMessages, - checkedMessagesIsModeratedByCurrentPartner: checkedMessages && checkedMessages.some(message => message.isModeratedByCurrentPartner), // for widget discuss, discussActiveId: discuss && discuss.activeId, // for widget discussActiveMobileNavbarTabId: discuss && discuss.activeMobileNavbarTabId, - discussHasModerationDiscardDialog: discuss && discuss.hasModerationDiscardDialog, - discussHasModerationRejectDialog: discuss && discuss.hasModerationRejectDialog, discussIsAddingChannel: discuss && discuss.isAddingChannel, discussIsAddingChat: discuss && discuss.isAddingChat, discussIsDoFocus: discuss && discuss.isDoFocus, @@ -200,7 +186,6 @@ export class Discuss extends Component { threadPublic: thread && thread.public, // for widget threadView, threadViewMessagesLength: threadView && threadView.messages.length, // for widget - uncheckedMessages: threadView ? threadView.uncheckedMessages : [], // for widget }; } @@ -208,20 +193,6 @@ export class Discuss extends Component { // Handlers //-------------------------------------------------------------------------- - /** - * @private - */ - _onDialogClosedModerationDiscard() { - this.discuss.update({ hasModerationDiscardDialog: false }); - } - - /** - * @private - */ - _onDialogClosedModerationReject() { - this.discuss.update({ hasModerationRejectDialog: false }); - } - /** * @private * @param {CustomEvent} ev diff --git a/addons/mail/static/src/components/discuss/discuss.xml b/addons/mail/static/src/components/discuss/discuss.xml index 997683aa58c..185b8f76343 100644 --- a/addons/mail/static/src/components/discuss/discuss.xml +++ b/addons/mail/static/src/components/discuss/discuss.xml @@ -21,12 +21,6 @@ - - - - - - @@ -56,7 +50,6 @@ hasComposer="discuss.thread.model !== 'mail.box'" hasComposerCurrentPartnerAvatar="!env.messaging.device.isMobile" hasComposerThreadTyping="true" - hasMessageCheckbox="true" hasSquashCloseMessages="discuss.thread.model !== 'mail.box'" haveMessagesMarkAsReadIcon="discuss.thread === env.messaging.inbox" haveMessagesReplyIcon="discuss.thread === env.messaging.inbox" diff --git a/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js index 26ce52cf3ba..96cf646b64e 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_domain_tests.js @@ -330,78 +330,6 @@ QUnit.test('discuss should refresh filtered thread on changing thread', async fu ); }); -QUnit.test('select all and unselect all buttons should work on filtered thread', async function (assert) { - assert.expect(4); - - this.data['mail.channel'].records.push({ - id: 20, - is_moderator: true, - moderation: true, - name: "general", - }); - this.data['mail.message'].records.push({ - body: "

test

", - model: 'mail.channel', - moderation_status: 'pending_moderation', - res_id: 20, - }); - await this.start(); - await this.afterEvent({ - eventName: 'o-thread-view-hint-processed', - func: () => { - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${this.env.messaging.moderation.localId}"] - `).click(); - }, - message: "should wait until moderation box is loaded after clicking on it", - predicate: ({ hint, threadViewer }) => { - return ( - hint.type === 'messages-loaded' && - threadViewer.thread.model === 'mail.box' && - threadViewer.thread.id === 'moderation' - ); - }, - }); - await this.afterEvent({ - eventName: 'o-thread-view-hint-processed', - func: () => { - // simulate control panel search - this.env.messaging.discuss.update({ - stringifiedDomain: JSON.stringify([['body', 'ilike', 'test']]), - }); - }, - message: "should wait until search filter is applied", - predicate: ({ hint, threadViewer }) => { - return ( - hint.type === 'messages-loaded' && - threadViewer.thread.model === 'mail.box' && - threadViewer.thread.id === 'moderation' - ); - }, - }); - assert.containsOnce( - document.body, - '.o_Message', - "should only have the 1 message containing 'test' in moderation box" - ); - assert.notOk( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should not be checked initially" - ); - - await afterNextRender(() => document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll').click()); - assert.ok( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should be checked after clicking on 'select all'" - ); - - await afterNextRender(() => document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll').click()); - assert.notOk( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should be unchecked after clicking on 'unselect all'" - ); -}); - }); }); }); diff --git a/addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js index a2b997532ef..8b137891791 100644 --- a/addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js +++ b/addons/mail/static/src/components/discuss/tests/discuss_moderation_tests.js @@ -1,1176 +1 @@ -/** @odoo-module **/ -import { - afterEach, - afterNextRender, - beforeEach, - start, -} from '@mail/utils/test_utils'; - -QUnit.module('mail', {}, function () { -QUnit.module('components', {}, function () { -QUnit.module('discuss', {}, function () { -QUnit.module('discuss_moderation_tests.js', { - beforeEach() { - beforeEach(this); - - this.start = async params => { - const { env, widget } = await start(Object.assign({}, params, { - autoOpenDiscuss: true, - data: this.data, - hasDiscuss: true, - })); - this.env = env; - this.widget = widget; - }; - }, - afterEach() { - afterEach(this); - }, -}); - -QUnit.test('as moderator, moderated channel with pending moderation message', async function (assert) { - assert.expect(37); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // for consistency, but not used in the scope of this test - name: "general", // random name, will be asserted in the test - }); - this.data['mail.message'].records.push({ - body: "

test

", // random body, will be asserted in the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending moderation - res_id: 20, // id of the channel - }); - await this.start(); - - assert.ok( - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `), - "should display the moderation box in the sidebar" - ); - const mailboxCounter = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - .o_DiscussSidebarItem_counter - `); - assert.ok( - mailboxCounter, - "there should be a counter next to the moderation mailbox in the sidebar" - ); - assert.strictEqual( - mailboxCounter.textContent.trim(), - "1", - "the mailbox counter of the moderation mailbox should display '1'" - ); - - // 1. go to moderation mailbox - await afterNextRender(() => - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `).click() - ); - // check message - assert.containsOnce( - document.body, - '.o_Message', - "should be only one message in moderation box" - ); - assert.strictEqual( - document.querySelector('.o_Message_content').textContent, - "test", - "this message pending moderation should have the correct content" - ); - assert.containsOnce( - document.body, - '.o_Message_originThreadLink', - "thee message should have one origin" - ); - assert.strictEqual( - document.querySelector('.o_Message_originThreadLink').textContent, - "#general", - "the message pending moderation should have correct origin as its linked document" - ); - assert.containsOnce( - document.body, - '.o_Message_checkbox', - "there should be a moderation checkbox next to the message" - ); - assert.notOk( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should be unchecked by default" - ); - // check select all (enabled) / unselect all (disabled) buttons - assert.containsOnce( - document.body, - '.o_widget_Discuss_controlPanelButtonSelectAll', - "there should be a 'Select All' button in the control panel" - ); - assert.doesNotHaveClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll'), - 'disabled', - "the 'Select All' button should not be disabled" - ); - assert.containsOnce( - document.body, - '.o_widget_Discuss_controlPanelButtonUnselectAll', - "there should be a 'Unselect All' button in the control panel" - ); - assert.hasClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll'), - 'disabled', - "the 'Unselect All' button should be disabled" - ); - // check moderate all buttons (invisible) - assert.containsN( - document.body, - '.o_widget_Discuss_controlPanelButtonModeration', - 3, - "there should be 3 buttons to moderate selected messages in the control panel" - ); - assert.containsOnce( - document.body, - '.o_widget_Discuss_controlPanelButtonModeration.o-accept', - "there should one moderate button to accept messages pending moderation" - ); - assert.isNotVisible( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'), - "the moderate button 'Accept' should be invisible by default" - ); - assert.containsOnce( - document.body, - '.o_widget_Discuss_controlPanelButtonModeration.o-reject', - "there should one moderate button to reject messages pending moderation" - ); - assert.isNotVisible( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-reject'), - "the moderate button 'Reject' should be invisible by default" - ); - assert.containsOnce( - document.body, - '.o_widget_Discuss_controlPanelButtonModeration.o-discard', - "there should one moderate button to discard messages pending moderation" - ); - assert.isNotVisible( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard'), - "the moderate button 'Discard' should be invisible by default" - ); - - // click on message moderation checkbox - await afterNextRender(() => document.querySelector('.o_Message_checkbox').click()); - assert.ok( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should become checked after click" - ); - // check select all (disabled) / unselect all buttons (enabled) - assert.hasClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll'), - 'disabled', - "the 'Select All' button should be disabled" - ); - assert.doesNotHaveClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll'), - 'disabled', - "the 'Unselect All' button should not be disabled" - ); - // check moderate all buttons updated (visible) - assert.isVisible( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'), - "the moderate button 'Accept' should be visible" - ); - assert.isVisible( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-reject'), - "the moderate button 'Reject' should be visible" - ); - assert.isVisible( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard'), - "the moderate button 'Discard' should be visible" - ); - - // test select buttons - await afterNextRender(() => - document.querySelector('.o_widget_Discuss_controlPanelButtonUnselectAll').click() - ); - assert.notOk( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should become unchecked after click" - ); - - await afterNextRender(() => - document.querySelector('.o_widget_Discuss_controlPanelButtonSelectAll').click() - ); - assert.ok( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should become checked again after click" - ); - - // 2. go to channel 'general' - await afterNextRender(() => - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }).localId - }"] - `).click() - ); - // check correct message - assert.containsOnce( - document.body, - '.o_Message', - "should be only one message in general channel" - ); - assert.containsOnce( - document.body, - '.o_Message_checkbox', - "there should be a moderation checkbox next to the message" - ); - assert.notOk( - document.querySelector('.o_Message_checkbox').checked, - "the moderation checkbox should not be checked here" - ); - await afterNextRender(() => document.querySelector('.o_Message_checkbox').click()); - // Don't test moderation actions visibility, since it is similar to moderation box. - - // 3. test discard button - await afterNextRender(() => - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-discard').click() - ); - assert.containsOnce( - document.body, - '.o_ModerationDiscardDialog', - "discard dialog should be open" - ); - // the dialog will be tested separately - await afterNextRender(() => - document.querySelector('.o_ModerationDiscardDialog .o-cancel').click() - ); - assert.containsNone( - document.body, - '.o_ModerationDiscardDialog', - "discard dialog should be closed" - ); - - // 4. test reject button - await afterNextRender(() => - document.querySelector(` - .o_widget_Discuss_controlPanelButtonModeration.o-reject - `).click() - ); - assert.containsOnce( - document.body, - '.o_ModerationRejectDialog', - "reject dialog should be open" - ); - // the dialog will be tested separately - await afterNextRender(() => - document.querySelector('.o_ModerationRejectDialog .o-cancel').click() - ); - assert.containsNone( - document.body, - '.o_ModerationRejectDialog', - "reject dialog should be closed" - ); - - // 5. test accept button - await afterNextRender(() => - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept').click() - ); - assert.containsOnce( - document.body, - '.o_Message', - "should still be only one message in general channel" - ); - assert.containsNone( - document.body, - '.o_Message_checkbox', - "there should not be a moderation checkbox next to the message" - ); -}); - -QUnit.test('as moderator, accept pending moderation message', async function (assert) { - assert.expect(12); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // for consistency, but not used in the scope of this test - name: "general", // random name, will be asserted in the test - }); - this.data['mail.message'].records.push({ - body: "

test

", // random body, will be asserted in the test - id: 100, // random unique id, will be asserted during the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending moderation - res_id: 20, // id of the channel - }); - await this.start({ - async mockRPC(route, args) { - if (args.method === 'moderate') { - assert.step('moderate'); - const messageIDs = args.args[0]; - const decision = args.args[1]; - assert.strictEqual( - messageIDs.length, - 1, - "should moderate one message" - ); - assert.strictEqual( - messageIDs[0], - 100, - "should moderate message with ID 100" - ); - assert.strictEqual( - decision, - 'accept', - "should accept the message" - ); - } - return this._super(...arguments); - }, - }); - - // 1. go to moderation box - const moderationBox = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `); - assert.ok( - moderationBox, - "should display the moderation box" - ); - - await afterNextRender(() => moderationBox.click()); - assert.ok( - document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - `), - "should display the message to moderate" - ); - const acceptButton = document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - .o_Message_moderationAction.o-accept - `); - assert.ok(acceptButton, "should display the accept button"); - - await afterNextRender(() => acceptButton.click()); - assert.verifySteps(['moderate']); - assert.containsOnce( - document.body, - '.o_MessageList_emptyTitle', - "should now have no message displayed in moderation box" - ); - - // 2. go to channel 'general' - const channel = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }).localId - }"] - `); - assert.ok( - channel, - "should display the general channel" - ); - - await afterNextRender(() => channel.click()); - const message = document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - `); - assert.ok( - message, - "should display the accepted message" - ); - assert.containsNone( - message, - '.o_Message_moderationPending', - "the message should not be pending moderation" - ); -}); - -QUnit.test('as moderator, reject pending moderation message (reject with explanation)', async function (assert) { - assert.expect(23); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // for consistency, but not used in the scope of this test - name: "general", // random name, will be asserted in the test - }); - this.data['mail.message'].records.push({ - body: "

test

", // random body, will be asserted in the test - id: 100, // random unique id, will be asserted during the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending moderation - res_id: 20, // id of the channel - }); - await this.start({ - async mockRPC(route, args) { - if (args.method === 'moderate') { - assert.step('moderate'); - const messageIDs = args.args[0]; - const decision = args.args[1]; - const kwargs = args.kwargs; - assert.strictEqual( - messageIDs.length, - 1, - "should moderate one message" - ); - assert.strictEqual( - messageIDs[0], - 100, - "should moderate message with ID 100" - ); - assert.strictEqual( - decision, - 'reject', - "should reject the message" - ); - assert.strictEqual( - kwargs.title, - "Message Rejected", - "should have correct reject message title" - ); - assert.strictEqual( - kwargs.comment, - "Your message was rejected by moderator.", - "should have correct reject message body / comment" - ); - } - return this._super(...arguments); - }, - }); - - // 1. go to moderation box - const moderationBox = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `); - assert.ok( - moderationBox, - "should display the moderation box" - ); - - await afterNextRender(() => moderationBox.click()); - const pendingMessage = document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - `); - assert.ok( - pendingMessage, - "should display the message to moderate" - ); - const rejectButton = pendingMessage.querySelector(':scope .o_Message_moderationAction.o-reject'); - assert.ok( - rejectButton, - "should display the reject button" - ); - - await afterNextRender(() => rejectButton.click()); - const dialog = document.querySelector('.o_ModerationRejectDialog'); - assert.ok( - dialog, - "a dialog should be prompt to the moderator on click reject" - ); - assert.strictEqual( - dialog.querySelector('.modal-title').textContent, - "Send explanation to author", - "dialog should have correct title" - ); - - const messageTitle = dialog.querySelector(':scope .o_ModerationRejectDialog_title'); - assert.ok( - messageTitle, - "should have a title for rejecting" - ); - assert.hasAttrValue( - messageTitle, - 'placeholder', - "Subject", - "title for reject reason should have correct placeholder" - ); - assert.strictEqual( - messageTitle.value, - "Message Rejected", - "title for reject reason should have correct default value" - ); - - const messageComment = dialog.querySelector(':scope .o_ModerationRejectDialog_comment'); - assert.ok( - messageComment, - "should have a comment for rejecting" - ); - assert.hasAttrValue( - messageComment, - 'placeholder', - "Mail Body", - "comment for reject reason should have correct placeholder" - ); - assert.strictEqual( - messageComment.value, - "Your message was rejected by moderator.", - "comment for reject reason should have correct default text content" - ); - const confirmReject = dialog.querySelector(':scope .o-reject'); - assert.ok( - confirmReject, - "should have reject button" - ); - assert.strictEqual( - confirmReject.textContent, - "Reject" - ); - - await afterNextRender(() => confirmReject.click()); - assert.verifySteps(['moderate']); - assert.containsOnce( - document.body, - '.o_MessageList_emptyTitle', - "should now have no message displayed in moderation box" - ); - - // 2. go to channel 'general' - const channel = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }).localId - }"] - `); - assert.ok( - channel, - 'should display the general channel' - ); - - await afterNextRender(() => channel.click()); - assert.containsNone( - document.body, - '.o_Message', - "should now have no message in channel" - ); -}); - -QUnit.test('as moderator, discard pending moderation message (reject without explanation)', async function (assert) { - assert.expect(16); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // for consistency, but not used in the scope of this test - name: "general", // random name, will be asserted in the test - }); - this.data['mail.message'].records.push({ - body: "

test

", // random body, will be asserted in the test - id: 100, // random unique id, will be asserted during the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending moderation - res_id: 20, // id of the channel - }); - await this.start({ - async mockRPC(route, args) { - if (args.method === 'moderate') { - assert.step('moderate'); - const messageIDs = args.args[0]; - const decision = args.args[1]; - assert.strictEqual(messageIDs.length, 1, "should moderate one message"); - assert.strictEqual(messageIDs[0], 100, "should moderate message with ID 100"); - assert.strictEqual(decision, 'discard', "should discard the message"); - } - return this._super(...arguments); - }, - }); - - // 1. go to moderation box - const moderationBox = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `); - assert.ok( - moderationBox, - "should display the moderation box" - ); - - await afterNextRender(() => moderationBox.click()); - const pendingMessage = document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - `); - assert.ok( - pendingMessage, - "should display the message to moderate" - ); - - const discardButton = pendingMessage.querySelector(` - :scope .o_Message_moderationAction.o-discard - `); - assert.ok( - discardButton, - "should display the discard button" - ); - - await afterNextRender(() => discardButton.click()); - const dialog = document.querySelector('.o_ModerationDiscardDialog'); - assert.ok( - dialog, - "a dialog should be prompt to the moderator on click discard" - ); - assert.strictEqual( - dialog.querySelector('.modal-title').textContent, - "Confirmation", - "dialog should have correct title" - ); - assert.strictEqual( - dialog.textContent, - "Confirmation×You are going to discard 1 message.Do you confirm the action?DiscardCancel", - "should warn the user on discard action" - ); - - const confirmDiscard = dialog.querySelector(':scope .o-discard'); - assert.ok( - confirmDiscard, - "should have discard button" - ); - assert.strictEqual( - confirmDiscard.textContent, - "Discard" - ); - - await afterNextRender(() => confirmDiscard.click()); - assert.verifySteps(['moderate']); - assert.containsOnce( - document.body, - '.o_MessageList_emptyTitle', - "should now have no message displayed in moderation box" - ); - - // 2. go to channel 'general' - const channel = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }).localId - }"] - `); - assert.ok( - channel, - "should display the general channel" - ); - - await afterNextRender(() => channel.click()); - assert.containsNone( - document.body, - '.o_Message', - "should now have no message in channel" - ); -}); - -QUnit.test('as author, send message in moderated channel', async function (assert) { - assert.expect(4); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - moderation: true, // channel must be moderated to test the feature - name: "general", // random name, will be asserted in the test - }); - await this.start(); - const channel = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }).localId - }"] - `); - assert.ok( - channel, - "should display the general channel" - ); - - // go to channel 'general' - await afterNextRender(() => channel.click()); - assert.containsNone( - document.body, - '.o_Message', - "should have no message in channel" - ); - - // post a message - await afterNextRender(() => { - const textInput = document.querySelector('.o_ComposerTextInput_textarea'); - textInput.focus(); - document.execCommand('insertText', false, "Some Text"); - }); - await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click()); - const messagePending = document.querySelector('.o_Message_moderationPending'); - assert.ok( - messagePending, - "should display the pending message with pending info" - ); - assert.hasClass( - messagePending, - 'o-author', - "the message should be pending moderation as author" - ); -}); - -QUnit.test('as author, sent message accepted in moderated channel', async function (assert) { - assert.expect(5); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - moderation: true, // for consistency, but not used in the scope of this test - name: "general", // random name, will be asserted in the test - }); - this.data['mail.message'].records.push({ - body: "not empty", - id: 100, // random unique id, will be referenced in the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending - res_id: 20, // id of the channel - }); - await this.start(); - - const channel = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }).localId - }"] - `); - assert.ok( - channel, - "should display the general channel" - ); - - await afterNextRender(() => channel.click()); - const messagePending = document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - .o_Message_moderationPending - `); - assert.ok( - messagePending, - "should display the pending message with pending info" - ); - assert.hasClass( - messagePending, - 'o-author', - "the message should be pending moderation as author" - ); - - // simulate accepted message - await afterNextRender(() => { - const messageData = { - id: 100, - moderation_status: 'accepted', - }; - const notification = [[false, 'mail.channel', 20], messageData]; - this.widget.call('bus_service', 'trigger', 'notification', [notification]); - }); - - // check message is accepted - const message = document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - `); - assert.ok( - message, - "should still display the message" - ); - assert.containsNone( - message, - '.o_Message_moderationPending', - "the message should not be in pending moderation anymore" - ); -}); - -QUnit.test('as author, sent message rejected in moderated channel', async function (assert) { - assert.expect(4); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - moderation: true, // for consistency, but not used in the scope of this test - name: "general", // random name, will be asserted in the test - }); - this.data['mail.message'].records.push({ - body: "not empty", - id: 100, // random unique id, will be referenced in the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending - res_id: 20, // id of the channel - }); - await this.start(); - - const channel = document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }).localId - }"] - `); - assert.ok( - channel, - "should display the general channel" - ); - - await afterNextRender(() => channel.click()); - const messagePending = document.querySelector(` - .o_Message[data-message-local-id="${ - this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }).localId - }"] - .o_Message_moderationPending - `); - assert.ok( - messagePending, - "should display the pending message with pending info" - ); - assert.hasClass( - messagePending, - 'o-author', - "the message should be pending moderation as author" - ); - - // simulate reject from moderator - await afterNextRender(() => { - const notifData = { - type: 'deletion', - message_ids: [100], - }; - const notification = [[false, 'res.partner', this.env.messaging.currentPartner.id], notifData]; - this.widget.call('bus_service', 'trigger', 'notification', [notification]); - }); - // check no message - assert.containsNone( - document.body, - '.o_Message', - "message should be removed from channel after reject" - ); -}); - -QUnit.test('as moderator, pending moderation message accessibility', async function (assert) { - // pending moderation message should appear in moderation box and in origin thread - assert.expect(3); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // channel must be moderated to test the feature - }); - this.data['mail.message'].records.push({ - body: "not empty", - id: 100, // random unique id, will be referenced in the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending - res_id: 20, // id of the channel - }); - await this.start(); - - const thread = this.env.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel' }); - assert.ok( - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `), - "should display the moderation box in the sidebar" - ); - - await afterNextRender(() => - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${thread.localId}"] - `).click() - ); - const message = this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }); - assert.containsOnce( - document.body, - `.o_Message[data-message-local-id="${message.localId}"]`, - "the pending moderation message should be in the channel" - ); - - await afterNextRender(() => - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `).click() - ); - assert.containsOnce( - document.body, - `.o_Message[data-message-local-id="${message.localId}"]`, - "the pending moderation message should be in moderation box" - ); -}); - -QUnit.test('as author, pending moderation message should appear in origin thread', async function (assert) { - assert.expect(1); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - moderation: true, // channel must be moderated to test the feature - }); - this.data['mail.message'].records.push({ - author_id: this.data.currentPartnerId, // test as author of message - body: "not empty", - id: 100, // random unique id, will be referenced in the test - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending - res_id: 20, // id of the channel - }); - await this.start(); - const thread = this.env.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel' }); - - await afterNextRender(() => - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${thread.localId}"] - `).click() - ); - const message = this.env.models['mail.message'].findFromIdentifyingData({ id: 100 }); - assert.containsOnce( - document.body, - `.o_Message[data-message-local-id="${message.localId}"]`, - "the pending moderation message should be in the channel" - ); -}); - -QUnit.test('as moderator, new pending moderation message posted by someone else', async function (assert) { - // the message should appear in origin thread and moderation box if I moderate it - assert.expect(3); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // channel must be moderated to test the feature - }); - await this.start(); - const thread = this.env.models['mail.thread'].findFromIdentifyingData({ id: 20, model: 'mail.channel' }); - - await afterNextRender(() => - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${thread.localId}"] - `).click() - ); - assert.containsNone( - document.body, - `.o_Message`, - "should have no message in the channel initially" - ); - - // simulate receiving the message - const messageData = { - author_id: [10, 'john doe'], // random id, different than current partner - body: "not empty", - id: 1, // random unique id - model: 'mail.channel', // expected value to link message to channel - moderation_status: 'pending_moderation', // message is expected to be pending - res_id: 20, // id of the channel - }; - await afterNextRender(() => { - const notifications = [[ - ['my-db', 'res.partner', this.env.messaging.currentPartner.id], - { type: 'moderator', message: messageData }, - ]]; - this.widget.call('bus_service', 'trigger', 'notification', notifications); - }); - const message = this.env.models['mail.message'].findFromIdentifyingData({ id: 1 }); - assert.containsOnce( - document.body, - `.o_Message[data-message-local-id="${message.localId}"]`, - "the pending moderation message should be in the channel" - ); - - await afterNextRender(() => - document.querySelector(` - .o_DiscussSidebar_item[data-thread-local-id="${ - this.env.messaging.moderation.localId - }"] - `).click() - ); - assert.containsOnce( - document.body, - `.o_Message[data-message-local-id="${message.localId}"]`, - "the pending moderation message should be in moderation box" - ); -}); - -QUnit.test('accept multiple moderation messages', async function (assert) { - assert.expect(5); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // channel must be moderated to test the feature - }); - this.data['mail.message'].records.push( - { - body: "not empty", - model: 'mail.channel', - moderation_status: 'pending_moderation', - res_id: 20, - }, - { - body: "not empty", - model: 'mail.channel', - moderation_status: 'pending_moderation', - res_id: 20, - }, - { - body: "not empty", - model: 'mail.channel', - moderation_status: 'pending_moderation', - res_id: 20, - } - ); - - await this.start({ - discuss: { - params: { - default_active_id: 'mail.box_moderation', - }, - }, - }); - - assert.containsN( - document.body, - '.o_Message', - 3, - "should initially display 3 messages" - ); - - await afterNextRender(() => { - document.querySelectorAll('.o_Message_checkbox')[0].click(); - document.querySelectorAll('.o_Message_checkbox')[1].click(); - }); - assert.containsN( - document.body, - '.o_Message_checkbox:checked', - 2, - "2 messages should have been checked after clicking on their respective checkbox" - ); - assert.doesNotHaveClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'), - 'o_hidden', - "global accept button should be displayed as two messages are selected" - ); - - await afterNextRender(() => - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept').click() - ); - assert.containsN( - document.body, - '.o_Message', - 1, - "should display 1 message as the 2 others have been accepted" - ); - assert.hasClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'), - 'o_hidden', - "global accept button should no longer be displayed as messages have been unselected" - ); -}); - -QUnit.test('accept multiple moderation messages after having accepted other messages', async function (assert) { - assert.expect(5); - - this.data['mail.channel'].records.push({ - id: 20, // random unique id, will be used to link message and will be referenced in the test - is_moderator: true, // current user is expected to be moderator of channel - moderation: true, // channel must be moderated to test the feature - }); - this.data['mail.message'].records.push( - { - body: "not empty", - model: 'mail.channel', - moderation_status: 'pending_moderation', - res_id: 20, - }, - { - body: "not empty", - model: 'mail.channel', - moderation_status: 'pending_moderation', - res_id: 20, - }, - { - body: "not empty", - model: 'mail.channel', - moderation_status: 'pending_moderation', - res_id: 20, - } - ); - await this.start({ - discuss: { - params: { - default_active_id: 'mail.box_moderation', - }, - }, - }); - assert.containsN( - document.body, - '.o_Message', - 3, - "should initially display 3 messages" - ); - - await afterNextRender(() => { - document.querySelectorAll('.o_Message_checkbox')[0].click(); - }); - await afterNextRender(() => - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept').click() - ); - await afterNextRender(() => document.querySelectorAll('.o_Message_checkbox')[0].click()); - assert.containsOnce( - document.body, - '.o_Message_checkbox:checked', - "a message should have been checked after clicking on its checkbox" - ); - assert.doesNotHaveClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'), - 'o_hidden', - "global accept button should be displayed as a message is selected" - ); - - await afterNextRender(() => - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept').click() - ); - assert.containsOnce( - document.body, - '.o_Message', - "should display only one message left after the two others has been accepted" - ); - assert.hasClass( - document.querySelector('.o_widget_Discuss_controlPanelButtonModeration.o-accept'), - 'o_hidden', - "global accept button should no longer be displayed as message has been unselected" - ); -}); - -}); -}); -}); diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js index 91b873c0c9e..22434f0dfcc 100644 --- a/addons/mail/static/src/components/message/message.js +++ b/addons/mail/static/src/components/message/message.js @@ -5,9 +5,6 @@ import { useStore } from '@mail/component_hooks/use_store/use_store'; import { useUpdate } from '@mail/component_hooks/use_update/use_update'; import { AttachmentList } from '@mail/components/attachment_list/attachment_list'; import { MessageSeenIndicator } from '@mail/components/message_seen_indicator/message_seen_indicator'; -import { ModerationBanDialog } from '@mail/components/moderation_ban_dialog/moderation_ban_dialog'; -import { ModerationDiscardDialog } from '@mail/components/moderation_discard_dialog/moderation_discard_dialog'; -import { ModerationRejectDialog } from '@mail/components/moderation_reject_dialog/moderation_reject_dialog'; import { NotificationPopover } from '@mail/components/notification_popover/notification_popover'; import { PartnerImStatusIcon } from '@mail/components/partner_im_status_icon/partner_im_status_icon'; import { isEventHandled, markEventHandled } from '@mail/utils/utils'; @@ -24,9 +21,6 @@ const READ_LESS = _lt("read less"); const components = { AttachmentList, MessageSeenIndicator, - ModerationBanDialog, - ModerationDiscardDialog, - ModerationRejectDialog, NotificationPopover, PartnerImStatusIcon, }; @@ -39,12 +33,6 @@ export class Message extends Component { constructor(...args) { super(...args); this.state = useState({ - // Determine if the moderation ban dialog is displayed. - hasModerationBanDialog: false, - // Determine if the moderation discard dialog is displayed. - hasModerationDiscardDialog: false, - // Determine if the moderation reject dialog is displayed. - hasModerationRejectDialog: false, /** * Determine whether the message is clicked. When message is in * clicked state, it keeps displaying the commands. @@ -68,11 +56,7 @@ export class Message extends Component { authorImStatus: author && author.im_status, authorNameOrDisplayName: author && author.nameOrDisplayName, correspondent: thread && thread.correspondent, - hasMessageCheckbox: message ? message.hasCheckbox : false, isDeviceMobile: this.env.messaging.device.isMobile, - isMessageChecked: message && threadView - ? message.isChecked(thread, threadView.stringifiedDomain) - : false, isMessageSelected: message && threadView && threadView.threadViewer ? threadView.threadViewer.selectedMessage === message : false, @@ -486,13 +470,6 @@ export class Message extends Component { // Handlers //-------------------------------------------------------------------------- - /** - * @private - */ - _onChangeCheckbox() { - this.message.toggleCheck(this.threadView.thread, this.threadView.stringifiedDomain); - } - /** * @private * @param {MouseEvent} ev @@ -560,51 +537,6 @@ export class Message extends Component { this.message.openResendAction(); } - /** - * @private - * @param {MouseEvent} ev - */ - _onClickModerationAccept(ev) { - ev.preventDefault(); - this.message.moderate('accept'); - } - - /** - * @private - * @param {MouseEvent} ev - */ - _onClickModerationAllow(ev) { - ev.preventDefault(); - this.message.moderate('allow'); - } - - /** - * @private - * @param {MouseEvent} ev - */ - _onClickModerationBan(ev) { - ev.preventDefault(); - this.state.hasModerationBanDialog = true; - } - - /** - * @private - * @param {MouseEvent} ev - */ - _onClickModerationDiscard(ev) { - ev.preventDefault(); - this.state.hasModerationDiscardDialog = true; - } - - /** - * @private - * @param {MouseEvent} ev - */ - _onClickModerationReject(ev) { - ev.preventDefault(); - this.state.hasModerationRejectDialog = true; - } - /** * @private * @param {MouseEvent} ev @@ -648,34 +580,11 @@ export class Message extends Component { this.message.replyTo(); } } - - /** - * @private - */ - _onDialogClosedModerationBan() { - this.state.hasModerationBanDialog = false; - } - - /** - * @private - */ - _onDialogClosedModerationDiscard() { - this.state.hasModerationDiscardDialog = false; - } - - /** - * @private - */ - _onDialogClosedModerationReject() { - this.state.hasModerationRejectDialog = false; - } - } Object.assign(Message, { components, defaultProps: { - hasCheckbox: false, hasMarkAsReadIcon: false, hasReplyIcon: false, isSquashed: false, @@ -686,7 +595,6 @@ Object.assign(Message, { optional: true, validate: prop => ['auto', 'card', 'hover', 'none'].includes(prop), }, - hasCheckbox: Boolean, hasMarkAsReadIcon: Boolean, hasReplyIcon: Boolean, isSquashed: Boolean, diff --git a/addons/mail/static/src/components/message/message.scss b/addons/mail/static/src/components/message/message.scss index 2a742fb3947..1c7477b7292 100644 --- a/addons/mail/static/src/components/message/message.scss +++ b/addons/mail/static/src/components/message/message.scss @@ -28,10 +28,6 @@ margin-inline-end: map-get($spacers, 2); } -.o_Message_checkbox { - margin-inline-end: map-get($spacers, 2); -} - .o_Message_commandStar { font-size: 1.3em; } @@ -116,20 +112,6 @@ width: $o-mail-discuss-message-highlight-indicator-width; } -.o_Message_moderationAction { - margin-inline-end: map-get($spacers, 3); -} - -.o_Message_moderationPending { - margin-inline-end: map-get($spacers, 3); -} - -.o_Message_moderationSubHeader { - display: flex; - flex-flow: row wrap; - align-items: center; -} - .o_Message_originThread { margin-inline-end: map-get($spacers, 2); } @@ -344,37 +326,6 @@ font-size: x-small; } -.o_Message_moderationAction { - font-weight: $font-weight-bold; - font-style: italic; - - &.o-accept, - &.o-allow { - color: $o-mail-moderation-accept-color; - @include hover-focus { - color: darken($o-mail-moderation-accept-color, $emphasized-link-hover-darken-percentage); - } - } - - &.o-ban, - &.o-discard, - &.o-reject { - color: $o-mail-moderation-reject-color; - @include hover-focus { - color: darken($o-mail-moderation-reject-color, $emphasized-link-hover-darken-percentage); - } - } -} - -.o_Message_moderationPending { - font-style: italic; - - &.o-author { - color: o-text-color('danger'); - font-weight: $font-weight-bold; - } -} - .o_Message_notificationIconClickable { color: gray('600'); cursor: pointer; diff --git a/addons/mail/static/src/components/message/message.xml b/addons/mail/static/src/components/message/message.xml index 75891fc859c..260576c3dbf 100644 --- a/addons/mail/static/src/components/message/message.xml +++ b/addons/mail/static/src/components/message/message.xml @@ -96,9 +96,6 @@ - - Pending moderation - @@ -119,7 +116,7 @@
- + -
- - - - Pending moderation: - Accept - Reject - Discard - Always Allow - Ban -
-
@@ -201,15 +185,6 @@
- - - - - - - - -
diff --git a/addons/mail/static/src/components/message/message_tests.js b/addons/mail/static/src/components/message/message_tests.js index bc0684587af..97c307d9c34 100644 --- a/addons/mail/static/src/components/message/message_tests.js +++ b/addons/mail/static/src/components/message/message_tests.js @@ -123,80 +123,6 @@ QUnit.test('basic rendering', async function (assert) { ); }); -QUnit.test('moderation: as author, moderated channel with pending moderation message', async function (assert) { - assert.expect(1); - - this.data['mail.channel'].records.push({ id: 20 }); - await this.start(); - const thread = this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }); - const message = this.env.models['mail.message'].create({ - author: insert({ id: 1, display_name: "Admin" }), - body: "

Test

", - id: 100, - moderation_status: 'pending_moderation', - originThread: link(thread), - }); - await this.createMessageComponent(message); - - assert.strictEqual( - document.querySelectorAll(`.o_Message_moderationPending.o-author`).length, - 1, - "should have the message pending moderation" - ); -}); - -QUnit.test('moderation: as moderator, moderated channel with pending moderation message', async function (assert) { - assert.expect(9); - - this.data['mail.channel'].records.push({ - id: 20, - is_moderator: true, - }); - await this.start(); - const thread = this.env.models['mail.thread'].findFromIdentifyingData({ - id: 20, - model: 'mail.channel', - }); - const message = this.env.models['mail.message'].create({ - author: insert({ id: 7, display_name: "Demo User" }), - body: "

Test

", - id: 100, - moderation_status: 'pending_moderation', - originThread: link(thread), - }); - await this.createMessageComponent(message); - const messageEl = document.querySelector('.o_Message'); - assert.ok(messageEl, "should display a message"); - assert.containsOnce(messageEl, `.o_Message_moderationSubHeader`, - "should have the message pending moderation" - ); - assert.containsNone(messageEl, `.o_Message_checkbox`, - "should not have the moderation checkbox by default" - ); - assert.containsN(messageEl, '.o_Message_moderationAction', 5, - "there should be 5 contextual moderation decisions next to the message" - ); - assert.containsOnce(messageEl, '.o_Message_moderationAction.o-accept', - "there should be a contextual moderation decision to accept the message" - ); - assert.containsOnce(messageEl, '.o_Message_moderationAction.o-reject', - "there should be a contextual moderation decision to reject the message" - ); - assert.containsOnce(messageEl, '.o_Message_moderationAction.o-discard', - "there should be a contextual moderation decision to discard the message" - ); - assert.containsOnce(messageEl, '.o_Message_moderationAction.o-allow', - "there should be a contextual moderation decision to allow the user of the message)" - ); - assert.containsOnce(messageEl, '.o_Message_moderationAction.o-ban', - "there should be a contextual moderation decision to ban the user of the message" - ); - // The actions are tested as part of discuss tests. -}); - QUnit.test('Notification Sent', async function (assert) { assert.expect(9); diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js index e0198220b63..5bc8ba772a0 100644 --- a/addons/mail/static/src/components/message_list/message_list.js +++ b/addons/mail/static/src/components/message_list/message_list.js @@ -315,12 +315,6 @@ export class MessageList extends Component { if (prevMessage.originThread !== message.originThread) { return false; } - if ( - prevMessage.moderation_status === 'pending_moderation' || - message.moderation_status === 'pending_moderation' - ) { - return false; - } if ( prevMessage.notifications.length > 0 || message.notifications.length > 0 @@ -583,7 +577,6 @@ export class MessageList extends Component { Object.assign(MessageList, { components, defaultProps: { - hasMessageCheckbox: false, hasScrollAdjust: true, hasSquashCloseMessages: false, haveMessagesMarkAsReadIcon: false, @@ -599,7 +592,6 @@ Object.assign(MessageList, { type: Function, optional: true, }, - hasMessageCheckbox: Boolean, hasSquashCloseMessages: Boolean, haveMessagesMarkAsReadIcon: Boolean, haveMessagesReplyIcon: Boolean, diff --git a/addons/mail/static/src/components/message_list/message_list.xml b/addons/mail/static/src/components/message_list/message_list.xml index 67bef1fdb6d..6d8c7051ea7 100644 --- a/addons/mail/static/src/components/message_list/message_list.xml +++ b/addons/mail/static/src/components/message_list/message_list.xml @@ -25,12 +25,6 @@ Messages marked as read will appear in the history. - -
- You have no messages to moderate. -
- Messages pending moderation appear here. -
There are no messages in this conversation. @@ -74,7 +68,6 @@ { - const messages = props.messageLocalIds.map(localId => - this.env.models['mail.message'].get(localId) - ); - return { - messages: messages.map(message => message ? message.__state : undefined), - }; - }, { - compareDepth: { - messages: 1, - }, - }); - // to manually trigger the dialog close event - this._dialogRef = useRef('dialog'); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @returns {mail.message[]} - */ - get messages() { - return this.props.messageLocalIds.map(localId => this.env.models['mail.message'].get(localId)); - } - - /** - * @returns {string} - */ - get CONFIRMATION() { - return this.env._t("Confirmation"); - } - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onClickBan() { - this._dialogRef.comp._close(); - this.env.models['mail.message'].moderate(this.messages, 'ban'); - } - - /** - * @private - */ - _onClickCancel() { - this._dialogRef.comp._close(); - } - -} - -Object.assign(ModerationBanDialog, { - components, - props: { - messageLocalIds: { - type: Array, - element: String, - }, - }, - template: 'mail.ModerationBanDialog', -}); diff --git a/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.xml b/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.xml deleted file mode 100644 index 1e29f731939..00000000000 --- a/addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - -

You are going to ban the following user:

-
- -

You are going to ban the following users:

-
-
    - -
  • - -
-

Do you confirm the action?

- - - - -
-
-
diff --git a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js b/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js deleted file mode 100644 index 36b79d8ae64..00000000000 --- a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js +++ /dev/null @@ -1,104 +0,0 @@ -/** @odoo-module **/ - -import { useShouldUpdateBasedOnProps } from '@mail/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props'; -import { useStore } from '@mail/component_hooks/use_store/use_store'; - -import Dialog from 'web.OwlDialog'; - -const { Component } = owl; -const { useRef } = owl.hooks; - -const components = { Dialog }; - -export class ModerationDiscardDialog extends Component { - - /** - * @override - */ - constructor(...args) { - super(...args); - useShouldUpdateBasedOnProps({ - compareDepth: { - messageLocalIds: 1, - }, - }); - useStore(props => { - const messages = props.messageLocalIds.map(localId => - this.env.models['mail.message'].get(localId) - ); - return { - messages: messages.map(message => message ? message.__state : undefined), - }; - }, { - compareDepth: { - messages: 1, - }, - }); - // to manually trigger the dialog close event - this._dialogRef = useRef('dialog'); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @returns {string} - */ - getBody() { - if (this.messages.length === 1) { - return this.env._t("You are going to discard 1 message."); - } - return _.str.sprintf( - this.env._t("You are going to discard %s messages."), - this.messages.length - ); - } - - /** - * @returns {mail.message[]} - */ - get messages() { - return this.props.messageLocalIds.map(localId => - this.env.models['mail.message'].get(localId) - ); - } - - /** - * @returns {string} - */ - getTitle() { - return this.env._t("Confirmation"); - } - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onClickCancel() { - this._dialogRef.comp._close(); - } - - /** - * @private - */ - _onClickDiscard() { - this._dialogRef.comp._close(); - this.env.models['mail.message'].moderate(this.messages, 'discard'); - } - -} - -Object.assign(ModerationDiscardDialog, { - components, - props: { - messageLocalIds: { - type: Array, - element: String, - }, - }, - template: 'mail.ModerationDiscardDialog', -}); diff --git a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.xml b/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.xml deleted file mode 100644 index 58dbc14dc5c..00000000000 --- a/addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - -

-

Do you confirm the action?

- - - - -
-
-
diff --git a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js b/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js deleted file mode 100644 index 931ea018698..00000000000 --- a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js +++ /dev/null @@ -1,99 +0,0 @@ -/** @odoo-module **/ - -import { useShouldUpdateBasedOnProps } from '@mail/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props'; -import { useStore } from '@mail/component_hooks/use_store/use_store'; - -import Dialog from 'web.OwlDialog'; - -const { Component, useState } = owl; -const { useRef } = owl.hooks; - -const components = { Dialog }; - -export class ModerationRejectDialog extends Component { - - /** - * @override - */ - constructor(...args) { - super(...args); - useShouldUpdateBasedOnProps({ - compareDepth: { - messageLocalIds: 1, - }, - }); - this.state = useState({ - title: this.env._t("Message Rejected"), - comment: this.env._t("Your message was rejected by moderator."), - }); - useStore(props => { - const messages = props.messageLocalIds.map(localId => - this.env.models['mail.message'].get(localId) - ); - return { - messages: messages.map(message => message ? message.__state : undefined), - }; - }, { - compareDepth: { - messages: 1, - }, - }); - // to manually trigger the dialog close event - this._dialogRef = useRef('dialog'); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @returns {mail.message[]} - */ - get messages() { - return this.props.messageLocalIds.map(localId => - this.env.models['mail.message'].get(localId) - ); - } - - /** - * @returns {string} - */ - get SEND_EXPLANATION_TO_AUTHOR() { - return this.env._t("Send explanation to author"); - } - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onClickCancel() { - this._dialogRef.comp._close(); - } - - /** - * @private - */ - _onClickReject() { - this._dialogRef.comp._close(); - const kwargs = { - title: this.state.title, - comment: this.state.comment, - }; - this.env.models['mail.message'].moderate(this.messages, 'reject', kwargs); - } - -} - -Object.assign(ModerationRejectDialog, { - components, - props: { - messageLocalIds: { - type: Array, - element: String, - }, - }, - template: 'mail.ModerationRejectDialog', -}); diff --git a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.xml b/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.xml deleted file mode 100644 index 182ffc7e8f0..00000000000 --- a/addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - -