Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions analytics_data_api/insights_snowflake/toggles.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,45 @@

INSIGHTS_SNOWFLAKE_FLAG = 'insights_snowflake_enabled'
COURSE_ACTIVITY_SNOWFLAKE_FLAG = 'insights_snowflake_course_activity'
ENROLLMENT_SNOWFLAKE_FLAG = 'insights_snowflake_enrollment_enabled'
COURSE_SUMMARIES_SNOWFLAKE_FLAG = 'insights_snowflake_course_summaries_enabled'
ENGAGEMENT_SNOWFLAKE_FLAG = 'insights_snowflake_engagement_enabled'
PERFORMANCE_SNOWFLAKE_FLAG = 'insights_snowflake_performance_enabled'


def is_insights_snowflake_enabled(request):
"""Return whether Snowflake-backed Insights endpoints are enabled globally."""
return flag_is_active(request, INSIGHTS_SNOWFLAKE_FLAG)


def is_insights_snowflake_group_enabled(request, group_flag):
"""Return whether the global switch and a specific group switch are active."""
return is_insights_snowflake_enabled(request) and flag_is_active(request, group_flag)


def is_enrollment_snowflake_enabled(request):
"""Return whether enrollment endpoints should read from Snowflake."""
return is_insights_snowflake_group_enabled(request, ENROLLMENT_SNOWFLAKE_FLAG)


def is_course_summaries_snowflake_enabled(request):
"""Return whether course summary endpoints should read from Snowflake."""
return is_insights_snowflake_group_enabled(request, COURSE_SUMMARIES_SNOWFLAKE_FLAG)


def is_engagement_snowflake_enabled(request):
"""Return whether engagement endpoints should read from Snowflake."""
return is_insights_snowflake_group_enabled(request, ENGAGEMENT_SNOWFLAKE_FLAG)


def is_performance_snowflake_enabled(request):
"""Return whether performance endpoints should read from Snowflake."""
return is_insights_snowflake_group_enabled(request, PERFORMANCE_SNOWFLAKE_FLAG)


def is_course_activity_snowflake_enabled(request):
"""Return whether course activity should be read from Snowflake."""
return (
is_insights_snowflake_enabled(request) or
flag_is_active(request, COURSE_ACTIVITY_SNOWFLAKE_FLAG)
return is_insights_snowflake_enabled(request) and (
flag_is_active(request, COURSE_ACTIVITY_SNOWFLAKE_FLAG) or
flag_is_active(request, ENGAGEMENT_SNOWFLAKE_FLAG)
)
46 changes: 39 additions & 7 deletions analytics_data_api/tests/test_insights_snowflake.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,12 @@
)
from analytics_data_api.insights_snowflake.toggles import (
COURSE_ACTIVITY_SNOWFLAKE_FLAG,
ENGAGEMENT_SNOWFLAKE_FLAG,
INSIGHTS_SNOWFLAKE_FLAG,
is_course_activity_snowflake_enabled,
is_engagement_snowflake_enabled,
is_insights_snowflake_enabled,
is_insights_snowflake_group_enabled,
)
from analytics_data_api.snowflake_client import SnowflakeConfigurationError

