def process(
        self, user_stats_model: user_models.UserStatsModel
    ) -> Iterable[user_models.UserStatsModel]:
        """Updates weekly dashboard stats with the current values.

        Args:
            user_stats_model: UserStatsModel. Model for which to update
                the weekly dashboard stats.

        Yields:
            UserStatsModel. The updated user stats model.
        """
        model = job_utils.clone_model(user_stats_model)

        schema_version = model.schema_version

        if schema_version != feconf.CURRENT_DASHBOARD_STATS_SCHEMA_VERSION:
            user_services.migrate_dashboard_stats_to_latest_schema(
                model)  # type: ignore[no-untyped-call]

        weekly_creator_stats = {
            user_services.get_current_date_as_string():
            {  # type: ignore[no-untyped-call]
                'num_ratings': model.num_ratings or 0,
                'average_ratings': model.average_ratings,
                'total_plays': model.total_plays or 0
            }
        }
        model.weekly_creator_stats_list.append(weekly_creator_stats)
        model.update_timestamps()
        yield model
Example #2
0
class UserDashboardStatsTests(test_utils.GenericTestBase):
    """Test whether exploration-related statistics of a user change as events
    are registered.
    """

    OWNER_EMAIL = '*****@*****.**'
    OWNER_USERNAME = '******'
    EXP_ID = 'exp1'

    USER_SESSION_ID = 'session1'

    CURRENT_DATE_AS_STRING = user_services.get_current_date_as_string()

    def setUp(self):
        super(UserDashboardStatsTests, self).setUp()
        self.signup(self.OWNER_EMAIL, self.OWNER_USERNAME)
        self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)

    def _mock_get_current_date_as_string(self):
        return self.CURRENT_DATE_AS_STRING

    def test_get_user_dashboard_stats(self):
        exploration = self.save_new_valid_exploration(self.EXP_ID,
                                                      self.owner_id,
                                                      end_state_name='End')
        init_state_name = exploration.init_state_name
        event_services.StartExplorationEventHandler.record(
            self.EXP_ID, 1, init_state_name, self.USER_SESSION_ID, {},
            feconf.PLAY_TYPE_NORMAL)
        self.assertEquals(
            user_jobs_continuous.UserStatsAggregator.get_dashboard_stats(
                self.owner_id), {
                    'total_plays': 0,
                    'num_ratings': 0,
                    'average_ratings': None
                })
        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         start_computation())
        self.process_and_flush_pending_tasks()
        self.assertEquals(
            user_jobs_continuous.UserStatsAggregator.get_dashboard_stats(
                self.owner_id), {
                    'total_plays': 1,
                    'num_ratings': 0,
                    'average_ratings': None
                })

    def test_get_weekly_dashboard_stats_when_stats_model_is_none(self):
        exploration = self.save_new_valid_exploration(self.EXP_ID,
                                                      self.owner_id,
                                                      end_state_name='End')
        init_state_name = exploration.init_state_name
        event_services.StartExplorationEventHandler.record(
            self.EXP_ID, 1, init_state_name, self.USER_SESSION_ID, {},
            feconf.PLAY_TYPE_NORMAL)
        self.assertEquals(
            user_services.get_weekly_dashboard_stats(self.owner_id), None)
        self.assertEquals(
            user_services.get_last_week_dashboard_stats(self.owner_id), None)

        with self.swap(user_services, 'get_current_date_as_string',
                       self._mock_get_current_date_as_string):
            user_services.update_dashboard_stats_log(self.owner_id)

        self.assertEquals(
            user_services.get_weekly_dashboard_stats(self.owner_id), [{
                self.CURRENT_DATE_AS_STRING: {
                    'total_plays': 0,
                    'num_ratings': 0,
                    'average_ratings': None
                }
            }])

    def test_get_weekly_dashboard_stats(self):
        exploration = self.save_new_valid_exploration(self.EXP_ID,
                                                      self.owner_id,
                                                      end_state_name='End')
        init_state_name = exploration.init_state_name
        event_services.StartExplorationEventHandler.record(
            self.EXP_ID, 1, init_state_name, self.USER_SESSION_ID, {},
            feconf.PLAY_TYPE_NORMAL)
        self.assertEquals(
            user_services.get_weekly_dashboard_stats(self.owner_id), None)
        self.assertEquals(
            user_services.get_last_week_dashboard_stats(self.owner_id), None)

        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         start_computation())
        self.process_and_flush_pending_tasks()

        self.assertEquals(
            user_services.get_weekly_dashboard_stats(self.owner_id), None)
        self.assertEquals(
            user_services.get_last_week_dashboard_stats(self.owner_id), None)

        with self.swap(user_services, 'get_current_date_as_string',
                       self._mock_get_current_date_as_string):
            user_services.update_dashboard_stats_log(self.owner_id)

        self.assertEquals(
            user_services.get_weekly_dashboard_stats(self.owner_id), [{
                self.CURRENT_DATE_AS_STRING: {
                    'total_plays': 1,
                    'num_ratings': 0,
                    'average_ratings': None
                }
            }])
