def test_thread_closed_status_changed(self):
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CONTINUOUS_COMPUTATION_MANAGERS_FOR_TESTS):
            # Create test objects.
            exp_id = 'eid'
            self.save_new_valid_exploration(exp_id, 'owner')

            # Trigger thread creation events.
            self.process_and_flush_pending_tasks()
            feedback_services.create_thread(exp_id, 'a_state_name', None,
                                            'a subject', 'some text')
            self.process_and_flush_pending_tasks()

            self.assertEqual(
                ModifiedFeedbackAnalyticsAggregator.get_thread_analytics(
                    exp_id), {
                        'num_open_threads': 1,
                        'num_total_threads': 1,
                    })

            # Trigger close event.
            threadlist = feedback_services.get_threadlist(exp_id)
            thread_id = threadlist[0]['thread_id']
            feedback_services.create_message(
                thread_id, 'author', feedback_models.STATUS_CHOICES_FIXED,
                None, 'some text')
            self.process_and_flush_pending_tasks()

            self.assertEqual(
                ModifiedFeedbackAnalyticsAggregator.get_thread_analytics(
                    exp_id), {
                        'num_open_threads': 0,
                        'num_total_threads': 1,
                    })

            # Trigger thread status change event.
            threadlist = feedback_services.get_threadlist(exp_id)
            thread_id = threadlist[0]['thread_id']
            feedback_services.create_message(
                thread_id, 'author', feedback_models.STATUS_CHOICES_IGNORED,
                None, 'some text')
            self.process_and_flush_pending_tasks()

            self.assertEqual(
                ModifiedFeedbackAnalyticsAggregator.get_thread_analytics(
                    exp_id), {
                        'num_open_threads': 0,
                        'num_total_threads': 1,
                    })
Beispiel #2
0
    def test_thread_closed_job_running(self):
        with self._get_swap_context():
            # Create test objects.
            exp_id = 'eid'
            self.save_new_valid_exploration(exp_id, 'owner')

            # Trigger thread creation events.
            self.process_and_flush_pending_tasks()
            feedback_services.create_thread(
                exp_id, 'a_state_name', None, 'a subject', 'some text')
            self._flush_tasks_and_check_analytics(exp_id, {
                'num_open_threads': 1,
                'num_total_threads': 1,
            })

            # Trigger close event.
            threadlist = feedback_services.get_threadlist(exp_id)
            thread_id = threadlist[0]['thread_id']
            feedback_services.create_message(
                thread_id, 'author', feedback_models.STATUS_CHOICES_FIXED,
                None, 'some text')
            self._flush_tasks_and_check_analytics(exp_id, {
                'num_open_threads': 0,
                'num_total_threads': 1,
            })
