From fe52930ddebb6b6c6c89d8353da0e57c97c91623 Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Thu, 24 Oct 2024 08:01:55 -0400 Subject: [PATCH] incorporate new renderer improvements into old views and make file filename more generic --- api/base/settings/defaults.py | 2 +- api/metrics/renderers.py | 5 +- api/metrics/views.py | 64 ++++++++++++------- .../test_institution_user_metric_list.py | 13 ++-- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/api/base/settings/defaults.py b/api/base/settings/defaults.py index a6ecb6b8e3f..7bbccb181ec 100644 --- a/api/base/settings/defaults.py +++ b/api/base/settings/defaults.py @@ -359,7 +359,7 @@ MAX_SIZE_OF_ES_QUERY = 10000 DEFAULT_ES_NULL_VALUE = 'N/A' -USER_INSTITUTION_REPORT_FILENAME = 'institution_user_report_{institution_id}_{date_created}.{format_type}' +REPORT_FILENAME_FORMAT = 'osf_report_{date_created}.{format_type}' CI_ENV = False diff --git a/api/metrics/renderers.py b/api/metrics/renderers.py index e99e5705d10..9ef51827a4a 100644 --- a/api/metrics/renderers.py +++ b/api/metrics/renderers.py @@ -1,7 +1,7 @@ import csv import io import json -from api.base.settings.defaults import USER_INSTITUTION_REPORT_FILENAME, MAX_SIZE_OF_ES_QUERY +from api.base.settings.defaults import REPORT_FILENAME_FORMAT, MAX_SIZE_OF_ES_QUERY import datetime from django.http import Http404 @@ -64,9 +64,8 @@ def get_filename(self, renderer_context: dict, format_type: str) -> str: """Generate the filename for the CSV/TSV file based on institution and current date.""" if renderer_context and 'view' in renderer_context: current_date = datetime.datetime.now().strftime('%Y-%m') # Format as 'YYYY-MM' - return USER_INSTITUTION_REPORT_FILENAME.format( + return REPORT_FILENAME_FORMAT.format( date_created=current_date, - institution_id=renderer_context['view'].kwargs['institution_id'], format_type=format_type, ) else: diff --git a/api/metrics/views.py b/api/metrics/views.py index 51556ddc89c..60a4835e98f 100644 --- a/api/metrics/views.py +++ b/api/metrics/views.py @@ -320,51 +320,67 @@ class RecentReportList(JSONAPIBaseView): MetricsReportsTsvRenderer, ) - def get(self, request, *args, report_name): + def get_default_search(self): try: - report_class = VIEWABLE_REPORTS[report_name] + report_class = VIEWABLE_REPORTS[self.kwargs['report_name']] except KeyError: - return Response( - { - 'errors': [{ - 'title': 'unknown report name', - 'detail': f'unknown report: "{report_name}"', - }], - }, - status=404, - ) + return None + is_daily = issubclass(report_class, reports.DailyReport) - days_back = request.GET.get('days_back', self.DEFAULT_DAYS_BACK if is_daily else None) is_monthly = issubclass(report_class, reports.MonthlyReport) + request = self.get_renderer_context()['request'] + days_back = request.GET.get('days_back', self.DEFAULT_DAYS_BACK if is_daily else None) + if is_daily: - serializer_class = DailyReportSerializer range_field_name = 'report_date' elif is_monthly: - serializer_class = MonthlyReportSerializer range_field_name = 'report_yearmonth' else: raise ValueError(f'report class must subclass DailyReport or MonthlyReport: {report_class}') + range_filter = parse_date_range(request.GET, is_monthly=is_monthly) - search_recent = ( - report_class.search() - .filter('range', **{range_field_name: range_filter}) - .sort(range_field_name) - [:self.MAX_COUNT] + search_recent = report_class.search().filter( + 'range', + **{range_field_name: range_filter}, + ).sort(range_field_name)[:self.MAX_COUNT] + + if is_daily and days_back: + search_recent = search_recent.filter('range', report_date={'gte': f'now/d-{days_back}d'}) + + return search_recent + + def get(self, request, *args, report_name): + search_response = self.get_default_search() + + if search_response is None: + return Response( + { + 'errors': [{ + 'title': 'unknown report name', + 'detail': f'unknown report: "{report_name}"', + }], + }, + status=404, + ) + + report_class = VIEWABLE_REPORTS[report_name] + serializer_class = ( + DailyReportSerializer if issubclass(report_class, reports.DailyReport) + else MonthlyReportSerializer ) - if days_back: - search_recent.filter('range', report_date={'gte': f'now/d-{days_back}d'}) - report_date_range = parse_date_range(request.GET) - search_response = search_recent.execute() serializer = serializer_class( search_response, many=True, context={'report_name': report_name}, ) + accepted_format = request.accepted_renderer.format response_headers = {} + if accepted_format in ('tsv', 'csv'): + report_date_range = parse_date_range(request.GET) from_date = report_date_range['gte'] until_date = report_date_range['lte'] filename = ( @@ -373,6 +389,8 @@ def get(self, request, *args, report_name): f'from_{from_date}.{accepted_format}' ) response_headers['Content-Disposition'] = f'attachment; filename={filename}' + + # Return the response with serialized data return Response( {'data': serializer.data}, headers=response_headers, diff --git a/api_tests/institutions/views/test_institution_user_metric_list.py b/api_tests/institutions/views/test_institution_user_metric_list.py index 2dcfcb7e3d0..046019e5616 100644 --- a/api_tests/institutions/views/test_institution_user_metric_list.py +++ b/api_tests/institutions/views/test_institution_user_metric_list.py @@ -8,7 +8,7 @@ import pytest from waffle.testutils import override_flag -from api.base.settings.defaults import API_BASE, DEFAULT_ES_NULL_VALUE, USER_INSTITUTION_REPORT_FILENAME +from api.base.settings.defaults import API_BASE, DEFAULT_ES_NULL_VALUE, REPORT_FILENAME_FORMAT import osf.features from osf_tests.factories import ( InstitutionFactory, @@ -435,9 +435,8 @@ def test_get_report_formats_csv_tsv(self, app, url, institutional_admin, institu assert resp.headers['Content-Type'] == content_type current_date = datetime.datetime.now().strftime('%Y-%m') - expected_filename = USER_INSTITUTION_REPORT_FILENAME.format( + expected_filename = REPORT_FILENAME_FORMAT.format( date_created=current_date, - institution_id=institution._id, format_type=format_type ) assert resp.headers['Content-Disposition'] == f'attachment; filename="{expected_filename}"' @@ -462,7 +461,7 @@ def test_get_report_formats_csv_tsv(self, app, url, institutional_admin, institu 'month_last_active', 'month_last_login', 'timestamp' - ], + ], [ '2024-08', institution._id, @@ -516,9 +515,8 @@ def test_get_report_format_json(self, app, url, institutional_admin, institution assert resp.headers['Content-Type'] == 'application/json; charset=utf-8' current_date = datetime.datetime.now().strftime('%Y-%m') - expected_filename = USER_INSTITUTION_REPORT_FILENAME.format( + expected_filename = REPORT_FILENAME_FORMAT.format( date_created=current_date, - institution_id=institution._id, format_type='json' ) assert resp.headers['Content-Disposition'] == f'attachment; filename="{expected_filename}"' @@ -602,9 +600,8 @@ def test_csv_tsv_ignores_pagination(self, app, url, institutional_admin, institu assert resp.headers['Content-Type'] == content_type current_date = datetime.datetime.now().strftime('%Y-%m') - expected_filename = USER_INSTITUTION_REPORT_FILENAME.format( + expected_filename = REPORT_FILENAME_FORMAT.format( date_created=current_date, - institution_id=institution._id, format_type=format_type ) assert resp.headers['Content-Disposition'] == f'attachment; filename="{expected_filename}"'