Expand Down Expand Up @@ -1237,25 +1240,56 @@ def test_is_insights_snowflake_enabled_uses_global_flag(self, mock_flag_is_activ

mock_flag_is_active.assert_called_once_with(request, INSIGHTS_SNOWFLAKE_FLAG)

@patch('analytics_data_api.insights_snowflake.toggles.flag_is_active')
def test_group_flag_requires_global_flag(self, mock_flag_is_active):
request = Mock()
mock_flag_is_active.return_value = False

self.assertFalse(is_insights_snowflake_group_enabled(request, ENGAGEMENT_SNOWFLAKE_FLAG))

mock_flag_is_active.assert_called_once_with(request, INSIGHTS_SNOWFLAKE_FLAG)

@patch('analytics_data_api.insights_snowflake.toggles.flag_is_active')
def test_group_flag_requires_group_flag(self, mock_flag_is_active):
request = Mock()
mock_flag_is_active.side_effect = [True, False]

self.assertFalse(is_insights_snowflake_group_enabled(request, ENGAGEMENT_SNOWFLAKE_FLAG))

self.assertEqual(mock_flag_is_active.call_args_list[0].args, (request, INSIGHTS_SNOWFLAKE_FLAG))
self.assertEqual(mock_flag_is_active.call_args_list[1].args, (request, ENGAGEMENT_SNOWFLAKE_FLAG))

@patch('analytics_data_api.insights_snowflake.toggles.flag_is_active')
def test_engagement_group_flag_uses_global_and_group_flags(self, mock_flag_is_active):
request = Mock()
mock_flag_is_active.side_effect = [True, True]

self.assertTrue(is_engagement_snowflake_enabled(request))

self.assertEqual(mock_flag_is_active.call_args_list[0].args, (request, INSIGHTS_SNOWFLAKE_FLAG))
self.assertEqual(mock_flag_is_active.call_args_list[1].args, (request, ENGAGEMENT_SNOWFLAKE_FLAG))

@patch('analytics_data_api.insights_snowflake.toggles.flag_is_active')
def test_is_course_activity_snowflake_enabled_uses_global_flag(self, mock_flag_is_active):
request = Mock()
mock_flag_is_active.return_value = True
mock_flag_is_active.side_effect = [True, True]

self.assertTrue(is_course_activity_snowflake_enabled(request))

mock_flag_is_active.assert_called_once_with(request, INSIGHTS_SNOWFLAKE_FLAG)
self.assertEqual(mock_flag_is_active.call_args_list[0].args, (request, INSIGHTS_SNOWFLAKE_FLAG))
self.assertEqual(mock_flag_is_active.call_args_list[1].args, (request, COURSE_ACTIVITY_SNOWFLAKE_FLAG))

@patch('analytics_data_api.insights_snowflake.toggles.flag_is_active')
def test_is_course_activity_snowflake_enabled_uses_endpoint_flag(self, mock_flag_is_active):
request = Mock()
mock_flag_is_active.side_effect = [False, True]
mock_flag_is_active.side_effect = [True, False, True]

self.assertTrue(is_course_activity_snowflake_enabled(request))

self.assertEqual(mock_flag_is_active.call_count, 2)
self.assertEqual(mock_flag_is_active.call_count, 3)
self.assertEqual(mock_flag_is_active.call_args_list[0].args, (request, INSIGHTS_SNOWFLAKE_FLAG))
self.assertEqual(mock_flag_is_active.call_args_list[1].args, (request, COURSE_ACTIVITY_SNOWFLAKE_FLAG))
self.assertEqual(mock_flag_is_active.call_args_list[2].args, (request, ENGAGEMENT_SNOWFLAKE_FLAG))

@patch('analytics_data_api.insights_snowflake.toggles.flag_is_active')
def test_is_course_activity_snowflake_enabled_returns_false_when_flags_disabled(self, mock_flag_is_active):
Expand All @@ -1264,6 +1298,4 @@ def test_is_course_activity_snowflake_enabled_returns_false_when_flags_disabled(

self.assertFalse(is_course_activity_snowflake_enabled(request))

self.assertEqual(mock_flag_is_active.call_count, 2)
self.assertEqual(mock_flag_is_active.call_args_list[0].args, (request, INSIGHTS_SNOWFLAKE_FLAG))
self.assertEqual(mock_flag_is_active.call_args_list[1].args, (request, COURSE_ACTIVITY_SNOWFLAKE_FLAG))
mock_flag_is_active.assert_called_once_with(request, INSIGHTS_SNOWFLAKE_FLAG)
15 changes: 12 additions & 3 deletions analytics_data_api/v0/tests/views/test_course_summaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,10 @@ def test_get_uses_aurora_when_global_snowflake_flag_disabled(self):
course_id = CourseSamples.course_ids[1]
self.generate_data(ids=[course_id])

with patch('analytics_data_api.v0.views.course_summaries.is_insights_snowflake_enabled', return_value=False):
with patch(
'analytics_data_api.v0.views.course_summaries.is_course_summaries_snowflake_enabled',
return_value=False,
):
with patch('analytics_data_api.v0.views.course_summaries.get_course_summaries') as mock_get_summaries:
response = self.authenticated_get(
self.path({self.ids_param: [course_id], 'exclude': ['created']})
Expand All @@ -296,7 +299,10 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
course_id = CourseSamples.course_ids[1]
snowflake_data = [self.snowflake_summary(course_id)]

with patch('analytics_data_api.v0.views.course_summaries.is_insights_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.course_summaries.is_course_summaries_snowflake_enabled',
return_value=True,
):
with patch(
'analytics_data_api.v0.views.course_summaries.get_course_summaries',
return_value=snowflake_data,
Expand All @@ -320,7 +326,10 @@ def test_post_uses_snowflake_service_with_programs_and_recent_date(self):
recent = (datetime.datetime.today() - datetime.timedelta(5)).strftime('%Y-%m-%d')
snowflake_data = [self.snowflake_summary(course_id, programs=True, recent_count_change=5)]

with patch('analytics_data_api.v0.views.course_summaries.is_insights_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.course_summaries.is_course_summaries_snowflake_enabled',
return_value=True,
):
with patch(
'analytics_data_api.v0.views.course_summaries.get_course_summaries',
return_value=snowflake_data,
Expand Down
18 changes: 9 additions & 9 deletions analytics_data_api/v0/tests/views/test_courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def test_get_with_intervals(self, course_id):
def assertSnowflakeResponse(self, course_id, path, view_class, snowflake_data, expected):
mock_get_data = Mock(return_value=snowflake_data)

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.courses.is_enrollment_snowflake_enabled', return_value=True):
with patch.object(view_class, 'snowflake_service_function', staticmethod(mock_get_data)):
response = self.authenticated_get(f'/api/v1/courses/{course_id}{path}')

Expand Down Expand Up @@ -380,7 +380,7 @@ def test_get_returns_404_when_global_flag_enabled_and_no_snowflake_data(self):
course_id = CourseSamples.course_ids[0]
mock_get_data = Mock(return_value=[])

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.courses.is_enrollment_snowflake_enabled', return_value=True):
with patch.object(course_views.CourseEnrollmentView, 'snowflake_service_function',
staticmethod(mock_get_data)):
response = self.authenticated_get(f'/api/v1/courses/{course_id}/enrollment/')
Expand Down Expand Up @@ -516,7 +516,7 @@ def test_get_uses_aurora_when_global_snowflake_flag_disabled(self):
expected = self.format_as_response(latest_enrollment)
mock_get_data = Mock()

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=False):
with patch('analytics_data_api.v0.views.courses.is_enrollment_snowflake_enabled', return_value=False):
with patch.object(course_views.CourseEnrollmentView, 'snowflake_service_function',
staticmethod(mock_get_data)):
response = self.authenticated_get(f'/api/v1/courses/{course_id}/enrollment/')
Expand Down Expand Up @@ -939,7 +939,7 @@ def test_get_uses_aurora_when_global_snowflake_flag_disabled(self):
created=created,
)

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=False):
with patch('analytics_data_api.v0.views.courses.is_performance_snowflake_enabled', return_value=False):
with patch('analytics_data_api.v0.views.courses.get_course_problems') as mock_get_problems:
response = self._get_data(course_id)

Expand All @@ -965,7 +965,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
'created': created.strftime(settings.DATETIME_FORMAT),
}]

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.courses.is_performance_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.courses.get_course_problems',
return_value=snowflake_data,
Expand All @@ -980,7 +980,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
def test_get_returns_404_when_snowflake_service_returns_no_data(self):
course_id = CourseSamples.course_ids[0]

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.courses.is_performance_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.courses.get_course_problems',
return_value=[],
Expand Down Expand Up @@ -1147,7 +1147,7 @@ def test_get_uses_aurora_when_global_snowflake_flag_disabled(self):
pipeline_video_id=video_id, duration=100, segment_length=1, users_at_start=50, users_at_end=10,
created=created)

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=False), \
with patch('analytics_data_api.v0.views.courses.is_engagement_snowflake_enabled', return_value=False), \
patch('analytics_data_api.v0.views.courses.get_course_videos') as mock_get_videos:
response = self._get_data(course_id)