Beispiel #3
0
    def test_feedback_thread_subscription(self):
        user_b_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_b_id, strict=False)
        user_c_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_c_id, strict=False)

        self.assertEqual(user_b_subscriptions_model, None)
        self.assertEqual(user_c_subscriptions_model, None)

        with self.swap(subscription_services, 'subscribe_to_thread',
                       self._null_fn), self.swap(subscription_services,
                                                 'subscribe_to_exploration',
                                                 self._null_fn):
            # User B starts a feedback thread.
            feedback_services.create_thread(self.EXP_ID_1, None,
                                            self.user_b_id, 'subject', 'text')
            # User C adds to that thread.
            thread_id = feedback_services.get_threadlist(
                self.EXP_ID_1)[0]['thread_id']
            feedback_services.create_message(thread_id, self.user_c_id, None,
                                             None, 'more text')

        self._run_one_off_job()

        # Both users are subscribed to the feedback thread.
        user_b_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_b_id)
        user_c_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_c_id)
        self.assertEqual(user_b_subscriptions_model.activity_ids, [])
        self.assertEqual(user_c_subscriptions_model.activity_ids, [])
        self.assertEqual(user_b_subscriptions_model.feedback_thread_ids,
                         [thread_id])
        self.assertEqual(user_c_subscriptions_model.feedback_thread_ids,
                         [thread_id])
    def test_feedback_ids(self):
        """Test various conventions for thread and message ids."""
        EXP_ID = '0'
        feedback_services.create_thread(
            EXP_ID, 'a_state_name', None, 'a subject', 'some text')
        threadlist = feedback_services.get_threadlist(EXP_ID)
        self.assertEqual(len(threadlist), 1)
        thread_id = threadlist[0]['thread_id']
        # The thread id should be prefixed with the exploration id and a full
        # stop.
        self.assertTrue(thread_id.startswith('%s.' % EXP_ID))
        # The rest of the thread id should not have any full stops.
        self.assertNotIn('.', thread_id[len(EXP_ID) + 1:])

        messages = feedback_services.get_messages(threadlist[0]['thread_id'])
        self.assertEqual(len(messages), 1)
        message_id = messages[0]['message_id']
        self.assertTrue(isinstance(message_id, int))

        # Retrieve the message instance from the storage layer.
        datastore_id = feedback_models.FeedbackMessageModel.get_messages(
            thread_id)[0].id
        # The message id should be prefixed with the thread id and a full stop,
        # followed by the message id.
        self.assertEqual(
            datastore_id, '%s.%s' % (thread_id, message_id))
 def test_status_of_newly_created_thread_is_open(self):
     EXP_ID = '0'
     feedback_services.create_thread(EXP_ID, 'a_state_name', None,
                                     'a subject', 'some text')
     threadlist = feedback_services.get_threadlist(EXP_ID)
     thread_status = threadlist[0]['status']
     self.assertEqual(thread_status, feedback_models.STATUS_CHOICES_OPEN)
    def test_feedback_ids(self):
        """Test various conventions for thread and message ids."""
        EXP_ID = '0'
        feedback_services.create_thread(EXP_ID, 'a_state_name', None,
                                        'a subject', 'some text')
        threadlist = feedback_services.get_threadlist(EXP_ID)
        self.assertEqual(len(threadlist), 1)
        thread_id = threadlist[0]['thread_id']
        # The thread id should be prefixed with the exploration id and a full
        # stop.
        self.assertTrue(thread_id.startswith('%s.' % EXP_ID))
        # The rest of the thread id should not have any full stops.
        self.assertNotIn('.', thread_id[len(EXP_ID) + 1:])

        messages = feedback_services.get_messages(threadlist[0]['thread_id'])
        self.assertEqual(len(messages), 1)
        message_id = messages[0]['message_id']
        self.assertTrue(isinstance(message_id, int))

        # Retrieve the message instance from the storage layer.
        datastore_id = feedback_models.FeedbackMessageModel.get_messages(
            thread_id)[0].id
        # The message id should be prefixed with the thread id and a full stop,
        # followed by the message id.
        self.assertEqual(datastore_id, '%s.%s' % (thread_id, message_id))
    def test_thread_closed_job_running(self):
        with self.swap(
            jobs_registry, "ALL_CONTINUOUS_COMPUTATION_MANAGERS", self.ALL_CONTINUOUS_COMPUTATION_MANAGERS_FOR_TESTS
        ):
            # Create test objects.
            exp_id = "eid"
            self.save_new_valid_exploration(exp_id, "owner")

            # Trigger thread creation events.
            self.process_and_flush_pending_tasks()
            feedback_services.create_thread(exp_id, "a_state_name", None, "a subject", "some text")
            self.process_and_flush_pending_tasks()

            self.assertEqual(
                ModifiedFeedbackAnalyticsAggregator.get_thread_analytics(exp_id),
                {"num_open_threads": 1, "num_total_threads": 1},
            )

            # Trigger close event.
            threadlist = feedback_services.get_threadlist(exp_id)
            thread_id = threadlist[0]["thread_id"]
            feedback_services.create_message(
                thread_id, "author", feedback_models.STATUS_CHOICES_FIXED, None, "some text"
            )
            self.process_and_flush_pending_tasks()

            self.assertEqual(
                ModifiedFeedbackAnalyticsAggregator.get_thread_analytics(exp_id),
                {"num_open_threads": 0, "num_total_threads": 1},
            )
 def test_status_of_newly_created_thread_is_open(self):
     EXP_ID = '0'
     feedback_services.create_thread(
         EXP_ID, 'a_state_name', None, 'a subject', 'some text')
     threadlist = feedback_services.get_threadlist(EXP_ID)
     thread_status = threadlist[0]['status']
     self.assertEqual(thread_status, feedback_models.STATUS_CHOICES_OPEN)   
