def test_get_multi_users_progress_in_stories(self) -> None: all_users_stories_progress = ( story_fetchers.get_multi_users_progress_in_stories( [self.USER_ID], [self.story_id, 'invalid_story_id'])) all_stories = story_fetchers.get_stories_by_ids( [self.story_id, 'invalid_story_id']) # Should return None for invalid story ID. self.assertIsNone(all_stories[1]) user_stories_progress = all_users_stories_progress[self.USER_ID] self.assertEqual(len(user_stories_progress), 1) assert all_stories[0] is not None self.assertEqual(user_stories_progress[0]['id'], self.story_id) self.assertEqual(user_stories_progress[0]['completed_node_titles'], []) self.assertEqual(len(user_stories_progress[0]['all_node_dicts']), len(all_stories[0].story_contents.nodes)) self.assertEqual(user_stories_progress[0]['topic_name'], 'Topic') story_services.record_completed_node_in_story_context( # type: ignore[no-untyped-call] self.USER_ID, self.story_id, self.NODE_ID_1) all_users_stories_progress = ( story_fetchers.get_multi_users_progress_in_stories( [self.USER_ID], [self.story_id, 'invalid_story_id'])) user_stories_progress = all_users_stories_progress[self.USER_ID] self.assertEqual(len(user_stories_progress), 1) # Ruling out the possibility of None for mypy type checking. assert user_stories_progress[0] is not None self.assertEqual(user_stories_progress[0]['id'], self.story_id) self.assertEqual(user_stories_progress[0]['completed_node_titles'], ['Title 1']) self.assertEqual(user_stories_progress[0]['topic_name'], 'Topic')
def test_get_stories_by_ids(self) -> None: expected_story = self.story.to_dict() stories = story_fetchers.get_stories_by_ids([self.story_id]) # Ruling out the possibility of None for mypy type checking. assert stories[0] is not None self.assertEqual(len(stories), 1) self.assertEqual(stories[0].to_dict(), expected_story)
def test_get_stories_by_ids_for_non_existing_story_returns_none(self): non_exiting_story_id = 'invalid_id' expected_story = self.story.to_dict() stories = story_fetchers.get_stories_by_ids( [self.STORY_ID, non_exiting_story_id]) self.assertEqual(len(stories), 2) self.assertEqual(stories[0].to_dict(), expected_story) self.assertEqual(stories[1], None)
def regenerate_opportunities_related_to_topic( topic_id, delete_existing_opportunities=False): """Regenerates opportunity models which belongs to a given topic. Args: topic_id: str. The ID of the topic. delete_existing_opportunities: bool. Whether to delete all the existing opportunities related to the given topic. Returns: int. The number of opportunity models created. Raises: Exception. Failure to regenerate opportunities for given topic. """ if delete_existing_opportunities: exp_opportunity_models = ( opportunity_models.ExplorationOpportunitySummaryModel.get_by_topic( topic_id)) opportunity_models.ExplorationOpportunitySummaryModel.delete_multi( exp_opportunity_models) topic = topic_fetchers.get_topic_by_id(topic_id) story_ids = topic.get_canonical_story_ids() stories = story_fetchers.get_stories_by_ids(story_ids) exp_ids = [] non_existing_story_ids = [] for index, story in enumerate(stories): if story is None: non_existing_story_ids.append(story_ids[index]) else: exp_ids += story.story_contents.get_all_linked_exp_ids() exp_ids_to_exp = exp_fetchers.get_multiple_explorations_by_id(exp_ids, strict=False) non_existing_exp_ids = set(exp_ids) - set(exp_ids_to_exp.keys()) if len(non_existing_exp_ids) > 0 or len(non_existing_story_ids) > 0: raise Exception( 'Failed to regenerate opportunities for topic id: %s, ' 'missing_exp_with_ids: %s, missing_story_with_ids: %s' % (topic_id, list(non_existing_exp_ids), non_existing_story_ids)) exploration_opportunity_summary_list = [] for story in stories: for exp_id in story.story_contents.get_all_linked_exp_ids(): exploration_opportunity_summary_list.append( create_exp_opportunity_summary(topic, story, exp_ids_to_exp[exp_id])) _save_multi_exploration_opportunity_summary( exploration_opportunity_summary_list) return len(exploration_opportunity_summary_list)
def test_get_stories_by_ids_for_non_existing_story_returns_none( self) -> None: non_exiting_story_id = 'invalid_id' expected_story = self.story.to_dict() stories = story_fetchers.get_stories_by_ids( [self.story_id, non_exiting_story_id]) # Ruling out the possibility of None for mypy type checking. assert stories[0] is not None self.assertEqual(len(stories), 2) self.assertEqual(stories[0].to_dict(), expected_story) self.assertEqual(stories[1], None)
def get_story_titles_in_topic(topic): """Returns titles of the stories present in the topic. Args: topic: Topic. The topic domain objects. Returns: list(str). The list of story titles in the topic. """ canonical_story_references = topic.canonical_story_references story_ids = [story.story_id for story in canonical_story_references] stories = story_fetchers.get_stories_by_ids(story_ids) story_titles = [story.title for story in stories if story is not None] return story_titles
def _get_reviewable_exploration_opportunity_summaries( self, user_id, topic_name ): """Returns exploration opportunity summaries that have translation suggestions that are reviewable by the supplied user. The result is sorted in descending order by topic, story, and story node order. Args: user_id: str. The user ID of the user for which to filter translation suggestions. topic_name: str. A topic name for which to filter the exploration opportunity summaries. If 'All' is supplied, all available exploration opportunity summaries will be returned. Returns: list(ExplorationOpportunitySummary). A list of the matching exploration opportunity summaries. """ # 1. Fetch the eligible topics. # 2. Fetch the stories for the topics. # 3. Get the reviewable translation suggestion target IDs for the user. # 4. Get story exploration nodes in order, filtering for explorations # that have in review translation suggestions. if topic_name is None: topics = topic_fetchers.get_all_topics() else: topic = topic_fetchers.get_topic_by_name(topic_name) if topic is None: raise self.InvalidInputException( 'The supplied input topic: %s is not valid' % topic_name) topics = [topic] topic_stories = story_fetchers.get_stories_by_ids([ reference.story_id for topic in topics for reference in topic.get_all_story_references() if reference.story_is_published]) topic_exp_ids = [ node.exploration_id for story in topic_stories for node in story.story_contents.get_ordered_nodes() ] in_review_suggestions, _ = ( suggestion_services .get_reviewable_translation_suggestions_by_offset( user_id, topic_exp_ids, None, 0)) # Filter out suggestions that should not be shown to the user. # This is defined as a set as we only care about the unique IDs. in_review_suggestion_target_ids = { suggestion.target_id for suggestion in suggestion_services.get_suggestions_with_translatable_explorations( in_review_suggestions) } exp_ids = [ exp_id for exp_id in topic_exp_ids if exp_id in in_review_suggestion_target_ids ] return ( opportunity_services.get_exploration_opportunity_summaries_by_ids( exp_ids).values())
def test_get_stories_by_ids(self): expected_story = self.story.to_dict() stories = story_fetchers.get_stories_by_ids([self.STORY_ID]) self.assertEqual(len(stories), 1) self.assertEqual(stories[0].to_dict(), expected_story)
def get_matching_story_syllabus_item_dicts( topic: topic_domain.Topic, group_story_ids: List[str], keyword: Optional[str] = None ) -> List[story_domain.LearnerGroupSyllabusStorySummaryDict]: """Returns the matching story syllabus item dicts of the given topic that can be added to the learner group syllabus. Args: topic: Topic. The topic whose stories are to be searched. group_story_ids: list(str). The story ids of the learner group. keyword: Optional[str]. The keyword to search the stories. It is compared with the title of the story if passed in arguments. Returns: list(dict). The matching story syllabus item dicts of the given topic. """ story_ids = [ story.story_id for story in topic.canonical_story_references if (story.story_id not in group_story_ids and story.story_is_published is True) ] matching_stories = story_fetchers.get_story_summaries_by_ids(story_ids) stories = story_fetchers.get_stories_by_ids(story_ids) matching_story_syllabus_item_dicts: List[ story_domain.LearnerGroupSyllabusStorySummaryDict] = [] for ind, story_summary in enumerate(matching_stories): if keyword is None or keyword in story_summary.title.lower(): story = stories[ind] # Ruling out the possibility of None for mypy type checking. assert story is not None summary_dict = story_summary.to_dict() matching_story_syllabus_item_dicts.append({ 'id': summary_dict['id'], 'title': summary_dict['title'], 'description': summary_dict['description'], 'language_code': summary_dict['language_code'], 'version': summary_dict['version'], 'node_titles': summary_dict['node_titles'], 'thumbnail_filename': summary_dict['thumbnail_filename'], 'thumbnail_bg_color': summary_dict['thumbnail_bg_color'], 'url_fragment': summary_dict['url_fragment'], 'story_model_created_on': summary_dict['story_model_created_on'], 'story_model_last_updated': summary_dict['story_model_last_updated'], 'story_is_published': True, 'completed_node_titles': [], 'all_node_dicts': [node.to_dict() for node in story.story_contents.nodes], 'topic_name': topic.name, 'topic_url_fragment': topic.url_fragment }) return matching_story_syllabus_item_dicts