diff --git a/ansible_wisdom/ai/api/telemetry/api_telemetry_settings_views.py b/ansible_wisdom/ai/api/telemetry/api_telemetry_settings_views.py index ae4571ceb..ac039ff2e 100644 --- a/ansible_wisdom/ai/api/telemetry/api_telemetry_settings_views.py +++ b/ansible_wisdom/ai/api/telemetry/api_telemetry_settings_views.py @@ -48,7 +48,7 @@ class TelemetrySettingsView(RetrieveAPIView, CreateAPIView): def get(self, request, *args, **kwargs): logger.debug("Telemetry settings:: GET handler") - if not settings.ADMIN_PORTAL_TELEMETRY_OPT_ENABLED: + if not settings.TELEMETRY_SCHEMA_2_ENABLED: raise ServiceUnavailable() exception = None @@ -94,7 +94,7 @@ def get(self, request, *args, **kwargs): def post(self, request, *args, **kwargs): logger.debug("Telemetry settings:: POST handler") - if not settings.ADMIN_PORTAL_TELEMETRY_OPT_ENABLED: + if not settings.TELEMETRY_SCHEMA_2_ENABLED: raise ServiceUnavailable() exception = None diff --git a/ansible_wisdom/ai/api/telemetry/tests/test_api_telemetry_settings_views.py b/ansible_wisdom/ai/api/telemetry/tests/test_api_telemetry_settings_views.py index 87534122e..1e5be73ac 100644 --- a/ansible_wisdom/ai/api/telemetry/tests/test_api_telemetry_settings_views.py +++ b/ansible_wisdom/ai/api/telemetry/tests/test_api_telemetry_settings_views.py @@ -36,7 +36,7 @@ def test_permission_classes(self, *args): for permission in required_permissions: self.assertTrue(permission in view.permission_classes) - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=False) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=False) def test_get_settings_when_feature_disabled(self, *args): self.client.force_authenticate(user=self.user) r = self.client.get(reverse('telemetry_settings')) @@ -80,7 +80,7 @@ def test_set_settings_authentication_error(self, *args): r = self.client.post(reverse('telemetry_settings')) self.assertEqual(r.status_code, HTTPStatus.UNAUTHORIZED) - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=False) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=False) def test_set_settings_when_feature_disabled(self, *args): self.client.force_authenticate(user=self.user) r = self.client.get(reverse('telemetry_settings')) diff --git a/ansible_wisdom/ai/api/utils/segment_analytics_telemetry.py b/ansible_wisdom/ai/api/utils/segment_analytics_telemetry.py index e4071ea05..eac01c3d4 100644 --- a/ansible_wisdom/ai/api/utils/segment_analytics_telemetry.py +++ b/ansible_wisdom/ai/api/utils/segment_analytics_telemetry.py @@ -3,6 +3,7 @@ from ai.api.utils.segment import base_send_segment_event, send_segment_event from attr import asdict from django.conf import settings +from organizations.models import Organization from segment.analytics import Client from users.models import User @@ -29,16 +30,29 @@ def send_segment_analytics_event(event_enum, event_payload_supplier, user: User) if not user.rh_user_has_seat: logger.info("Skipping analytics telemetry event for users that has no seat.") return - if not settings.ADMIN_PORTAL_TELEMETRY_OPT_ENABLED: - logger.info("Analytics telemetry not active.") - return - organization = user.organization + + organization: Organization = user.organization if not organization: logger.info("Analytics telemetry not active, because of no organization assigned for user.") return + if organization.telemetry_opt_out: - logger.info("Analytics telemetry not active for organization.") - return + if not organization.is_schema_2_telemetry_override_enabled: + logger.info("Analytics telemetry not active for organization.") + return + logger.info( + f'Organization {organization.id} telemetry settings overridden. ' + f'Telemetry will be captured for Organization {organization.id}.' + ) + + if not settings.TELEMETRY_SCHEMA_2_ENABLED: + if not organization.is_schema_2_telemetry_override_enabled: + logger.info("Analytics telemetry not active.") + return + logger.info( + f'System telemetry settings overridden. ' + f'Telemetry will be captured for Organization {organization.id}.' + ) event_name = event_enum.value try: diff --git a/ansible_wisdom/ai/api/utils/tests/test_segment_analytics_telemetry.py b/ansible_wisdom/ai/api/utils/tests/test_segment_analytics_telemetry.py index cd5f18622..d5517d61e 100644 --- a/ansible_wisdom/ai/api/utils/tests/test_segment_analytics_telemetry.py +++ b/ansible_wisdom/ai/api/utils/tests/test_segment_analytics_telemetry.py @@ -1,5 +1,6 @@ from unittest.mock import Mock, patch +import ai.feature_flags as feature_flags from ai.api.tests.test_views import WisdomServiceAPITestCaseBase from ai.api.utils import segment_analytics_telemetry from ai.api.utils.analytics_telemetry_model import ( @@ -76,9 +77,9 @@ def _assert_segment_analytics_error_sent(self, error, send_segment_event): @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=True) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=True) def test_send_segment_analytics_event(self, base_send_segment_event): - analytics_event_object = AnalyticsProductFeedback("3", 123) + analytics_event_object = AnalyticsProductFeedback(3, 123) payload = Mock(return_value=analytics_event_object) send_segment_analytics_event(AnalyticsTelemetryEvents.PRODUCT_FEEDBACK, payload, self.user) payload.assert_called() @@ -91,7 +92,7 @@ def test_send_segment_analytics_event(self, base_send_segment_event): @patch("ai.api.utils.segment_analytics_telemetry.send_segment_event") @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=True) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=True) def test_send_segment_analytics_event_error_validation(self, send_segment_event): payload = Mock(side_effect=ValueError) send_segment_analytics_event(AnalyticsTelemetryEvents.PRODUCT_FEEDBACK, payload, self.user) @@ -108,41 +109,70 @@ def test_send_segment_analytics_event_error_validation(self, send_segment_event) ) @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=True) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=True) def test_send_segment_analytics_event_error_not_write_key(self, base_send_segment_event): self._assert_event_not_sent(base_send_segment_event) @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=True) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=True) def test_send_segment_analytics_event_error_user_no_seat(self, base_send_segment_event): self.user.rh_user_has_seat = False self._assert_event_not_sent(base_send_segment_event) @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=False) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=False) def test_send_segment_analytics_event_error_no_telemetry_enabled(self, base_send_segment_event): self._assert_event_not_sent(base_send_segment_event) @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=True) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=True) def test_send_segment_analytics_event_error_no_org(self, base_send_segment_event): self.user.organization = None self._assert_event_not_sent(base_send_segment_event) @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") - @override_settings(ADMIN_PORTAL_TELEMETRY_OPT_ENABLED=True) + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=True) def test_send_segment_analytics_event_error_no_org_telemetry_enabled( self, base_send_segment_event ): self.user.organization.telemetry_opt_out = True self._assert_event_not_sent(base_send_segment_event) + @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=False) + @override_settings(LAUNCHDARKLY_SDK_KEY='dummy_key') + @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") + @patch.object(feature_flags, 'LDClient') + def test_send_segment_analytics_event_no_telemetry_enabled_with_override( + self, base_send_segment_event, LDClient + ): + LDClient.return_value.variation.return_value = str(self.user.organization.id) + self._assert_event_sent(base_send_segment_event) + + @override_settings(SEGMENT_ANALYTICS_WRITE_KEY="testWriteKey") + @override_settings(TELEMETRY_SCHEMA_2_ENABLED=True) + @override_settings(LAUNCHDARKLY_SDK_KEY='dummy_key') + @patch("ai.api.utils.segment_analytics_telemetry.base_send_segment_event") + @patch.object(feature_flags, 'LDClient') + def test_send_segment_analytics_event_no_org_telemetry_enabled_with_override( + self, base_send_segment_event, LDClient + ): + LDClient.return_value.variation.return_value = str(self.user.organization.id) + self.user.organization.telemetry_opt_out = True + self._assert_event_sent(base_send_segment_event) + + def _assert_event_sent(self, base_send_segment_event): + payload = Mock(return_value=AnalyticsProductFeedback(3, 123)) + send_segment_analytics_event(AnalyticsTelemetryEvents.PRODUCT_FEEDBACK, payload, self.user) + payload.assert_not_called() + base_send_segment_event.assert_called() + def _assert_event_not_sent(self, base_send_segment_event): - payload = Mock(return_value=AnalyticsProductFeedback("3", 123)) + payload = Mock(return_value=AnalyticsProductFeedback(3, 123)) send_segment_analytics_event(AnalyticsTelemetryEvents.PRODUCT_FEEDBACK, payload, self.user) payload.assert_not_called() base_send_segment_event.assert_not_called() diff --git a/ansible_wisdom/ai/feature_flags.py b/ansible_wisdom/ai/feature_flags.py index 395b15e00..93770c278 100644 --- a/ansible_wisdom/ai/feature_flags.py +++ b/ansible_wisdom/ai/feature_flags.py @@ -1,6 +1,7 @@ import logging import os.path from enum import Enum +from typing import Union from django.conf import settings from ldclient import Context @@ -13,7 +14,10 @@ class WisdomFlags(str, Enum): - MODEL_NAME = "model_name" # model name selection + # model name selection + MODEL_NAME = "model_name" + # white list of org_id's for which Schema 2 Telemetry is enabled overriding all other settings. + SCHEMA_2_TELEMETRY_ORG_ID_WHITE_LIST = "schema_2_telemetry_org_id_white_list" class FeatureFlags: @@ -42,9 +46,9 @@ def __init__(self): ) logger.info("feature flag client initialized") - def get(self, name: str, user: User, default: str): + def get(self, name: str, user: Union[User | None], default: str): if self.client: - if user.is_anonymous: + if not user or user.is_anonymous: user_context = Context.builder("AnonymousUser").anonymous(True).build() else: groups = list(user.groups.values_list("name", flat=True)) diff --git a/ansible_wisdom/ai/tests/test_feature_flags.py b/ansible_wisdom/ai/tests/test_feature_flags.py index 74385d51a..1d160eb17 100644 --- a/ansible_wisdom/ai/tests/test_feature_flags.py +++ b/ansible_wisdom/ai/tests/test_feature_flags.py @@ -23,7 +23,18 @@ def test_feature_flags_with_sdk_key(self, LDClient): ff = feature_flags.FeatureFlags() value = ff.get('model_name', self.user, 'default_value') + self.assert_test_feature_flags_with_sdk_key(LDClient, value) + @override_settings(LAUNCHDARKLY_SDK_KEY='dummy_key') + @patch.object(feature_flags, 'LDClient') + def test_feature_flags_with_sdk_key_without_user(self, LDClient): + LDClient.return_value.variation.return_value = 'server:port:model_name:index' + + ff = feature_flags.FeatureFlags() + value = ff.get('model_name', None, 'default_value') + self.assert_test_feature_flags_with_sdk_key(LDClient, value) + + def assert_test_feature_flags_with_sdk_key(self, LDClient, value): self.assertEqual(value, 'server:port:model_name:index') LDClient.assert_called_once() _, config_arg, kwargs = LDClient.mock_calls[0] diff --git a/ansible_wisdom/main/settings/development.py b/ansible_wisdom/main/settings/development.py index 0c7c482ba..a6257bc77 100644 --- a/ansible_wisdom/main/settings/development.py +++ b/ansible_wisdom/main/settings/development.py @@ -82,4 +82,4 @@ WCA_CLIENT_BACKEND_TYPE = os.getenv("WCA_CLIENT_BACKEND_TYPE", "dummy") # or wcaclient # Enable Telemetry Opt In/Out settings in the Admin Portal -ADMIN_PORTAL_TELEMETRY_OPT_ENABLED = True +TELEMETRY_SCHEMA_2_ENABLED = True diff --git a/ansible_wisdom/main/settings/production.py b/ansible_wisdom/main/settings/production.py index 9b1feb465..e8c9e1980 100644 --- a/ansible_wisdom/main/settings/production.py +++ b/ansible_wisdom/main/settings/production.py @@ -41,4 +41,4 @@ SESSION_CACHE_ALIAS = "default" # Disable Telemetry Opt In/Out settings in the Admin Portal -ADMIN_PORTAL_TELEMETRY_OPT_ENABLED = os.getenv('ADMIN_PORTAL_TELEMETRY_OPT_ENABLED', False) +TELEMETRY_SCHEMA_2_ENABLED = os.getenv('TELEMETRY_SCHEMA_2_ENABLED', False) diff --git a/ansible_wisdom/main/templates/console/console.html b/ansible_wisdom/main/templates/console/console.html index 46b655c0a..4db1bcf2c 100644 --- a/ansible_wisdom/main/templates/console/console.html +++ b/ansible_wisdom/main/templates/console/console.html @@ -18,5 +18,5 @@