Edgewall Software

Custom Notification Subscriptions

Subscriptions allow site administrators and users to subscribe to notifications under different conditions. TracNotification customizations can often be implemented with a short plugin. Some examples of custom subscriptions are given on this page.

Subscribe to all ticket notifications

The [notification] smtp_always_cc config option will send all notifications to the specified address. Without plugins this is the same as all ticket notifications, but some plugins may send non-ticket notifications that you don't want to send to that address. Then it may be useful to have a [notification] ticket_always_cc config option that will only send all ticket notifications to the specified address:

  1. Create a single file plugin that implements INotificationSubscriber:
    # -*- coding: utf-8 -*-
    #
    # Copyright (C) 2017 Edgewall Software
    # All rights reserved.
    #
    # This software is licensed as described in the file COPYING, which
    # you should have received as part of this distribution. The terms
    # are also available at http://trac.edgewall.org/wiki/TracLicense.
    #
    # This software consists of voluntary contributions made by many
    # individuals. For the exact contribution history, see the revision
    # history and logs, available at http://trac.edgewall.org/log/.
    
    from trac.core import Component, implements
    from trac.notification.api import INotificationSubscriber
    from trac.notification.mail import RecipientMatcher
    
    class TicketAlwaysEmailSubscriber(Component):
        """Implement a policy to send an email to a certain address for all ticket
        notifications.
    
        Controlled via the ticket_always_cc and smtp_always_bcc option in the
        notification section of trac.ini.
        """
    
        implements(INotificationSubscriber)
    
        def matches(self, event):
            if event.realm != 'ticket':
                return
            matcher = RecipientMatcher(self.env)
            klass = self.__class__.__name__
            format = 'text/plain'
            priority = 0
            for address in self._get_address_list():
                recipient = matcher.match_recipient(address)
                if recipient:
                    sid, authenticated, address = recipient
                    yield (klass, 'email', sid, authenticated, address, format,
                           priority, 'always')
    
        def description(self):
            return None  # not configurable
    
        def requires_authentication(self):
            return False
    
        def default_subscriptions(self):
            return ()
    
        def _get_address_list(self):
            section = self.config['notification']
            def getlist(name):
                return section.getlist(name, sep=(',', ' '), keep_empty=False)
            return set(getlist('ticket_always_cc')) | \
                   set(getlist('ticket_always_bcc'))
    
  2. Configure the [notification] ticket_always_cc (or ..._bcc) config option.

See also: #9148

Subscribe to new created tickets

Note: This subscriber was added to Trac in release 1.2.3.

Some projects / users want to subscribe only to newly created ticket notifications:

  1. Create a single file plugin that implements INotificationSubscriber:
    # -*- coding: utf-8 -*-
    #
    # Copyright (C) 2017 Edgewall Software
    # All rights reserved.
    #
    # This software is licensed as described in the file COPYING, which
    # you should have received as part of this distribution. The terms
    # are also available at http://trac.edgewall.org/wiki/TracLicense.
    #
    # This software consists of voluntary contributions made by many
    # individuals. For the exact contribution history, see the revision
    # history and logs, available at http://trac.edgewall.org/log/.
    
    from trac.core import Component, implements
    from trac.notification.api import INotificationSubscriber
    from trac.notification.model import Subscription
    from trac.util.translation import _
    
    class NewTicketSubscriber(Component):
        """Subscribe to new created tickets."""
    
        implements(INotificationSubscriber)
    
        # INotificationSubscriber methods
    
        def matches(self, event):
            if event.realm != 'ticket':
                return
            if event.category != 'created':
                return
    
            klass = self.__class__.__name__
            for s in Subscription.find_by_class(self.env, klass):
                yield s.subscription_tuple()
    
        def description(self):
            return _("Any ticket is created")
    
        def default_subscriptions(self):
            return []
    
        def requires_authentication(self):
            return False
    
  2. Users can manage this subscription in their preferences.

See also: #6613

Unsubscribe from ticket updates without comments

