Эх сурвалжийг харах

[api] Add usage analytics public API endpoints (#4117)

Implements GET and POST endpoints for managing usage analytics:
- GET /usage_analytics to retrieve preference
- POST /usage_analytics/update to modify preference
Harsh Gupta 7 сар өмнө
parent
commit
d0cc3fe2d8

+ 90 - 0
apps/about/src/about/api.py

@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+
+from rest_framework import status
+from rest_framework.response import Response
+
+from desktop.auth.backend import is_admin
+from desktop.lib.conf import coerce_bool
+from desktop.models import Settings
+
+LOG = logging.getLogger()
+
+
+def get_usage_analytics(request) -> Response:
+  """
+  Retrieve the user preference for analytics settings.
+
+  Args:
+    request (Request): The HTTP request object.
+
+  Returns:
+    Response: JSON response containing the analytics_enabled preference or an error message.
+
+  Raises:
+    403: If the user is not a Hue admin.
+    500: If there is an error retrieving preference.
+  """
+  if not is_admin(request.user):
+    return Response({'message': "You must be a Hue admin to access this endpoint."}, status=status.HTTP_403_FORBIDDEN)
+
+  try:
+    settings = Settings.get_settings()
+    return Response({'analytics_enabled': settings.collect_usage}, status=status.HTTP_200_OK)
+
+  except Exception as e:
+    message = f"Error retrieving usage analytics: {e}"
+    LOG.error(message)
+    return Response({'message': message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+def update_usage_analytics(request) -> Response:
+  """
+  Update the user preference for analytics settings.
+
+  Args:
+    request (Request): The HTTP request object containing 'analytics_enabled' parameter.
+
+  Returns:
+    Response: JSON response with the updated analytics_enabled preference or an error message.
+
+  Raises:
+    403: If the user is not a Hue admin.
+    400: If 'analytics_enabled' parameter is missing or invalid.
+    500: If there is an error updating preference.
+  """
+  if not is_admin(request.user):
+    return Response({'message': "You must be a Hue admin to access this endpoint."}, status=status.HTTP_403_FORBIDDEN)
+
+  try:
+    analytics_enabled = request.POST.get('analytics_enabled')
+
+    if analytics_enabled is None:
+      return Response({'message': 'Missing parameter: analytics_enabled is required.'}, status=status.HTTP_400_BAD_REQUEST)
+
+    settings = Settings.get_settings()
+    settings.collect_usage = coerce_bool(analytics_enabled)
+    settings.save()
+
+    return Response({'analytics_enabled': settings.collect_usage}, status=status.HTTP_200_OK)
+
+  except Exception as e:
+    message = f"Error updating usage analytics: {e}"
+    LOG.error(message)
+    return Response({'message': message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

+ 102 - 0
apps/about/src/about/api_tests.py

@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from unittest.mock import Mock, patch
+
+from rest_framework import status
+
+from about.api import get_usage_analytics, update_usage_analytics
+
+
+class TestUsageAnalyticsAPI:
+  def test_get_usage_analytics_success(self):
+    with patch('about.api.is_admin') as mock_is_admin:
+      with patch('about.api.Settings.get_settings') as mock_get_settings:
+        mock_is_admin.return_value = True
+        mock_get_settings.return_value = Mock(collect_usage=True)
+
+        request = Mock(method='GET', user=Mock())
+        response = get_usage_analytics(request)
+
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data == {'analytics_enabled': True}
+
+  def test_get_usage_analytics_unauthorized(self):
+    with patch('about.api.is_admin') as mock_is_admin:
+      mock_is_admin.return_value = False
+
+      request = Mock(method='GET', user=Mock())
+      response = get_usage_analytics(request)
+
+      assert response.status_code == status.HTTP_403_FORBIDDEN
+      assert response.data['message'] == "You must be a Hue admin to access this endpoint."
+
+  def test_get_usage_analytics_error(self):
+    with patch('about.api.is_admin') as mock_is_admin:
+      with patch('about.api.Settings.get_settings') as mock_get_settings:
+        mock_is_admin.return_value = True
+        mock_get_settings.side_effect = Exception("Test error")
+
+        request = Mock(method='GET', user=Mock())
+        response = get_usage_analytics(request)
+
+        assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
+        assert "Error retrieving usage analytics" in response.data['message']
+
+  def test_update_usage_analytics_success(self):
+    with patch('about.api.is_admin') as mock_is_admin:
+      with patch('about.api.Settings.get_settings') as mock_get_settings:
+        mock_is_admin.return_value = True
+        mock_get_settings.return_value = Mock(save=Mock())
+
+        request = Mock(method='POST', user=Mock(), POST={'analytics_enabled': 'true'})
+        response = update_usage_analytics(request)
+
+        assert response.status_code == status.HTTP_200_OK
+        assert mock_get_settings.return_value.save.called
+        assert response.data == {'analytics_enabled': True}
+
+  def test_update_usage_analytics_unauthorized(self):
+    with patch('about.api.is_admin') as mock_is_admin:
+      mock_is_admin.return_value = False
+
+      request = Mock(method='POST', user=Mock(), data={'analytics_enabled': 'true'})
+      response = update_usage_analytics(request)
+
+      assert response.status_code == status.HTTP_403_FORBIDDEN
+      assert response.data['message'] == "You must be a Hue admin to access this endpoint."
+
+  def test_update_usage_analytics_missing_param(self):
+    with patch('about.api.is_admin') as mock_is_admin:
+      mock_is_admin.return_value = True
+
+      request = Mock(method='POST', user=Mock(), POST={})
+      response = update_usage_analytics(request)
+
+      assert response.status_code == status.HTTP_400_BAD_REQUEST
+      assert response.data['message'] == 'Missing parameter: analytics_enabled is required.'
+
+  def test_update_usage_analytics_error(self):
+    with patch('about.api.is_admin') as mock_is_admin:
+      with patch('about.api.Settings.get_settings') as mock_get_settings:
+        mock_is_admin.return_value = True
+        mock_get_settings.side_effect = Exception("Test error")
+
+        request = Mock(method='POST', user=Mock(), POST={'analytics_enabled': 'true'})
+        response = update_usage_analytics(request)
+
+        assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
+        assert "Error updating usage analytics" in response.data['message']

+ 13 - 0
desktop/core/src/desktop/api_public.py

@@ -22,6 +22,7 @@ from django.http import HttpResponse, QueryDict
 from rest_framework.decorators import api_view, authentication_classes, permission_classes
 from rest_framework.permissions import AllowAny
 
+from about import api as about_api
 from beeswax import api as beeswax_api
 from desktop import api2 as desktop_api
 from desktop.auth.backend import rewrite_user
@@ -89,6 +90,18 @@ def available_app_examples(request):
   return desktop_api.available_app_examples(django_request)
 
 
+@api_view(["GET"])
+def get_usage_analytics(request):
+  django_request = get_django_request(request)
+  return about_api.get_usage_analytics(django_request)
+
+
+@api_view(["POST"])
+def update_usage_analytics(request):
+  django_request = get_django_request(request)
+  return about_api.update_usage_analytics(django_request)
+
+
 # Editor
 
 

+ 2 - 0
desktop/core/src/desktop/api_public_urls_v1.py

@@ -35,6 +35,8 @@ urlpatterns += [
   re_path(r'^logs/download/?$', api_public.download_hue_logs, name='core_download_hue_logs'),
   re_path(r'^install_app_examples/?$', api_public.install_app_examples, name='core_install_app_examples'),
   re_path(r'^available_app_examples/?$', api_public.available_app_examples, name='core_available_app_examples'),
+  re_path(r'^usage_analytics/?$', api_public.get_usage_analytics, name='core_get_usage_analytics'),
+  re_path(r'^usage_analytics/update/?$', api_public.update_usage_analytics, name='core_update_usage_analytics'),
   re_path(r'^get_config/?$', api_public.get_config),
   re_path(r'^check_config/?$', api_public.check_config, name='core_check_config'),
   re_path(r'^get_namespaces/(?P<interface>[\w\-]+)/?$', api_public.get_context_namespaces),  # To remove