Skip to content

Commit

Permalink
Create endpoint for retrieving move case events
Browse files Browse the repository at this point in the history
  • Loading branch information
wkeeling committed Sep 21, 2021
1 parent 0466935 commit 839be76
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 19 deletions.
46 changes: 46 additions & 0 deletions api/data_workspace/audit_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.contrib.contenttypes.models import ContentType
from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination


from api.audit_trail.enums import AuditType
from api.audit_trail.models import Audit
from api.cases.models import Case
from api.core.authentication import DataWorkspaceOnlyAuthentication
from api.data_workspace.serializers import AuditMoveCaseSerializer


class AuditMoveCaseListView(viewsets.ReadOnlyModelViewSet):
"""Expose 'move case' audit events to data workspace."""

authentication_classes = (DataWorkspaceOnlyAuthentication,)
serializer_class = AuditMoveCaseSerializer
pagination_class = LimitOffsetPagination

def get_queryset(self):
content_type = ContentType.objects.get_for_model(Case)

# This returns a queryset of audit records for the "move case" audit event.
# For each record, it exposes the nested "queues" JSON property as a top
# level column called "queue" and splits into multiple rows when "queues"
# contains multiple entries.
# It also deals with the fact that the value of "queues" is sometimes an
# array of queue names but sometimes a single string.
return Audit.objects.raw(
"""
with audit_move_case as (
select *,
case when jsonb_typeof(payload->'queues') = 'array'
then payload->'queues'
else jsonb_build_array(payload->'queues')
end as queues
from audit_trail_audit
where verb = %(verb)s
and (action_object_content_type_id = %(action_type)s
or target_content_type_id = %(target_type)s)
order by created_at
)
select *, value->>0 as "queue" from audit_move_case cross join jsonb_array_elements(queues)
""",
{"verb": AuditType.MOVE_CASE, "action_type": content_type.pk, "target_type": content_type.pk},
)
30 changes: 29 additions & 1 deletion api/data_workspace/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from api.cases.models import EcjuQuery, CaseAssignment
from api.audit_trail.models import Audit
from api.cases.models import CaseAssignment, EcjuQuery
from api.queues.models import Queue

from rest_framework import serializers
import api.cases.serializers as cases_serializers
Expand All @@ -15,3 +17,29 @@ class CaseAssignmentSerializer(cases_serializers.CaseAssignmentSerializer):
class Meta:
model = CaseAssignment
fields = "__all__"


class AuditMoveCaseSerializer(serializers.ModelSerializer):
"""Serializer for serializing 'move case' audit events."""

user = serializers.SerializerMethodField()
case = serializers.SerializerMethodField()
queue = serializers.SerializerMethodField()

class Meta:
model = Audit
fields = ("created_at", "user", "case", "queue")

def get_user(self, instance):
if instance.actor:
return instance.actor.pk
return None

def get_case(self, instance):
return instance.action_object_object_id or instance.target_object_id or None

def get_queue(self, instance):
queue = Queue.objects.filter(name=instance.queue).first()
if queue:
return queue.pk
return None
57 changes: 57 additions & 0 deletions api/data_workspace/tests/test_audit_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from django.urls import reverse
from functools import partial
from rest_framework import status

from api.audit_trail.enums import AuditType
from api.staticdata.statuses.enums import CaseStatusEnum
from api.staticdata.statuses.libraries.get_case_status import get_case_status_by_status
from test_helpers.clients import DataTestClient


class DataWorkspaceAuditMoveCaseTests(DataTestClient):
def setUp(self):
super().setUp()
self.url = reverse("data_workspace:dw-audit-move-case-list")
case = self.create_standard_application_case(self.organisation, "Test Application")
# Audit events are only created for non-draft cases
case.status = get_case_status_by_status(CaseStatusEnum.OPEN)
case.save()
self.create_audit = partial(
super().create_audit, verb=AuditType.MOVE_CASE, actor=self.gov_user, target=case.get_case()
)

self.create_audit(payload={"queues": "Initial Queue"})
self.create_audit(verb=AuditType.CREATED_USER_ADVICE)

def test_audit_move_case(self):
expected_fields = ("created_at", "user", "case", "queue")

response = self.client.get(self.url)

