Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: refactoring in dashboard user endpoint #329

Merged
merged 1 commit into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions chats/apps/api/v1/dashboard/dto.py
Original file line number Diff line number Diff line change
@@ -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
80 changes: 80 additions & 0 deletions chats/apps/api/v1/dashboard/repository.py
Original file line number Diff line number Diff line change
@@ -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
61 changes: 7 additions & 54 deletions chats/apps/api/v1/dashboard/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions chats/apps/api/v1/dashboard/service.py
Original file line number Diff line number Diff line change
@@ -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)
52 changes: 37 additions & 15 deletions chats/apps/api/v1/dashboard/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@

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,
)
from chats.apps.api.v1.permissions import HasDashboardAccess
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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
45 changes: 45 additions & 0 deletions chats/apps/dashboard/tests/test_repository.py
Original file line number Diff line number Diff line change
@@ -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, "[email protected]")
self.assertEqual(agents_fields[2].agent_status, "OFFLINE")
self.assertEqual(agents_fields[2].closed_rooms, 0)
self.assertEqual(agents_fields[2].opened_rooms, 1)
41 changes: 17 additions & 24 deletions chats/apps/dashboard/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"], "[email protected]")
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"] = "[email protected]"
serializer_data["first_name"] = {}
serializer = DashboardAgentsSerializer(data=serializer_data)
self.assertFalse(serializer.is_valid())
Loading