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:
- 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'))
- 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:
- 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
- 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:
- 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
- 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:
- 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
- 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.
- 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']
- 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.
- Create a custom ticket fields
tester
. - 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
- Users can manage this subscription in their preferences.
- A default subscription can be configured in the TracIni#notification-subscriber-section:
[notification-subscriber] always_notify_tester = TesterCustomFieldSubscriber