Beispiel #9
0
    def test_multiple_commits_and_feedback_messages(self):
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CONTINUOUS_COMPUTATION_MANAGERS_FOR_TESTS):
            EXP_1_ID = 'eid1'
            EXP_1_TITLE = 'Title1'
            EXP_2_ID = 'eid2'
            EXP_2_TITLE = 'Title2'
            FEEDBACK_THREAD_SUBJECT = 'feedback thread subject'

            self.signup(self.EDITOR_EMAIL, self.EDITOR_USERNAME)
            self.EDITOR_ID = self.get_user_id_from_email(self.EDITOR_EMAIL)

            # User creates an exploration.
            self.save_new_valid_exploration(EXP_1_ID,
                                            self.EDITOR_ID,
                                            title=EXP_1_TITLE,
                                            category='Category')
            exp1_last_updated_ms = utils.get_time_in_millisecs(
                exp_services.get_exploration_by_id(EXP_1_ID).last_updated)

            # User gives feedback on it.
            feedback_services.create_thread(EXP_1_ID, None, self.EDITOR_ID,
                                            FEEDBACK_THREAD_SUBJECT, 'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_1_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            # User creates another exploration.
            self.save_new_valid_exploration(EXP_2_ID,
                                            self.EDITOR_ID,
                                            title=EXP_2_TITLE,
                                            category='Category')
            exp2_last_updated_ms = utils.get_time_in_millisecs(
                exp_services.get_exploration_by_id(EXP_2_ID).last_updated)

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT), 1)
            self.process_and_flush_pending_tasks()

            recent_notifications = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    self.EDITOR_ID)[1])
            self.assertEqual([(self._get_expected_exploration_created_dict(
                self.EDITOR_ID, EXP_2_ID, EXP_2_TITLE, exp2_last_updated_ms)),
                              {
                                  'activity_id': EXP_1_ID,
                                  'activity_title': EXP_1_TITLE,
                                  'author_id': self.EDITOR_ID,
                                  'last_updated_ms': message['created_on'],
                                  'subject': FEEDBACK_THREAD_SUBJECT,
                                  'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
                              },
                              (self._get_expected_exploration_created_dict(
                                  self.EDITOR_ID, EXP_1_ID, EXP_1_TITLE,
                                  exp1_last_updated_ms))],
                             recent_notifications)
    def test_making_feedback_thread_does_not_subscribe_to_exploration(self):
        with self._get_test_context():
            self.signup(USER_A_EMAIL, USER_A_USERNAME)
            user_a_id = self.get_user_id_from_email(USER_A_EMAIL)
            self.signup(USER_B_EMAIL, USER_B_USERNAME)
            user_b_id = self.get_user_id_from_email(USER_B_EMAIL)

            # User A creates an exploration.
            self.save_new_valid_exploration(
                EXP_ID, user_a_id, title=EXP_TITLE, category='Category')
            exp_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_ID))

            # User B starts a feedback thread.
            feedback_services.create_thread(
                EXP_ID, None, user_b_id, FEEDBACK_THREAD_SUBJECT, 'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT),
                1)
            self.process_and_flush_pending_tasks()

            recent_notifications_for_user_a = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_a_id)[1])
            recent_notifications_for_user_b = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_b_id)[1])
            expected_thread_notification = {
                'activity_id': EXP_ID,
                'activity_title': EXP_TITLE,
                'author_id': user_b_id,
                'last_updated_ms': message['created_on'],
                'subject': FEEDBACK_THREAD_SUBJECT,
                'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
            }
            expected_creation_notification = (
                self._get_expected_activity_created_dict(
                    user_a_id, EXP_ID, EXP_TITLE, 'exploration',
                    feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    exp_last_updated_ms))

            # User A sees A's commit and B's feedback thread.
            self.assertEqual(recent_notifications_for_user_a, [
                expected_thread_notification,
                expected_creation_notification
            ])
            # User B sees only her feedback thread, but no commits.
            self.assertEqual(recent_notifications_for_user_b, [
                expected_thread_notification,
            ])
    def test_multiple_exploration_commits_and_feedback_messages(self):
        with self._get_test_context():
            self.signup(self.EDITOR_EMAIL, self.EDITOR_USERNAME)
            editor_id = self.get_user_id_from_email(self.EDITOR_EMAIL)

            # User creates an exploration.
            self.save_new_valid_exploration(
                EXP_1_ID, editor_id, title=EXP_1_TITLE,
                category='Category')

            exp1_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_1_ID))

            # User gives feedback on it.
            feedback_services.create_thread(
                EXP_1_ID, None, editor_id, FEEDBACK_THREAD_SUBJECT,
                'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_1_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            # User creates another exploration.
            self.save_new_valid_exploration(
                EXP_2_ID, editor_id, title=EXP_2_TITLE,
                category='Category')
            exp2_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_2_ID))

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT),
                1)
            self.process_and_flush_pending_tasks()

            recent_notifications = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    editor_id)[1])
            self.assertEqual([(
                self._get_expected_activity_created_dict(
                    editor_id, EXP_2_ID, EXP_2_TITLE, 'exploration',
                    feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    exp2_last_updated_ms)
            ), {
                'activity_id': EXP_1_ID,
                'activity_title': EXP_1_TITLE,
                'author_id': editor_id,
                'last_updated_ms': message['created_on'],
                'subject': FEEDBACK_THREAD_SUBJECT,
                'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
            }, (
                self._get_expected_activity_created_dict(
                    editor_id, EXP_1_ID, EXP_1_TITLE, 'exploration',
                    feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    exp1_last_updated_ms)
            )], recent_notifications)
    def test_making_feedback_thread_does_not_subscribe_to_exploration(self):
        with self._get_test_context():
            self.signup(USER_A_EMAIL, USER_A_USERNAME)
            user_a_id = self.get_user_id_from_email(USER_A_EMAIL)
            self.signup(USER_B_EMAIL, USER_B_USERNAME)
            user_b_id = self.get_user_id_from_email(USER_B_EMAIL)

            # User A creates an exploration.
            self.save_new_valid_exploration(EXP_ID,
                                            user_a_id,
                                            title=EXP_TITLE,
                                            category='Category')
            exp_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_ID))

            # User B starts a feedback thread.
            feedback_services.create_thread(EXP_ID, None, user_b_id,
                                            FEEDBACK_THREAD_SUBJECT, 'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT), 1)
            self.process_and_flush_pending_tasks()

            recent_notifications_for_user_a = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_a_id)[1])
            recent_notifications_for_user_b = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_b_id)[1])
            expected_thread_notification = {
                'activity_id': EXP_ID,
                'activity_title': EXP_TITLE,
                'author_id': user_b_id,
                'last_updated_ms': message['created_on'],
                'subject': FEEDBACK_THREAD_SUBJECT,
                'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
            }
            expected_creation_notification = (
                self._get_expected_activity_created_dict(
                    user_a_id, EXP_ID, EXP_TITLE, 'exploration',
                    feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    exp_last_updated_ms))

            # User A sees A's commit and B's feedback thread.
            self.assertEqual(
                recent_notifications_for_user_a,
                [expected_thread_notification, expected_creation_notification])
            # User B sees only her feedback thread, but no commits.
            self.assertEqual(recent_notifications_for_user_b, [
                expected_thread_notification,
            ])
