Skip to content

Commit d540b98

Browse files
authored
Log changes to project sharing (#5199)
1 parent a2c4ca0 commit d540b98

File tree

5 files changed

+140
-26
lines changed

5 files changed

+140
-26
lines changed

kobo/apps/audit_log/audit_actions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ class AuditAction(models.TextChoices):
1515
REDEPLOY = 'redeploy'
1616
UPDATE_NAME = 'update-name'
1717
UPDATE_SETTINGS = 'update-settings'
18+
ENABLE_SHARING = 'enable-sharing'
19+
DISABLE_SHARING = 'disable-sharing'
20+
MODIFY_SHARING = 'modify_sharing'

kobo/apps/audit_log/models.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
from kpi.models import Asset
2222
from kpi.utils.log import logging
2323

24+
NEW = 'new'
25+
OLD = 'old'
26+
ADDED = 'added'
27+
REMOVED = 'removed'
28+
2429

2530
class AuditType(models.TextChoices):
2631
ACCESS = 'access'
@@ -384,6 +389,7 @@ def create_from_detail_request(cls, request):
384389
changed_field_to_action_map = {
385390
'name': cls.name_change,
386391
'settings': cls.settings_change,
392+
'data_sharing': cls.sharing_change,
387393
}
388394

389395
for field, method in changed_field_to_action_map.items():
@@ -405,7 +411,7 @@ def create_from_detail_request(cls, request):
405411

406412
@staticmethod
407413
def name_change(old_field, new_field):
408-
metadata = {'name': {'old': old_field, 'new': new_field}}
414+
metadata = {'name': {OLD: old_field, NEW: new_field}}
409415
return AuditAction.UPDATE_NAME, metadata
410416

411417
@staticmethod
@@ -420,10 +426,39 @@ def settings_change(old_field, new_field):
420426
if isinstance(old, list) and isinstance(new, list):
421427
removed_values = [val for val in old if val not in new]
422428
added_values = [val for val in new if val not in old]
423-
metadata_field_subdict['added'] = added_values
424-
metadata_field_subdict['removed'] = removed_values
429+
metadata_field_subdict[ADDED] = added_values
430+
metadata_field_subdict[REMOVED] = removed_values
425431
else:
426-
metadata_field_subdict['old'] = old
427-
metadata_field_subdict['new'] = new
432+
metadata_field_subdict[OLD] = old
433+
metadata_field_subdict[NEW] = new
428434
settings[setting_name] = metadata_field_subdict
429435
return AuditAction.UPDATE_SETTINGS, {'settings': settings}
436+
437+
@staticmethod
438+
def sharing_change(old_fields, new_fields):
439+
old_enabled = old_fields.get('enabled', False)
440+
old_shared_fields = old_fields.get('fields', [])
441+
new_enabled = new_fields.get('enabled', False)
442+
new_shared_fields = new_fields.get('fields', [])
443+
shared_fields_dict = {}
444+
# anything falsy means it was disabled, anything truthy means enabled
445+
if old_enabled and not new_enabled:
446+
# sharing went from enabled to disabled
447+
action = AuditAction.DISABLE_SHARING
448+
return action, {}
449+
elif not old_enabled and new_enabled:
450+
# sharing went from disabled to enabled
451+
action = AuditAction.ENABLE_SHARING
452+
shared_fields_dict[ADDED] = new_shared_fields
453+
else:
454+
# the specific fields shared changed
455+
removed_fields = [
456+
field for field in old_shared_fields if field not in new_shared_fields
457+
]
458+
added_fields = [
459+
field for field in new_shared_fields if field not in old_shared_fields
460+
]
461+
action = AuditAction.MODIFY_SHARING
462+
shared_fields_dict[ADDED] = added_fields
463+
shared_fields_dict[REMOVED] = removed_fields
464+
return action, {'shared_fields': shared_fields_dict}

kobo/apps/audit_log/tests/test_project_history_logs.py

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.urls import reverse
55

66
from kobo.apps.audit_log.audit_actions import AuditAction
7-
from kobo.apps.audit_log.models import ProjectHistoryLog
7+
from kobo.apps.audit_log.models import ADDED, NEW, OLD, REMOVED, ProjectHistoryLog
88
from kobo.apps.audit_log.tests.test_models import BaseAuditLogTestCase
99
from kobo.apps.kobo_auth.shortcuts import User
1010
from kpi.models import Asset
@@ -173,8 +173,8 @@ def test_change_project_name_creates_log(self):
173173
expected_action=AuditAction.UPDATE_NAME,
174174
)
175175

