diff --git a/api/institutions/serializers.py b/api/institutions/serializers.py index 70afb93c157..e385044d9c9 100644 --- a/api/institutions/serializers.py +++ b/api/institutions/serializers.py @@ -359,6 +359,67 @@ def get_absolute_url(self): return None # there is no detail view for institution-users +class NewInstitutionSummaryMetricsSerializer(JSONAPISerializer): + '''serializer for institution-summary metrics + + used only when the INSTITUTIONAL_DASHBOARD_2024 feature flag is active + (and should be renamed without "New" when that flag is permanently active) + + Summary contains counts of + - Total users in the institution + - Total public project count for the institution + - Total private project count for the institution + - Total public registration count for the institution + - Total private registration count for the institution + - Total published preprint count for the institution + + ''' + + class Meta: + type_ = 'institution-monthly-summary' + + filterable_fields = frozenset({ + 'id', + 'user_count', + 'public_project_count', + 'private_project_count', + 'public_registration_count', + 'embargoed_registration_count', + 'published_preprint_count', + 'public_file_count', + 'storage_byte_count', + 'monthly_logged_in_user_count', + 'monthly_active_user_count', + }) + + id = IDField(read_only=True) + + user_count = ser.IntegerField(read_only=True) + public_project_count = ser.IntegerField(read_only=True) + private_project_count = ser.IntegerField(read_only=True) + public_registration_count = ser.IntegerField(read_only=True) + embargoed_registration_count = ser.IntegerField(read_only=True) + published_preprint_count = ser.IntegerField(read_only=True) + public_file_count = ser.IntegerField(read_only=True) + storage_byte_count = ser.IntegerField(read_only=True) + monthly_logged_in_user_count = ser.IntegerField(read_only=True) + monthly_active_user_count = ser.IntegerField(read_only=True) + + user = RelationshipField( + related_view='users:user-detail', + related_view_kwargs={'user_id': ''}, + ) + institution = RelationshipField( + related_view='institutions:institution-detail', + related_view_kwargs={'institution_id': ''}, + ) + + links = LinksField({}) + + def get_absolute_url(self): + return None # there is no detail view for institution-users + + class InstitutionRelated(JSONAPIRelationshipSerializer): id = ser.CharField(source='_id', required=False, allow_null=True) class Meta: diff --git a/api/institutions/urls.py b/api/institutions/urls.py index eb13d5728f0..9daffa65f73 100644 --- a/api/institutions/urls.py +++ b/api/institutions/urls.py @@ -13,7 +13,7 @@ re_path(r'^(?P\w+)/relationships/registrations/$', views.InstitutionRegistrationsRelationship.as_view(), name=views.InstitutionRegistrationsRelationship.view_name), re_path(r'^(?P\w+)/relationships/nodes/$', views.InstitutionNodesRelationship.as_view(), name=views.InstitutionNodesRelationship.view_name), re_path(r'^(?P\w+)/users/$', views.InstitutionUserList.as_view(), name=views.InstitutionUserList.view_name), - re_path(r'^(?P\w+)/metrics/summary/$', views.InstitutionSummaryMetrics.as_view(), name=views.InstitutionSummaryMetrics.view_name), + re_path(r'^(?P\w+)/metrics/summary/$', views.institution_summary_metrics_list_view, name=views.institution_summary_metrics_list_view.view_name), re_path(r'^(?P\w+)/metrics/departments/$', views.InstitutionDepartmentList.as_view(), name=views.InstitutionDepartmentList.view_name), re_path(r'^(?P\w+)/metrics/users/$', views.institution_user_metrics_list_view, name=views.institution_user_metrics_list_view.view_name), ] diff --git a/api/institutions/views.py b/api/institutions/views.py index c21dd6bddb7..1853b5da46e 100644 --- a/api/institutions/views.py +++ b/api/institutions/views.py @@ -12,7 +12,7 @@ from osf.metrics import InstitutionProjectCounts from osf.models import OSFUser, Node, Institution, Registration from osf.metrics import UserInstitutionProjectCounts -from osf.metrics.reports import InstitutionalUserReport +from osf.metrics.reports import InstitutionalUserReport, InstitutionMonthlySummaryReport from osf.utils import permissions as osf_permissions from api.base import permissions as base_permissions @@ -48,6 +48,7 @@ InstitutionDepartmentMetricsSerializer, NewInstitutionUserMetricsSerializer, OldInstitutionUserMetricsSerializer, + NewInstitutionSummaryMetricsSerializer ) from api.institutions.permissions import UserIsAffiliated from api.institutions.renderers import InstitutionDepartmentMetricsCSVRenderer, InstitutionUserMetricsCSVRenderer, MetricsCSVRenderer @@ -391,7 +392,7 @@ def create(self, *args, **kwargs): return ret -class InstitutionSummaryMetrics(JSONAPIBaseView, generics.RetrieveAPIView, InstitutionMixin): +class _OldInstitutionSummaryMetrics(JSONAPIBaseView, generics.RetrieveAPIView, InstitutionMixin): permission_classes = ( drf_permissions.IsAuthenticatedOrReadOnly, base_permissions.TokenHasScope, @@ -581,6 +582,66 @@ def get_default_search(self): ) +class _NewInstitutionSummaryMetricsList(InstitutionMixin, ElasticsearchListView): + '''list view for institution-summary metrics + + used only when the INSTITUTIONAL_DASHBOARD_2024 feature flag is active + (and should be renamed without "New" when that flag is permanently active) + ''' + permission_classes = ( + drf_permissions.IsAuthenticatedOrReadOnly, + base_permissions.TokenHasScope, + IsInstitutionalMetricsUser, + ) + + required_read_scopes = [CoreScopes.INSTITUTION_METRICS_READ] + required_write_scopes = [CoreScopes.NULL] + + view_category = 'institutions' + view_name = 'institution-summary-metrics' + + serializer_class = NewInstitutionSummaryMetricsSerializer + + ordering_fields = frozenset(( + 'id', + 'user_count', + 'public_project_count', + 'private_project_count', + 'public_registration_count', + 'embargoed_registration_count', + 'published_preprint_count', + 'public_file_count', + 'storage_byte_count', + 'monthly_logged_in_user_count', + 'monthly_active_user_count', + )) + + def get_default_search(self): + _yearmonth = InstitutionMonthlySummaryReport.most_recent_yearmonth() + if _yearmonth is None: + return None + return ( + InstitutionMonthlySummaryReport.search() + .filter('term', report_yearmonth=str(_yearmonth)) + .filter('term', institution_id=self.get_institution()._id) + ) + + +institution_user_metrics_list_view = toggle_view_by_flag( + flag_name=osf.features.INSTITUTIONAL_DASHBOARD_2024, + old_view=_OldInstitutionUserMetricsList.as_view(), + new_view=_NewInstitutionUserMetricsList.as_view(), +) +institution_user_metrics_list_view.view_name = 'institution-user-metrics' + +institution_summary_metrics_list_view = toggle_view_by_flag( + flag_name=osf.features.INSTITUTIONAL_DASHBOARD_2024, + old_view=_OldInstitutionSummaryMetrics.as_view(), + new_view=_NewInstitutionSummaryMetricsList.as_view(), +) +institution_summary_metrics_list_view.view_name = 'institution-summary-metrics' + + institution_user_metrics_list_view = toggle_view_by_flag( flag_name=osf.features.INSTITUTIONAL_DASHBOARD_2024, old_view=_OldInstitutionUserMetricsList.as_view(), diff --git a/osf/metrics/reports.py b/osf/metrics/reports.py index cee4efc7c02..08d14867ae0 100644 --- a/osf/metrics/reports.py +++ b/osf/metrics/reports.py @@ -270,3 +270,18 @@ class InstitutionalUserReport(MonthlyReport): published_preprint_count = metrics.Integer() public_file_count = metrics.Long() storage_byte_count = metrics.Long() + + +class InstitutionMonthlySummaryReport(MonthlyReport): + UNIQUE_TOGETHER_FIELDS = ('report_yearmonth', 'institution_id', ) + institution_id = metrics.Keyword() + user_count = metrics.Integer() + public_project_count = metrics.Integer() + private_project_count = metrics.Integer() + public_registration_count = metrics.Integer() + embargoed_registration_count = metrics.Integer() + published_preprint_count = metrics.Integer() + storage_byte_count = metrics.Long() + public_file_count = metrics.Long() + monthly_logged_in_user_count = metrics.Long() + monthly_active_user_count = metrics.Long()