Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed search fetch crashing because of server taking too long for api request logs #2178

Merged
merged 10 commits into from
Jul 26, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Unreleased
----------
* nothing unreleased

[4.21.9]
---------
* fix: fixed search fetch crashing because of server taking too long for api request logs.

[4.21.8]
---------
* fix: fixed 500 error for search filter for api request logs in admin view.
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "4.21.8"
__version__ = "4.21.9"
27 changes: 18 additions & 9 deletions integrated_channels/integrated_channel/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,10 @@ class IntegratedChannelAPIRequestLogAdmin(admin.ModelAdmin):
"status_code",
]
search_fields = [
"status_code",
"enterprise_customer__name",
"enterprise_customer__uuid",
"enterprise_customer_configuration_id",
"endpoint",
"time_taken",
"response_body",
"payload",
"enterprise_customer__name__icontains",
"enterprise_customer__uuid__iexact",
"enterprise_customer_configuration_id__iexact",
"endpoint__icontains",
]
readonly_fields = [
"status_code",
Expand All @@ -122,12 +118,25 @@ class IntegratedChannelAPIRequestLogAdmin(admin.ModelAdmin):
"response_body",
"payload",
]
list_filter = ('status_code',)

list_per_page = 20

def get_queryset(self, request):
"""
Optimize queryset by selecting related 'enterprise_customer' and limiting fields.
"""
queryset = super().get_queryset(request)
return queryset.select_related('enterprise_customer')
return queryset.select_related('enterprise_customer').only(
'id',
'endpoint',
'enterprise_customer_id',
'time_taken',
'status_code',
'enterprise_customer__name',
'enterprise_customer__uuid',
'enterprise_customer_configuration_id'
)

class Meta:
model = IntegratedChannelAPIRequestLogs
77 changes: 77 additions & 0 deletions tests/test_admin/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Tests for the IntegratedChannelAPIRequest admin module in `edx-enterprise`.
"""

from django.contrib.admin.sites import AdminSite
from django.db import connection
from django.test import TestCase
from django.test.utils import CaptureQueriesContext

from integrated_channels.integrated_channel.admin import IntegratedChannelAPIRequestLogAdmin
from integrated_channels.integrated_channel.models import IntegratedChannelAPIRequestLogs
from test_utils import factories


class IntegratedChannelAPIRequestLogAdminTest(TestCase):
"""
Test the admin functionality for the IntegratedChannelAPIRequestLogs model.
"""

def setUp(self):
"""
Set up the test environment by creating a test admin instance and sample data.
"""
self.site = AdminSite()
self.admin = IntegratedChannelAPIRequestLogAdmin(IntegratedChannelAPIRequestLogs, self.site)

self.enterprise_customer = factories.EnterpriseCustomerFactory(name='Test Enterprise')
self.log_entry = IntegratedChannelAPIRequestLogs.objects.create(
enterprise_customer=self.enterprise_customer,
enterprise_customer_configuration_id=1,
endpoint="http://test.com",
payload="test payload",
time_taken=1.0,
status_code=200,
response_body="test response"
)

def test_get_queryset_optimization(self):
"""
Test that the get_queryset method optimizes query by using select_related and only selecting specified fields.
"""
request = None

with CaptureQueriesContext(connection) as queries:
queryset = self.admin.get_queryset(request)

list(queryset)

self.assertEqual(len(queries), 1)

query = queries[0]['sql']
self.assertIn('integrated_channel_integratedchannelapirequestlogs', query)
self.assertIn('enterprise_enterprisecustomer', query)

self.assertIn('"integrated_channel_integratedchannelapirequestlogs"."id"', query)
self.assertIn('"integrated_channel_integratedchannelapirequestlogs"."endpoint"', query)
self.assertIn('"integrated_channel_integratedchannelapirequestlogs"."enterprise_customer_id"', query)
self.assertIn('"integrated_channel_integratedchannelapirequestlogs"."time_taken"', query)
self.assertIn('"integrated_channel_integratedchannelapirequestlogs"."status_code"', query)
self.assertIn('"enterprise_enterprisecustomer"."name"', query)
self.assertIn('"enterprise_enterprisecustomer"."uuid"', query)
self.assertIn(
'"integrated_channel_integratedchannelapirequestlogs"."enterprise_customer_configuration_id"', query
)

self.assertNotIn('payload', query)
self.assertNotIn('response_body', query)

log_entry = queryset.get(id=self.log_entry.id)
self.assertEqual(log_entry.endpoint, "http://test.com")
self.assertEqual(log_entry.enterprise_customer.name, "Test Enterprise")

# Verify that accessing an unselected field causes an additional query
with CaptureQueriesContext(connection) as queries:
_ = log_entry.payload

self.assertEqual(len(queries), 1, "Accessing unselected field should cause exactly one additional query")
Loading