Some projects / users don't want to subscribe to ticket notifications if only some ticket properties were changed but no comment was added:

  1. Create a single file plugin that implements INotificationSubscriber:
    # -*- coding: utf-8 -*-
    #
    # Copyright (C) 2017 Edgewall Software
    # All rights reserved.
    #
    # This software is licensed as described in the file COPYING, which
    # you should have received as part of this distribution. The terms
    # are also available at http://trac.edgewall.org/wiki/TracLicense.
    #
    # This software consists of voluntary contributions made by many
    # individuals. For the exact contribution history, see the revision
    # history and logs, available at http://trac.edgewall.org/log/.
    
    from trac.core import Component, implements
    from trac.notification.api import INotificationSubscriber
    from trac.notification.model import Subscription
    from trac.util.translation import _
    
    class TicketPropSubscriber(Component):
        """Subscribe to (or unsubscribe from) ticket updates without
        comments."""
    
        implements(INotificationSubscriber)
    
        # INotificationSubscriber methods
    
        def matches(self, event):
            if event.realm != 'ticket':
                return
            if event.category != 'changed':
                return
            if event.comment != '':
                return
    
            klass = self.__class__.__name__
            for s in Subscription.find_by_class(self.env, klass):
                yield s.subscription_tuple()
    
        def description(self):
            return _("Only ticket props are changed")
    
        def default_subscriptions(self):
            return []
    
        def requires_authentication(self):
            return False
    
  2. Users can manage this subscription in their preferences. Add a rule to Never notify: Only ticket props are changed to unsubscribe from ticket notifications where only ticket props are changed.

See also: #10326

Subscribe to ticket changes with blocker severity

Some projects / users want to subscribe only to ticket notifications where certain ticket field values are set. For example maybe some users are interested in tickets with blocker severity:

  1. Create a single file plugin that implements INotificationSubscriber:
    # -*- coding: utf-8 -*-
    #
    # Copyright (C) 2017 Edgewall Software
    # All rights reserved.
    #
    # This software is licensed as described in the file COPYING, which
    # you should have received as part of this distribution. The terms
    # are also available at http://trac.edgewall.org/wiki/TracLicense.
    #
    # This software consists of voluntary contributions made by many
    # individuals. For the exact contribution history, see the revision
    # history and logs, available at http://trac.edgewall.org/log/.
    
    from trac.core import Component, implements
    from trac.notification.api import INotificationSubscriber
    from trac.notification.model import Subscription
    from trac.util.translation import _
    
    class BlockerSeverityTicketSubscriber(Component):
        """Subscribe to tickets with severity blocker."""
    
        implements(INotificationSubscriber)
    
        # INotificationSubscriber methods
    
        def matches(self, event):
            if event.realm != 'ticket':
                return
            ticket = event.target
            if ticket['severity'] != 'blocker':
                return
    
            klass = self.__class__.__name__
            for s in Subscription.find_by_class(self.env, klass):
                yield s.subscription_tuple()
    
        def description(self):
            return _("Ticket with severity 'blocker' is created or modified")
    
        def default_subscriptions(self):
            return []
    
        def requires_authentication(self):
            return False
    
  2. Users can manage this subscription in their preferences.

Variations

  • Change 'severity' to 'priority' or some other ticket field, and / or 'blocker' to some other value.
  • Only consider changes to a specific ticket field by checking for example event.changes['fields']['severity']['new'].
  • Create complex combinations of multiple ticket field with boolean operators and, or, not etc.

See also: #11758

Subscribe to ticket change replies

