mirror of
https://github.com/kjanat/livegraphs-django.git
synced 2026-01-16 09:02:11 +01:00
Implement CSV export functionality for chat sessions with filtering options
This commit is contained in:
22
TODO.md
22
TODO.md
@ -1,7 +1,23 @@
|
|||||||
# TODO List
|
# 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?
|
- 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.
|
- 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.
|
- 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.
|
- Add possibility to add a company logo in the admin interface.
|
||||||
@ -10,4 +26,6 @@
|
|||||||
- URL: https://proto.notso.ai/jumbo/chats
|
- URL: https://proto.notso.ai/jumbo/chats
|
||||||
- Username: jumboadmin
|
- Username: jumboadmin
|
||||||
- Password: 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.
|
||||||
|
|||||||
5
__init__.py
Normal file
5
__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"""
|
||||||
|
LiveGraphsDjango - Dashboard for analyzing chat session data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
44
dashboard_project/__main__.py
Normal file
44
dashboard_project/__main__.py
Normal file
@ -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()
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views, views_export
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.dashboard_view, name="dashboard"),
|
path("", views.dashboard_view, name="dashboard"),
|
||||||
@ -40,4 +40,6 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("search/", views.search_chat_sessions, name="search_chat_sessions"),
|
path("search/", views.search_chat_sessions, name="search_chat_sessions"),
|
||||||
path("data-view/", views.data_view, name="data_view"),
|
path("data-view/", views.data_view, name="data_view"),
|
||||||
|
# Export to CSV
|
||||||
|
path("export/csv/", views_export.export_chats_csv, name="export_chats_csv"),
|
||||||
]
|
]
|
||||||
|
|||||||
137
dashboard_project/dashboard/views_export.py
Normal file
137
dashboard_project/dashboard/views_export.py
Normal file
@ -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
|
||||||
@ -23,6 +23,12 @@
|
|||||||
>
|
>
|
||||||
<i class="fas fa-trash"></i> Delete
|
<i class="fas fa-trash"></i> Delete
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
href="{% url 'export_chats_csv' %}?dashboard_id={{ selected_dashboard.id }}"
|
||||||
|
class="btn btn-sm btn-outline-success"
|
||||||
|
>
|
||||||
|
<i class="fas fa-file-csv"></i> Export CSV
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -13,6 +13,12 @@
|
|||||||
<a href="{% url 'upload_data' %}" class="btn btn-sm btn-outline-secondary me-2">
|
<a href="{% url 'upload_data' %}" class="btn btn-sm btn-outline-secondary me-2">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Data Sources
|
<i class="fas fa-arrow-left"></i> Back to Data Sources
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
href="{% url 'export_chats_csv' %}?data_source_id={{ data_source.id }}"
|
||||||
|
class="btn btn-sm btn-outline-success me-2"
|
||||||
|
>
|
||||||
|
<i class="fas fa-file-csv"></i> Export CSV
|
||||||
|
</a>
|
||||||
<a href="{% url 'delete_data_source' data_source.id %}" class="btn btn-sm btn-outline-danger">
|
<a href="{% url 'delete_data_source' data_source.id %}" class="btn btn-sm btn-outline-danger">
|
||||||
<i class="fas fa-trash"></i> Delete
|
<i class="fas fa-trash"></i> Delete
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -98,6 +98,65 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Export to CSV -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title mb-0">Export Data</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="export-form" method="get" action="{% url 'export_chats_csv' %}" class="row g-3">
|
||||||
|
<!-- Pass current filters to export -->
|
||||||
|
<input type="hidden" name="data_source_id" value="{{ selected_data_source.id }}" />
|
||||||
|
<input type="hidden" name="view" value="{{ view }}" />
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label for="start_date" class="form-label">Start Date</label>
|
||||||
|
<input type="date" name="start_date" id="start_date" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label for="end_date" class="form-label">End Date</label>
|
||||||
|
<input type="date" name="end_date" id="end_date" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label for="country" class="form-label">Country</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="country"
|
||||||
|
id="country"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Country"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label for="sentiment" class="form-label">Sentiment</label>
|
||||||
|
<select name="sentiment" id="sentiment" class="form-select">
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="positive">Positive</option>
|
||||||
|
<option value="negative">Negative</option>
|
||||||
|
<option value="neutral">Neutral</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label for="escalated" class="form-label">Escalated</label>
|
||||||
|
<select name="escalated" id="escalated" class="form-select">
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="true">Yes</option>
|
||||||
|
<option value="false">No</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 d-flex align-items-end">
|
||||||
|
<button type="submit" class="btn btn-success w-100">
|
||||||
|
<i class="fas fa-file-csv me-1"></i> Export to CSV
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Data Table -->
|
<!-- Data Table -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|||||||
@ -17,6 +17,17 @@ dependencies = [
|
|||||||
"whitenoise>=6.9.0",
|
"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]
|
[tool.ruff]
|
||||||
# Exclude a variety of commonly ignored directories.
|
# Exclude a variety of commonly ignored directories.
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|||||||
19
setup.py
Normal file
19
setup.py
Normal file
@ -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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user