diff --git a/chats/apps/api/v1/dashboard/dto.py b/chats/apps/api/v1/dashboard/dto.py new file mode 100644 index 00000000..e1eaf96a --- /dev/null +++ b/chats/apps/api/v1/dashboard/dto.py @@ -0,0 +1,21 @@ +from dataclasses import dataclass + + +@dataclass +class Agent: + first_name: str = None + email: str = None + agent_status: str = None + closed_rooms: int = None + opened_rooms: int = None + + +@dataclass +class Filters: + start_date: str = None + end_date: str = None + agent: str = None + sector: str = None + tag: str = None + is_weni_admin: bool = None + user_request: str = None diff --git a/chats/apps/api/v1/dashboard/repository.py b/chats/apps/api/v1/dashboard/repository.py new file mode 100644 index 00000000..c599522f --- /dev/null +++ b/chats/apps/api/v1/dashboard/repository.py @@ -0,0 +1,80 @@ +from typing import List + +from django.db.models import Count, OuterRef, Q, Subquery +from django.utils import timezone +from pendulum.parser import parse as pendulum_parse + +from chats.apps.accounts.models import User +from chats.apps.projects.models import ProjectPermission + +from .dto import Agent, Filters + + +class AgentRepository: + def __init__(self): + self.model = User.objects + + def get_agents_data(self, filters: Filters, project) -> List[Agent]: + tz = project.timezone + initial_datetime = ( + timezone.now() + .astimezone(tz) + .replace(hour=0, minute=0, second=0, microsecond=0) + ) + rooms_filter = {} + closed_rooms = {"rooms__queue__sector__project": project} + opened_rooms = {"rooms__queue__sector__project": project} + if filters.start_date and filters.end_date: + start_time = pendulum_parse(filters.start_date, tzinfo=tz) + end_time = pendulum_parse(filters.end_date + " 23:59:59", tzinfo=tz) + + rooms_filter["rooms__created_on__range"] = [start_time, end_time] + rooms_filter["rooms__is_active"] = False + closed_rooms["rooms__ended_at__range"] = [start_time, end_time] + + else: + closed_rooms["rooms__ended_at__gte"] = initial_datetime + opened_rooms["rooms__is_active"] = True + closed_rooms["rooms__is_active"] = False + + if filters.agent: + rooms_filter["rooms__user"] = filters.agent + + if filters.sector: + rooms_filter["rooms__queue__sector"] = filters.sector + if filters.tag: + rooms_filter["rooms__tags__uuid"] = filters.tag + + project_permission_subquery = ProjectPermission.objects.filter( + project_id=project, + user_id=OuterRef("email"), + ).values("status")[:1] + + agents_query = self.model + if not filters.is_weni_admin: + agents_query = agents_query.exclude(email__endswith="weni.ai") + + agents_query = ( + agents_query.filter(project_permissions__project=project, is_active=True) + .annotate( + agent_status=Subquery(project_permission_subquery), + closed_rooms=Count("rooms", filter=Q(**closed_rooms, **rooms_filter)), + opened_rooms=Count("rooms", filter=Q(**opened_rooms, **rooms_filter)), + ) + .values( + "first_name", "email", "agent_status", "closed_rooms", "opened_rooms" + ) + ) + + user_agents = [ + Agent( + first_name=user_agent["first_name"], + email=user_agent["email"], + agent_status=user_agent["agent_status"], + closed_rooms=user_agent["closed_rooms"], + opened_rooms=user_agent["opened_rooms"], + ) + for user_agent in agents_query + ] + + return user_agents diff --git a/chats/apps/api/v1/dashboard/serializers.py b/chats/apps/api/v1/dashboard/serializers.py index 8e44519a..2f9eab8c 100644 --- a/chats/apps/api/v1/dashboard/serializers.py +++ b/chats/apps/api/v1/dashboard/serializers.py @@ -3,12 +3,11 @@ import pendulum from django.conf import settings -from django.db.models import Avg, Count, F, OuterRef, Q, Subquery, Sum +from django.db.models import Avg, F, Sum from django.utils import timezone from django_redis import get_redis_connection from rest_framework import serializers -from chats.apps.accounts.models import User from chats.apps.dashboard.models import RoomMetrics from chats.apps.projects.models import ProjectPermission from chats.apps.rooms.models import Room @@ -107,58 +106,12 @@ def dashboard_general_data(context: dict, project): # Maybe separate each serializer in it's own serializer module/file -def dashboard_agents_data(context, project): - tz = project.timezone - initial_datetime = ( - timezone.now().astimezone(tz).replace(hour=0, minute=0, second=0, microsecond=0) - ) - - rooms_filter = {} - closed_rooms = {"rooms__queue__sector__project": project} - opened_rooms = {"rooms__queue__sector__project": project} - if context.get("start_date") and context.get("end_date"): - start_time = pendulum.parse(context.get("start_date")).replace(tzinfo=tz) - end_time = pendulum.parse(context.get("end_date") + " 23:59:59").replace( - tzinfo=tz - ) - - rooms_filter["rooms__created_on__range"] = [start_time, end_time] - rooms_filter["rooms__is_active"] = False - closed_rooms["rooms__ended_at__range"] = [start_time, end_time] - - else: - closed_rooms["rooms__ended_at__gte"] = initial_datetime - opened_rooms["rooms__is_active"] = True - closed_rooms["rooms__is_active"] = False - - if context.get("agent"): - rooms_filter["rooms__user"] = context.get("agent") - - if context.get("sector"): - rooms_filter["rooms__queue__sector"] = context.get("sector") - if context.get("tag"): - rooms_filter["rooms__tags__uuid"] = context.get("tag") - - project_permission_subquery = ProjectPermission.objects.filter( - project_id=project, - user_id=OuterRef("email"), - ).values("status")[:1] - - agents_query = User.objects - if not context.get("is_weni_admin"): - agents_query = agents_query.exclude(email__endswith="weni.ai") - - agents_query = ( - agents_query.filter(project_permissions__project=project, is_active=True) - .annotate( - agent_status=Subquery(project_permission_subquery), - closed_rooms=Count("rooms", filter=Q(**closed_rooms, **rooms_filter)), - opened_rooms=Count("rooms", filter=Q(**opened_rooms, **rooms_filter)), - ) - .values("first_name", "email", "agent_status", "closed_rooms", "opened_rooms") - ) - - return agents_query +class DashboardAgentsSerializer(serializers.Serializer): + first_name = serializers.CharField(allow_null=True, required=False) + email = serializers.EmailField(allow_null=True, required=False) + agent_status = serializers.CharField(allow_null=True, required=False) + closed_rooms = serializers.IntegerField(allow_null=True, required=False) + opened_rooms = serializers.IntegerField(allow_null=True, required=False) # Maybe separate each serializer in it's own serializer module/file diff --git a/chats/apps/api/v1/dashboard/service.py b/chats/apps/api/v1/dashboard/service.py new file mode 100644 index 00000000..334df83b --- /dev/null +++ b/chats/apps/api/v1/dashboard/service.py @@ -0,0 +1,10 @@ +from typing import List + +from .dto import Agent, Filters +from .repository import AgentRepository + + +class AgentsService: + def get_agents_data(self, filters: Filters, project) -> List[Agent]: + agents_repository = AgentRepository() + return agents_repository.get_agents_data(filters, project) diff --git a/chats/apps/api/v1/dashboard/viewsets.py b/chats/apps/api/v1/dashboard/viewsets.py index 3f4af1a3..45c6a38a 100644 --- a/chats/apps/api/v1/dashboard/viewsets.py +++ b/chats/apps/api/v1/dashboard/viewsets.py @@ -9,8 +9,8 @@ from chats.apps.api.v1.dashboard.presenter import get_export_data from chats.apps.api.v1.dashboard.serializers import ( + DashboardAgentsSerializer, DashboardRawDataSerializer, - dashboard_agents_data, dashboard_division_data, dashboard_general_data, ) @@ -18,6 +18,9 @@ from chats.apps.projects.models import Project from chats.core.excel_storage import ExcelStorage +from .dto import Filters +from .service import AgentsService + class DashboardLiveViewset(viewsets.GenericViewSet): lookup_field = "uuid" @@ -51,15 +54,24 @@ def general(self, request, *args, **kwargs): def agent(self, request, *args, **kwargs): """Agent metrics for the project or the sector""" project = self.get_object() - context = request.query_params.dict() - context["is_weni_admin"] = ( - True if request.user and "weni.ai" in request.user.email else False - ) - serialized_data = dashboard_agents_data( - project=project, - context=context, + params = request.query_params.dict() + filters = Filters( + start_date=params.get("start_date"), + end_date=params.get("end_date"), + agent=params.get("agent"), + sector=params.get("sector"), + tag=params.get("tag"), + user_request=request.user, + is_weni_admin=True + if request.user and "weni.ai" in request.user.email + else False, ) - return Response({"project_agents": serialized_data}, status.HTTP_200_OK) + + agents_service = AgentsService() + agents_data = agents_service.get_agents_data(filters, project) + agents = DashboardAgentsSerializer(agents_data, many=True) + + return Response({"project_agents": agents.data}, status.HTTP_200_OK) @action( detail=True, @@ -169,24 +181,34 @@ def export_dashboard(self, request, *args, **kwargs): """ project = self.get_object() filter = request.query_params - - user_info_context = {} - user_info_context["filters"] = request.query_params - # General data general_dataset = dashboard_general_data(context=filter, project=project) raw_dataset = DashboardRawDataSerializer(instance=project, context=filter) combined_dataset = {**general_dataset, **raw_dataset.data} # Agents Data - userinfo_dataset = dashboard_agents_data(context=filter, project=project) + agents_service = AgentsService() + filters = Filters( + start_date=filter.get("start_date"), + end_date=filter.get("end_date"), + agent=filter.get("agent"), + sector=filter.get("sector"), + tag=filter.get("tag"), + user_request=request.user, + is_weni_admin=True + if request.user and "weni.ai" in request.user.email + else False, + ) + agents_data = agents_service.get_agents_data(filters, project) + agents = DashboardAgentsSerializer(agents_data, many=True) + # # Sectors Data sector_dataset = dashboard_division_data(context=filter, project=project) filename = "dashboard_export_data" data_frame = pandas.DataFrame([combined_dataset]) - data_frame_1 = pandas.DataFrame(userinfo_dataset) + data_frame_1 = pandas.DataFrame(agents.data) data_frame_2 = pandas.DataFrame(sector_dataset) if "xls" in filter: diff --git a/chats/apps/dashboard/tests/test_repository.py b/chats/apps/dashboard/tests/test_repository.py new file mode 100644 index 00000000..861840b6 --- /dev/null +++ b/chats/apps/dashboard/tests/test_repository.py @@ -0,0 +1,45 @@ +from django.test import TestCase + +from chats.apps.accounts.models import User +from chats.apps.api.v1.dashboard.dto import Filters +from chats.apps.api.v1.dashboard.repository import AgentRepository +from chats.apps.projects.models import Project + + +class RepositoryTests(TestCase): + fixtures = ["chats/fixtures/fixture_sector.json"] + + def setUp(self): + self.project = Project.objects.get(pk="34a93b52-231e-11ed-861d-0242ac120002") + self.user = User.objects.get(pk="8") + + def test_returned_fields_from_get_agents_data(self): + project = Project.objects.get(uuid="34a93b52-231e-11ed-861d-0242ac120002") + + instance = AgentRepository() + filter = Filters(is_weni_admin=True) + agents_fields = instance.get_agents_data( + filters=filter, + project=project, + ) + for fields in agents_fields: + self.assertTrue(hasattr(fields, "first_name")) + self.assertTrue(hasattr(fields, "email")) + self.assertTrue(hasattr(fields, "agent_status")) + self.assertTrue(hasattr(fields, "closed_rooms")) + self.assertTrue(hasattr(fields, "opened_rooms")) + + def test_field_value_from_dashboard_agent_serializer(self): + project = Project.objects.get(uuid="34a93b52-231e-11ed-861d-0242ac120002") + instance = AgentRepository() + filter = Filters(is_weni_admin=True) + agents_fields = instance.get_agents_data( + filters=filter, + project=project, + ) + + self.assertEqual(agents_fields[2].first_name, "") + self.assertEqual(agents_fields[2].email, "amywong@chats.weni.ai") + self.assertEqual(agents_fields[2].agent_status, "OFFLINE") + self.assertEqual(agents_fields[2].closed_rooms, 0) + self.assertEqual(agents_fields[2].opened_rooms, 1) diff --git a/chats/apps/dashboard/tests/test_serializers.py b/chats/apps/dashboard/tests/test_serializers.py index 4646af4a..39c8881f 100644 --- a/chats/apps/dashboard/tests/test_serializers.py +++ b/chats/apps/dashboard/tests/test_serializers.py @@ -1,8 +1,10 @@ +from typing import Any, Dict + from django.test import TestCase from chats.apps.accounts.models import User from chats.apps.api.v1.dashboard.serializers import ( - dashboard_agents_data, + DashboardAgentsSerializer, dashboard_general_data, ) from chats.apps.projects.models import Project @@ -43,26 +45,17 @@ def test_active_chats_function_without_filter(self): self.assertEqual(serializer["active_chats"], 1) def test_returned_fields_from_dashboard_agent_serializer(self): - project = Project.objects.get(uuid="34a93b52-231e-11ed-861d-0242ac120002") - instance = dashboard_agents_data( - project=project, - context={"is_weni_admin": True}, - ) - self.assertEqual(list(instance[0].keys())[0], "first_name") - self.assertEqual(list(instance[0].keys())[1], "email") - self.assertEqual(list(instance[0].keys())[2], "agent_status") - self.assertEqual(list(instance[0].keys())[3], "closed_rooms") - self.assertEqual(list(instance[0].keys())[4], "opened_rooms") - - def test_field_value_from_dashboard_agent_serializer(self): - project = Project.objects.get(uuid="34a93b52-231e-11ed-861d-0242ac120002") - instance = dashboard_agents_data( - project=project, - context={"is_weni_admin": True}, - ) - - self.assertEqual(instance[2]["first_name"], "") - self.assertEqual(instance[2]["email"], "amywong@chats.weni.ai") - self.assertEqual(instance[2]["agent_status"], "OFFLINE") - self.assertEqual(instance[2]["closed_rooms"], 0) - self.assertEqual(instance[2]["opened_rooms"], 1) + serializer_data: Dict[str, Any] = { + "first_name": "John", + "email": "invalid_email", + "agent_status": "ACTIVE", + "closed_rooms": 3, + "opened_rooms": 5, + } + serializer = DashboardAgentsSerializer(data=serializer_data) + self.assertFalse(serializer.is_valid()) + + serializer_data["email"] = "john@example.com" + serializer_data["first_name"] = {} + serializer = DashboardAgentsSerializer(data=serializer_data) + self.assertFalse(serializer.is_valid())