self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.json()["results"]
self.assertEqual(len(results), 1)
self.assertEqual(tuple(results[0].keys()), expected_fields)
self.assertEqual(results[0]["queue"], str(self.queue.pk))

response = self.client.options(self.url)

self.assertEqual(response.status_code, status.HTTP_200_OK)
options = response.json()["actions"]["OPTIONS"]
self.assertEqual(tuple(options.keys()), expected_fields)

def test_payload_multiple_queues(self):
queue1 = self.create_queue("MOD - DSR Cases to Review", self.team)
queue2 = self.create_queue("MOD - WECA Cases to Review", self.team)
# Create a single audit record with multiple queues in the payload
self.create_audit(payload={"queues": ["MOD - DSR Cases to Review", "MOD - WECA Cases to Review"]})

response = self.client.get(self.url)

self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.json()["results"]
self.assertEqual(len(results), 3)
self.assertEqual(results[0]["queue"], str(self.queue.pk))
# The record with multiple queues should have been split into separate entries.
self.assertEqual(results[1]["queue"], str(queue1.pk))
self.assertEqual(results[2]["queue"], str(queue2.pk))
12 changes: 11 additions & 1 deletion api/data_workspace/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from api.data_workspace.serializers import EcjuQuerySerializer, CaseAssignmentSerializer
from api.audit_trail.models import AuditType
from api.audit_trail.tests.factories import AuditFactory
from api.data_workspace.serializers import AuditMoveCaseSerializer, CaseAssignmentSerializer, EcjuQuerySerializer
from api.cases.tests.factories import EcjuQueryFactory, CaseAssignmentFactory


Expand All @@ -15,3 +17,11 @@ def test_CaseAssignmentSerializer(db):
serialized = CaseAssignmentSerializer(case_assignment)
expected_fields = {"case", "user", "id", "queue", "created_at", "updated_at"}
assert set(serialized.data.keys()) == expected_fields


def test_AuditMoveCaseSerializer(db):
audit = AuditFactory(verb=AuditType.MOVE_CASE, payload={"queues": ["test_queue_1", "test_queue_2"]})
audit.queue = "test_queue_1"
serialized = AuditMoveCaseSerializer(audit)
expected_fields = {"created_at", "user", "case", "queue"}
assert set(serialized.data.keys()) == expected_fields
7 changes: 3 additions & 4 deletions api/data_workspace/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from api.data_workspace import (
application_views,
audit_views,
case_views,
good_views,
license_views,
Expand Down Expand Up @@ -63,8 +64,6 @@
)
router_v1.register("users-base-users", users_views.BaseUserListView, basename="dw-users-base-users")
router_v1.register("users-gov-users", users_views.GovUserListView, basename="dw-users-gov-users")
router_v1.register("audit-move-case", audit_views.AuditMoveCaseListView, basename="dw-audit-move-case")