Example #3
0
class DashboardStatsOneOffJobTests(test_utils.GenericTestBase):
    """Tests for the one-off dashboard stats job."""

    CURRENT_DATE_AS_STRING = user_services.get_current_date_as_string()
    DATE_AFTER_ONE_WEEK = (
        (datetime.datetime.utcnow() + datetime.timedelta(7)).strftime(
            feconf.DASHBOARD_STATS_DATETIME_STRING_FORMAT))

    USER_SESSION_ID = 'session1'

    EXP_ID_1 = 'exp_id_1'
    EXP_ID_2 = 'exp_id_2'
    EXP_VERSION = 1

    def _run_one_off_job(self):
        """Runs the one-off MapReduce job."""
        job_id = user_jobs_one_off.DashboardStatsOneOffJob.create_new()
        user_jobs_one_off.DashboardStatsOneOffJob.enqueue(job_id)
        self.assertEqual(
            self.count_jobs_in_taskqueue(
                taskqueue_services.QUEUE_NAME_ONE_OFF_JOBS), 1)
        self.process_and_flush_pending_tasks()

    def setUp(self):
        super(DashboardStatsOneOffJobTests, self).setUp()

        self.signup(self.OWNER_EMAIL, self.OWNER_USERNAME)
        self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)

    def _mock_get_current_date_as_string(self):
        return self.CURRENT_DATE_AS_STRING

    def _rate_exploration(self, user_id, exp_id, rating):
        rating_services.assign_rating_to_exploration(user_id, exp_id, rating)

    def _record_play(self, exp_id, state):
        event_services.StartExplorationEventHandler.record(
            exp_id, self.EXP_VERSION, state, self.USER_SESSION_ID, {},
            feconf.PLAY_TYPE_NORMAL)

    def test_weekly_stats_if_continuous_stats_job_has_not_been_run(self):
        exploration = self.save_new_valid_exploration(
            self.EXP_ID_1, self.owner_id)
        exp_id = exploration.id
        init_state_name = exploration.init_state_name
        self._record_play(exp_id, init_state_name)
        self._rate_exploration('user1', exp_id, 5)

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, None)
        self.assertEquals(
            user_services.get_last_week_dashboard_stats(self.owner_id), None)

        with self.swap(user_services,
                       'get_current_date_as_string',
                       self._mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        expected_results_list = [{
            self._mock_get_current_date_as_string(): {
                'num_ratings': 0,
                'average_ratings': None,
                'total_plays': 0
            }
        }]
        self.assertEqual(weekly_stats, expected_results_list)
        self.assertEquals(
            user_services.get_last_week_dashboard_stats(self.owner_id),
            expected_results_list[0])

    def test_weekly_stats_if_no_explorations(self):
        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         start_computation())
        self.process_and_flush_pending_tasks()

        with self.swap(user_services,
                       'get_current_date_as_string',
                       self._mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self._mock_get_current_date_as_string(): {
                'num_ratings': 0,
                'average_ratings': None,
                'total_plays': 0
            }
        }])

    def test_weekly_stats_for_single_exploration(self):
        exploration = self.save_new_valid_exploration(
            self.EXP_ID_1, self.owner_id)
        exp_id = exploration.id
        init_state_name = exploration.init_state_name
        self._record_play(exp_id, init_state_name)
        self._rate_exploration('user1', exp_id, 5)

        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         start_computation())
        self.process_and_flush_pending_tasks()

        with self.swap(user_services,
                       'get_current_date_as_string',
                       self._mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self._mock_get_current_date_as_string(): {
                'num_ratings': 1,
                'average_ratings': 5.0,
                'total_plays': 1
            }
        }])

    def test_weekly_stats_for_multiple_explorations(self):
        exploration_1 = self.save_new_valid_exploration(
            self.EXP_ID_1, self.owner_id)
        exp_id_1 = exploration_1.id
        exploration_2 = self.save_new_valid_exploration(
            self.EXP_ID_2, self.owner_id)
        exp_id_2 = exploration_2.id
        init_state_name_1 = exploration_1.init_state_name
        self._record_play(exp_id_1, init_state_name_1)
        self._rate_exploration('user1', exp_id_1, 5)
        self._rate_exploration('user2', exp_id_2, 4)

        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         start_computation())
        self.process_and_flush_pending_tasks()

        with self.swap(user_services,
                       'get_current_date_as_string',
                       self._mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self._mock_get_current_date_as_string(): {
                'num_ratings': 2,
                'average_ratings': 4.5,
                'total_plays': 1
            }
        }])

    def test_stats_for_multiple_weeks(self):
        exploration = self.save_new_valid_exploration(
            self.EXP_ID_1, self.owner_id)
        exp_id = exploration.id
        init_state_name = exploration.init_state_name
        self._rate_exploration('user1', exp_id, 4)
        self._record_play(exp_id, init_state_name)
        self._record_play(exp_id, init_state_name)

        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         start_computation())
        self.process_and_flush_pending_tasks()

        with self.swap(user_services,
                       'get_current_date_as_string',
                       self._mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self._mock_get_current_date_as_string(): {
                'num_ratings': 1,
                'average_ratings': 4.0,
                'total_plays': 2
            }
        }])

        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         stop_computation(self.owner_id))
        self.process_and_flush_pending_tasks()

        self._rate_exploration('user2', exp_id, 2)

        (user_jobs_continuous_test.ModifiedUserStatsAggregator.
         start_computation())
        self.process_and_flush_pending_tasks()

        def _mock_get_date_after_one_week():
            """Returns the date of the next week."""
            return self.DATE_AFTER_ONE_WEEK

        with self.swap(user_services,
                       'get_current_date_as_string',
                       _mock_get_date_after_one_week):
            self._run_one_off_job()

        expected_results_list = [
            {
                self._mock_get_current_date_as_string(): {
                    'num_ratings': 1,
                    'average_ratings': 4.0,
                    'total_plays': 2
                }
            },
            {
                _mock_get_date_after_one_week(): {
                    'num_ratings': 2,
                    'average_ratings': 3.0,
                    'total_plays': 2
                }
            }
        ]
        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, expected_results_list)
        self.assertEquals(
            user_services.get_last_week_dashboard_stats(self.owner_id),
            expected_results_list[1])
