Skip to content

Commit

Permalink
Merge pull request #429 from dimagi/cz/dashboard-tweaks
Browse files Browse the repository at this point in the history
admin dashboard updates
  • Loading branch information
czue authored Nov 18, 2024
2 parents 9dcdce8 + 4ad4ac4 commit 7b63bc5
Show file tree
Hide file tree
Showing 6 changed files with 594 additions and 91 deletions.
4 changes: 2 additions & 2 deletions commcare_connect/reports/tests/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def all(self):
assert feature1["geometry"]["coordinates"] == [10.123, 20.456]
assert feature1["properties"]["status"] == "approved"
assert feature1["properties"]["other_field"] == "value1"
assert feature1["properties"]["color"] == "#00FF00"
assert feature1["properties"]["color"] == "#4ade80"

# Check the second feature
feature2 = geojson["features"][1]
Expand All @@ -115,7 +115,7 @@ def all(self):
assert feature2["geometry"]["coordinates"] == [30.789, 40.012]
assert feature2["properties"]["status"] == "rejected"
assert feature2["properties"]["other_field"] == "value2"
assert feature2["properties"]["color"] == "#FF0000"
assert feature2["properties"]["color"] == "#f87171"

# Check that the other cases are not included
assert all(f["properties"]["other_field"] not in ["value3", "value4", "value5"] for f in geojson["features"])
1 change: 1 addition & 0 deletions commcare_connect/reports/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
path("delivery_stats", view=views.DeliveryStatsReportView.as_view(), name="delivery_stats_report"),
path("api/visit_map_data/", views.visit_map_data, name="visit_map_data"),
path("api/dashboard_stats/", views.dashboard_stats_api, name="dashboard_stats_api"),
path("api/dashboard_charts/", views.dashboard_charts_api, name="dashboard_charts_api"),
]
117 changes: 111 additions & 6 deletions commcare_connect/reports/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.db.models import Max, Q, Sum
from django.db.models import Count, Max, Q, Sum
from django.http import JsonResponse
from django.shortcuts import render
from django.urls import reverse
Expand Down Expand Up @@ -204,7 +204,7 @@ def __init__(self, *args, **kwargs):

# Set default dates
today = date.today()
default_from = today - timedelta(days=90)
default_from = today - timedelta(days=30)

# Set the default values
self.data["to_date"] = today.strftime("%Y-%m-%d")
Expand All @@ -223,7 +223,6 @@ class Meta:
@user_passes_test(lambda u: u.is_superuser)
def program_dashboard_report(request):
filterset = DashboardFilters(request.GET)

return render(
request,
"reports/dashboard.html",
Expand Down Expand Up @@ -258,8 +257,8 @@ def visit_map_data(request):
def _results_to_geojson(results):
geojson = {"type": "FeatureCollection", "features": []}
status_to_color = {
"approved": "#00FF00",
"rejected": "#FF0000",
"approved": "#4ade80",
"rejected": "#f87171",
}
for i, result in enumerate(results.all()):
location_str = result.get("location_str")
Expand Down Expand Up @@ -287,7 +286,7 @@ def _results_to_geojson(results):
key: value for key, value in result.items() if key not in ["gps_location_lat", "gps_location_long"]
},
}
color = status_to_color.get(result.get("status", ""), "#FFFF00")
color = status_to_color.get(result.get("status", ""), "#fbbf24")
feature["properties"]["color"] = color
geojson["features"].append(feature)

Expand Down Expand Up @@ -419,3 +418,109 @@ def dashboard_stats_api(request):
"percent_verified": f"{percent_verified:.1f}%",
}
)


@login_required
@user_passes_test(lambda u: u.is_superuser)
def dashboard_charts_api(request):
filterset = DashboardFilters(request.GET)
queryset = UserVisit.objects.all()
# Use the filtered queryset if available, else use last 30 days
if filterset.is_valid():
queryset = filterset.filter_queryset(queryset)
from_date = filterset.form.cleaned_data["from_date"]
to_date = filterset.form.cleaned_data["to_date"]
else:
to_date = datetime.now().date()
from_date = to_date - timedelta(days=30)
queryset = queryset.filter(visit_date__gte=from_date, visit_date__lte=to_date)

return JsonResponse(
{
"time_series": _get_time_series_data(queryset, from_date, to_date),
"program_pie": _get_program_pie_data(queryset),
"status_pie": _get_status_pie_data(queryset),
}
)


def _get_time_series_data(queryset, from_date, to_date):
"""Example output:
{
"labels": ["Jan 01", "Jan 02", "Jan 03"],
"datasets": [
{
"name": "Program A",
"data": [5, 3, 7]
},
{
"name": "Program B",
"data": [2, 4, 1]
}
]
}
"""
# Get visits over time by program
visits_by_program_time = (
queryset.values("visit_date", "opportunity__delivery_type__name")
.annotate(count=Count("id"))
.order_by("visit_date", "opportunity__delivery_type__name")
)

# Process time series data
program_data = {}
for visit in visits_by_program_time:
program_name = visit["opportunity__delivery_type__name"]
if program_name not in program_data:
program_data[program_name] = {}
program_data[program_name][visit["visit_date"]] = visit["count"]

# Create labels and datasets for time series
labels = []
time_datasets = []
current_date = from_date

while current_date <= to_date:
labels.append(current_date.strftime("%b %d"))
current_date += timedelta(days=1)

for program_name in program_data.keys():
data = []
current_date = from_date
while current_date <= to_date:
data.append(program_data[program_name].get(current_date, 0))
current_date += timedelta(days=1)

time_datasets.append({"name": program_name or "Unknown", "data": data})

return {"labels": labels, "datasets": time_datasets}


def _get_program_pie_data(queryset):
"""Example output:
{
"labels": ["Program A", "Program B", "Unknown"],
"data": [10, 5, 2]
}
"""
visits_by_program = (
queryset.values("opportunity__delivery_type__name").annotate(count=Count("id")).order_by("-count")
)
return {
"labels": [item["opportunity__delivery_type__name"] or "Unknown" for item in visits_by_program],
"data": [item["count"] for item in visits_by_program],
}


def _get_status_pie_data(queryset):
"""Example output:
{
"labels": ["Approved", "Pending", "Rejected", "Unknown"],
"data": [15, 8, 4, 1]
}
"""
visits_by_status = queryset.values("status").annotate(count=Count("id")).order_by("-count")
return {
"labels": [item["status"] or "Unknown" for item in visits_by_status],
"data": [item["count"] for item in visits_by_status],
}
Loading

0 comments on commit 7b63bc5

Please sign in to comment.