Beispiel #13
0
    def map(item):
        user_id = item.id
        job_queued_msec = RecentUpdatesMRJobManager._get_job_queued_msec()
        reducer_key = '%s@%s' % (user_id, job_queued_msec)

        activity_ids_list = item.activity_ids
        feedback_thread_ids_list = item.feedback_thread_ids

        activities = exp_models.ExplorationModel.get_multi(
            activity_ids_list, include_deleted=True)

        for ind, activity in enumerate(activities):
            if activity is None:
                logging.error(
                    'Could not find activity %s' % activity_ids_list[ind])
                continue

            metadata_obj = exp_models.ExplorationModel.get_snapshots_metadata(
                activity.id, [activity.version], allow_deleted=True)[0]
            yield (reducer_key, {
                'type': feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                'activity_id': activity.id,
                'activity_title': activity.title,
                'author_id': metadata_obj['committer_id'],
                'last_updated_ms': utils.get_time_in_millisecs(
                    activity.last_updated),
                'subject': (
                    feconf.COMMIT_MESSAGE_EXPLORATION_DELETED
                    if activity.deleted else metadata_obj['commit_message']
                ),
            })

            # If the user subscribes to this activity, he/she is automatically
            # subscribed to all feedback threads for this activity.
            if not activity.deleted:
                threads = feedback_services.get_threadlist(activity.id)
                for thread in threads:
                    if thread['thread_id'] not in feedback_thread_ids_list:
                        feedback_thread_ids_list.append(thread['thread_id'])

        for feedback_thread_id in feedback_thread_ids_list:
            last_message = (
                feedback_models.FeedbackMessageModel.get_most_recent_message(
                    feedback_thread_id))

            yield (reducer_key, {
                'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
                'activity_id': last_message.exploration_id,
                'activity_title': exp_models.ExplorationModel.get_by_id(
                    last_message.exploration_id).title,
                'author_id': last_message.author_id,
                'last_updated_ms': utils.get_time_in_millisecs(
                    last_message.created_on),
                'subject': last_message.get_thread_subject(),
            })