class DashboardStatsOneOffJobTests(test_utils.GenericTestBase):
    """Tests for the one-off dashboard stats job."""

    CURRENT_DATE_AS_STRING = user_services.get_current_date_as_string()
    DATE_AFTER_ONE_WEEK = ((datetime.datetime.utcnow() +
                            datetime.timedelta(7)).strftime(
                                feconf.DASHBOARD_STATS_DATETIME_STRING_FORMAT))

    USER_SESSION_ID = 'session1'

    EXP_ID_1 = 'exp_id_1'
    EXP_ID_2 = 'exp_id_2'
    EXP_VERSION = 1

    def _run_one_off_job(self):
        """Runs the one-off MapReduce job."""
        job_id = user_jobs_one_off.DashboardStatsOneOffJob.create_new()
        user_jobs_one_off.DashboardStatsOneOffJob.enqueue(job_id)
        self.assertEqual(
            self.count_jobs_in_mapreduce_taskqueue(
                taskqueue_services.QUEUE_NAME_ONE_OFF_JOBS), 1)
        self.process_and_flush_pending_mapreduce_tasks()

    def setUp(self):
        super(DashboardStatsOneOffJobTests, self).setUp()

        self.signup(self.OWNER_EMAIL, self.OWNER_USERNAME)
        self.owner_id = self.get_user_id_from_email(self.OWNER_EMAIL)

    def mock_get_current_date_as_string(self):
        return self.CURRENT_DATE_AS_STRING

    def _rate_exploration(self, user_id, exp_id, rating):
        """Assigns rating to the exploration corresponding to the given
        exploration id.

        Args:
            user_id: str. The user id.
            exp_id: str. The exploration id.
            rating: int. The rating to be assigned to the given exploration.
        """
        rating_services.assign_rating_to_exploration(user_id, exp_id, rating)

    def _record_play(self, exp_id, state):
        """Calls StartExplorationEventHandler and records the 'play' event
        corresponding to the given exploration id.

        Args:
            exp_id: str. The exploration id.
            state: dict(str, *). The state of the exploration corresponding to
                the given id.
        """
        event_services.StartExplorationEventHandler.record(
            exp_id, self.EXP_VERSION, state, self.USER_SESSION_ID, {},
            feconf.PLAY_TYPE_NORMAL)

    def test_weekly_stats(self):
        exploration = self.save_new_valid_exploration(self.EXP_ID_1,
                                                      self.owner_id)
        exp_id = exploration.id
        init_state_name = exploration.init_state_name
        self._record_play(exp_id, init_state_name)
        self._rate_exploration('user1', exp_id, 5)

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, None)
        self.assertEqual(
            user_services.get_last_week_dashboard_stats(self.owner_id), None)

        with self.swap(user_services, 'get_current_date_as_string',
                       self.mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self.mock_get_current_date_as_string(): {
                'num_ratings': 1,
                'average_ratings': 5.0,
                'total_plays': 1
            }
        }])
        self.assertEqual(
            user_services.get_last_week_dashboard_stats(self.owner_id), {
                self.mock_get_current_date_as_string(): {
                    'num_ratings': 1,
                    'average_ratings': 5.0,
                    'total_plays': 1
                }
            })

    def test_weekly_stats_if_no_explorations(self):
        with self.swap(user_services, 'get_current_date_as_string',
                       self.mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self.mock_get_current_date_as_string(): {
                'num_ratings': 0,
                'average_ratings': None,
                'total_plays': 0
            }
        }])

    def test_weekly_stats_for_single_exploration(self):
        exploration = self.save_new_valid_exploration(self.EXP_ID_1,
                                                      self.owner_id)
        exp_id = exploration.id
        init_state_name = exploration.init_state_name
        self._record_play(exp_id, init_state_name)
        self._rate_exploration('user1', exp_id, 5)
        event_services.StatsEventsHandler.record(
            self.EXP_ID_1, 1, {
                'num_starts': 1,
                'num_actual_starts': 0,
                'num_completions': 0,
                'state_stats_mapping': {}
            })

        self.process_and_flush_pending_tasks()

        with self.swap(user_services, 'get_current_date_as_string',
                       self.mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self.mock_get_current_date_as_string(): {
                'num_ratings': 1,
                'average_ratings': 5.0,
                'total_plays': 1
            }
        }])

    def test_weekly_stats_for_multiple_explorations(self):
        exploration_1 = self.save_new_valid_exploration(
            self.EXP_ID_1, self.owner_id)
        exp_id_1 = exploration_1.id
        exploration_2 = self.save_new_valid_exploration(
            self.EXP_ID_2, self.owner_id)
        exp_id_2 = exploration_2.id
        init_state_name_1 = exploration_1.init_state_name
        self._record_play(exp_id_1, init_state_name_1)
        self._rate_exploration('user1', exp_id_1, 5)
        self._rate_exploration('user2', exp_id_2, 4)
        event_services.StatsEventsHandler.record(
            self.EXP_ID_1, 1, {
                'num_starts': 1,
                'num_actual_starts': 0,
                'num_completions': 0,
                'state_stats_mapping': {}
            })

        self.process_and_flush_pending_tasks()

        with self.swap(user_services, 'get_current_date_as_string',
                       self.mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self.mock_get_current_date_as_string(): {
                'num_ratings': 2,
                'average_ratings': 4.5,
                'total_plays': 1
            }
        }])

    def test_stats_for_multiple_weeks(self):
        exploration = self.save_new_valid_exploration(self.EXP_ID_1,
                                                      self.owner_id)
        exp_id = exploration.id
        init_state_name = exploration.init_state_name
        self._rate_exploration('user1', exp_id, 4)
        self._record_play(exp_id, init_state_name)
        self._record_play(exp_id, init_state_name)
        event_services.StatsEventsHandler.record(
            self.EXP_ID_1, 1, {
                'num_starts': 2,
                'num_actual_starts': 0,
                'num_completions': 0,
                'state_stats_mapping': {}
            })

        self.process_and_flush_pending_tasks()

        with self.swap(user_services, 'get_current_date_as_string',
                       self.mock_get_current_date_as_string):
            self._run_one_off_job()

        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, [{
            self.mock_get_current_date_as_string(): {
                'num_ratings': 1,
                'average_ratings': 4.0,
                'total_plays': 2
            }
        }])

        self._rate_exploration('user2', exp_id, 2)

        def _mock_get_date_after_one_week():
            """Returns the date of the next week."""
            return self.DATE_AFTER_ONE_WEEK

        with self.swap(user_services, 'get_current_date_as_string',
                       _mock_get_date_after_one_week):
            self._run_one_off_job()

        expected_results_list = [{
            self.mock_get_current_date_as_string(): {
                'num_ratings': 1,
                'average_ratings': 4.0,
                'total_plays': 2
            }
        }, {
            _mock_get_date_after_one_week(): {
                'num_ratings': 2,
                'average_ratings': 3.0,
                'total_plays': 2
            }
        }]
        weekly_stats = user_services.get_weekly_dashboard_stats(self.owner_id)
        self.assertEqual(weekly_stats, expected_results_list)
        self.assertEqual(
            user_services.get_last_week_dashboard_stats(self.owner_id),
            expected_results_list[1])