urlpatterns = [
path("v0/", include(router_v0.urls)),
path("v1/", include(router_v1.urls)),
]
urlpatterns = [path("v0/", include(router_v0.urls)), path("v1/", include(router_v1.urls))]
48 changes: 35 additions & 13 deletions test_helpers/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
)
from api.audit_trail import service as audit_trail_service
from api.audit_trail.enums import AuditType
from api.audit_trail.models import Audit
from api.cases.enums import AdviceType, CaseDocumentState, CaseTypeEnum, CaseTypeSubTypeEnum
from api.cases.generated_documents.models import GeneratedCaseDocument
from api.cases.models import (
Expand Down Expand Up @@ -208,7 +209,7 @@ def create_exporter_user(self, organisation=None, first_name=None, last_name=Non
if not first_name and not last_name:
first_name = self.faker.first_name()
last_name = self.faker.last_name()
base_user = BaseUser(first_name=first_name, last_name=last_name, email=self.faker.email(),)
base_user = BaseUser(first_name=first_name, last_name=last_name, email=self.faker.email())
base_user.save()
exporter_user = ExporterUser(baseuser_ptr=base_user)
exporter_user.organisation = organisation
Expand All @@ -232,7 +233,7 @@ def add_exporter_user_to_org(organisation, exporter_user, role=None):
@staticmethod
def create_external_location(name, org, country="GB"):
external_location = ExternalLocation(
name=name, address="20 Questions Road, Enigma", country=get_country(country), organisation=org,
name=name, address="20 Questions Road, Enigma", country=get_country(country), organisation=org
)
external_location.save()
return external_location
Expand Down Expand Up @@ -269,10 +270,8 @@ def create_party(
return party

@staticmethod
def create_case_note(
case: Case, text: str, user: BaseUser, is_visible_to_exporter: bool = False,
):
case_note = CaseNote(case=case, text=text, user=user, is_visible_to_exporter=is_visible_to_exporter,)
def create_case_note(case: Case, text: str, user: BaseUser, is_visible_to_exporter: bool = False):
case_note = CaseNote(case=case, text=text, user=user, is_visible_to_exporter=is_visible_to_exporter)
case_note.save()
return case_note

Expand Down Expand Up @@ -376,15 +375,15 @@ def create_good_document(
@staticmethod
def create_document_for_party(party: Party, name="document_name.pdf", safe=True):
document = PartyDocument(
party=party, name=name, s3_key="s3_keykey.pdf", size=123456, virus_scanned_at=None, safe=safe,
party=party, name=name, s3_key="s3_keykey.pdf", size=123456, virus_scanned_at=None, safe=safe
)
document.save()
return document

@staticmethod
def create_document_for_goods_type(goods_type: GoodsType, name="document_name.pdf", safe=True):
document = GoodsTypeDocument(
goods_type=goods_type, name=name, s3_key="s3_keykey.pdf", size=123456, virus_scanned_at=None, safe=safe,
goods_type=goods_type, name=name, s3_key="s3_keykey.pdf", size=123456, virus_scanned_at=None, safe=safe
)
document.save()
return document
Expand Down Expand Up @@ -618,7 +617,7 @@ def create_advice(
@staticmethod
def create_good_on_application(application, good):
return GoodOnApplication.objects.create(
good=good, application=application, quantity=10, unit=Units.NAR, value=500,
good=good, application=application, quantity=10, unit=Units.NAR, value=500
)

@staticmethod
Expand Down Expand Up @@ -799,7 +798,7 @@ def create_mod_clearance_application_case(self, organisation, case_type):

def create_incorporated_good_and_ultimate_end_user_on_application(self, organisation, application):
good = Good.objects.create(
is_good_controlled=True, organisation=self.organisation, description="a good", part_number="123456",
is_good_controlled=True, organisation=self.organisation, description="a good", part_number="123456"
)

GoodOnApplication.objects.create(
Expand All @@ -814,13 +813,13 @@ def create_incorporated_good_and_ultimate_end_user_on_application(self, organisa
return application

def create_standard_application_with_incorporated_good(
self, organisation: Organisation, reference_name="Standard Draft", safe_document=True,
self, organisation: Organisation, reference_name="Standard Draft", safe_document=True
):

application = self.create_draft_standard_application(organisation, reference_name, safe_document)

GoodOnApplication(
good=GoodFactory(is_good_controlled=True, organisation=self.organisation,),
good=GoodFactory(is_good_controlled=True, organisation=self.organisation),
application=application,
quantity=17,
value=18,
Expand Down Expand Up @@ -878,7 +877,7 @@ def create_open_application_case(self, organisation: Organisation, reference_nam
return self.submit_application(draft, self.exporter_user)

def create_hmrc_query(
self, organisation: Organisation, reference_name="HMRC Query", safe_document=True, have_goods_departed=False,
self, organisation: Organisation, reference_name="HMRC Query", safe_document=True, have_goods_departed=False
):
application = HmrcQuery(
name=reference_name,
Expand Down Expand Up @@ -1063,6 +1062,29 @@ def create_routing_rule(self, team_id, queue_id, tier, status_id, additional_rul
rule.save()
return rule

def create_audit(
self,
actor,
verb,
action_object=None,
target=None,
payload=None,
ignore_case_status=False,
send_notification=True,
):
if not payload:
payload = {}

return Audit.objects.create(
actor=actor,
verb=verb.value,
action_object=action_object,
target=target,
payload=payload,
ignore_case_status=ignore_case_status,
send_notification=send_notification,
)


@pytest.mark.performance
# we need to set debug to true otherwise we can't see the amount of queries
Expand Down

0 comments on commit 839be76

Please sign in to comment.