176-
self.assertEqual(log_metadata['name']['new'], 'new_name')
177-
self.assertEqual(log_metadata['name']['old'], old_name)
176+
self.assertEqual(log_metadata['name'][NEW], 'new_name')
177+
self.assertEqual(log_metadata['name'][OLD], old_name)
178178

179179
def test_change_standard_project_settings_creates_log(self):
180180
old_settings = copy.deepcopy(self.asset.settings)
@@ -195,17 +195,15 @@ def test_change_standard_project_settings_creates_log(self):
195195

196196
# check non-list settings just store old and new information
197197
settings_dict = log_metadata['settings']
198-
self.assertEqual(
199-
settings_dict['description']['old'], old_settings['description']
200-
)
201-
self.assertEqual(settings_dict['description']['new'], 'New description')
198+
self.assertEqual(settings_dict['description'][OLD], old_settings['description'])
199+
self.assertEqual(settings_dict['description'][NEW], 'New description')
202200
# check list settings store added and removed fields
203201
self.assertListEqual(
204-
settings_dict['country']['added'],
202+
settings_dict['country'][ADDED],
205203
[{'label': 'Albania', 'value': 'ALB'}],
206204
)
207205
self.assertListEqual(
208-
settings_dict['country']['removed'],
206+
settings_dict['country'][REMOVED],
209207
[{'label': 'United States', 'value': 'USA'}],
210208
)
211209
# check default settings not recorded if not included in request
@@ -265,7 +263,6 @@ def test_nullify_settings_creates_log(self):
265263
request_data={'settings': {}},
266264
expected_action=AuditAction.UPDATE_SETTINGS,
267265
)
268-
269266
for setting, old_value in old_settings.items():
270267
if setting in Asset.STANDARDIZED_SETTINGS:
271268
# if the setting is one of the standard ones, the new value after
@@ -283,14 +280,14 @@ def test_nullify_settings_creates_log(self):
283280
removed_values = [val for val in old_value if val not in new_value]
284281
added_values = [val for val in new_value if val not in old_value]
285282
self.assertListEqual(
286-
log_metadata['settings'][setting]['added'], added_values
283+
log_metadata['settings'][setting][ADDED], added_values
287284
)
288285
self.assertListEqual(
289-
log_metadata['settings'][setting]['removed'], removed_values
286+
log_metadata['settings'][setting][REMOVED], removed_values
290287
)
291288
else:
292-
self.assertEqual(log_metadata['settings'][setting]['new'], new_value)
293-
self.assertEqual(log_metadata['settings'][setting]['old'], old_value)
289+
self.assertEqual(log_metadata['settings'][setting][NEW], new_value)
290+
self.assertEqual(log_metadata['settings'][setting][OLD], old_value)
294291

295292
def test_add_new_settings_creates_log(self):
296293
log_metadata = self._base_endpoint_test(
@@ -301,5 +298,80 @@ def test_add_new_settings_creates_log(self):
301298
expected_action=AuditAction.UPDATE_SETTINGS,
302299
)
303300