Beispiel #14
0
    def map(item):
        user_id = item.id
        job_queued_msec = RecentUpdatesMRJobManager._get_job_queued_msec()
        reducer_key = "%s@%s" % (user_id, job_queued_msec)

        exploration_ids_list = item.activity_ids
        collection_ids_list = item.collection_ids
        feedback_thread_ids_list = item.feedback_thread_ids

        (
            most_recent_activity_commits,
            tracked_exp_models_for_feedback,
        ) = RecentUpdatesMRJobManager._get_most_recent_activity_commits(
            exp_models.ExplorationModel,
            exploration_ids_list,
            "exploration",
            feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
            feconf.COMMIT_MESSAGE_EXPLORATION_DELETED,
        )

        for exp_model in tracked_exp_models_for_feedback:
            threads = feedback_services.get_threadlist(exp_model.id)
            for thread in threads:
                if thread["thread_id"] not in feedback_thread_ids_list:
                    feedback_thread_ids_list.append(thread["thread_id"])

        # TODO(bhenning): Implement a solution to having feedback threads for
        # collections.
        most_recent_activity_commits += (
            RecentUpdatesMRJobManager._get_most_recent_activity_commits(
                collection_models.CollectionModel,
                collection_ids_list,
                "collection",
                feconf.UPDATE_TYPE_COLLECTION_COMMIT,
                feconf.COMMIT_MESSAGE_COLLECTION_DELETED,
            )
        )[0]

        for recent_activity_commit_dict in most_recent_activity_commits:
            yield (reducer_key, recent_activity_commit_dict)

        for feedback_thread_id in feedback_thread_ids_list:
            last_message = feedback_models.FeedbackMessageModel.get_most_recent_message(feedback_thread_id)

            yield (
                reducer_key,
                {
                    "type": feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
                    "activity_id": last_message.exploration_id,
                    "activity_title": exp_models.ExplorationModel.get_by_id(last_message.exploration_id).title,
                    "author_id": last_message.author_id,
                    "last_updated_ms": utils.get_time_in_millisecs(last_message.created_on),
                    "subject": last_message.get_thread_subject(),
                },
            )
    def test_multiple_exploration_commits_and_feedback_messages(self):
        with self._get_test_context():
            self.signup(self.EDITOR_EMAIL, self.EDITOR_USERNAME)
            editor_id = self.get_user_id_from_email(self.EDITOR_EMAIL)

            # User creates an exploration.
            self.save_new_valid_exploration(EXP_1_ID,
                                            editor_id,
                                            title=EXP_1_TITLE,
                                            category='Category')

            exp1_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_1_ID))

            # User gives feedback on it.
            feedback_services.create_thread(EXP_1_ID, None, editor_id,
                                            FEEDBACK_THREAD_SUBJECT, 'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_1_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            # User creates another exploration.
            self.save_new_valid_exploration(EXP_2_ID,
                                            editor_id,
                                            title=EXP_2_TITLE,
                                            category='Category')
            exp2_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_2_ID))

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT), 1)
            self.process_and_flush_pending_tasks()

            recent_notifications = (ModifiedRecentUpdatesAggregator.
                                    get_recent_notifications(editor_id)[1])
            self.assertEqual(
                [(self._get_expected_activity_created_dict(
                    editor_id, EXP_2_ID, EXP_2_TITLE, 'exploration',
                    feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    exp2_last_updated_ms)), {
                        'activity_id': EXP_1_ID,
                        'activity_title': EXP_1_TITLE,
                        'author_id': editor_id,
                        'last_updated_ms': message['created_on'],
                        'subject': FEEDBACK_THREAD_SUBJECT,
                        'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
                    },
                 (self._get_expected_activity_created_dict(
                     editor_id, EXP_1_ID, EXP_1_TITLE, 'exploration',
                     feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                     exp1_last_updated_ms))], recent_notifications)
    def map(item):
        user_id = item.id
        job_queued_msec = RecentUpdatesMRJobManager._get_job_queued_msec()
        reducer_key = '%s@%s' % (user_id, job_queued_msec)

        exploration_ids_list = item.activity_ids
        collection_ids_list = item.collection_ids
        feedback_thread_ids_list = item.feedback_thread_ids

        (most_recent_activity_commits, tracked_exp_models_for_feedback) = (
            RecentUpdatesMRJobManager._get_most_recent_activity_commits(
                exp_models.ExplorationModel, exploration_ids_list,
                'exploration', feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                feconf.COMMIT_MESSAGE_EXPLORATION_DELETED))

        for exp_model in tracked_exp_models_for_feedback:
            threads = feedback_services.get_threadlist(exp_model.id)
            for thread in threads:
                if thread['thread_id'] not in feedback_thread_ids_list:
                    feedback_thread_ids_list.append(thread['thread_id'])

        # TODO(bhenning): Implement a solution to having feedback threads for
        # collections.
        most_recent_activity_commits += (
            RecentUpdatesMRJobManager._get_most_recent_activity_commits(
                collection_models.CollectionModel, collection_ids_list,
                'collection', feconf.UPDATE_TYPE_COLLECTION_COMMIT,
                feconf.COMMIT_MESSAGE_COLLECTION_DELETED))[0]

        for recent_activity_commit_dict in most_recent_activity_commits:
            yield (reducer_key, recent_activity_commit_dict)

        for feedback_thread_id in feedback_thread_ids_list:
            last_message = (feedback_models.FeedbackMessageModel.
                            get_most_recent_message(feedback_thread_id))

            yield (reducer_key, {
                'type':
                feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
                'activity_id':
                last_message.exploration_id,
                'activity_title':
                exp_models.ExplorationModel.get_by_id(
                    last_message.exploration_id).title,
                'author_id':
                last_message.author_id,
                'last_updated_ms':
                utils.get_time_in_millisecs(last_message.created_on),
                'subject':
                last_message.get_thread_subject(),
            })
    def test_feedback_thread_subscription(self):
        user_b_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_b_id, strict=False)
        user_c_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_c_id, strict=False)

        self.assertEqual(user_b_subscriptions_model, None)
        self.assertEqual(user_c_subscriptions_model, None)

        with self.swap(
                subscription_services, 'subscribe_to_thread', self._null_fn
            ), self.swap(
                subscription_services, 'subscribe_to_exploration',
                self._null_fn):
            # User B starts a feedback thread.
            feedback_services.create_thread(
                self.EXP_ID_1, None, self.user_b_id, 'subject', 'text')
            # User C adds to that thread.
            thread_id = feedback_services.get_threadlist(
                self.EXP_ID_1)[0]['thread_id']
            feedback_services.create_message(
                thread_id, self.user_c_id, None, None, 'more text')

        self._run_one_off_job()

        # Both users are subscribed to the feedback thread.
        user_b_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_b_id)
        user_c_subscriptions_model = user_models.UserSubscriptionsModel.get(
            self.user_c_id)

        self.assertEqual(user_b_subscriptions_model.activity_ids, [])
        self.assertEqual(user_c_subscriptions_model.activity_ids, [])
        self.assertEqual(
            user_b_subscriptions_model.feedback_thread_ids, [thread_id])
        self.assertEqual(
            user_c_subscriptions_model.feedback_thread_ids, [thread_id])
