Skip to content

Commit 7b744e3

Browse files
authored
feat(projectHistoryLogs): record project history logs for imports TASK-944 (#5230)
1 parent 5d9ca2d commit 7b744e3

File tree

11 files changed

+387
-37
lines changed

11 files changed

+387
-37
lines changed

kobo/apps/audit_log/audit_actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class AuditAction(models.TextChoices):
2222
REDEPLOY = 'redeploy'
2323
REGISTER_SERVICE = 'register-service'
2424
REMOVE = 'remove'
25+
REPLACE_FORM = 'replace-form'
2526
UNARCHIVE = 'unarchive'
2627
UPDATE = 'update'
2728
UPDATE_CONTENT = 'update-content'

kobo/apps/audit_log/models.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
2323
)
2424
from kpi.fields.kpi_uid import UUID_LENGTH
25-
from kpi.models import Asset
25+
from kpi.models import Asset, ImportTask
2626
from kpi.utils.log import logging
2727

2828
NEW = 'new'
@@ -561,3 +561,40 @@ def create_from_related_request(
561561
ProjectHistoryLog.objects.create(
562562
user=request.user, object_id=object_id, action=action, metadata=metadata
563563
)
564+
565+
@classmethod
566+
def create_from_import_task(cls, task: ImportTask):
567+
# this will probably only ever be a list of size 1 or 0,
568+
# sent as a list because of how ImportTask is implemented
569+
# if somehow a task updates multiple assets, this should handle it
570+
audit_log_blocks = task.messages.get('audit_logs', [])
571+
for audit_log_info in audit_log_blocks:
572+
metadata = {
573+
'asset_uid': audit_log_info['asset_uid'],
574+
'latest_version_uid': audit_log_info['latest_version_uid'],
575+
'ip_address': audit_log_info['ip_address'],
576+
'source': audit_log_info['source'],
577+
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
578+
}
579+
ProjectHistoryLog.objects.create(
580+
user=task.user,
581+
object_id=audit_log_info['asset_id'],
582+
action=AuditAction.REPLACE_FORM,
583+
metadata=metadata,
584+
)
585+
# imports may change the name of an asset, log that too
586+
if audit_log_info['old_name'] != audit_log_info['new_name']:
587+
metadata.update(
588+
{
589+
'name': {
590+
OLD: audit_log_info['old_name'],
591+
NEW: audit_log_info['new_name'],
592+
}
593+
}
594+
)
595+
ProjectHistoryLog.objects.create(
596+
user=task.user,
597+
object_id=audit_log_info['asset_id'],
598+
action=AuditAction.UPDATE_NAME,
599+
metadata=metadata,
600+
)

kobo/apps/audit_log/signals.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
from celery.signals import task_success
12
from django.contrib.auth.signals import user_logged_in
23
from django.dispatch import receiver
34

5+
from kpi.models import ImportTask
6+
from kpi.tasks import import_in_background
47
from kpi.utils.log import logging
5-
from .models import AccessLog
8+
from .models import AccessLog, ProjectHistoryLog
69

710

811
@receiver(user_logged_in)
@@ -14,3 +17,9 @@ def create_access_log(sender, user, **kwargs):
1417
AccessLog.create_from_request(request, user)
1518
else:
1619
AccessLog.create_from_request(request)
20+
21+
22+
@receiver(task_success, sender=import_in_background)
23+
def create_ph_log_for_import(sender, result, **kwargs):
24+
task = ImportTask.objects.get(uid=result)
25+
ProjectHistoryLog.create_from_import_task(task)

kobo/apps/audit_log/tests/test_models.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
from kpi.constants import (
2323
ACCESS_LOG_SUBMISSION_AUTH_TYPE,
2424
ACCESS_LOG_SUBMISSION_GROUP_AUTH_TYPE,
25+
PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
2526
)
26-
from kpi.models import Asset
27+
from kpi.models import Asset, ImportTask
2728
from kpi.tests.base_test_case import BaseTestCase
2829

2930

@@ -556,3 +557,90 @@ def test_create_from_related_request_no_log_created_if_no_data(self):
556557
modify_action=AuditAction.UPDATE,
557558
)
558559
self.assertEqual(ProjectHistoryLog.objects.count(), 0)
560+
561+
def test_create_from_import_task_no_name_change(self):
562+
asset = Asset.objects.get(pk=1)
563+
task = ImportTask.objects.create(
564+
user=User.objects.get(username='someuser'), data={}
565+
)
566+
task.messages = {
567+
'audit_logs': [
568+
{
569+
'asset_uid': asset.uid,
570+
'latest_version_uid': 'av12345',
571+
'ip_address': '1.2.3.4',
572+
'source': 'source',
573+
'asset_id': asset.id,
574+
'old_name': asset.name,
575+
'new_name': asset.name,
576+
}
577+
]
578+
}
579+
ProjectHistoryLog.create_from_import_task(task)
580+
self.assertEqual(ProjectHistoryLog.objects.count(), 1)
581+
log = ProjectHistoryLog.objects.first()
582+
self.assertEqual(log.action, AuditAction.REPLACE_FORM)
583+
self.assertEqual(log.object_id, asset.id)
584+
585+
# data from 'messages' should be copied to the log
586+
self.assertDictEqual(
587+
log.metadata,
588+
{
589+
'ip_address': '1.2.3.4',
590+
'asset_uid': asset.uid,
591+
'source': 'source',
592+
'latest_version_uid': 'av12345',
593+
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
594+
},
595+
)
596+
597+
def test_create_from_import_task_with_name_change(self):
598+
asset = Asset.objects.get(pk=1)
599+
old_name = asset.name
600+
task = ImportTask.objects.create(
601+
user=User.objects.get(username='someuser'), data={}
602+
)
603+
task.messages = {
604+
'audit_logs': [
605+
{
606+
'asset_uid': asset.uid,
607+
'latest_version_uid': 'av12345',
608+
'ip_address': '1.2.3.4',
609+
'source': 'source',
610+
'asset_id': asset.id,
611+
'old_name': old_name,
612+
'new_name': 'new_name',
613+
}
614+
]
615+
}
616+
ProjectHistoryLog.create_from_import_task(task)
617+
self.assertEqual(ProjectHistoryLog.objects.count(), 2)
618+
log = ProjectHistoryLog.objects.filter(action=AuditAction.REPLACE_FORM).first()
619+
self.assertEqual(log.object_id, asset.id)
620+
621+
self.assertDictEqual(
622+
log.metadata,
623+
{
624+
'ip_address': '1.2.3.4',
625+
'asset_uid': asset.uid,
626+
'source': 'source',
627+
'latest_version_uid': 'av12345',
628+
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
629+
},
630+
)
631+
name_log = ProjectHistoryLog.objects.filter(
632+
action=AuditAction.UPDATE_NAME
633+
).first()
634+
self.assertEqual(log.object_id, asset.id)
635+
636+
self.assertDictEqual(
637+
name_log.metadata,
638+
{
639+
'ip_address': '1.2.3.4',
640+
'asset_uid': asset.uid,
641+
'source': 'source',
642+
'latest_version_uid': 'av12345',
643+
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
644+
'name': {'old': old_name, 'new': 'new_name'},
645+
},
646+
)

0 commit comments

Comments
 (0)