Repository: Kinto/kinto

Issue:
Add a configuration of collections that the history plugin needs to keep track on
Today the history plugin applies to all the collection but most of them don't need it.
For instance with the kinto-signer plugin we don't want to track history of changes in the preview and signed collection.
The same goes with the kinto-changes plugin when we don't want to track monitor changes modifications.

The same way we can configure the kinto-signer resources we want to track, we should be able to configure the list of collections we want the history plugin to track.
Add a configuration of collections that the history plugin needs to keep track on
Today the history plugin applies to all the collection but most of them don't need it.
For instance with the kinto-signer plugin we don't want to track history of changes in the preview and signed collection.
The same goes with the kinto-changes plugin when we don't want to track monitor changes modifications.

The same way we can configure the kinto-signer resources we want to track, we should be able to configure the list of collections we want the history plugin to track.


Discussion:
I'd rather go with exclude on this one.

If I start a new project, I don't want to ask an administrator to change the server configuration to enable something like history on a collection...

For the signer plugin we could have:
```ini
kinto.history.exclude_resources = /buckets/blocklists-preview /buckets/blocklists
```

And I don't think the kinto-changes generates any event, so it might not be tracked in the history. We can also consider getting rid of the `ResourceChanged` events in the  kinto-signer if that makes sense.
Yes exclude works too 👍 
I'd rather go with exclude on this one.

If I start a new project, I don't want to ask an administrator to change the server configuration to enable something like history on a collection...

For the signer plugin we could have:
```ini
kinto.history.exclude_resources = /buckets/blocklists-preview /buckets/blocklists
```

And I don't think the kinto-changes generates any event, so it might not be tracked in the history. We can also consider getting rid of the `ResourceChanged` events in the  kinto-signer if that makes sense.
Yes exclude works too 👍 

Resolving patch:
diff --git a/kinto/plugins/history/listener.py b/kinto/plugins/history/listener.py
--- a/kinto/plugins/history/listener.py
+++ b/kinto/plugins/history/listener.py
@@ -1,3 +1,5 @@
+from pyramid.settings import aslist
+
 from kinto.core.utils import instance_uri
 from datetime import datetime
 
@@ -18,6 +20,9 @@ def on_resource_changed(event):
 
     storage = event.request.registry.storage
     permission = event.request.registry.permission
+    settings = event.request.registry.settings
+
+    excluded_resources = aslist(settings.get('history.exclude_resources', ''))
 
     targets = []
     for impacted in event.impacted_records:
@@ -31,12 +36,17 @@ def on_resource_changed(event):
             bucket_id = obj_id
         bucket_uri = instance_uri(event.request, 'bucket', id=bucket_id)
 
+        if bucket_uri in excluded_resources:
+            continue
+
         if 'collection_id' in payload:
             collection_id = payload['collection_id']
             collection_uri = instance_uri(event.request,
                                           'collection',
                                           bucket_id=bucket_id,
                                           id=collection_id)
+            if collection_uri in excluded_resources:
+                continue
 
         # On POST .../records, the URI does not contain the newly created
         # record id.
@@ -47,8 +57,15 @@ def on_resource_changed(event):
             # Make sure the id is correct on grouped events.
             parts[-1] = obj_id
         uri = '/'.join(parts)
+
+        if uri in excluded_resources:
+            continue
+
         targets.append((uri, target))
 
+    if not targets:
+        return  # Nothing to do.
+
     # Prepare a list of object ids to be fetched from permission backend,
     # and fetch them all at once. Use a mapping for later convenience.
     all_perms_objects_ids = [oid for (oid, _) in targets]


Test patch:
diff --git a/tests/plugins/test_history.py b/tests/plugins/test_history.py
--- a/tests/plugins/test_history.py
+++ b/tests/plugins/test_history.py
@@ -681,3 +681,47 @@ def test_history_entries_are_not_listed_in_permissions_endpoint(self):
                             headers=self.headers)
         entries = [e['resource_name'] == 'history' for e in resp.json["data"]]
         assert not any(entries)
+
+
+class ExcludeResourcesTest(HistoryWebTest):
+
+    def get_app_settings(self, extras=None):
+        settings = super(ExcludeResourcesTest, self).get_app_settings(extras)
+        settings['history.exclude_resources'] = ('/buckets/a '
+                                                 '/buckets/b/collections/a '
+                                                 '/buckets/b/groups/a')
+        return settings
+
+    def setUp(self):
+        group = {'data': {'members': []}}
+        self.app.put_json('/buckets/a', headers=self.headers)
+        self.app.put_json('/buckets/a/groups/admins', group, headers=self.headers)
+        self.app.put_json('/buckets/b', headers=self.headers)
+        self.app.put_json('/buckets/b/groups/a', group, headers=self.headers)
+        self.app.put_json('/buckets/b/collections/a', headers=self.headers)
+        self.app.put_json('/buckets/b/collections/a/records/1', headers=self.headers)
+        self.app.put_json('/buckets/b/collections/b', headers=self.headers)
+        self.app.put_json('/buckets/b/collections/b/records/1', headers=self.headers)
+
+    def test_whole_buckets_can_be_excluded(self):
+        resp = self.app.get('/buckets/a/history',
+                            headers=self.headers)
+        entries = resp.json['data']
+        assert len(entries) == 0  # nothing.
+
+    def test_some_specific_collection_can_be_excluded(self):
+        resp = self.app.get('/buckets/b/history?collection_id=b',
+                            headers=self.headers)
+        entries = resp.json['data']
+        assert len(entries) > 0
+
+        resp = self.app.get('/buckets/b/history?collection_id=a',
+                            headers=self.headers)
+        entries = resp.json['data']
+        assert len(entries) == 0  # nothing.
+
+    def test_some_specific_object_can_be_excluded(self):
+        resp = self.app.get('/buckets/b/history?group_id=a',
+                            headers=self.headers)
+        entries = resp.json['data']
+        assert len(entries) == 0  # nothing.