Expand Down Expand Up @@ -1177,7 +1177,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
'created': created.strftime(settings.DATETIME_FORMAT),
}]

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.courses.is_engagement_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.courses.get_course_videos',
return_value=snowflake_data,
Expand All @@ -1192,7 +1192,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
def test_get_returns_404_when_snowflake_service_returns_no_data(self):
course_id = CourseSamples.course_ids[0]

with patch('analytics_data_api.v0.views.courses.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.courses.is_engagement_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.courses.get_course_videos',
return_value=[],
Expand Down
6 changes: 3 additions & 3 deletions analytics_data_api/v0/tests/views/test_problems.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def test_get_404(self):
self.assertEqual(response.status_code, 404)

def test_get_uses_aurora_when_global_snowflake_flag_disabled(self):
with patch('analytics_data_api.v0.views.problems.is_insights_snowflake_enabled', return_value=False):
with patch('analytics_data_api.v0.views.problems.is_performance_snowflake_enabled', return_value=False):
with patch(
'analytics_data_api.v0.views.problems.get_problem_answer_distribution',
) as mock_get_answer_distribution:
Expand Down Expand Up @@ -190,7 +190,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
),
]

with patch('analytics_data_api.v0.views.problems.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.problems.is_performance_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.problems.get_problem_answer_distribution',
return_value=snowflake_data,
Expand All @@ -207,7 +207,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
mock_get_answer_distribution.assert_called_once_with(self.module_id1)

def test_get_returns_404_when_snowflake_service_returns_no_data(self):
with patch('analytics_data_api.v0.views.problems.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.problems.is_performance_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.problems.get_problem_answer_distribution',
return_value=[],
Expand Down
4 changes: 2 additions & 2 deletions analytics_data_api/v0/tests/views/test_programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def test_get_uses_aurora_when_global_snowflake_flag_disabled(self):
program_id = CourseSamples.program_ids[0]
self.generate_data(ids=[program_id])

with patch('analytics_data_api.v0.views.programs.is_insights_snowflake_enabled', return_value=False):
with patch('analytics_data_api.v0.views.programs.is_course_summaries_snowflake_enabled', return_value=False):
with patch('analytics_data_api.v0.views.programs.get_program_metadata') as mock_get_program_metadata:
response = self.authenticated_get(f'/api/v0/programs/?program_ids={program_id}&exclude=created')

Expand All @@ -131,7 +131,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
'course_ids': [self.course_id],
}]

