diff --git a/cl/api/utils.py b/cl/api/utils.py index 570c48c769..e7ab931147 100644 --- a/cl/api/utils.py +++ b/cl/api/utils.py @@ -735,25 +735,40 @@ def invert_user_logs( pipe = r.pipeline() dates = make_date_str_list(start, end) - for d in dates: - pipe.zrange(f"api:v3.user.d:{d}.counts", 0, -1, withscores=True) - results = pipe.execute() + + versions = ["v3", "v4"] + + for version in versions: + for d in dates: + pipe.zrange( + f"api:{version}.user.d:{d}.counts", 0, -1, withscores=True + ) + + all_results = pipe.execute() + results_by_version_all_dates = [ + all_results[i : i + len(dates)] + for i in range(0, len(all_results), len(dates)) + ] # results is a list of results for each of the zrange queries above. Zip # those results with the date that created it, and invert the whole thing. out: defaultdict = defaultdict(dict) - for d, result in zip(dates, results): - for user_id, count in result: - if user_id == "None" or user_id == "AnonymousUser": - user_id = "AnonymousUser" - else: - user_id = int(user_id) - count = int(count) - if out.get(user_id): - out[user_id][d] = count - out[user_id]["total"] += count - else: - out[user_id] = {d: count, "total": count} + for d, *results_by_version_for_date in zip( + dates, *results_by_version_all_dates + ): + for result in results_by_version_for_date: + for user_id, count in result: + if user_id == "None" or user_id == "AnonymousUser": + user_id = "AnonymousUser" + else: + user_id = int(user_id) + count = int(count) + if out.get(user_id): + existing_count = out[user_id].get(d, 0) + out[user_id][d] = existing_count + count + out[user_id]["total"] += count + else: + out[user_id] = {d: count, "total": count} # Sort the values for k, v in out.items(): diff --git a/cl/tests/api/utils.py b/cl/tests/api/utils.py new file mode 100644 index 0000000000..a15ce276a8 --- /dev/null +++ b/cl/tests/api/utils.py @@ -0,0 +1,94 @@ +from unittest.mock import MagicMock, patch + +from cl.api.utils import invert_user_logs +from cl.tests.cases import SimpleTestCase + +USER_1_ID = 1 +USER_2_ID = 2 +USER_3_ID = 3 + + +class TestApiUsage(SimpleTestCase): + + @patch("cl.api.utils.get_redis_interface") + def test_invert_user_logs_shows_v4_logs_one_date( + self, mock_get_redis_interface + ): + mock_redis = MagicMock() + mock_pipeline = MagicMock() + mock_get_redis_interface.return_value = mock_redis + mock_redis.pipeline.return_value = mock_pipeline + + mock_pipeline.execute.return_value = [ + # v3 + [(USER_1_ID, 10), (USER_2_ID, 20), (USER_3_ID, 30)], + # v4 + [(USER_1_ID, 15), (USER_2_ID, 25), (USER_3_ID, 35)], + ] + + results = invert_user_logs( + "2023-01-01", "2023-01-01", add_usernames=False + ) + + for date in ["2023-01-01"]: + mock_pipeline.zrange.assert_any_call( + f"api:v3.user.d:{date}.counts", 0, -1, withscores=True + ) + mock_pipeline.zrange.assert_any_call( + f"api:v4.user.d:{date}.counts", 0, -1, withscores=True + ) + + self.assertIn(USER_1_ID, results) + self.assertIn(USER_2_ID, results) + + self.assertEqual(results[USER_1_ID]["2023-01-01"], 25) + self.assertEqual(results[USER_1_ID]["total"], 25) + self.assertEqual(results[USER_2_ID]["2023-01-01"], 45) + self.assertEqual(results[USER_2_ID]["total"], 45) + self.assertEqual(results[USER_3_ID]["2023-01-01"], 65) + self.assertEqual(results[USER_3_ID]["total"], 65) + + @patch("cl.api.utils.get_redis_interface") + def test_invert_user_logs_shows_v4_logs_two_dates( + self, mock_get_redis_interface + ): + mock_redis = MagicMock() + mock_pipeline = MagicMock() + mock_get_redis_interface.return_value = mock_redis + mock_redis.pipeline.return_value = mock_pipeline + + mock_pipeline.execute.return_value = [ + # v3 + [(USER_2_ID, 20), (USER_3_ID, 30)], + [(USER_1_ID, 15), (USER_2_ID, 25), (USER_3_ID, 35)], + # v4 + [(USER_1_ID, 20), (USER_2_ID, 30)], + [(USER_1_ID, 25), (USER_2_ID, 35), (USER_3_ID, 45)], + ] + + results = invert_user_logs( + "2023-01-01", "2023-01-02", add_usernames=False + ) + + for date in ["2023-01-01", "2023-01-02"]: + mock_pipeline.zrange.assert_any_call( + f"api:v3.user.d:{date}.counts", 0, -1, withscores=True + ) + mock_pipeline.zrange.assert_any_call( + f"api:v4.user.d:{date}.counts", 0, -1, withscores=True + ) + + self.assertIn(USER_1_ID, results) + self.assertIn(USER_2_ID, results) + + self.assertEqual(results[USER_1_ID]["2023-01-01"], 20) + self.assertEqual(results[USER_1_ID]["2023-01-02"], 40) + self.assertEqual(results[USER_1_ID]["total"], 60) + + self.assertEqual(results[USER_2_ID]["2023-01-01"], 50) + self.assertEqual(results[USER_2_ID]["2023-01-02"], 60) + self.assertEqual(results[USER_2_ID]["total"], 110) + + self.assertEqual(results[USER_3_ID]["2023-01-01"], 30) + self.assertEqual(results[USER_3_ID]["2023-01-02"], 80) + self.assertEqual(results[USER_3_ID]["total"], 110)