From 59d6bb23d75cd8ddf8e62dad8aa8ad13d0cf4a0a Mon Sep 17 00:00:00 2001 From: std-odoo Date: Wed, 26 May 2021 08:38:22 +0000 Subject: [PATCH] [REM] mail: remove mail channel moderation Rationale ========= Currently, mail channels have 2 modes - They can be used like Discuss channel (chat, livechat, group) - They can be used like a Mailing List (with the "email_send" field) The mix of both feature in the same model causes some code complexity. Several fields are not used in both cases (moderation related field) and the future "Discord like" Discuss will even push the mail channel further than the usage of the mailing list. Because of all those reasons, we want to split the 2 mains features of the mail channels into 2 different modules and models. Purpose ======= This commit remove the moderation feature of the mail channel (email_send=True). This will be re-implemented in a separate module in the next commit. Do not be able to check messages in Discuss anymore because this feature is only used to moderate the message and this feature will be spitted in a new module "mail_group". Links ===== Task-2510267 See odoo/odoo/pull/71599 See odoo/enterprise/pull/19296 See odoo/upgrade/pull/2600 --- addons/mail/__manifest__.py | 10 +- addons/mail/controllers/main.py | 2 - addons/mail/data/ir_cron_data.xml | 13 - addons/mail/data/mail_templates.xml | 24 - addons/mail/models/__init__.py | 1 - addons/mail/models/mail_channel.py | 219 +-- addons/mail/models/mail_message.py | 285 +--- addons/mail/models/mail_moderation.py | 20 - addons/mail/models/mail_thread.py | 3 - addons/mail/models/res_users.py | 31 - addons/mail/security/ir.model.access.csv | 1 - addons/mail/security/mail_security.xml | 6 - .../static/src/components/discuss/discuss.js | 31 +- .../static/src/components/discuss/discuss.xml | 7 - .../discuss/tests/discuss_domain_tests.js | 72 - .../discuss/tests/discuss_moderation_tests.js | 1175 ----------------- .../static/src/components/message/message.js | 92 -- .../src/components/message/message.scss | 49 - .../static/src/components/message/message.xml | 27 +- .../src/components/message/message_tests.js | 74 -- .../components/message_list/message_list.js | 8 - .../components/message_list/message_list.xml | 7 - .../moderation_ban_dialog.js | 89 -- .../moderation_ban_dialog.xml | 23 - .../moderation_discard_dialog.js | 104 -- .../moderation_discard_dialog.xml | 13 - .../moderation_reject_dialog.js | 99 -- .../moderation_reject_dialog.xml | 13 - .../src/components/thread_icon/thread_icon.js | 1 - .../components/thread_icon/thread_icon.xml | 3 - .../src/components/thread_view/thread_view.js | 3 - .../components/thread_view/thread_view.xml | 1 - .../mail/static/src/models/discuss/discuss.js | 12 - .../mail/static/src/models/message/message.js | 144 +- .../static/src/models/messaging/messaging.js | 4 - .../messaging_initializer.js | 30 - .../messaging_notification_handler.js | 46 - .../mail/static/src/models/partner/partner.js | 6 - .../mail/static/src/models/thread/thread.js | 42 - .../src/models/thread_cache/thread_cache.js | 45 - .../src/models/thread_view/thread_view.js | 6 - addons/mail/static/src/variables.scss | 3 - .../static/src/widgets/discuss/discuss.js | 82 -- .../static/src/widgets/discuss/discuss.xml | 5 - .../mail/static/tests/helpers/mock_models.js | 4 - .../mail/static/tests/helpers/mock_server.js | 74 +- addons/mail/tests/test_mail_channel.py | 270 ---- addons/mail/views/mail_channel_views.xml | 19 - addons/mail/views/mail_menus.xml | 5 - addons/mail/views/mail_message_views.xml | 2 - addons/mail/views/mail_moderation_views.xml | 46 - addons/test_mail/tests/test_mail_message.py | 26 - 52 files changed, 37 insertions(+), 3340 deletions(-) delete mode 100644 addons/mail/models/mail_moderation.py delete mode 100644 addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.js delete mode 100644 addons/mail/static/src/components/moderation_ban_dialog/moderation_ban_dialog.xml delete mode 100644 addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.js delete mode 100644 addons/mail/static/src/components/moderation_discard_dialog/moderation_discard_dialog.xml delete mode 100644 addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.js delete mode 100644 addons/mail/static/src/components/moderation_reject_dialog/moderation_reject_dialog.xml delete mode 100644 addons/mail/views/mail_moderation_views.xml 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 @@ - - - - - -