예제 #1
0
    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')
예제 #2
0
 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)
예제 #3
0
 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)
예제 #4
0
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)
예제 #5
0
 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)
예제 #6
0
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
예제 #7
0
    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())
예제 #8
0
 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)
예제 #9
0
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