Skip to content

Commit 3288199

Browse files
authored
perf(userReports): remove count from API response DEV-1354 (#6505)
### 📣 Summary Improve user report performance by removing the expensive count calculation from the response. ### 📖 Description This change removes the `count` field from the user report API response because calculating the total number of documents is a major bottleneck on large datasets. The count query requires scanning the entire collection, significantly slowing down the endpoint. Clients still receive the paginated results as before, just without the heavy count operation.
1 parent 97862a0 commit 3288199

File tree

5 files changed

+19
-23
lines changed

5 files changed

+19
-23
lines changed

jsapp/js/api/models/paginatedUserReportsListResponseList.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ The endpoints are grouped by area of intended use. Each category contains relate
1212
import type { UserReportsListResponse } from './userReportsListResponse'
1313

1414
export interface PaginatedUserReportsListResponseList {
15-
count: number
1615
/** @nullable */
1716
next?: string | null
1817
/** @nullable */

kobo/apps/user_reports/tests/test_user_reports.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@
2121
refresh_user_reports_materialized_view,
2222
)
2323
from kpi.tests.base_test_case import BaseTestCase
24+
from kpi.urls.router_api_v2 import URL_NAMESPACE as ROUTER_URL_NAMESPACE
2425

2526

2627
class UserReportsViewSetAPITestCase(BaseTestCase):
28+
2729
fixtures = ['test_data']
30+
URL_NAMESPACE = ROUTER_URL_NAMESPACE
2831

2932
def setUp(self):
3033
self.client.login(username='adminuser', password='pass')
31-
self.url = reverse(self._get_endpoint('api_v2:user-reports-list'))
34+
self.url = reverse(self._get_endpoint('user-reports-list'))
3235

3336
self.someuser = User.objects.get(username='someuser')
3437
self.organization = self.someuser.organization
@@ -261,7 +264,7 @@ def test_accepted_tos_field(self):
261264
self.assertEqual(results[0]['accepted_tos'], False)
262265

263266
# POST to the tos endpoint to accept the terms of service
264-
tos_url = reverse(self._get_endpoint('tos'))
267+
tos_url = reverse('tos')
265268
response = self.client.post(tos_url)
266269
assert response.status_code == status.HTTP_204_NO_CONTENT
267270

@@ -311,11 +314,13 @@ def _get_someuser_data(self):
311314

312315

313316
class UserReportsFilterAndOrderingTestCase(BaseTestCase):
317+
314318
fixtures = ['test_data']
319+
URL_NAMESPACE = ROUTER_URL_NAMESPACE
315320

316321
def setUp(self):
317322
self.client.login(username='adminuser', password='pass')
318-
self.url = reverse(self._get_endpoint('api_v2:user-reports-list'))
323+
self.url = reverse(self._get_endpoint('user-reports-list'))
319324

320325
self.someuser = User.objects.get(username='someuser')
321326
self.organization = self.someuser.organization
@@ -332,21 +337,23 @@ def _get_results(self, params=None):
332337

333338
def test_username_prefix_filter(self):
334339
res = self._get_results({'q': 'username__icontains:some'})
335-
self.assertEqual(res['count'], 1)
340+
self.assertEqual(len(res['results']), 1)
336341
self.assertEqual(res['results'][0]['username'], 'someuser')
337342

338343
def test_email_prefix_filter(self):
339344
res = self._get_results({'q': 'email__icontains:some@user'})
340-
self.assertEqual(res['count'], 1)
345+
self.assertEqual(len(res['results']), 1)
341346
self.assertEqual(res['results'][0]['email'], '[email protected]')
342347

343348
def test_date_joined_gte_and_lte_filters(self):
344-
all_res = self._get_results()
345-
res_all = self._get_results({'q': 'date_joined__gte:2012-01-01'})
346-
self.assertLessEqual(res_all['count'], all_res['count'])
349+
all_results = self._get_results()
350+
filtered_results = self._get_results({'q': 'date_joined__gte:2012-01-01'})
351+
self.assertLessEqual(
352+
len(filtered_results['results']), len(all_results['results'])
353+
)
347354

348-
res_none = self._get_results({'q': 'date_joined__gte:3020-01-01'})
349-
self.assertEqual(res_none['count'], 0)
355+
no_results = self._get_results({'q': 'date_joined__gte:3020-01-01'})
356+
self.assertEqual(len(no_results['results']), 0)
350357

351358
def test_storage_bytes_gte_and_lte_filters(self):
352359
# Update someuser's storage to simulate storage usage

kobo/apps/user_reports/views.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from kobo.apps.user_reports.models import UserReports
1010
from kobo.apps.user_reports.seralizers import UserReportsSerializer
1111
from kpi.filters import SearchFilter
12-
from kpi.paginators import LimitOffsetPagination
12+
from kpi.paginators import NoCountPagination
1313
from kpi.permissions import IsAuthenticated
1414
from kpi.schema_extensions.v2.user_reports.serializers import UserReportsListResponse
1515
from kpi.utils.schema_extensions.markdown import read_md
@@ -40,7 +40,7 @@ class UserReportsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
4040

4141
queryset = UserReports.objects.all()
4242
serializer_class = UserReportsSerializer
43-
pagination_class = LimitOffsetPagination
43+
pagination_class = NoCountPagination
4444
permission_classes = (IsAuthenticated, SuperUserPermission)
4545
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
4646

@@ -61,7 +61,6 @@ class UserReportsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
6161
]
6262
ordering = ['username']
6363
search_fields = ['username', 'email', 'first_name', 'last_name']
64-
skip_distinct = True
6564

6665
def list(self, request, *args, **kwargs):
6766
try:

static/openapi/schema_v2.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17712,14 +17712,9 @@
1771217712
"PaginatedUserReportsListResponseList": {
1771317713
"type": "object",
1771417714
"required": [
17715-
"count",
1771617715
"results"
1771717716
],
1771817717
"properties": {
17719-
"count": {
17720-
"type": "integer",
17721-
"example": 123
17722-
},
1772317718
"next": {
1772417719
"type": "string",
1772517720
"nullable": true,

static/openapi/schema_v2.yaml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12804,12 +12804,8 @@ components:
1280412804
PaginatedUserReportsListResponseList:
1280512805
type: object
1280612806
required:
12807-
- count
1280812807
- results
1280912808
properties:
12810-
count:
12811-
type: integer
12812-
example: 123
1281312809
next:
1281412810
type: string
1281512811
nullable: true

0 commit comments

Comments
 (0)