Beispiel #18
0
    def test_multiple_commits_and_feedback_messages(self):
        with self.swap(
                jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                self.ALL_CONTINUOUS_COMPUTATION_MANAGERS_FOR_TESTS):
            EXP_1_ID = 'eid1'
            EXP_1_TITLE = 'Title1'
            EXP_2_ID = 'eid2'
            EXP_2_TITLE = 'Title2'
            FEEDBACK_THREAD_SUBJECT = 'feedback thread subject'

            self.signup(self.EDITOR_EMAIL, self.EDITOR_USERNAME)
            self.EDITOR_ID = self.get_user_id_from_email(self.EDITOR_EMAIL)

            # User creates an exploration.
            self.save_new_valid_exploration(
                EXP_1_ID, self.EDITOR_ID, title=EXP_1_TITLE,
                category='Category')
            exp1_last_updated_ms = utils.get_time_in_millisecs(
                exp_services.get_exploration_by_id(EXP_1_ID).last_updated)

            # User gives feedback on it.
            feedback_services.create_thread(
                EXP_1_ID, None, self.EDITOR_ID, FEEDBACK_THREAD_SUBJECT,
                'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_1_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            # User creates another exploration.
            self.save_new_valid_exploration(
                EXP_2_ID, self.EDITOR_ID, title=EXP_2_TITLE,
                category='Category')
            exp2_last_updated_ms = utils.get_time_in_millisecs(
                exp_services.get_exploration_by_id(EXP_2_ID).last_updated)

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT),
                1)
            self.process_and_flush_pending_tasks()

            recent_notifications = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    self.EDITOR_ID)[1])
            self.assertEqual([(
                self._get_expected_exploration_created_dict(
                    self.EDITOR_ID, EXP_2_ID, EXP_2_TITLE,
                    exp2_last_updated_ms)
            ), {
                'activity_id': EXP_1_ID,
                'activity_title': EXP_1_TITLE,
                'author_id': self.EDITOR_ID,
                'last_updated_ms': message['created_on'],
                'subject': FEEDBACK_THREAD_SUBJECT,
                'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
            }, (
                self._get_expected_exploration_created_dict(
                    self.EDITOR_ID, EXP_1_ID, EXP_1_TITLE,
                    exp1_last_updated_ms)
            )], recent_notifications)
    def test_subscribing_to_exploration_subscribes_to_its_feedback_threads(self):
        with self.swap(
                jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                self.ALL_CONTINUOUS_COMPUTATION_MANAGERS_FOR_TESTS):
            EXP_ID = 'eid'
            EXP_TITLE = 'Title'
            FEEDBACK_THREAD_SUBJECT = 'feedback thread subject'

            USER_A_EMAIL = '*****@*****.**'
            USER_A_USERNAME = '******'
            self.signup(USER_A_EMAIL, USER_A_USERNAME)
            user_a_id = self.get_user_id_from_email(USER_A_EMAIL)

            USER_B_EMAIL = '*****@*****.**'
            USER_B_USERNAME = '******'
            self.signup(USER_B_EMAIL, USER_B_USERNAME)
            user_b_id = self.get_user_id_from_email(USER_B_EMAIL)

            # User A creates an exploration.
            self.save_new_valid_exploration(
                EXP_ID, user_a_id, title=EXP_TITLE, category='Category')
            exp_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_ID))

            # User B starts a feedback thread.
            feedback_services.create_thread(
                EXP_ID, None, user_b_id, FEEDBACK_THREAD_SUBJECT, 'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            # User A adds user B as an editor of the exploration.
            rights_manager.assign_role_for_exploration(
                user_a_id, EXP_ID, user_b_id, rights_manager.ROLE_EDITOR)

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT),
                1)
            self.process_and_flush_pending_tasks()

            recent_notifications_for_user_a = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_a_id)[1])
            recent_notifications_for_user_b = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_b_id)[1])
            expected_feedback_thread_notification_dict = {
                'activity_id': EXP_ID,
                'activity_title': EXP_TITLE,
                'author_id': user_b_id,
                'last_updated_ms': message['created_on'],
                'subject': FEEDBACK_THREAD_SUBJECT,
                'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
            }
            expected_exploration_created_notification_dict = (
                self._get_expected_activity_created_dict(
                    user_a_id, EXP_ID, EXP_TITLE, 'exploration',
                    feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    exp_last_updated_ms))

            # User A sees A's commit and B's feedback thread.
            self.assertEqual(recent_notifications_for_user_a, [
                expected_feedback_thread_notification_dict,
                expected_exploration_created_notification_dict
            ])
            # User B sees A's commit and B's feedback thread.
            self.assertEqual(recent_notifications_for_user_b, [
                expected_feedback_thread_notification_dict,
                expected_exploration_created_notification_dict,
            ])