Some users only want to be notified when someone replies to their ticket changes.

  1. Create a single file plugin that implements INotificationSubscriber:
    # -*- coding: utf-8 -*-
    #
    # Copyright (C) 2017 Edgewall Software
    # All rights reserved.
    #
    # This software is licensed as described in the file COPYING, which
    # you should have received as part of this distribution. The terms
    # are also available at http://trac.edgewall.org/wiki/TracLicense.
    #
    # This software consists of voluntary contributions made by many
    # individuals. For the exact contribution history, see the revision
    # history and logs, available at http://trac.edgewall.org/log/.
    
    from trac.core import Component, implements
    from trac.notification.api import INotificationSubscriber, NotificationSystem
    from trac.notification.mail import RecipientMatcher
    from trac.notification.model import Subscription
    from trac.ticket.model import Ticket
    from trac.util.translation import _
    
    class ReplyToTicketSubscriber(Component):
        """Subscribe to new created tickets."""
    
        implements(INotificationSubscriber)
    
        # INotificationSubscriber methods
    
        def matches(self, event):
            if event.realm != 'ticket':
                return
            if event.category != 'changed':
                return
            if not event.changes:
                return
    
            replyto_author = self._get_replyto_author(event)
            matcher = RecipientMatcher(self.env)
            recipient = matcher.match_recipient(replyto_author)
            if not recipient:
                return
            sid, auth, addr = recipient
    
            # Default subscription
            for s in self.default_subscriptions():
                yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]
    
            if sid:
                klass = self.__class__.__name__
                for s in Subscription \
                         .find_by_sids_and_class(self.env, ((sid, auth),), klass):
                    yield s.subscription_tuple()
    
        def description(self):
            return _("My ticket change is replied to")
    
        def default_subscriptions(self):
            klass = self.__class__.__name__
            return NotificationSystem(self.env).default_subscriptions(klass)
    
        def requires_authentication(self):
            return False
    
        def _get_replyto_author(self, event):
            if 'comment' in event.changes['fields'] and \
                    'old' in event.changes['fields']['comment'] and \
                    '.' in event.changes['fields']['comment']['old']:
                old = event.changes['fields']['comment']['old']
                replyto_comment = old.split('.', 1)[0]
                replyto_change = event.target.get_change(replyto_comment)
                if replyto_change and 'comment' in replyto_change['fields']:
                    return replyto_change['fields']['comment']['author']
    
  2. Users can manage this subscription in their preferences.

See also: #12728

Subscribe via custom ticket field

Custom ticket fields can be configured to behave like an additional Cc field. For example a tester field can notify employees responsible for testing a feature, separately from other Cc'd users.

  1. Create a custom ticket fields tester.
  2. Create a single file plugin that implements INotificationSubscriber:
    # -*- coding: utf-8 -*-
    #
    # Copyright (C) 2018 Edgewall Software
    # All rights reserved.
    #
    # This software is licensed as described in the file COPYING, which
    # you should have received as part of this distribution. The terms
    # are also available at http://trac.edgewall.org/wiki/TracLicense.
    #
    # This software consists of voluntary contributions made by many
    # individuals. For the exact contribution history, see the revision
    # history and logs, available at http://trac.edgewall.org/log/.
    
    from trac.core import Component, implements
    from trac.notification.api import INotificationSubscriber, NotificationSystem
    from trac.notification.mail import RecipientMatcher
    from trac.notification.model import Subscription
    from trac.web.chrome import Chrome
    
    class TesterCustomFieldSubscriber(Component):
        """Subscriber for custom ticket field."""
    
        implements(INotificationSubscriber)
       
        field_name = 'tester'
    
        # INotificationSubscriber methods
    
        def matches(self, event):
            if event.realm != 'ticket':
                return
            if event.category not in ('created', 'changed', 'attachment added',
                                      'attachment deleted'):
                return
    
            # Custom field with comma-separated string. Parse to set.
            chrome = Chrome(self.env)
            to_set = lambda field: set(chrome.cc_list(field))
            field_set = to_set(event.target[self.field_name] or '')
    
            # Harvest previous field values
            if 'fields' in event.changes and self.field_name in event.changes['fields']:
                field_set.update(to_set(event.changes['fields'][self.field_name]['old']))
    
            matcher = RecipientMatcher(self.env)
            klass = self.__class__.__name__
            sids = set()
            for field in field_set:
                recipient = matcher.match_recipient(field)
                if not recipient:
                    continue
                sid, auth, addr = recipient
    
                # Default subscription
                for s in self.default_subscriptions():
                    yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]
                if sid:
                    sids.add((sid, auth))
    
            for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
                yield s.subscription_tuple()
    
        def description(self):
            return "Ticket that I'm listed in as %s is modified" % self.field_name
    
        def default_subscriptions(self):
            klass = self.__class__.__name__
            return NotificationSystem(self.env).default_subscriptions(klass)
    
        def requires_authentication(self):
            return True
    
  3. Users can manage this subscription in their preferences.
  4. A default subscription can be configured in the TracIni#notification-subscriber-section:
    [notification-subscriber]
    always_notify_tester = TesterCustomFieldSubscriber
    
Last modified 5 years ago Last modified on Feb 20, 2019, 9:58:53 PM
Note: See TracWiki for help on using the wiki.