From 8a4f2b95d0283871be0c6adb90011a26d488ffef Mon Sep 17 00:00:00 2001 From: AlanJaeger Date: Wed, 29 May 2024 16:37:46 -0300 Subject: [PATCH] feature: creating dashboards, widgets and reports when a project its created --- .../dashboards/usecases/dashboard_creation.py | 253 ++++++++++++++++++ insights/dashboards/usecases/exceptions.py | 10 + insights/dashboards/utils.py | 0 insights/projects/usecases/create.py | 8 +- .../0002_remove_widget_report_report.py | 66 +++++ insights/widgets/models.py | 29 +- 6 files changed, 358 insertions(+), 8 deletions(-) create mode 100644 insights/dashboards/usecases/dashboard_creation.py create mode 100644 insights/dashboards/usecases/exceptions.py create mode 100644 insights/dashboards/utils.py create mode 100644 insights/widgets/migrations/0002_remove_widget_report_report.py diff --git a/insights/dashboards/usecases/dashboard_creation.py b/insights/dashboards/usecases/dashboard_creation.py new file mode 100644 index 0000000..660cb2b --- /dev/null +++ b/insights/dashboards/usecases/dashboard_creation.py @@ -0,0 +1,253 @@ +from insights.dashboards.models import Dashboard +from insights.widgets.models import Widget, Report +from django.db import transaction +from insights.dashboards.usecases.exceptions import ( + InvalidDashboardObject, + InvalidWidgetsObject, + InvalidReportsObject, +) + + +class create_atendimento_humano: + def create_dashboard(self, project): + try: + with transaction.atomic(): + atendimento_humano = Dashboard.objects.create( + project=project, + name="Atendimento Humano", + description="Dashboard de atendimento humano", + is_default=False, + ) + self.create_widgets(atendimento_humano) + + except Exception as exception: + raise InvalidDashboardObject(f"Error creating dashboard: {exception}") + + def create_widgets(self, dashboard_atendimento_humano): + try: + with transaction.atomic(): + pico_de_atendimento = Widget.objects.create( + name="Picos de atendimentos abertos", + w_type="graph_column", + source="chats", + config={ + "end_time": "18:00", + "interval": "60", + "start_time": "07:00", + }, + dashboard=dashboard_atendimento_humano, + position={"rows": [1, 1], "columns": [1, 12]}, + ) + em_andamento = Widget.objects.create( + name="Em andamento", + w_type="card", + source="chats", + config={"operation": "count", "type_result": "executions"}, + dash=dashboard_atendimento_humano, + position={"rows": [2, 2], "columns": [1, 4]}, + ) + Widget.objects.create( + name="Tempo de espera", + w_type="card", + source="chats", + config={"operation": "AVG", "type_result": "executions"}, + dash=dashboard_atendimento_humano, + position={"rows": [2, 2], "columns": [5, 8]}, + ) + encerrados = Widget.objects.create( + name="Encerrados", + w_type="card", + source="chats", + config={"operation": "AVG", "type_result": "executions"}, + dash=dashboard_atendimento_humano, + position={"rows": [2, 2], "columns": [9, 12]}, + ) + Widget.objects.create( + name="Tempo de resposta", + w_type="card", + source="chats", + config={"operation": "count", "type_result": "executions"}, + dash=dashboard_atendimento_humano, + position={"rows": [3, 3], "columns": [1, 4]}, + ) + aguardando_atendimento = Widget.objects.create( + name="Aguardando atendimento", + w_type="card", + source="chats", + config={"operation": "count", "type_result": "executions"}, + dash=dashboard_atendimento_humano, + position={"rows": [3, 3], "columns": [5, 8]}, + ) + Widget.objects.create( + name="Tempo de interação", + w_type="card", + source="chats", + config={"operation": "count", "type_result": "executions"}, + dash=dashboard_atendimento_humano, + position={"rows": [3, 3], "columns": [9, 12]}, + ) + Widget.objects.create( + name="Chats por agente", + w_type="table_dynamic_by_filter", + source="chats", + config={ + "default": { + "icon": "forum:weni-600", + "fields": [ + { + "name": "Agente", + "value": "agent", + "display": True, + "hidden_name": False, + }, + { + "name": "Em andamento", + "value": "open", + "display": True, + "hidden_name": False, + }, + { + "name": "Encerrados", + "value": "close", + "display": True, + "hidden_name": False, + }, + { + "name": "Status", + "value": "status", + "display": True, + "hidden_name": False, + }, + ], + "name_overwrite": "Agentes online", + } + }, + dash=dashboard_atendimento_humano, + position={"rows": [1, 3], "columns": [13, 18]}, + ) + + self.create_reports( + pico_de_atendimento, + em_andamento, + encerrados, + aguardando_atendimento, + ) + except Exception as exception: + raise InvalidWidgetsObject(f"Error creating widgets: {exception}") + + def create_reports( + self, pico_de_atendimento, em_andamento, encerrados, aguardando_atendimento + ): + try: + with transaction.atomic(): + Report.objects.create( + name="Pico de chats abertos por hora", + w_type="graph_column", + source="chats", + config={}, + widget=pico_de_atendimento, + ) + Report.objects.create( + name="Em andamento", + w_type="table_group", + source="chats", + config={}, + widget=em_andamento, + ) + Report.objects.create( + name="Encerrados", + w_type="table_group", + source="chats", + config={}, + widget=encerrados, + ) + Report.objects.create( + name="Aguardando atendimento", + w_type="table_group", + source="chats", + config={}, + widget=aguardando_atendimento, + ) + except Exception as exception: + raise InvalidReportsObject(f"Error creating dashboard: {exception}") + + +class create_resultado_de_fluxo: + def create_dashboard(self, project): + try: + with transaction.atomic(): + dashboard_resultado_de_fluxo = Dashboard.objects.create( + project=project, + name="Resultado de fluxo", + description="Dashboard de resultado de fluxo", + is_default=False, + ) + self.create_widgets(dashboard_resultado_de_fluxo) + + except Exception as exception: + raise InvalidDashboardObject(f"Error creating dashboard: {exception}") + + def create_widgets(self, dashboard_resultado_de_fluxo): + try: + with transaction.atomic(): + Widget.objects.create( + name="Métrica vazia", + w_type="card", + source="", + config={}, + dash=dashboard_resultado_de_fluxo, + position={"rows": [1, 1], "columns": [1, 4]}, + ) + Widget.objects.create( + name="Métrica vazia", + w_type="card", + source="", + config={}, + dash=dashboard_resultado_de_fluxo, + position={"rows": [2, 2], "columns": [1, 4]}, + ) + Widget.objects.create( + name="Métrica vazia", + w_type="card", + source="", + config={}, + dash=dashboard_resultado_de_fluxo, + position={"rows": [3, 3], "columns": [1, 4]}, + ) + Widget.objects.create( + name="Métrica vazia", + w_type="card", + source="", + config={}, + dash=dashboard_resultado_de_fluxo, + position={"rows": [1, 1], "columns": [5, 8]}, + ) + Widget.objects.create( + name="Métrica vazia", + w_type="card", + source="", + config={}, + dash=dashboard_resultado_de_fluxo, + position={"rows": [2, 2], "columns": [5, 8]}, + ) + Widget.objects.create( + name="Métrica vazia", + w_type="card", + source="", + config={}, + dash=dashboard_resultado_de_fluxo, + position={"rows": [3, 3], "columns": [5, 8]}, + ) + Widget.objects.create( + name="Métrica vazia", + w_type="graph_funnel", + source="", + config={}, + dash=dashboard_resultado_de_fluxo, + position={"rows": [1, 3], "columns": [9, 12]}, + ) + except Exception as exception: + raise InvalidWidgetsObject(f"Error creating widgets: {exception}") + + def create_reports(): + pass diff --git a/insights/dashboards/usecases/exceptions.py b/insights/dashboards/usecases/exceptions.py new file mode 100644 index 0000000..cd9ff5e --- /dev/null +++ b/insights/dashboards/usecases/exceptions.py @@ -0,0 +1,10 @@ +class InvalidDashboardObject(Exception): + pass + + +class InvalidWidgetsObject(Exception): + pass + + +class InvalidReportsObject(Exception): + pass diff --git a/insights/dashboards/utils.py b/insights/dashboards/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/insights/projects/usecases/create.py b/insights/projects/usecases/create.py index 8441160..8022424 100644 --- a/insights/projects/usecases/create.py +++ b/insights/projects/usecases/create.py @@ -2,6 +2,11 @@ from .project_dto import ProjectCreationDTO +from insights.dashboards.usecases.dashboard_creation import ( + create_atendimento_humano, + create_resultado_de_fluxo, +) + class ProjectsUseCase: @@ -23,5 +28,6 @@ def create_project(self, project_dto: ProjectCreationDTO) -> Project: timezone=project_dto.timezone, date_format=project_dto.date_format, ) - + create_atendimento_humano.create_dashboard(project) + create_resultado_de_fluxo.create_dashboard(project) return project diff --git a/insights/widgets/migrations/0002_remove_widget_report_report.py b/insights/widgets/migrations/0002_remove_widget_report_report.py new file mode 100644 index 0000000..55d7b54 --- /dev/null +++ b/insights/widgets/migrations/0002_remove_widget_report_report.py @@ -0,0 +1,66 @@ +# Generated by Django 5.0.4 on 2024-05-28 18:55 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("widgets", "0001_initial"), + ] + + operations = [ + migrations.RemoveField( + model_name="widget", + name="report", + ), + migrations.CreateModel( + name="Report", + fields=[ + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, primary_key=True, serialize=False + ), + ), + ( + "created_on", + models.DateTimeField(auto_now_add=True, verbose_name="Created on"), + ), + ( + "modified_on", + models.DateTimeField(auto_now=True, verbose_name="Modified on"), + ), + ( + "name", + models.CharField(default=None, max_length=255, verbose_name="Name"), + ), + ( + "w_type", + models.CharField( + default=None, max_length=50, verbose_name="Widget Type" + ), + ), + ( + "source", + models.CharField( + default=None, max_length=50, verbose_name="Data Source" + ), + ), + ("config", models.JSONField(verbose_name="Widget Configuration")), + ( + "widget", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="report", + to="widgets.widget", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/insights/widgets/models.py b/insights/widgets/models.py index c220f79..af1e0fb 100644 --- a/insights/widgets/models.py +++ b/insights/widgets/models.py @@ -3,10 +3,7 @@ from insights.shared.models import BaseModel, ConfigurableModel -class Widget(BaseModel, ConfigurableModel): - dashboard = models.ForeignKey( - "dashboards.Dashboard", related_name="widgets", on_delete=models.CASCADE - ) +class BaseWidget(BaseModel, ConfigurableModel): name = models.CharField( "Name", max_length=255, null=False, blank=False, default=None ) @@ -16,9 +13,27 @@ class Widget(BaseModel, ConfigurableModel): source = models.CharField( "Data Source", max_length=50, null=False, blank=False, default=None ) - position = models.JSONField("Widget position") + # config needs to be required in widget config = models.JSONField("Widget Configuration") - report = models.JSONField("Widget Report") + + class Meta: + abstract = True + + +class Widget(BaseWidget): + dashboard = models.ForeignKey( + "dashboards.Dashboard", related_name="widgets", on_delete=models.CASCADE + ) + position = models.JSONField("Widget position") + + def __str__(self): + return self.name + + +class Report(BaseWidget): + widget = models.OneToOneField( + Widget, related_name="report", on_delete=models.CASCADE + ) def __str__(self): - return self.description + return self.name