304-
self.assertEqual(log_metadata['settings']['new_setting']['new'], 'new_value')
305-
self.assertEqual(log_metadata['settings']['new_setting']['old'], None)
301+
self.assertEqual(log_metadata['settings']['new_setting'][NEW], 'new_value')
302+
self.assertEqual(log_metadata['settings']['new_setting'][OLD], None)
303+
304+
def test_enable_sharing_creates_log(self):
305+
log_metadata = self._base_endpoint_test(
306+
patch=True,
307+
url_name=self.detail_url,
308+
request_data={'data_sharing': {'enabled': True, 'fields': []}},
309+
expected_action=AuditAction.ENABLE_SHARING,
310+
)
311+
self.assertEqual(log_metadata['shared_fields'][ADDED], [])
312+
313+
def test_truthy_field_creates_sharing_enabled_log(self):
314+
log_metadata = self._base_endpoint_test(
315+
patch=True,
316+
url_name=self.detail_url,
317+
request_data={'data_sharing': {'enabled': 'truthy'}},
318+
expected_action=AuditAction.ENABLE_SHARING,
319+
)
320+
self.assertEqual(log_metadata['shared_fields'][ADDED], [])
321+
322+
def test_disable_sharing_creates_log(self):
323+
self.asset.data_sharing = {
324+
'enabled': True,
325+
'fields': [],
326+
}
327+
self.asset.save()
328+
329+
self._base_endpoint_test(
330+
patch=True,
331+
url_name=self.detail_url,
332+
request_data={'data_sharing': {'enabled': False}},
333+
expected_action=AuditAction.DISABLE_SHARING,
334+
)
335+
336+
def test_nullify_sharing_creates_sharing_disabled_log(self):
337+
self.asset.data_sharing = {
338+
'enabled': True,
339+
'fields': [],
340+
}
341+
self.asset.save()
342+
343+
self._base_endpoint_test(
344+
patch=True,
345+
url_name=self.detail_url,
346+
request_data={'data_sharing': {}},
347+
expected_action=AuditAction.DISABLE_SHARING,
348+
)
349+
350+
def test_falsy_field_creates_sharing_disabled_log(self):
351+
self.asset.data_sharing = {
352+
'enabled': True,
353+
'fields': [],
354+
}
355+
self.asset.save()
356+
357+
self._base_endpoint_test(
358+
patch=True,
359+
url_name=self.detail_url,
360+
request_data={'data_sharing': {'enabled': 0}},
361+
expected_action=AuditAction.DISABLE_SHARING,
362+
)
363+
364+
def test_modify_sharing_creates_log(self):
365+
self.asset.data_sharing = {
366+
'enabled': True,
367+
'fields': ['q1'],
368+
}
369+
self.asset.save()
370+
log_metadata = self._base_endpoint_test(
371+
patch=True,
372+
url_name=self.detail_url,
373+
request_data={'data_sharing': {'enabled': True, 'fields': ['q2']}},
374+
expected_action=AuditAction.MODIFY_SHARING,
375+
)
376+
self.assertEqual(log_metadata['shared_fields'][ADDED], ['q2'])
377+
self.assertEqual(log_metadata['shared_fields'][REMOVED], ['q1'])

kpi/fixtures/asset_with_settings_and_qa.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@
3737
"organization" : "An organization"
3838
},
3939
"data_sharing":{
40-
"fields":[
41-
42-
],
40+
"fields":[],
4341
"enabled":false
4442
},
4543
"advanced_features":{

kpi/views/v2/asset.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,14 @@ class AssetViewSet(
380380
'uid__icontains',
381381
]
382382

383-
logged_fields = ['has_deployment', 'id', 'name', 'settings', 'latest_version.uid']
384-
383+
logged_fields = [
384+
'has_deployment',
385+
'id',
386+
'name',
387+
'settings',
388+
'latest_version.uid',
389+
'data_sharing',
390+
]
385391
log_type = AuditType.PROJECT_HISTORY
386392

387393
def get_object_override(self):

0 commit comments

Comments
 (0)