Skip to content

Commit

Permalink
add pie chart for product type
Browse files Browse the repository at this point in the history
  • Loading branch information
danangmassandy committed Oct 19, 2024
1 parent 4db2faf commit a680ef5
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 35 deletions.
53 changes: 42 additions & 11 deletions django_project/gap_api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
.. note:: Admin for API Tracking
"""

import random
from django.contrib import admin
from django.db.models import Count
from django.db.models.functions import TruncDay
from django.db.models import Count, TextField
from django.db.models.fields.json import KeyTextTransform
from django.db.models.functions import TruncDay, Cast
from rest_framework_tracking.admin import APIRequestLogAdmin
from rest_framework_tracking.models import APIRequestLog as BaseAPIRequestLog

Expand All @@ -18,6 +20,11 @@
admin.site.unregister(BaseAPIRequestLog)


def generate_random_color():
"""Generate random color for product type."""
return "#{:06x}".format(random.randint(0, 0xFFFFFF))


class ProductTypeFilter(admin.SimpleListFilter):
"""Custom filter for product type field."""

Expand Down Expand Up @@ -81,7 +88,16 @@ def changelist_view(self, request, extra_context=None):
# Aggregate api logs per day
chart_data = self._generate_chart_data(request)

extra_context = extra_context or {"chart_data": list(chart_data)}
# generate color for products
product_counts = []
for product in chart_data['product']:
product['color'] = generate_random_color()
product_counts.append(product)

extra_context = extra_context or {
"chart_data": list(chart_data['total_requests']),
"product_chart_data": product_counts
}

# Call the superclass changelist_view to render the page
return super().changelist_view(request, extra_context=extra_context)
Expand Down Expand Up @@ -127,15 +143,30 @@ def _do_query_chart_data(
filters['user__id'] = user_id

filters.update(other_filters)
return (
APIRequestLog.objects.filter(
**filters
return {
'total_requests': (
APIRequestLog.objects.filter(
**filters
)
.annotate(date=TruncDay("requested_at"))
.values("date")
.annotate(y=Count("id"))
.order_by("-date")
),
'product': (
APIRequestLog.objects.filter(
**filters
).annotate(
product=Cast(
KeyTextTransform('product', 'query_params'),
TextField()
)
)
.values('product')
.annotate(count=Count("id"))
.order_by('product')
)
.annotate(date=TruncDay("requested_at"))
.values("date")
.annotate(y=Count("id"))
.order_by("-date")
)
}


admin.site.register(APIRequestLog, GapAPIRequestLogAdmin)
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,36 @@
{% block extrahead %}
{{ block.super }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
<style>
.ranges li {
list-style: none !important;
/* Flexbox container to align the charts side by side */
.chart-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.daterangepicker {
background-color: var(--secondary);

/* Give more width to the bar chart */
.bar-chart-container {
width: 65%;
}

.pie-chart-container {
width: 30%;
}
.daterangepicker .ranges li:hover {
background-color: var(--darkened-bg);

/* Canvas styling for responsive charts */
canvas {
max-width: 100%;
height: auto;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
var ctx = document.getElementById('chart').getContext('2d');
const ctx = document.getElementById('myBarChart').getContext('2d');

// parse chart data
var chartData = JSON.parse(document.getElementById('chartData').textContent);
Expand All @@ -34,7 +45,7 @@
});

// Render the chart
var chart = new Chart(ctx, {
const chart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [{
Expand Down Expand Up @@ -64,17 +75,54 @@
},
},
});

// Render pie chart
const productData = JSON.parse(document.getElementById('productChartData').textContent);
const pieLabels = productData.map(product => product.product);
const pieData = productData.map(product => product.count);

const pieCtx = document.getElementById('myPieChart').getContext('2d');
const myPieChart = new Chart(pieCtx, {
type: 'pie',
data: {
labels: pieLabels,
datasets: [{
data: pieData,
backgroundColor: productData.map(product => product.color)
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
},
tooltip: {
enabled: true
}
}
}
});

});
</script>

{{ chart_data|json_script:"chartData" }}
{{ product_chart_data|json_script:"productChartData" }}

{% endblock %}

{% block content %}
<!-- Render the chart -->
<div style="width: 80%;">
<canvas style="margin-bottom: 30px; width: 60%; height: 50%;" id="chart"></canvas>
<div class="chart-container">
<!-- Bar chart container (left, wider) -->
<div class="bar-chart-container">
<canvas id="myBarChart"></canvas>
</div>
<!-- Pie chart container (right, narrower) -->
<div class="pie-chart-container">
<canvas id="myPieChart"></canvas>
</div>
</div>
<!-- Render the rest of the ChangeList view -->
{{ block.super }}
Expand Down
26 changes: 14 additions & 12 deletions django_project/gap_api/tests/test_api_request_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
import datetime
from django.test import TestCase, RequestFactory
from django.contrib.admin import ModelAdmin
from django.core.cache import cache

from core.factories import UserF
from gap.models import DatasetType
from gap_api.models import APIRequestLog
from gap_api.mixins import GAPAPILoggingMixin
from gap_api.tasks import store_api_logs
from gap_api.admin import ProductTypeFilter, GapAPIRequestLogAdmin
from gap_api.factories import APIRequestLogFactory
Expand Down Expand Up @@ -43,10 +41,11 @@ def setUp(self):
self.admin_instance = GapAPIRequestLogAdmin(
APIRequestLog, admin_site=mock.MagicMock())

def test_store_api_logs(self):
@mock.patch('django.core.cache.cache._cache.get_client')
def test_store_api_logs(self, mock_get_client):
"""Test store api logs from cache."""
cache._cache.get_client().rpush(
GAPAPILoggingMixin.CACHE_KEY,
mock_redis_client = mock_get_client.return_value
mock_redis_client.lpop.side_effect = [
json.dumps({
"requested_at": "2024-10-17 17:56:52.270889+00:00",
"data": None,
Expand Down Expand Up @@ -74,8 +73,10 @@ def test_store_api_logs(self):
"response": None,
"status_code": 200
})
)
] + [None] * 499
store_api_logs()
# default batch size
self.assertEqual(mock_redis_client.lpop.call_count, 500)
logs = APIRequestLog.objects.all()
self.assertEqual(logs.count(), 1)
self.assertEqual(logs.first().user, self.user)
Expand Down Expand Up @@ -123,7 +124,10 @@ def test_admin_product_type(self):
def test_change_list_view(
self, mock_super_changelist_view, mock_generate_chart_data):
"""Test change_list view."""
mock_chart_data = [{"date": "2024-10-18", "count": 10}]
mock_chart_data = {
'total_requests': [{"date": "2024-10-18", "count": 10}],
'product': [{"product": "product a", "count": 10}]
}
mock_generate_chart_data.return_value = mock_chart_data

# Simulate a GET request to the changelist view
Expand All @@ -135,10 +139,7 @@ def test_change_list_view(
# Assert that _generate_chart_data was called
mock_generate_chart_data.assert_called_once_with(request)

# Assert that the extra_context includes the correct chart data
expected_extra_context = {"chart_data": list(mock_chart_data)}
mock_super_changelist_view.assert_called_once_with(
request, extra_context=expected_extra_context)
mock_super_changelist_view.assert_called_once()

def test_generate_chart_data(self):
"""Test generate chart data."""
Expand All @@ -157,4 +158,5 @@ def test_generate_chart_data(self):
'user__id__exact': f'{self.user.id}'
}
data = self.admin_instance._generate_chart_data(req)
self.assertEqual(len(data), 1)
self.assertEqual(len(data['total_requests']), 1)
self.assertEqual(len(data['product']), 1)

0 comments on commit a680ef5

Please sign in to comment.