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, })
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, })
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)
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, ])
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(), })
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])
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, ])
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, ])
def get(self, exploration_id): self.values.update({ 'threads': feedback_services.get_threadlist(exploration_id)}) self.render_json(self.values)
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(), })
def get(self, exploration_id): self.values.update({"threads": feedback_services.get_threadlist(exploration_id)}) self.render_json(self.values)
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(), }, )