Skip to content

Commit

Permalink
feat(projectHistoryLogs): record project history logs for imports TAS…
Browse files Browse the repository at this point in the history
…K-944 (#5230)
  • Loading branch information
rgraber authored Nov 12, 2024
1 parent 5d9ca2d commit 7b744e3
Show file tree
Hide file tree
Showing 11 changed files with 387 additions and 37 deletions.
1 change: 1 addition & 0 deletions kobo/apps/audit_log/audit_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class AuditAction(models.TextChoices):
REDEPLOY = 'redeploy'
REGISTER_SERVICE = 'register-service'
REMOVE = 'remove'
REPLACE_FORM = 'replace-form'
UNARCHIVE = 'unarchive'
UPDATE = 'update'
UPDATE_CONTENT = 'update-content'
Expand Down
39 changes: 38 additions & 1 deletion kobo/apps/audit_log/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
)
from kpi.fields.kpi_uid import UUID_LENGTH
from kpi.models import Asset
from kpi.models import Asset, ImportTask
from kpi.utils.log import logging

NEW = 'new'
Expand Down Expand Up @@ -561,3 +561,40 @@ def create_from_related_request(
ProjectHistoryLog.objects.create(
user=request.user, object_id=object_id, action=action, metadata=metadata
)

@classmethod
def create_from_import_task(cls, task: ImportTask):
# this will probably only ever be a list of size 1 or 0,
# sent as a list because of how ImportTask is implemented
# if somehow a task updates multiple assets, this should handle it
audit_log_blocks = task.messages.get('audit_logs', [])
for audit_log_info in audit_log_blocks:
metadata = {
'asset_uid': audit_log_info['asset_uid'],
'latest_version_uid': audit_log_info['latest_version_uid'],
'ip_address': audit_log_info['ip_address'],
'source': audit_log_info['source'],
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
}
ProjectHistoryLog.objects.create(
user=task.user,
object_id=audit_log_info['asset_id'],
action=AuditAction.REPLACE_FORM,
metadata=metadata,
)
# imports may change the name of an asset, log that too
if audit_log_info['old_name'] != audit_log_info['new_name']:
metadata.update(
{
'name': {
OLD: audit_log_info['old_name'],
NEW: audit_log_info['new_name'],
}
}
)
ProjectHistoryLog.objects.create(
user=task.user,
object_id=audit_log_info['asset_id'],
action=AuditAction.UPDATE_NAME,
metadata=metadata,
)
11 changes: 10 additions & 1 deletion kobo/apps/audit_log/signals.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from celery.signals import task_success
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver

from kpi.models import ImportTask
from kpi.tasks import import_in_background
from kpi.utils.log import logging
from .models import AccessLog
from .models import AccessLog, ProjectHistoryLog


@receiver(user_logged_in)
Expand All @@ -14,3 +17,9 @@ def create_access_log(sender, user, **kwargs):
AccessLog.create_from_request(request, user)
else:
AccessLog.create_from_request(request)


@receiver(task_success, sender=import_in_background)
def create_ph_log_for_import(sender, result, **kwargs):
task = ImportTask.objects.get(uid=result)
ProjectHistoryLog.create_from_import_task(task)
90 changes: 89 additions & 1 deletion kobo/apps/audit_log/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
from kpi.constants import (
ACCESS_LOG_SUBMISSION_AUTH_TYPE,
ACCESS_LOG_SUBMISSION_GROUP_AUTH_TYPE,
PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
)
from kpi.models import Asset
from kpi.models import Asset, ImportTask
from kpi.tests.base_test_case import BaseTestCase


Expand Down Expand Up @@ -556,3 +557,90 @@ def test_create_from_related_request_no_log_created_if_no_data(self):
modify_action=AuditAction.UPDATE,
)
self.assertEqual(ProjectHistoryLog.objects.count(), 0)

def test_create_from_import_task_no_name_change(self):
asset = Asset.objects.get(pk=1)
task = ImportTask.objects.create(
user=User.objects.get(username='someuser'), data={}
)
task.messages = {
'audit_logs': [
{
'asset_uid': asset.uid,
'latest_version_uid': 'av12345',
'ip_address': '1.2.3.4',
'source': 'source',
'asset_id': asset.id,
'old_name': asset.name,
'new_name': asset.name,
}
]
}
ProjectHistoryLog.create_from_import_task(task)
self.assertEqual(ProjectHistoryLog.objects.count(), 1)
log = ProjectHistoryLog.objects.first()
self.assertEqual(log.action, AuditAction.REPLACE_FORM)
self.assertEqual(log.object_id, asset.id)

# data from 'messages' should be copied to the log
self.assertDictEqual(
log.metadata,
{
'ip_address': '1.2.3.4',
'asset_uid': asset.uid,
'source': 'source',
'latest_version_uid': 'av12345',
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
},
)

def test_create_from_import_task_with_name_change(self):
asset = Asset.objects.get(pk=1)
old_name = asset.name
task = ImportTask.objects.create(
user=User.objects.get(username='someuser'), data={}
)
task.messages = {
'audit_logs': [
{
'asset_uid': asset.uid,
'latest_version_uid': 'av12345',
'ip_address': '1.2.3.4',
'source': 'source',
'asset_id': asset.id,
'old_name': old_name,
'new_name': 'new_name',
}
]
}
ProjectHistoryLog.create_from_import_task(task)
self.assertEqual(ProjectHistoryLog.objects.count(), 2)
log = ProjectHistoryLog.objects.filter(action=AuditAction.REPLACE_FORM).first()
self.assertEqual(log.object_id, asset.id)

self.assertDictEqual(
log.metadata,
{
'ip_address': '1.2.3.4',
'asset_uid': asset.uid,
'source': 'source',
'latest_version_uid': 'av12345',
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
},
)
name_log = ProjectHistoryLog.objects.filter(
action=AuditAction.UPDATE_NAME
).first()
self.assertEqual(log.object_id, asset.id)

self.assertDictEqual(
name_log.metadata,
{
'ip_address': '1.2.3.4',
'asset_uid': asset.uid,
'source': 'source',
'latest_version_uid': 'av12345',
'log_subtype': PROJECT_HISTORY_LOG_PROJECT_SUBTYPE,
'name': {'old': old_name, 'new': 'new_name'},
},
)
Loading

0 comments on commit 7b744e3

Please sign in to comment.