Beispiel #20
0
    def test_subscribing_to_exploration_subscribes_to_its_feedback_threads(
            self):
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CONTINUOUS_COMPUTATION_MANAGERS_FOR_TESTS):
            EXP_ID = 'eid'
            EXP_TITLE = 'Title'
            FEEDBACK_THREAD_SUBJECT = 'feedback thread subject'

            USER_A_EMAIL = '*****@*****.**'
            USER_A_USERNAME = '******'
            self.signup(USER_A_EMAIL, USER_A_USERNAME)
            user_a_id = self.get_user_id_from_email(USER_A_EMAIL)

            USER_B_EMAIL = '*****@*****.**'
            USER_B_USERNAME = '******'
            self.signup(USER_B_EMAIL, USER_B_USERNAME)
            user_b_id = self.get_user_id_from_email(USER_B_EMAIL)

            # User A creates an exploration.
            self.save_new_valid_exploration(EXP_ID,
                                            user_a_id,
                                            title=EXP_TITLE,
                                            category='Category')
            exp_last_updated_ms = (
                self._get_most_recent_exp_snapshot_created_on_ms(EXP_ID))

            # User B starts a feedback thread.
            feedback_services.create_thread(EXP_ID, None, user_b_id,
                                            FEEDBACK_THREAD_SUBJECT, 'text')
            thread_id = (
                feedback_services.get_threadlist(EXP_ID)[0]['thread_id'])
            message = feedback_services.get_messages(thread_id)[0]

            # User A adds user B as an editor of the exploration.
            rights_manager.assign_role_for_exploration(
                user_a_id, EXP_ID, user_b_id, rights_manager.ROLE_EDITOR)

            ModifiedRecentUpdatesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    queue_name=taskqueue_services.QUEUE_NAME_DEFAULT), 1)
            self.process_and_flush_pending_tasks()

            recent_notifications_for_user_a = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_a_id)[1])
            recent_notifications_for_user_b = (
                ModifiedRecentUpdatesAggregator.get_recent_notifications(
                    user_b_id)[1])
            expected_feedback_thread_notification_dict = {
                'activity_id': EXP_ID,
                'activity_title': EXP_TITLE,
                'author_id': user_b_id,
                'last_updated_ms': message['created_on'],
                'subject': FEEDBACK_THREAD_SUBJECT,
                'type': feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
            }
            expected_exploration_created_notification_dict = (
                self._get_expected_activity_created_dict(
                    user_a_id, EXP_ID, EXP_TITLE, 'exploration',
                    feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    exp_last_updated_ms))

            # User A sees A's commit and B's feedback thread.
            self.assertEqual(recent_notifications_for_user_a, [
                expected_feedback_thread_notification_dict,
                expected_exploration_created_notification_dict
            ])
            # User B sees A's commit and B's feedback thread.
            self.assertEqual(recent_notifications_for_user_b, [
                expected_feedback_thread_notification_dict,
                expected_exploration_created_notification_dict,
            ])
Beispiel #21
0
 def get(self, exploration_id):
     self.values.update({
         'threads': feedback_services.get_threadlist(exploration_id)})
     self.render_json(self.values)