with patch('analytics_data_api.v0.views.programs.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.programs.is_course_summaries_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.programs.get_program_metadata',
return_value=snowflake_data,
Expand Down
6 changes: 3 additions & 3 deletions analytics_data_api/v0/tests/views/test_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_get_uses_aurora_when_global_snowflake_flag_disabled(self):
G(models.VideoTimeline, pipeline_video_id=video_id, segment=0, num_users=10,
num_views=50, created=created)

with patch('analytics_data_api.v0.views.videos.is_insights_snowflake_enabled', return_value=False), \
with patch('analytics_data_api.v0.views.videos.is_engagement_snowflake_enabled', return_value=False), \
patch('analytics_data_api.v0.views.videos.get_video_timeline') as mock_get_timeline:
response = self._get_data(video_id)

Expand All @@ -96,7 +96,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
'created': created.strftime(settings.DATETIME_FORMAT),
}]

with patch('analytics_data_api.v0.views.videos.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.videos.is_engagement_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.videos.get_video_timeline',
return_value=snowflake_data,
Expand All @@ -111,7 +111,7 @@ def test_get_uses_snowflake_service_when_global_flag_enabled(self):
def test_get_returns_404_when_snowflake_service_returns_no_data(self):
video_id = 'v1d30'

with patch('analytics_data_api.v0.views.videos.is_insights_snowflake_enabled', return_value=True):
with patch('analytics_data_api.v0.views.videos.is_engagement_snowflake_enabled', return_value=True):
with patch(
'analytics_data_api.v0.views.videos.get_video_timeline',
return_value=[],
Expand Down
4 changes: 2 additions & 2 deletions analytics_data_api/v0/views/course_summaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from analytics_data_api.constants import enrollment_modes
from analytics_data_api.insights_snowflake.response_headers import InsightsDataSourceResponseMixin
from analytics_data_api.insights_snowflake.service import get_course_summaries
from analytics_data_api.insights_snowflake.toggles import is_insights_snowflake_enabled
from analytics_data_api.insights_snowflake.toggles import is_course_summaries_snowflake_enabled
from analytics_data_api.v0 import models, serializers
from analytics_data_api.v0.views import APIListView
from analytics_data_api.v0.views.utils import split_query_argument, validate_course_id
Expand Down Expand Up @@ -259,10 +259,10 @@
)
if data:
return data
raise Http404

Check failure on line 262 in analytics_data_api/v0/views/course_summaries.py

View workflow job for this annotation

GitHub Actions / Tests (ubuntu-latest, 3.11, main.test)

Missing coverage

Missing coverage on line 262

def get_queryset(self):
if is_insights_snowflake_enabled(self.request):
if is_course_summaries_snowflake_enabled(self.request):
self.set_insights_data_source_snowflake()
return self.get_snowflake_queryset()

Expand Down
Loading
Loading