Skip to content

Commit

Permalink
Support configuration from bucket metadata (fixes #27)
Browse files Browse the repository at this point in the history
  • Loading branch information
leplatrem committed Jan 27, 2017
1 parent 865d287 commit 7fb0e5c
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 32 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ This document describes changes between each past release.
0.3.0 (unreleased)
------------------

- Nothing changed yet.
**New features**

- Support configuration from bucket metadata (fixes #27)


0.2.0 (2017-01-27)
Expand Down
9 changes: 7 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ How does it work?
=================

Some information — like monitored action or list of recipients — are defined in
the collection metadata. When an event occurs, the plugin sends emails if one of
the collection or the bucket metadata. When an event occurs, the plugin sends emails if one of
the expected condition is met.


Usage
=====

The metadata on the collection must look like this:
The metadata on the collection (or the bucket) must look like this:

.. code-block:: js
Expand All @@ -80,8 +80,11 @@ The metadata on the collection must look like this:
In the above example, every action on the collection metadata or any record in that
collection will trigger an email notification.

The metadata of the collection override the bucket metadata, they are not merged.

Optional:

* ``subject`` (e.g. ``"An action was performed"``)
* ``sender`` (e.g. ``"Kinto team <developers@kinto-storage.org>"``)


Expand Down Expand Up @@ -124,6 +127,8 @@ The possible filters are:

* ``resource_name``: ``record`` or ``collection`` (default: all)
* ``action``: ``create``, ``update``, ``delete`` (default: all)
* ``collection_id`` (default: all)
* ``record_id`` (default: all)
* ``event``: ``kinto.core.events.AfterResourceChanged`` (default), or
``kinto_signer.events.ReviewRequested``, ``kinto_signer.events.ReviewApproved``,
``kinto_signer.events.ReviewRejected``
Expand Down
55 changes: 32 additions & 23 deletions kinto_emailer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,27 @@ def send_notification(event):
mailer.send(message)


def _get_collection_record(storage, bucket_id, collection_id):
parent_id = '/buckets/%s' % bucket_id
record_type = 'collection'

return storage.get(
parent_id=parent_id,
collection_id=record_type,
object_id=collection_id)


def _expand_recipients(storage, recipients):
def _get_emailer_hooks(storage, context):
bucket_id = context['bucket_id']
collection_id = context['collection_id']
bucket_uri = '/buckets/%s' % bucket_id
# Look-up collection metadata.
metadata = storage.get(parent_id=bucket_uri,
collection_id='collection',
object_id=collection_id)
if 'kinto-emailer' not in metadata:
# Try in bucket metadata.
metadata = storage.get(parent_id='',
collection_id='bucket',
object_id=bucket_id)
# Returns empty list of hooks.
return metadata.get('kinto-emailer', {}).get('hooks', [])


def _expand_recipients(storage, recipients, context):
emails = [r for r in recipients if not GROUP_REGEXP.match(r)]
groups = [r for r in recipients if GROUP_REGEXP.match(r)]
groups = [r.format(**context) for r in recipients
if GROUP_REGEXP.match(r.format(**context))]
for group_uri in groups:
bucket_uri, group_id = group_uri.split('/groups/')
try:
Expand All @@ -73,25 +81,26 @@ def _expand_recipients(storage, recipients):
return emails


def get_messages(storage, payload):
collection_record = _get_collection_record(storage,
payload['bucket_id'],
payload['collection_id'])
filters = ('event', 'action', 'resource_name', 'id')
hooks = collection_record.get('kinto-emailer', {}).get('hooks', [])
def get_messages(storage, context):
hooks = _get_emailer_hooks(storage, context)
filters = ('event', 'action', 'resource_name',
'id', 'record_id', 'collection_id')
messages = []
for hook in hooks:
# Filter out hook if it doesn't meet current event attributes, and keep
# if nothing is specified.
conditions_met = all([field not in hook or field not in payload or
hook[field] == payload[field]
conditions_met = all([field not in hook or field not in context or
hook[field] == context[field]
for field in filters])
if not conditions_met:
continue

msg = hook['template'].format(**payload)
subject = hook.get('subject', 'New message').format(**payload)
recipients = _expand_recipients(storage, hook['recipients'])
msg = hook['template'].format(**context)
subject = hook.get('subject', 'New message').format(**context)
recipients = _expand_recipients(storage, hook['recipients'], context)

if not recipients:
continue

messages.append(Message(subject=subject,
sender=hook.get('sender'),
Expand Down
58 changes: 52 additions & 6 deletions kinto_emailer/tests/test_includeme.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,26 +209,72 @@ def test_get_messages_can_filter_by_event_class(self):
assert len(messages) == 1


class GroupExpansionTest(unittest.TestCase):
class BucketTest(unittest.TestCase):

def test_recipients_are_expanded_from_group_members(self):
def test_hooks_can_be_defined_on_buckets(self):
storage = mock.MagicMock()
collection_record = {
collection_metadata = {}
bucket_metadata = {
'kinto-emailer': {
'hooks': [{
'template': 'Poll changed.',
'recipients': ['from@bucket.com'],
}]
}
}
storage.get.side_effect = [collection_metadata, bucket_metadata]
payload = {'bucket_id': 'b', 'collection_id': 'c'}
message, = get_messages(storage, payload)
assert message.recipients == ['from@bucket.com']


class GroupExpansionTest(unittest.TestCase):

def setUp(self):
self.storage = mock.MagicMock()
self.collection_record = {
'kinto-emailer': {
'hooks': [{
'template': 'Poll changed.',
'recipients': ['/buckets/b/groups/g'],
}]
}
}
group_record = {
self.group_record = {
'members': ['fxa:225689876', 'portier:devnull@localhost.com']
}
storage.get.side_effect = [collection_record, group_record]
self.storage.get.side_effect = [self.collection_record, self.group_record]

def test_recipients_are_expanded_from_group_members(self):
payload = {'bucket_id': 'b', 'collection_id': 'c'}
message, = get_messages(storage, payload)
message, = get_messages(self.storage, payload)
assert message.recipients == ['devnull@localhost.com']

def test_no_message_no_email_in_group_members(self):
group_record = {
'members': ['fxa:225689876', 'basicauth:toto-la-mamposina']
}
self.storage.get.side_effect = [self.collection_record, group_record]
payload = {'bucket_id': 'b', 'collection_id': 'c'}
messages = get_messages(self.storage, payload)
assert not messages

def test_email_group_can_contain_placeholders(self):
collection_record = {
'kinto-emailer': {
'hooks': [{
'template': 'Poll changed.',
'recipients': ['/buckets/b/groups/{collection_id}'],
}]
}
}
self.storage.get.side_effect = [collection_record, self.group_record]
payload = {'bucket_id': 'b', 'collection_id': 'c'}
get_messages(self.storage, payload)
self.storage.get.assert_called_with(parent_id='/buckets/b',
collection_id='group',
object_id='c')


class SendNotificationTest(unittest.TestCase):
def test_send_notification_does_not_call_the_mailer_if_no_message(self):
Expand Down

0 comments on commit 7fb0e5c

Please sign in to comment.