Beispiel #22
0
    def map(item):
        user_id = item.id
        job_queued_msec = RecentUpdatesMRJobManager._get_job_queued_msec()
        reducer_key = '%s@%s' % (user_id, job_queued_msec)

        activity_ids_list = item.activity_ids
        feedback_thread_ids_list = item.feedback_thread_ids

        activities = exp_models.ExplorationModel.get_multi(
            activity_ids_list, include_deleted=True)

        for ind, activity in enumerate(activities):
            if activity is None:
                logging.error('Could not find activity %s' %
                              activity_ids_list[ind])
                continue

            metadata_obj = exp_models.ExplorationModel.get_snapshots_metadata(
                activity.id, [activity.version], allow_deleted=True)[0]
            yield (reducer_key, {
                'type':
                feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                'activity_id':
                activity.id,
                'activity_title':
                activity.title,
                'author_id':
                metadata_obj['committer_id'],
                'last_updated_ms':
                utils.get_time_in_millisecs(activity.last_updated),
                'subject':
                (feconf.COMMIT_MESSAGE_EXPLORATION_DELETED
                 if activity.deleted else metadata_obj['commit_message']),
            })

            # If the user subscribes to this activity, he/she is automatically
            # subscribed to all feedback threads for this activity.
            if not activity.deleted:
                threads = feedback_services.get_threadlist(activity.id)
                for thread in threads:
                    if thread['thread_id'] not in feedback_thread_ids_list:
                        feedback_thread_ids_list.append(thread['thread_id'])

        for feedback_thread_id in feedback_thread_ids_list:
            last_message = (feedback_models.FeedbackMessageModel.
                            get_most_recent_message(feedback_thread_id))

            yield (reducer_key, {
                'type':
                feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
                'activity_id':
                last_message.exploration_id,
                'activity_title':
                exp_models.ExplorationModel.get_by_id(
                    last_message.exploration_id).title,
                'author_id':
                last_message.author_id,
                'last_updated_ms':
                utils.get_time_in_millisecs(last_message.created_on),
                'subject':
                last_message.get_thread_subject(),
            })
Beispiel #23
0
 def get(self, exploration_id):
     self.values.update({"threads": feedback_services.get_threadlist(exploration_id)})
     self.render_json(self.values)
Beispiel #24
0
    def map(item):
        user_id = item.id
        job_queued_msec = RecentUpdatesMRJobManager._get_job_queued_msec()
        reducer_key = "%s@%s" % (user_id, job_queued_msec)

        activity_ids_list = item.activity_ids
        feedback_thread_ids_list = item.feedback_thread_ids

        activity_models = exp_models.ExplorationModel.get_multi(activity_ids_list, include_deleted=True)

        for ind, activity_model in enumerate(activity_models):
            if activity_model is None:
                logging.error("Could not find activity %s" % activity_ids_list[ind])
                continue

            # Find the last commit that is not due to an automatic migration.
            latest_manual_commit_version = activity_model.version
            metadata_obj = exp_models.ExplorationModel.get_snapshots_metadata(
                activity_model.id, [latest_manual_commit_version], allow_deleted=True
            )[0]
            while metadata_obj["committer_id"] == feconf.MIGRATION_BOT_USER_ID:
                latest_manual_commit_version -= 1
                metadata_obj = exp_models.ExplorationModel.get_snapshots_metadata(
                    activity_model.id, [latest_manual_commit_version], allow_deleted=True
                )[0]

            yield (
                reducer_key,
                {
                    "type": feconf.UPDATE_TYPE_EXPLORATION_COMMIT,
                    "activity_id": activity_model.id,
                    "activity_title": activity_model.title,
                    "author_id": metadata_obj["committer_id"],
                    "last_updated_ms": metadata_obj["created_on_ms"],
                    "subject": (
                        feconf.COMMIT_MESSAGE_EXPLORATION_DELETED
                        if activity_model.deleted
                        else metadata_obj["commit_message"]
                    ),
                },
            )

            # If the user subscribes to this activity, he/she is automatically
            # subscribed to all feedback threads for this activity.
            if not activity_model.deleted:
                threads = feedback_services.get_threadlist(activity_model.id)
                for thread in threads:
                    if thread["thread_id"] not in feedback_thread_ids_list:
                        feedback_thread_ids_list.append(thread["thread_id"])

        for feedback_thread_id in feedback_thread_ids_list:
            last_message = feedback_models.FeedbackMessageModel.get_most_recent_message(feedback_thread_id)

            yield (
                reducer_key,
                {
                    "type": feconf.UPDATE_TYPE_FEEDBACK_MESSAGE,
                    "activity_id": last_message.exploration_id,
                    "activity_title": exp_models.ExplorationModel.get_by_id(last_message.exploration_id).title,
                    "author_id": last_message.author_id,
                    "last_updated_ms": utils.get_time_in_millisecs(last_message.created_on),
                    "subject": last_message.get_thread_subject(),
                },
            )