diff --git a/TODO.md b/TODO.md index a85dde3..509e2d0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,23 @@ # TODO List - When I zoom into the dasboard page, the graphs don't scale/adjust to fit the window until I completely refresh the page, can we solve that? -- Add export to CSV and PDF functionality to the dashboard. +- Add export functionality to the dashboard: + - File formats: + - CSV + - Excel + - JSON + - XML + - HTML + - PDF + - Make the export button a dropdown with the following options: + - Export as CSV + - Export as Excel + - Export as JSON + - Export as XML + - Export as HTML + - Export as PDF + - Make the export data section folded by default and only show the export button. + - Adjust the downloaded file name to include the company name, date and time of the export. - Add a button to download the CSV file for the selected company. - Make it possible to modify the column names in the CSV file through the admin interface. - Add possibility to add a company logo in the admin interface. @@ -10,4 +26,6 @@ - URL: https://proto.notso.ai/jumbo/chats - Username: jumboadmin - Password: jumboadmin - +- Reduce amount of rows in the table to fit the screen. +- Add dark mode/theming to the dashboard. +- Add Notso AI branding to the dashboard. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..4da1f97 --- /dev/null +++ b/__init__.py @@ -0,0 +1,5 @@ +""" +LiveGraphsDjango - Dashboard for analyzing chat session data. +""" + +__version__ = "0.1.0" diff --git a/dashboard_project/__main__.py b/dashboard_project/__main__.py new file mode 100644 index 0000000..d2e6220 --- /dev/null +++ b/dashboard_project/__main__.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +Entry point for Django commands executed as Python modules. +This enables commands like `python -m runserver`. +""" + +import os +import sys +from pathlib import Path + + +def main(): + """Determine the command to run and execute it.""" + # Get the command name from the entry point + cmd_name = Path(sys.argv[0]).stem + + # Default to 'manage.py' if no specific command + if cmd_name == "__main__": + # When running as `python -m dashboard_project`, just pass control to manage.py + from dashboard_project.manage import main as manage_main + + manage_main() + return + + # Add current directory to path if needed + cwd = str(Path.cwd()) + if cwd not in sys.path: + sys.path.insert(0, cwd) + + # Set Django settings module + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings") + + # For specific commands, insert the command name at the start of argv + if cmd_name in ["runserver", "migrate", "makemigrations", "collectstatic", "createsuperuser", "shell", "test"]: + sys.argv.insert(1, cmd_name) + + # Execute the Django management command + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/dashboard_project/dashboard/urls.py b/dashboard_project/dashboard/urls.py index 25076c2..529ba7b 100644 --- a/dashboard_project/dashboard/urls.py +++ b/dashboard_project/dashboard/urls.py @@ -2,7 +2,7 @@ from django.urls import path -from . import views +from . import views, views_export urlpatterns = [ path("", views.dashboard_view, name="dashboard"), @@ -40,4 +40,6 @@ urlpatterns = [ ), path("search/", views.search_chat_sessions, name="search_chat_sessions"), path("data-view/", views.data_view, name="data_view"), + # Export to CSV + path("export/csv/", views_export.export_chats_csv, name="export_chats_csv"), ] diff --git a/dashboard_project/dashboard/views_export.py b/dashboard_project/dashboard/views_export.py new file mode 100644 index 0000000..1037e18 --- /dev/null +++ b/dashboard_project/dashboard/views_export.py @@ -0,0 +1,137 @@ +# dashboard/views_export.py + +import csv +from datetime import timedelta + +from django.contrib.auth.decorators import login_required +from django.db.models import Q +from django.http import HttpResponse +from django.shortcuts import get_object_or_404 +from django.utils import timezone + +from .models import ChatSession, Dashboard, DataSource + + +@login_required +def export_chats_csv(request): + """Export chat sessions to CSV with filtering options""" + user = request.user + company = user.company + + if not company: + return HttpResponse("You are not associated with any company.", status=403) + + # Get and apply filters + data_source_id = request.GET.get("data_source_id") + dashboard_id = request.GET.get("dashboard_id") + view = request.GET.get("view", "all") + start_date = request.GET.get("start_date") + end_date = request.GET.get("end_date") + country = request.GET.get("country") + sentiment = request.GET.get("sentiment") + escalated = request.GET.get("escalated") + + # Base queryset + sessions = ChatSession.objects.filter(data_source__company=company) + + # Apply data source filter if selected + if data_source_id: + data_source = get_object_or_404(DataSource, id=data_source_id, company=company) + sessions = sessions.filter(data_source=data_source) + + # Apply dashboard filter if selected + if dashboard_id: + dashboard = get_object_or_404(Dashboard, id=dashboard_id, company=company) + data_sources = dashboard.data_sources.all() + sessions = sessions.filter(data_source__in=data_sources) + + # Apply view filter + if view == "recent": + seven_days_ago = timezone.now() - timedelta(days=7) + sessions = sessions.filter(start_time__gte=seven_days_ago) + elif view == "positive": + sessions = sessions.filter(Q(sentiment__icontains="positive")) + elif view == "negative": + sessions = sessions.filter(Q(sentiment__icontains="negative")) + elif view == "escalated": + sessions = sessions.filter(escalated=True) + + # Apply additional filters + if start_date: + sessions = sessions.filter(start_time__date__gte=start_date) + if end_date: + sessions = sessions.filter(start_time__date__lte=end_date) + if country: + sessions = sessions.filter(country__icontains=country) + if sentiment: + sessions = sessions.filter(sentiment__icontains=sentiment) + if escalated: + escalated_val = escalated.lower() == "true" + sessions = sessions.filter(escalated=escalated_val) + + # Order by most recent first + sessions = sessions.order_by("-start_time") + + # Create the HttpResponse with CSV header + filename = "chat_sessions" + if dashboard_id: + dashboard = Dashboard.objects.get(id=dashboard_id) + filename = f"{dashboard.name.replace(' ', '_').lower()}_chat_sessions" + elif data_source_id: + data_source = DataSource.objects.get(id=data_source_id) + filename = f"{data_source.name.replace(' ', '_').lower()}_chat_sessions" + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = f'attachment; filename="{filename}.csv"' + + # Create CSV writer + writer = csv.writer(response) + + # Write CSV header + writer.writerow( + [ + "Session ID", + "Start Time", + "End Time", + "IP Address", + "Country", + "Language", + "Messages Sent", + "Sentiment", + "Escalated", + "Forwarded HR", + "Full Transcript", + "Avg Response Time (s)", + "Tokens", + "Tokens EUR", + "Category", + "Initial Message", + "User Rating", + ] + ) + + # Write data rows + for session in sessions: + writer.writerow( + [ + session.session_id, + session.start_time, + session.end_time, + session.ip_address, + session.country, + session.language, + session.messages_sent, + session.sentiment, + "Yes" if session.escalated else "No", + "Yes" if session.forwarded_hr else "No", + session.full_transcript, + session.avg_response_time, + session.tokens, + session.tokens_eur, + session.category, + session.initial_msg, + session.user_rating, + ] + ) + + return response diff --git a/dashboard_project/templates/dashboard/dashboard.html b/dashboard_project/templates/dashboard/dashboard.html index cabbdfd..3ff364f 100644 --- a/dashboard_project/templates/dashboard/dashboard.html +++ b/dashboard_project/templates/dashboard/dashboard.html @@ -23,6 +23,12 @@ > Delete + + Export CSV + + +
+
+
+
+
Export Data
+
+
+
+ + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+
diff --git a/pyproject.toml b/pyproject.toml index c5db254..35129db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,17 @@ dependencies = [ "whitenoise>=6.9.0", ] +[project.scripts] +# Django management commands +"manage" = "dashboard_project.manage:main" +"runserver" = "dashboard_project.manage:main" +"migrate" = "dashboard_project.manage:main" +"makemigrations" = "dashboard_project.manage:main" +"collectstatic" = "dashboard_project.manage:main" +"createsuperuser" = "dashboard_project.manage:main" +"shell" = "dashboard_project.manage:main" +"test" = "dashboard_project.manage:main" + [tool.ruff] # Exclude a variety of commonly ignored directories. exclude = [ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d4281f3 --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup + +setup( + name="livegraphsdjango", + version="0.1.0", + packages=["dashboard_project"], + entry_points={ + "console_scripts": [ + "manage=dashboard_project.manage:main", + "runserver=dashboard_project.__main__:main", + "migrate=dashboard_project.__main__:main", + "makemigrations=dashboard_project.__main__:main", + "collectstatic=dashboard_project.__main__:main", + "createsuperuser=dashboard_project.__main__:main", + "shell=dashboard_project.__main__:main", + "test=dashboard_project.__main__:main", + ], + }, +)