Exemplo n.º 1
0
    def map(item):
        if item.deleted:
            return

        # Do not upgrade explorations that fail non-strict validation.
        old_exploration = exp_services.get_exploration_by_id(item.id)
        try:
            old_exploration.validate()
        except Exception as e:
            logging.error('Exploration %s failed non-strict validation: %s' %
                          (item.id, e))
            return

        # If the exploration model being stored in the datastore is not the
        # most up-to-date states schema version, then update it.
        if (item.states_schema_version != feconf.CURRENT_STATE_SCHEMA_VERSION):
            # Note: update_exploration does not need to apply a change list in
            # order to perform a migration. See the related comment in
            # exp_services.apply_change_list for more information.
            #
            # Note: from_version and to_version really should be int, but left
            # as str to conform with legacy data.
            commit_cmds = [
                exp_domain.ExplorationChange({
                    'cmd':
                    exp_domain.CMD_MIGRATE_STATES_SCHEMA_TO_LATEST_VERSION,
                    'from_version':
                    str(item.states_schema_version),
                    'to_version':
                    str(feconf.CURRENT_STATE_SCHEMA_VERSION)
                })
            ]
            exp_services.update_exploration(
                feconf.MIGRATION_BOT_USERNAME, item.id, commit_cmds,
                'Update exploration states from schema version %d to %d.' %
                (item.states_schema_version,
                 feconf.CURRENT_STATE_SCHEMA_VERSION))
            yield ('SUCCESS', item.id)
Exemplo n.º 2
0
    def test_handler_returns_correct_data(self):
        exp_services.update_exploration(self.owner_id, '0', [
            exp_domain.ExplorationChange({
                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                'property_name': exp_domain.STATE_PROPERTY_CONTENT,
                'state_name': 'Introduction',
                'new_value': {
                    'content_id': 'content',
                    'html': '<p>A content to translate.</p>'
                }
            })
        ], 'Changes content.')

        output = self.get_json('/gettranslatabletexthandler',
                               params={
                                   'language_code': 'hi',
                                   'exp_id': '0'
                               })

        expected_output = {
            'version': 2,
            'state_names_to_content_id_mapping': {
                'Introduction': {
                    'content': {
                        'content': ('<p>A content to translate.</p>'),
                        'data_format': 'html'
                    }
                },
                'End State': {
                    'content': {
                        'content': '',
                        'data_format': 'html'
                    }
                }
            }
        }

        self.assertEqual(output, expected_output)
Exemplo n.º 3
0
    def test_pre_update_validate_change_cmd(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'], self.fake_date)

        change = {
            'cmd': exp_domain.CMD_ADD_STATE,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': suggestion.change.state_name,
            'new_value': 'new suggestion content',
            'old_value': None
        }
        with self.assertRaisesRegexp(
                Exception,
            ('The following extra attributes are present: new_value, '
             'old_value, property_name')):
            suggestion.pre_update_validate(
                exp_domain.ExplorationChange(change))
Exemplo n.º 4
0
    def test_get_exploration_version_history(self) -> None:
        version_history = exp_fetchers.get_exploration_version_history(
            self.EXP_1_ID, 2)

        self.assertIsNone(version_history)

        exp_services.update_exploration(  # type: ignore[no-untyped-call]
            self.owner_id, self.EXP_1_ID, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': 'New state'
                })
            ], 'A commit message.')
        version_history = exp_fetchers.get_exploration_version_history(
            self.EXP_1_ID, 2)

        self.assertIsNotNone(version_history)
        if version_history is not None:
            self.assertEqual(version_history.committer_ids, [self.owner_id])
            self.assertEqual(
                version_history.state_version_history['New state'].to_dict(),
                state_domain.StateVersionHistory(None, None,
                                                 self.owner_id).to_dict())
    def test_missing_state_id_mapping_model_failure(self):
        exp_services.update_exploration(
            self.owner_id, '0', [exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'New title'
            })], 'Changes.')
        exp_services.delete_state_id_mapping_model_for_exploration('0', 1)

        job = prod_validation_jobs_one_off.ProdValidationAuditOneOffJob
        job_id = job.create_new()
        job.enqueue(job_id)
        self.process_and_flush_pending_tasks()
        actual_output = job.get_output(job_id)
        expected_output = [
            (
                u'[u\'failed validation check for state_id_mapping_model '
                'field check of ExplorationModel\', '
                '[u"Model id 0: based on field state_id_mapping_model having '
                'value 0.1, expect model StateIdMappingModel with id 0.1 but '
                'it doesn\'t exist"]]'),
            u'[u\'fully-validated ExplorationModel\', 2]']
        self.assertEqual(sorted(actual_output), sorted(expected_output))
Exemplo n.º 6
0
    def put(self, exploration_id):
        """Updates properties of the given exploration.

        Args:
            exploration_id: str. Id of exploration to be updated.

        Raises:
            InvalidInputException: The exploration update operation failed.
            PageNotFoundException: No exploration data exist for given user id
                and exploration id.
        """
        exploration = exp_services.get_exploration_by_id(exploration_id)
        version = self.payload.get('version')
        _require_valid_version(version, exploration.version)
        commit_message = self.payload.get('commit_message')
        change_list_dict = self.payload.get('change_list')
        change_list = [
            exp_domain.ExplorationChange(change) for change in change_list_dict
        ]

        try:
            exp_services.update_exploration(self.user_id,
                                            exploration_id,
                                            change_list,
                                            commit_message,
                                            is_by_voice_artist=True)
        except utils.ValidationError as e:
            raise self.InvalidInputException(e)

        try:
            exploration_data = exp_services.get_user_exploration_data(
                self.user_id, exploration_id)
        except:
            raise self.PageNotFoundException

        self.values.update(exploration_data)
        self.render_json(self.values)
Exemplo n.º 7
0
    def _apply_state_name_changes(cls, prev_stats_dict, change_list_dict):
        """Update the state_stats_mapping to correspond with the changes
        in change_list.

        Args:
            prev_stats_dict: dict. A dict representation of an
                ExplorationStatsModel.
            change_list_dict: list(dict). A list of all of the commit cmds from
                the old_stats_model up to the next version.

        Returns:
            dict. A dict representation of an ExplorationStatsModel
                with updated state_stats_mapping and version.
        """
        change_list = [
            exp_domain.ExplorationChange(change) for change in change_list_dict
        ]
        exp_versions_diff = exp_domain.ExplorationVersionsDiff(change_list)

        # Handling state deletions, renames and additions (in that order). The
        # order in which the above changes are handled is important.

        for state_name in exp_versions_diff.deleted_state_names:
            prev_stats_dict['state_stats_mapping'].pop(state_name)

        for old_state_name, new_state_name in (
                exp_versions_diff.old_to_new_state_names.iteritems()):
            prev_stats_dict['state_stats_mapping'][new_state_name] = (
                prev_stats_dict['state_stats_mapping'].pop(old_state_name))

        for state_name in exp_versions_diff.added_state_names:
            prev_stats_dict['state_stats_mapping'][state_name] = (
                stats_domain.StateStats.create_default())

        prev_stats_dict['exp_version'] += 1

        return prev_stats_dict
Exemplo n.º 8
0
    def test_contributors_with_only_reverts_not_counted(self):
        """Test that contributors who have only done reverts do not
        have their user id appear in the contributor list.
        """
        # Sign up two users.
        self.signup(self.EMAIL_A, self.USERNAME_A)
        user_a_id = self.get_user_id_from_email(self.EMAIL_A)
        self.signup(self.EMAIL_B, self.USERNAME_B)
        user_b_id = self.get_user_id_from_email(self.EMAIL_B)
        # Have one user make two commits.
        exploration = self.save_new_valid_exploration(self.EXP_ID,
                                                      user_a_id,
                                                      title='Original Title')
        change_list = [
            exp_domain.ExplorationChange({
                'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                'property_name': 'title',
                'new_value': 'New title'
            })
        ]
        exp_services.update_exploration(user_a_id, self.EXP_ID, change_list,
                                        'Changed title.')

        # Have the second user revert version 2 to version 1.
        exp_services.revert_exploration(user_b_id, self.EXP_ID, 2, 1)

        # Run the job to compute the contributor ids.
        job_id = (
            exp_jobs_one_off.ExpSummariesContributorsOneOffJob.create_new())
        exp_jobs_one_off.ExpSummariesContributorsOneOffJob.enqueue(job_id)
        self.process_and_flush_pending_tasks()

        # Verify that the committer list does not contain the user
        # who only reverted.
        exploration_summary = exp_services.get_exploration_summary_by_id(
            exploration.id)
        self.assertEqual([user_a_id], exploration_summary.contributor_ids)
Exemplo n.º 9
0
    def put(self, exploration_id):
        """Updates properties of the given exploration."""
        exploration = exp_services.get_exploration_by_id(exploration_id)
        version = self.payload.get('version')
        _require_valid_version(version, exploration.version)

        commit_message = self.payload.get('commit_message')
        change_list_dict = self.payload.get('change_list')
        change_list = [
            exp_domain.ExplorationChange(change) for change in change_list_dict
        ]
        try:
            exp_services.update_exploration(self.user_id, exploration_id,
                                            change_list, commit_message)
        except utils.ValidationError as e:
            raise self.InvalidInputException(e)

        try:
            exploration_data = exp_services.get_user_exploration_data(
                self.user_id, exploration_id)
        except:
            raise self.PageNotFoundException
        self.values.update(exploration_data)
        self.render_json(self.values)
Exemplo n.º 10
0
    def test_pre_update_validate_change_new_value(self):
        expected_suggestion_dict = self.suggestion_dict
        suggestion = suggestion_registry.SuggestionEditStateContent(
            expected_suggestion_dict['suggestion_id'],
            expected_suggestion_dict['target_id'],
            expected_suggestion_dict['target_version_at_submission'],
            expected_suggestion_dict['status'], self.author_id,
            self.reviewer_id, expected_suggestion_dict['change'],
            expected_suggestion_dict['score_category'], self.fake_date)
        new_content = state_domain.SubtitledHtml(
            'content', '<p>new suggestion html</p>').to_dict()

        suggestion.change.new_value = new_content

        change = {
            'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
            'property_name': exp_domain.STATE_PROPERTY_CONTENT,
            'state_name': suggestion.change.state_name,
            'new_value': new_content,
            'old_value': None
        }
        with self.assertRaisesRegexp(
            Exception, 'The new html must not match the old html'):
            suggestion.pre_update_validate(exp_domain.ExplorationChange(change))
Exemplo n.º 11
0
    def _convert_states_v30_dict_to_v31_dict(cls, draft_change_list):
        """Converts draft change list from state version 30 to 31. State
        Version 31 adds the duration_secs float for the Voiceover
        section of state.

        Args:
            draft_change_list: list(ExplorationChange). The list of
                ExplorationChange domain objects to upgrade.

        Returns:
            list(ExplorationChange). The converted draft_change_list.
        """
        for i, change in enumerate(draft_change_list):
            if (change.cmd == exp_domain.CMD_EDIT_STATE_PROPERTY
                    and change.property_name
                    == exp_domain.STATE_PROPERTY_RECORDED_VOICEOVERS):
                # Get the language code to access the language code correctly.
                new_voiceovers_mapping = change.new_value['voiceovers_mapping']
                # Initialize the value to migrate draft state to v31.
                language_codes_to_audio_metadata = (
                    new_voiceovers_mapping.values())
                for language_codes in language_codes_to_audio_metadata:
                    for audio_metadata in language_codes.values():
                        audio_metadata['duration_secs'] = 0.0
                draft_change_list[i] = exp_domain.ExplorationChange({
                    'cmd':
                    exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'property_name':
                    (exp_domain.STATE_PROPERTY_RECORDED_VOICEOVERS),
                    'state_name':
                    change.state_name,
                    'new_value': {
                        'voiceovers_mapping': new_voiceovers_mapping
                    }
                })
        return draft_change_list
Exemplo n.º 12
0
    def test_answers_across_multiple_exp_versions_different_interactions(self):
        """Same as
        test_ignores_old_answers_if_new_interaction_has_no_new_answers except
        this also adds additional answers after changing the interaction a few
        times to ensure the aggregation job does not include answers across
        interaction changes, even if the interaction reverts back to a past
        interaction type with answers submitted to both versions of the
        exploration.
        """
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CC_MANAGERS_FOR_TESTS):

            # Setup example exploration.
            exp_id = 'eid'
            exp = self.save_new_valid_exploration(exp_id, '*****@*****.**')
            init_state_name = exp.init_state_name

            time_spent = 5.0
            params = {}

            # Add a few different answers.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'verb')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, '2')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'verb')

            # Change the interaction ID.
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'state_name': init_state_name,
                    'property_name': exp_domain.STATE_PROPERTY_INTERACTION_ID,
                    'new_value': 'NumericInput',
                })
            ], 'Change to NumericInput')

            # Submit an answer to the numeric interaction.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 2, init_state_name, 'NumericInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 2)

            # Change back the interaction ID.
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'state_name': init_state_name,
                    'property_name': exp_domain.STATE_PROPERTY_INTERACTION_ID,
                    'new_value': 'TextInput',
                })
            ], 'Change to TextInput')

            # Submit another number-like answer.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 3, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, '2')

            # Create a 4th exploration version by changing the state's content.
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'state_name': init_state_name,
                    'property_name': exp_domain.STATE_PROPERTY_CONTENT,
                    'new_value': {
                        'content_id': 'content',
                        'html': '<p>New content description</p>'
                    }
                })
            ], 'Change content description')

            # Submit some more answers to the latest exploration version.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 4, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'noun')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 4, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'verb')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 4, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'noun')

            exp = exp_fetchers.get_exploration_by_id(exp_id)
            self.assertEqual(exp.version, 4)

            # Run the answers aggregation job.
            MockInteractionAnswerSummariesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)
            self.process_and_flush_pending_tasks()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            calc_id = 'Top10AnswerFrequencies'

            # Check the output of the job.
            calc_output_latest_version_model = self._get_calc_output_model(
                exp_id, init_state_name, calc_id, exploration_version='4')
            calc_output_all_model = self._get_calc_output_model(
                exp_id, init_state_name, calc_id)

            self.assertEqual('Top10AnswerFrequencies',
                             calc_output_latest_version_model.calculation_id)
            self.assertEqual('Top10AnswerFrequencies',
                             calc_output_all_model.calculation_id)

            expected_calculation_latest_version_output = [{
                'answer': 'noun',
                'frequency': 2
            }, {
                'answer': 'verb',
                'frequency': 1
            }]

            # Only includes versions 3-4 since version 2 has a different
            # interaction ID. Note that the output is dependent on the order of
            # submission (verb submitted before 2 -> verb ranked higher).
            expected_calculation_all_versions_output = [{
                'answer': 'noun',
                'frequency': 2
            }, {
                'answer': 'verb',
                'frequency': 1
            }, {
                'answer': '2',
                'frequency': 1
            }]

            calculation_latest_version_output = (
                calc_output_latest_version_model.calculation_output)
            calculation_output_all = calc_output_all_model.calculation_output

            self.assertEqual(calculation_latest_version_output,
                             expected_calculation_latest_version_output)
            self.assertEqual(calculation_output_all,
                             expected_calculation_all_versions_output)
Exemplo n.º 13
0
    def test_uses_old_answers_if_updated_exploration_has_same_interaction(
            self):
        """Similar to
        test_ignores_old_answers_if_new_interaction_has_no_new_answers except
        this is demonstrating that if an exploration is updated and no new
        answers are submitted to the new version, but the interaction ID is the
        same then old answers should still be aggregated.
        """
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CC_MANAGERS_FOR_TESTS):

            # Setup example exploration.
            exp_id = 'eid'
            exp = self.save_new_valid_exploration(exp_id, '*****@*****.**')
            init_state_name = exp.init_state_name

            time_spent = 5.0
            params = {}

            # Add a few different answers.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'verb')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, '2')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'verb')

            # Change something other than the interaction ID.
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'state_name': init_state_name,
                    'property_name': exp_domain.STATE_PROPERTY_CONTENT,
                    'new_value': {
                        'content_id': 'content',
                        'html': '<p>New content</p>'
                    },
                })
            ], 'Change state content')

            exp = exp_fetchers.get_exploration_by_id(exp_id)
            self.assertEqual(exp.version, 2)

            # Run the answers aggregation job.
            MockInteractionAnswerSummariesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)
            self.process_and_flush_pending_tasks()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            calc_id = 'Top10AnswerFrequencies'

            # Extract the output from the job.
            calc_output_model_latest_version = self._get_calc_output_model(
                exp_id, init_state_name, calc_id, exploration_version='2')
            calc_output_model_all = self._get_calc_output_model(
                exp_id, init_state_name, calc_id)

            # Since no answers were submitted to the latest version of the
            # exploration, there should be no calculated output for it.
            self.assertIsNone(calc_output_model_latest_version)

            self.assertEqual('Top10AnswerFrequencies',
                             calc_output_model_all.calculation_id)
            calculation_output_all = calc_output_model_all.calculation_output
            expected_calculation_output_all_answers = [{
                'answer': 'verb',
                'frequency': 2
            }, {
                'answer': '2',
                'frequency': 1
            }]
            self.assertEqual(calculation_output_all,
                             expected_calculation_output_all_answers)
Exemplo n.º 14
0
    def test_ignores_old_answers_if_new_interaction_has_no_new_answers(self):
        """Similar to test_answers_across_multiple_exploration_versions except
        the exploration has changed interactions in the new versions. The
        aggregation job should not include answers corresponding to exploration
        versions which do not match the latest version's interaction ID.
        """
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CC_MANAGERS_FOR_TESTS):

            # Setup example exploration.
            exp_id = 'eid'
            exp = self.save_new_valid_exploration(exp_id, '*****@*****.**')
            init_state_name = exp.init_state_name

            time_spent = 5.0
            params = {}

            # Add a few different answers.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'verb')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, '2')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, 1, init_state_name, 'TextInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, 'verb')

            # Change the interaction ID.
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'state_name': init_state_name,
                    'property_name': exp_domain.STATE_PROPERTY_INTERACTION_ID,
                    'new_value': 'NumericInput',
                })
            ], 'Change to NumericInput')

            exp = exp_fetchers.get_exploration_by_id(exp_id)
            self.assertEqual(exp.version, 2)

            # Run the answers aggregation job.
            MockInteractionAnswerSummariesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)
            self.process_and_flush_pending_tasks()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            calc_id = 'Top10AnswerFrequencies'

            # Check the output of the job.
            calc_output_model_latest_version = self._get_calc_output_model(
                exp_id, init_state_name, calc_id, exploration_version='2')
            calc_output_model_first_version = self._get_calc_output_model(
                exp_id, init_state_name, calc_id, exploration_version='1')
            calc_output_model_all = self._get_calc_output_model(
                exp_id, init_state_name, calc_id)

            # Since no answers were submitted to the latest version of the
            # exploration, there should be no calculated output for it.
            self.assertIsNone(calc_output_model_latest_version)

            # Top answers will still be computed for the first version.
            self.assertEqual('Top10AnswerFrequencies',
                             calc_output_model_first_version.calculation_id)
            calculation_output_first = (
                calc_output_model_first_version.calculation_output)
            expected_calculation_output_first_answer = [{
                'answer': 'verb',
                'frequency': 2
            }, {
                'answer': '2',
                'frequency': 1
            }]
            self.assertEqual(calculation_output_first,
                             expected_calculation_output_first_answer)

            self.assertEqual('Top10AnswerFrequencies',
                             calc_output_model_all.calculation_id)

            # No answers should be aggregated since all past answers do not
            # match the newly submitted interaction ID.
            calculation_output_all = calc_output_model_all.calculation_output
            self.assertEqual(calculation_output_all, [])
Exemplo n.º 15
0
    def test_answers_across_multiple_exploration_versions(self):
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CC_MANAGERS_FOR_TESTS):

            # Setup example exploration.
            exp_id = 'eid'
            exp = self.save_new_valid_exploration(exp_id, '*****@*****.**')
            first_state_name = exp.init_state_name
            second_state_name = 'State 2'
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange(
                    {
                        'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                        'state_name': first_state_name,
                        'property_name':
                        exp_domain.STATE_PROPERTY_INTERACTION_ID,
                        'new_value': 'MultipleChoiceInput',
                    }),
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': second_state_name,
                }),
                exp_domain.ExplorationChange(
                    {
                        'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                        'state_name': second_state_name,
                        'property_name':
                        exp_domain.STATE_PROPERTY_INTERACTION_ID,
                        'new_value': 'MultipleChoiceInput',
                    })
            ], 'Add new state')
            exp = exp_fetchers.get_exploration_by_id(exp_id)
            exp_version = exp.version

            time_spent = 5.0
            params = {}

            # Add an answer.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, exp_version, first_state_name, 'MultipleChoiceInput',
                0, 0, exp_domain.EXPLICIT_CLASSIFICATION, 'session1',
                time_spent, params, 'answer1')

            # Run the answers aggregation job.
            MockInteractionAnswerSummariesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)
            self.process_and_flush_pending_tasks()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            calc_id = 'AnswerFrequencies'

            # Check the output of the job.
            calc_output_first_model = self._get_calc_output_model(
                exp_id, first_state_name, calc_id, exploration_version='2')
            calc_output_all_model = self._get_calc_output_model(
                exp_id, first_state_name, calc_id)

            self.assertEqual('AnswerFrequencies',
                             calc_output_first_model.calculation_id)
            self.assertEqual('AnswerFrequencies',
                             calc_output_all_model.calculation_id)

            calculation_output_first = (
                calc_output_first_model.calculation_output)
            calculation_output_all = calc_output_all_model.calculation_output

            expected_calculation_output_first_answer = [{
                'answer': 'answer1',
                'frequency': 1
            }]

            self.assertEqual(calculation_output_first,
                             expected_calculation_output_first_answer)
            self.assertEqual(calculation_output_all,
                             expected_calculation_output_first_answer)

            # Try modifying the exploration and adding another answer.
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': 'third state',
                })
            ], 'Adding yet another state')
            exp = exp_fetchers.get_exploration_by_id(exp_id)
            self.assertNotEqual(exp.version, exp_version)

            # Submit another answer.
            exp_version = exp.version
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, exp_version, first_state_name, 'MultipleChoiceInput',
                0, 0, exp_domain.EXPLICIT_CLASSIFICATION, 'session2',
                time_spent, params, 'answer1')

            # Run the aggregator again.
            MockInteractionAnswerSummariesAggregator.stop_computation('a')
            MockInteractionAnswerSummariesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)
            self.process_and_flush_pending_tasks()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            # Extract the output from the job.
            calc_output_first_model = self._get_calc_output_model(
                exp_id, first_state_name, calc_id, exploration_version='2')
            calc_output_second_model = self._get_calc_output_model(
                exp_id, first_state_name, calc_id, exploration_version='3')
            calc_output_all_model = self._get_calc_output_model(
                exp_id, first_state_name, calc_id)

            self.assertEqual('AnswerFrequencies',
                             calc_output_first_model.calculation_id)
            self.assertEqual('AnswerFrequencies',
                             calc_output_second_model.calculation_id)
            self.assertEqual('AnswerFrequencies',
                             calc_output_all_model.calculation_id)

            calculation_output_first = (
                calc_output_first_model.calculation_output)
            calculation_output_second = (
                calc_output_second_model.calculation_output)
            calculation_output_all = (calc_output_all_model.calculation_output)

            # The output for version 2 of the exploration should be the same,
            # but the total combined output should include both answers. Also,
            # the output for version 3 should only include the second answer.
            expected_calculation_output_second_answer = [{
                'answer': 'answer1',
                'frequency': 1
            }]
            expected_calculation_output_all_answers = [{
                'answer': 'answer1',
                'frequency': 2
            }]

            self.assertEqual(calculation_output_first,
                             expected_calculation_output_first_answer)
            self.assertEqual(calculation_output_second,
                             expected_calculation_output_second_answer)
            self.assertEqual(calculation_output_all,
                             expected_calculation_output_all_answers)
Exemplo n.º 16
0
def get_summary_of_change_list(base_exploration, change_list):
    """Applies a changelist to a pristine exploration and returns a summary.

    Each entry in change_list is a dict that represents an ExplorationChange
    object.

    Returns:
      a dict with five keys:
        exploration_property_changes: a dict, where each key is a property_name
          of the exploration, and the corresponding values are dicts with keys
          old_value and new_value.
        state_property_changes: a dict, where each key is a state name, and the
          corresponding values are dicts; the keys of these dicts represent
          properties of the state, and the corresponding values are dicts with
          keys old_value and new_value. If a state name is changed, this is
          listed as a property name change under the old state name in the
          outer dict.
        changed_states: a list of state names. This indicates that the state
          has changed but we do not know what the changes are. This can happen
          for complicated operations like removing a state and later adding a
          new state with the same name as the removed state.
        added_states: a list of added state names.
        deleted_states: a list of deleted state names.
    """
    # TODO(sll): This really needs tests, especially the diff logic. Probably
    # worth comparing with the actual changed exploration.

    # Ensure that the original exploration does not get altered.
    exploration = copy.deepcopy(base_exploration)

    changes = [
        exp_domain.ExplorationChange(change_dict)
        for change_dict in change_list]

    exploration_property_changes = {}
    state_property_changes = {}
    changed_states = []
    added_states = []
    deleted_states = []

    original_state_names = {
        state_name: state_name for state_name in exploration.states.keys()
    }

    for change in changes:
        if change.cmd == 'add_state':
            if change.state_name in changed_states:
                continue
            elif change.state_name in deleted_states:
                changed_states.append(change.state_name)
                del state_property_changes[change.state_name]
                deleted_states.remove(change.state_name)
            else:
                added_states.append(change.state_name)
                original_state_names[change.state_name] = change.state_name
        elif change.cmd == 'rename_state':
            orig_state_name = original_state_names[change.old_state_name]
            original_state_names[change.new_state_name] = orig_state_name

            if orig_state_name in changed_states:
                continue

            if orig_state_name not in state_property_changes:
                state_property_changes[orig_state_name] = {}
            if 'name' not in state_property_changes[orig_state_name]:
                state_property_changes[orig_state_name]['name'] = {
                    'old_value': change.old_state_name
                }
            state_property_changes[orig_state_name]['name']['new_value'] = (
                change.new_state_name)
        elif change.cmd == 'delete_state':
            orig_state_name = original_state_names[change.state_name]
            if orig_state_name in changed_states:
                continue
            elif orig_state_name in added_states:
                added_states.remove(orig_state_name)
            else:
                deleted_states.append(orig_state_name)
        elif change.cmd == 'edit_state_property':
            orig_state_name = original_state_names[change.state_name]
            if orig_state_name in changed_states:
                continue

            property_name = change.property_name

            if orig_state_name not in state_property_changes:
                state_property_changes[orig_state_name] = {}
            if property_name not in state_property_changes[orig_state_name]:
                state_property_changes[orig_state_name][property_name] = {
                    'old_value': change.old_value
                }
            state_property_changes[orig_state_name][property_name][
                'new_value'] = change.new_value
        elif change.cmd == 'edit_exploration_property':
            property_name = change.property_name

            if property_name not in exploration_property_changes:
                exploration_property_changes[property_name] = {
                    'old_value': change.old_value
                }
            exploration_property_changes[property_name]['new_value'] = (
                change.new_value)

    unchanged_exploration_properties = []
    for property_name in exploration_property_changes:
        if (exploration_property_changes[property_name]['old_value'] ==
                exploration_property_changes[property_name]['new_value']):
            unchanged_exploration_properties.append(property_name)
    for property_name in unchanged_exploration_properties:
        del exploration_property_changes[property_name]

    unchanged_state_names = []
    for state_name in state_property_changes:
        unchanged_state_properties = []
        changes = state_property_changes[state_name]
        for property_name in changes:
            if (changes[property_name]['old_value'] ==
                    changes[property_name]['new_value']):
                unchanged_state_properties.append(property_name)
        for property_name in unchanged_state_properties:
            del changes[property_name]

        if len(changes) == 0:
            unchanged_state_names.append(state_name)
    for state_name in unchanged_state_names:
        del state_property_changes[state_name]

    return {
        'exploration_property_changes': exploration_property_changes,
        'state_property_changes': state_property_changes,
        'changed_states': changed_states,
        'added_states': added_states,
        'deleted_states': deleted_states,
    }
Exemplo n.º 17
0
def apply_change_list(exploration_id, change_list):
    """Applies a changelist to a pristine exploration and returns the result.

    Each entry in change_list is a dict that represents an ExplorationChange
    object.

    Returns:
      the resulting exploration domain object.
    """
    exploration = get_exploration_by_id(exploration_id)
    try:
        changes = [exp_domain.ExplorationChange(change_dict)
                   for change_dict in change_list]

        for change in changes:
            if change.cmd == 'add_state':
                exploration.add_states([change.state_name])
            elif change.cmd == 'rename_state':
                exploration.rename_state(
                    change.old_state_name, change.new_state_name)
            elif change.cmd == 'delete_state':
                exploration.delete_state(change.state_name)
            elif change.cmd == 'edit_state_property':
                state = exploration.states[change.state_name]
                if change.property_name == 'param_changes':
                    state.update_param_changes(change.new_value)
                elif change.property_name == 'content':
                    state.update_content(change.new_value)
                elif change.property_name == 'widget_id':
                    state.update_widget_id(change.new_value)
                elif change.property_name == 'widget_customization_args':
                    state.update_widget_customization_args(change.new_value)
                elif change.property_name == 'widget_sticky':
                    state.update_widget_sticky(change.new_value)
                elif change.property_name == 'widget_handlers':
                    state.update_widget_handlers(change.new_value)
            elif change.cmd == 'edit_exploration_property':
                if change.property_name == 'title':
                    exploration.update_title(change.new_value)
                elif change.property_name == 'category':
                    exploration.update_category(change.new_value)
                elif change.property_name == 'objective':
                    exploration.update_objective(change.new_value)
                elif change.property_name == 'language_code':
                    exploration.update_language_code(change.new_value)
                elif change.property_name == 'skill_tags':
                    exploration.update_skill_tags(change.new_value)
                elif change.property_name == 'blurb':
                    exploration.update_blurb(change.new_value)
                elif change.property_name == 'author_notes':
                    exploration.update_author_notes(change.new_value)
                elif change.property_name == 'param_specs':
                    exploration.update_param_specs(change.new_value)
                elif change.property_name == 'param_changes':
                    exploration.update_param_changes(change.new_value)
                elif change.property_name == 'default_skin_id':
                    exploration.update_default_skin_id(change.new_value)
        return exploration
    except Exception as e:
        logging.error(
            '%s %s %s %s' % (
                e.__class__.__name__, e, exploration_id, change_list)
        )
        raise
Exemplo n.º 18
0
    def test_library_handler_demo_exploration(self):
        """Test the library data handler on demo explorations."""
        response_dict = self.get_json(feconf.LIBRARY_SEARCH_DATA_URL)
        self.assertEqual(
            {
                'is_admin': False,
                'is_moderator': False,
                'is_super_admin': False,
                'activity_list': [],
                'search_cursor': None,
                'profile_picture_data_url': None,
            }, response_dict)

        # Load a public demo exploration.
        exp_services.load_demo('0')

        # Load the search results with an empty query.
        response_dict = self.get_json(feconf.LIBRARY_SEARCH_DATA_URL)
        self.assertEqual(len(response_dict['activity_list']), 1)
        self.assertDictContainsSubset(
            {
                'id': '0',
                'category': 'Welcome',
                'title': 'Welcome to Oppia!',
                'language_code': 'en',
                'objective': 'become familiar with Oppia\'s capabilities',
                'status': rights_manager.ACTIVITY_STATUS_PUBLIC,
            }, response_dict['activity_list'][0])

        self.set_admins([self.ADMIN_USERNAME])

        # Run migration job to create exploration summaries.
        # This is not necessary, but serves as additional check that
        # the migration job works well and gives correct results.
        self.process_and_flush_pending_tasks()
        job_id = (exp_jobs_one_off.ExpSummariesCreationOneOffJob.create_new())
        exp_jobs_one_off.ExpSummariesCreationOneOffJob.enqueue(job_id)
        self.assertGreaterEqual(
            self.count_jobs_in_taskqueue(
                taskqueue_services.QUEUE_NAME_ONE_OFF_JOBS), 1)
        self.process_and_flush_pending_tasks()
        self.assertEqual(
            self.count_jobs_in_taskqueue(
                taskqueue_services.QUEUE_NAME_ONE_OFF_JOBS), 0)

        # change title and category.
        exp_services.update_exploration(self.editor_id, '0', [
            exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'A new title!'
            }),
            exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'category',
                'new_value': 'A new category'
            })
        ], 'Change title and category')

        # Load the search results with an empty query.
        response_dict = self.get_json(feconf.LIBRARY_SEARCH_DATA_URL)
        self.assertEqual(len(response_dict['activity_list']), 1)
        self.assertDictContainsSubset(
            {
                'id': '0',
                'category': 'A new category',
                'title': 'A new title!',
                'language_code': 'en',
                'objective': 'become familiar with Oppia\'s capabilities',
                'status': rights_manager.ACTIVITY_STATUS_PUBLIC,
            }, response_dict['activity_list'][0])
Exemplo n.º 19
0
    def map(item):
        if item.deleted:
            return
        try:
            exploration = exp_services.get_exploration_from_model(item)
        except Exception as e:
            yield ('ERROR get_exp_from_model: exp_id %s' % item.id, str(e))
            return

        # Commit commands which are required to generate state id mapping for
        # given version of exploration from previous version of exploration.
        RELEVANT_COMMIT_CMDS = [
            exp_domain.CMD_ADD_STATE,
            exp_domain.CMD_RENAME_STATE,
            exp_domain.CMD_DELETE_STATE,
            exp_models.ExplorationModel.CMD_REVERT_COMMIT
        ]

        explorations = []

        # Fetch all versions of the exploration, if they exist.
        if exploration.version > 1:
            # Ignore latest version since we already have corresponding
            # exploration.
            versions = range(1, exploration.version)

            # Get all exploration versions for current exploration id.
            try:
                explorations = (
                    exp_services.get_multiple_explorations_by_version(
                        exploration.id, versions))
            except Exception as e:
                yield (
                    'ERROR get_multiple_exp_by_version exp_id %s' % item.id,
                    str(e))
                return

        # Append latest exploration to the list of explorations.
        explorations.append(exploration)

        # Retrieve list of snapshot models representing each version of the
        # exploration.
        versions = range(1, exploration.version + 1)
        snapshots_by_version = (
            exp_models.ExplorationModel.get_snapshots_metadata(
                exploration.id, versions))

        # Create and save state id mapping model for all exploration versions.
        for exploration, snapshot in zip(explorations, snapshots_by_version):
            if snapshot is None:
                yield (
                    'ERROR with exp_id %s' % item.id,
                    'Error: No exploration snapshot metadata model instance '
                    'found for exploration %s, version %d' % (
                        exploration.id, exploration.version))
                return

            change_list = [
                exp_domain.ExplorationChange(change_cmd)
                for change_cmd in snapshot['commit_cmds']
                if change_cmd['cmd'] in RELEVANT_COMMIT_CMDS
            ]

            try:
                # Check if commit is to revert the exploration.
                if change_list and change_list[0].cmd == (
                        exp_models.ExplorationModel.CMD_REVERT_COMMIT):
                    reverted_version = change_list[0].version_number
                    # pylint: disable=line-too-long
                    state_id_mapping = exp_services.generate_state_id_mapping_model_for_reverted_exploration(
                        exploration.id, exploration.version - 1,
                        reverted_version)
                    # pylint: enable=line-too-long
                else:
                    state_id_mapping = (
                        exp_services.generate_state_id_mapping_model(
                            exploration, change_list))

                state_id_mapping_model = exp_models.StateIdMappingModel.create(
                    state_id_mapping.exploration_id,
                    state_id_mapping.exploration_version,
                    state_id_mapping.state_names_to_ids,
                    state_id_mapping.largest_state_id_used, overwrite=True)
                state_id_mapping_model.put()

            except Exception as e:
                yield (
                    'ERROR with exp_id %s version %s' % (
                        item.id, exploration.version),
                    traceback.format_exc())
                return

        yield (exploration.id, exploration.version)
Exemplo n.º 20
0
    def test_opportunity_updates_with_updating_exploration(self):
        self.add_exploration_0_to_story()

        translation_opportunities, _, _ = (
            opportunity_services.get_translation_opportunities('hi', None))
        self.assertEqual(len(translation_opportunities), 1)
        self.assertEqual(translation_opportunities[0].content_count, 2)

        answer_group_dict = {
            'outcome': {
                'dest': 'Introduction',
                'feedback': {
                    'content_id': 'feedback_1',
                    'html': '<p>Feedback</p>'
                },
                'labelled_as_correct': False,
                'param_changes': [],
                'refresher_exploration_id': None,
                'missing_prerequisite_skill_id': None
            },
            'rule_specs': [{
                'inputs': {
                    'x': 'Test'
                },
                'rule_type': 'Contains'
            }],
            'training_data': [],
            'tagged_skill_misconception_id': None
        }

        hints_list = []
        hints_list.append({
            'hint_content': {
                'content_id': 'hint_1',
                'html': '<p>hint one</p>'
            },
        })

        solution_dict = {
            'answer_is_exclusive': False,
            'correct_answer': 'helloworld!',
            'explanation': {
                'content_id': 'solution',
                'html': '<p>hello_world is a string</p>'
            },
        }
        exp_services.update_exploration(self.owner_id, '0', [
            exp_domain.ExplorationChange({
                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                'property_name': exp_domain.STATE_PROPERTY_INTERACTION_ID,
                'state_name': 'Introduction',
                'new_value': 'TextInput'
            }),
            exp_domain.ExplorationChange({
                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                'property_name':
                exp_domain.STATE_PROPERTY_INTERACTION_CUST_ARGS,
                'state_name': 'Introduction',
                'new_value': {
                    'placeholder': {
                        'value': {
                            'content_id': 'ca_placeholder_0',
                            'unicode_str': ''
                        }
                    },
                    'rows': {
                        'value': 1
                    }
                }
            }),
            exp_domain.ExplorationChange({
                'cmd':
                exp_domain.CMD_EDIT_STATE_PROPERTY,
                'property_name':
                (exp_domain.STATE_PROPERTY_INTERACTION_ANSWER_GROUPS),
                'state_name':
                'Introduction',
                'new_value': [answer_group_dict]
            }),
            exp_domain.ExplorationChange({
                'cmd':
                exp_domain.CMD_EDIT_STATE_PROPERTY,
                'property_name': (exp_domain.STATE_PROPERTY_INTERACTION_HINTS),
                'state_name':
                'Introduction',
                'new_value':
                hints_list
            }),
            exp_domain.ExplorationChange({
                'cmd':
                exp_domain.CMD_EDIT_STATE_PROPERTY,
                'property_name':
                (exp_domain.STATE_PROPERTY_INTERACTION_SOLUTION),
                'state_name':
                'Introduction',
                'new_value':
                solution_dict
            })
        ], 'Add state name')
        translation_opportunities, _, _ = (
            opportunity_services.get_translation_opportunities('hi', None))
        self.assertEqual(len(translation_opportunities), 1)
        self.assertEqual(translation_opportunities[0].content_count, 5)
Exemplo n.º 21
0
    def test_multiple_computations_in_one_job(self):
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CC_MANAGERS_FOR_TESTS):

            # Setup example exploration.
            exp_id = 'eid'
            exp = self.save_new_valid_exploration(exp_id, '*****@*****.**')
            first_state_name = exp.init_state_name
            second_state_name = 'State 2'
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'state_name': first_state_name,
                    'property_name': exp_domain.STATE_PROPERTY_INTERACTION_ID,
                    'new_value': 'SetInput',
                }),
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': second_state_name,
                }),
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                    'state_name': second_state_name,
                    'property_name': exp_domain.STATE_PROPERTY_INTERACTION_ID,
                    'new_value': 'SetInput',
                })
            ], 'Add new state')
            exp = exp_fetchers.get_exploration_by_id(exp_id)
            exp_version = exp.version

            time_spent = 5.0
            params = {}

            # Add an answer.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, exp_version, first_state_name, 'SetInput', 0, 0,
                exp_domain.EXPLICIT_CLASSIFICATION, 'session1', time_spent,
                params, ['answer1', 'answer2'])

            # Run the aggregator job.
            MockInteractionAnswerSummariesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)
            self.process_and_flush_pending_tasks()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            # Retrieve outputs for all of the computations running on this
            # interaction.
            answer_frequencies_calc_output_model = self._get_calc_output_model(
                exp_id, first_state_name, 'Top10AnswerFrequencies')
            self.assertEqual(
                'Top10AnswerFrequencies',
                answer_frequencies_calc_output_model.calculation_id)

            common_elements_calc_output_model = self._get_calc_output_model(
                exp_id, first_state_name, 'FrequencyCommonlySubmittedElements')
            self.assertEqual('FrequencyCommonlySubmittedElements',
                             common_elements_calc_output_model.calculation_id)

            calculation_output_first = (
                answer_frequencies_calc_output_model.calculation_output)
            calculation_output_second = (
                common_elements_calc_output_model.calculation_output)

            self.assertEqual(calculation_output_first,
                             [{
                                 'answer': ['answer1', 'answer2'],
                                 'frequency': 1
                             }])
            self.assertEqual(calculation_output_second, [{
                'answer': 'answer1',
                'frequency': 1
            }, {
                'answer': 'answer2',
                'frequency': 1
            }])
Exemplo n.º 22
0
    def test_for_recently_published_explorations(self):
        """Tests for recently published explorations."""

        self.process_and_flush_pending_tasks()
        recently_published_exploration_summaries = (
            summary_services.get_recently_published_exp_summary_dicts(
                feconf.RECENTLY_PUBLISHED_QUERY_LIMIT_FOR_LIBRARY_PAGE))
        test_summary_1 = {
            'status': 'public',
            'thumbnail_bg_color': '#a33f40',
            'community_owned': False,
            'tags': [],
            'thumbnail_icon_url': '/subjects/Lightbulb.svg',
            'language_code': constants.DEFAULT_LANGUAGE_CODE,
            'id': self.EXP_ID_1,
            'category': u'A category',
            'ratings': feconf.get_empty_ratings(),
            'title': u'A title',
            'num_views': 0,
            'objective': u'An objective'
        }
        test_summary_2 = {
            'status': 'public',
            'thumbnail_bg_color': '#a33f40',
            'community_owned': False,
            'tags': [],
            'thumbnail_icon_url': '/subjects/Lightbulb.svg',
            'language_code': constants.DEFAULT_LANGUAGE_CODE,
            'id': self.EXP_ID_2,
            'category': u'A category',
            'ratings': feconf.get_empty_ratings(),
            'title': u'A title',
            'num_views': 0,
            'objective': u'An objective'
        }
        test_summary_3 = {
            'status': 'public',
            'thumbnail_bg_color': '#a33f40',
            'community_owned': False,
            'tags': [],
            'thumbnail_icon_url': '/subjects/Lightbulb.svg',
            'language_code': constants.DEFAULT_LANGUAGE_CODE,
            'id': self.EXP_ID_3,
            'category': u'A category',
            'ratings': feconf.get_empty_ratings(),
            'title': u'A title',
            'num_views': 0,
            'objective': u'An objective'
        }

        self.assertDictContainsSubset(
            test_summary_3, recently_published_exploration_summaries[0])
        self.assertDictContainsSubset(
            test_summary_1, recently_published_exploration_summaries[1])
        self.assertDictContainsSubset(
            test_summary_2, recently_published_exploration_summaries[2])

        # Test that editing an exploration does not change its
        # 'recently-published' status.
        exp_services.update_exploration(
            self.albert_id, self.EXP_ID_1, [exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'New title'
            })], 'Changed title.')
        self.process_and_flush_pending_tasks()

        recently_published_exploration_summaries = (
            summary_services.get_recently_published_exp_summary_dicts(
                feconf.RECENTLY_PUBLISHED_QUERY_LIMIT_FOR_LIBRARY_PAGE))
        self.assertEqual(
            recently_published_exploration_summaries[1]['title'], 'New title')
        self.assertDictContainsSubset(
            test_summary_3, recently_published_exploration_summaries[0])
Exemplo n.º 23
0
    def setUp(self):
        """Populate the database of explorations and their summaries.

        The sequence of events is:
        - (1) Albert creates EXP_ID_1.
        - (2) Bob edits the title of EXP_ID_1.
        - (3) Albert creates EXP_ID_2.
        - (4) Albert edits the title of EXP_ID_1.
        - (5) Albert edits the title of EXP_ID_2.
        - (6) Bob reverts Albert's last edit to EXP_ID_1.
        - Bob tries to publish EXP_ID_2, and is denied access.
        - (7) Albert publishes EXP_ID_2.
        - (8) Albert creates EXP_ID_3
        - (9) Albert publishes EXP_ID_3
        - (10) Albert deletes EXP_ID_3

        - (1) User_3 (has a profile_picture) creates EXP_ID_4.
        - (2) User_4 edits the title of EXP_ID_4.
        - (3) User_4 edits the title of EXP_ID_4.
        """

        super(ExplorationDisplayableSummariesTest, self).setUp()

        self.signup(self.ALBERT_EMAIL, self.ALBERT_NAME)
        self.signup(self.BOB_EMAIL, self.BOB_NAME)

        self.albert_id = self.get_user_id_from_email(self.ALBERT_EMAIL)
        self.bob_id = self.get_user_id_from_email(self.BOB_EMAIL)

        self.albert = user_services.get_user_actions_info(self.albert_id)
        self.bob = user_services.get_user_actions_info(self.bob_id)

        self.save_new_valid_exploration(self.EXP_ID_1, self.albert_id)

        exp_services.update_exploration(
            self.bob_id, self.EXP_ID_1, [exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'Exploration 1 title'
            })], 'Changed title.')

        self.save_new_valid_exploration(self.EXP_ID_2, self.albert_id)

        exp_services.update_exploration(
            self.albert_id, self.EXP_ID_1, [exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'Exploration 1 Albert title'
            })], 'Changed title to Albert1 title.')

        exp_services.update_exploration(
            self.albert_id, self.EXP_ID_2, [exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'Exploration 2 Albert title'
            })], 'Changed title to Albert2 title.')

        exp_services.revert_exploration(self.bob_id, self.EXP_ID_1, 3, 2)

        with self.assertRaisesRegex(
            Exception, 'This exploration cannot be published'
            ):
            rights_manager.publish_exploration(self.bob, self.EXP_ID_2)

        rights_manager.publish_exploration(self.albert, self.EXP_ID_2)

        self.save_new_valid_exploration(self.EXP_ID_3, self.albert_id)
        rights_manager.publish_exploration(self.albert, self.EXP_ID_3)
        exp_services.delete_exploration(self.albert_id, self.EXP_ID_3)
        self.signup(self.USER_C_EMAIL, self.USER_C_NAME)
        self.signup(self.USER_D_EMAIL, self.USER_D_NAME)
        self.user_c_id = self.get_user_id_from_email(self.USER_C_EMAIL)
        self.user_d_id = self.get_user_id_from_email(self.USER_D_EMAIL)
        user_services.update_profile_picture_data_url(
            self.user_c_id, self.USER_C_PROFILE_PICTURE)

        self.save_new_valid_exploration(self.EXP_ID_4, self.user_c_id)
        exp_services.update_exploration(
            self.user_d_id, self.EXP_ID_4, [exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'Exploration updated title'
            })], 'Changed title once.')

        exp_services.update_exploration(
            self.user_d_id, self.EXP_ID_4, [exp_domain.ExplorationChange({
                'cmd': 'edit_exploration_property',
                'property_name': 'title',
                'new_value': 'Exploration updated title again'
            })], 'Changed title twice.')

        self.save_new_valid_exploration(self.EXP_ID_5, self.bob_id)
Exemplo n.º 24
0
    def _load_dummy_new_structures_data(self):
        """Loads the database with two topics (one of which is empty), a story
        and three skills in the topic (two of them in a subtopic) and a question
        attached to each skill.

        Raises:
            Exception. Cannot load new structures data in production mode.
            Exception. User does not have enough rights to generate data.
        """
        if constants.DEV_MODE:
            if feconf.ROLE_ID_CURRICULUM_ADMIN not in self.user.roles:
                raise Exception(
                    'User does not have enough rights to generate data.')
            topic_id_1 = topic_fetchers.get_new_topic_id()
            topic_id_2 = topic_fetchers.get_new_topic_id()
            story_id = story_services.get_new_story_id()
            skill_id_1 = skill_services.get_new_skill_id()
            skill_id_2 = skill_services.get_new_skill_id()
            skill_id_3 = skill_services.get_new_skill_id()
            question_id_1 = question_services.get_new_question_id()
            question_id_2 = question_services.get_new_question_id()
            question_id_3 = question_services.get_new_question_id()

            skill_1 = self._create_dummy_skill(skill_id_1, 'Dummy Skill 1',
                                               '<p>Dummy Explanation 1</p>')
            skill_2 = self._create_dummy_skill(skill_id_2, 'Dummy Skill 2',
                                               '<p>Dummy Explanation 2</p>')
            skill_3 = self._create_dummy_skill(skill_id_3, 'Dummy Skill 3',
                                               '<p>Dummy Explanation 3</p>')

            question_1 = self._create_dummy_question(question_id_1,
                                                     'Question 1',
                                                     [skill_id_1])
            question_2 = self._create_dummy_question(question_id_2,
                                                     'Question 2',
                                                     [skill_id_2])
            question_3 = self._create_dummy_question(question_id_3,
                                                     'Question 3',
                                                     [skill_id_3])
            question_services.add_question(self.user_id, question_1)
            question_services.add_question(self.user_id, question_2)
            question_services.add_question(self.user_id, question_3)

            question_services.create_new_question_skill_link(
                self.user_id, question_id_1, skill_id_1, 0.3)
            question_services.create_new_question_skill_link(
                self.user_id, question_id_2, skill_id_2, 0.5)
            question_services.create_new_question_skill_link(
                self.user_id, question_id_3, skill_id_3, 0.7)

            topic_1 = topic_domain.Topic.create_default_topic(
                topic_id_1, 'Dummy Topic 1', 'dummy-topic-one', 'description')
            topic_2 = topic_domain.Topic.create_default_topic(
                topic_id_2, 'Empty Topic', 'empty-topic', 'description')

            topic_1.add_canonical_story(story_id)
            topic_1.add_uncategorized_skill_id(skill_id_1)
            topic_1.add_uncategorized_skill_id(skill_id_2)
            topic_1.add_uncategorized_skill_id(skill_id_3)
            topic_1.add_subtopic(1, 'Dummy Subtopic Title')
            topic_1.move_skill_id_to_subtopic(None, 1, skill_id_2)
            topic_1.move_skill_id_to_subtopic(None, 1, skill_id_3)

            subtopic_page = (
                subtopic_page_domain.SubtopicPage.create_default_subtopic_page(
                    1, topic_id_1))
            # These explorations were chosen since they pass the validations
            # for published stories.
            self._reload_exploration('15')
            self._reload_exploration('25')
            self._reload_exploration('13')
            exp_services.update_exploration(
                self.user_id, '15', [
                    exp_domain.ExplorationChange({
                        'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                        'property_name': 'correctness_feedback_enabled',
                        'new_value': True
                    })
                ], 'Changed correctness_feedback_enabled.')
            exp_services.update_exploration(
                self.user_id, '25', [
                    exp_domain.ExplorationChange({
                        'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                        'property_name': 'correctness_feedback_enabled',
                        'new_value': True
                    })
                ], 'Changed correctness_feedback_enabled.')
            exp_services.update_exploration(
                self.user_id, '13', [
                    exp_domain.ExplorationChange({
                        'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                        'property_name': 'correctness_feedback_enabled',
                        'new_value': True
                    })
                ], 'Changed correctness_feedback_enabled.')

            story = story_domain.Story.create_default_story(
                story_id, 'Help Jaime win the Arcade', 'Description',
                topic_id_1, 'help-jamie-win-arcade')

            story_node_dicts = [{
                'exp_id':
                '15',
                'title':
                'What are the place values?',
                'description':
                'Jaime learns the place value of each digit ' +
                'in a big number.'
            }, {
                'exp_id':
                '25',
                'title':
                'Finding the value of a number',
                'description':
                'Jaime understands the value of his ' + 'arcade score.'
            }, {
                'exp_id':
                '13',
                'title':
                'Comparing Numbers',
                'description':
                'Jaime learns if a number is smaller or ' +
                'greater than another number.'
            }]

            def generate_dummy_story_nodes(node_id, exp_id, title,
                                           description):
                """Generates and connects sequential story nodes.

                Args:
                    node_id: int. The node id.
                    exp_id: str. The exploration id.
                    title: str. The title of the story node.
                    description: str. The description of the story node.
                """

                story.add_node('%s%d' % (story_domain.NODE_ID_PREFIX, node_id),
                               title)
                story.update_node_description(
                    '%s%d' % (story_domain.NODE_ID_PREFIX, node_id),
                    description)
                story.update_node_exploration_id(
                    '%s%d' % (story_domain.NODE_ID_PREFIX, node_id), exp_id)

                if node_id != len(story_node_dicts):
                    story.update_node_destination_node_ids(
                        '%s%d' % (story_domain.NODE_ID_PREFIX, node_id),
                        ['%s%d' % (story_domain.NODE_ID_PREFIX, node_id + 1)])

                exp_services.update_exploration(self.user_id, exp_id, [
                    exp_domain.ExplorationChange({
                        'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
                        'property_name': 'category',
                        'new_value': 'Astronomy'
                    })
                ], 'Change category')

            for i, story_node_dict in enumerate(story_node_dicts):
                generate_dummy_story_nodes(i + 1, **story_node_dict)

            skill_services.save_new_skill(self.user_id, skill_1)
            skill_services.save_new_skill(self.user_id, skill_2)
            skill_services.save_new_skill(self.user_id, skill_3)
            story_services.save_new_story(self.user_id, story)
            topic_services.save_new_topic(self.user_id, topic_1)
            topic_services.save_new_topic(self.user_id, topic_2)
            subtopic_page_services.save_subtopic_page(
                self.user_id, subtopic_page, 'Added subtopic', [
                    topic_domain.TopicChange({
                        'cmd': topic_domain.CMD_ADD_SUBTOPIC,
                        'subtopic_id': 1,
                        'title': 'Dummy Subtopic Title'
                    })
                ])

            # Generates translation opportunities for the Contributor Dashboard.
            exp_ids_in_story = story.story_contents.get_all_linked_exp_ids()
            opportunity_services.add_new_exploration_opportunities(
                story_id, exp_ids_in_story)

            topic_services.publish_story(topic_id_1, story_id, self.user_id)
        else:
            raise Exception('Cannot load new structures data in production.')
Exemplo n.º 25
0
    def test_one_answer(self):
        with self.swap(jobs_registry, 'ALL_CONTINUOUS_COMPUTATION_MANAGERS',
                       self.ALL_CC_MANAGERS_FOR_TESTS):

            # Setup example exploration.
            exp_id = 'eid'
            exp = self.save_new_valid_exploration(exp_id, '*****@*****.**')
            first_state_name = exp.init_state_name
            second_state_name = 'State 2'
            exp_services.update_exploration('*****@*****.**', exp_id, [
                exp_domain.ExplorationChange(
                    {
                        'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                        'state_name': first_state_name,
                        'property_name':
                        exp_domain.STATE_PROPERTY_INTERACTION_ID,
                        'new_value': 'MultipleChoiceInput',
                    }),
                exp_domain.ExplorationChange({
                    'cmd': exp_domain.CMD_ADD_STATE,
                    'state_name': second_state_name,
                }),
                exp_domain.ExplorationChange(
                    {
                        'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                        'state_name': second_state_name,
                        'property_name':
                        exp_domain.STATE_PROPERTY_INTERACTION_ID,
                        'new_value': 'MultipleChoiceInput',
                    })
            ], 'Add new state')
            exp = exp_fetchers.get_exploration_by_id(exp_id)
            exp_version = exp.version

            time_spent = 5.0
            params = {}

            self._record_start(exp_id, exp_version, first_state_name,
                               'session1')
            self._record_start(exp_id, exp_version, first_state_name,
                               'session2')
            self.process_and_flush_pending_tasks()

            # Add some answers.
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, exp_version, first_state_name, 'MultipleChoiceInput',
                0, 0, exp_domain.EXPLICIT_CLASSIFICATION, 'session1',
                time_spent, params, 'answer1')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, exp_version, first_state_name, 'MultipleChoiceInput',
                0, 0, exp_domain.EXPLICIT_CLASSIFICATION, 'session2',
                time_spent, params, 'answer1')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, exp_version, first_state_name, 'MultipleChoiceInput',
                0, 0, exp_domain.EXPLICIT_CLASSIFICATION, 'session1',
                time_spent, params, 'answer2')
            event_services.AnswerSubmissionEventHandler.record(
                exp_id, exp_version, second_state_name, 'MultipleChoiceInput',
                0, 0, exp_domain.EXPLICIT_CLASSIFICATION, 'session2',
                time_spent, params, 'answer3')

            # Run job on exploration with answers.
            MockInteractionAnswerSummariesAggregator.start_computation()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 1)
            self.process_and_flush_pending_tasks()
            self.assertEqual(
                self.count_jobs_in_taskqueue(
                    taskqueue_services.QUEUE_NAME_CONTINUOUS_JOBS), 0)

            calc_id = 'AnswerFrequencies'

            # Get job output of first state and check it.
            calc_output_model = self._get_calc_output_model(
                exp_id,
                first_state_name,
                calc_id,
                exploration_version=exp_version)
            self.assertEqual('AnswerFrequencies',
                             calc_output_model.calculation_id)

            calculation_output = calc_output_model.calculation_output

            expected_calculation_output = [{
                'answer': 'answer1',
                'frequency': 2
            }, {
                'answer': 'answer2',
                'frequency': 1
            }]

            self.assertEqual(calculation_output, expected_calculation_output)

            # Get job output of second state and check it.
            calc_output_model = self._get_calc_output_model(
                exp_id,
                second_state_name,
                calc_id,
                exploration_version=exp_version)

            self.assertEqual('AnswerFrequencies',
                             calc_output_model.calculation_id)

            calculation_output = calc_output_model.calculation_output

            expected_calculation_output = [{
                'answer': 'answer3',
                'frequency': 1
            }]

            self.assertEqual(calculation_output, expected_calculation_output)
 def test_convert_states_v32_dict_to_v33_dict(self):
     draft_change_list = [
         exp_domain.ExplorationChange({
             'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
             'state_name': 'state1',
             'property_name': 'widget_customization_args',
             'new_value': {
                 'choices': {
                     'value': [
                         '<p>1</p>',
                         '<p>2</p>',
                         '<p>3</p>',
                         '<p>4</p>'
                     ]
                 }
             }
         }),
         exp_domain.ExplorationChange({
             'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
             'state_name': 'state2',
             'property_name': 'widget_customization_args',
             'new_value': {
                 'choices': {
                     'value': [
                         '<p>1</p>',
                         '<p>2</p>',
                         '<p>3</p>',
                         '<p>4</p>'
                     ]
                 },
                 'maxAllowableSelectionCount': {
                     'value': 1
                 },
                 'minAllowableSelectionCount': {
                     'value': 1
                 }
             }
         })]
     expected_draft_change_list = (
         draft_upgrade_services.DraftUpgradeUtil._convert_states_v32_dict_to_v33_dict(  # pylint: disable=protected-access,line-too-long
             draft_change_list))
     self.assertEqual(
         expected_draft_change_list[1].to_dict(),
         draft_change_list[1].to_dict())
     self.assertEqual(
         expected_draft_change_list[0].to_dict(),
         exp_domain.ExplorationChange({
             'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
             'state_name': 'state1',
             'property_name': 'widget_customization_args',
             'new_value': {
                 'choices': {
                     'value': [
                         '<p>1</p>',
                         '<p>2</p>',
                         '<p>3</p>',
                         '<p>4</p>'
                     ]
                 },
                 'showChoicesInShuffledOrder': {
                     'value': False
                 }
             }
         }).to_dict())
Exemplo n.º 27
0
    def test_completing_translation_removes_language_from_incomplete_language_codes(  # pylint: disable=line-too-long
            self):
        story_services.update_story(self.owner_id, self.STORY_ID, [
            story_domain.StoryChange({
                'cmd': 'add_story_node',
                'node_id': 'node_1',
                'title': 'Node1',
            }),
            story_domain.StoryChange({
                'cmd': 'update_story_node_property',
                'property_name': 'exploration_id',
                'node_id': 'node_1',
                'old_value': None,
                'new_value': '0'
            })
        ], 'Changes.')
        translation_opportunities, _, _ = (
            opportunity_services.get_translation_opportunities('hi', None))
        self.assertEqual(len(translation_opportunities), 1)

        change_list = [
            exp_domain.ExplorationChange({
                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                'state_name': 'Introduction',
                'property_name': 'content',
                'new_value': {
                    'html': '<p><strong>Test content</strong></p>',
                    'content_id': 'content',
                }
            }),
            exp_domain.ExplorationChange({
                'cmd':
                exp_domain.CMD_ADD_TRANSLATION,
                'state_name':
                'Introduction',
                'content_id':
                'content',
                'language_code':
                'hi',
                'content_html':
                '<p><strong>Test content</strong></p>',
                'translation_html':
                '<p>Translated text</p>'
            }),
            exp_domain.ExplorationChange({
                'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
                'state_name': 'End State',
                'property_name': 'content',
                'new_value': {
                    'html': '<p><strong>Test content</strong></p>',
                    'content_id': 'content',
                }
            }),
            exp_domain.ExplorationChange({
                'cmd':
                exp_domain.CMD_ADD_TRANSLATION,
                'state_name':
                'End State',
                'content_id':
                'content',
                'language_code':
                'hi',
                'content_html':
                '<p><strong>Test content</strong></p>',
                'translation_html':
                '<p>Translated text</p>'
            }),
        ]
        exp_services.update_exploration(self.owner_id, '0', change_list,
                                        'commit message')

        # get_translation_opportunities should no longer return the opportunity
        # after translation completion.
        translation_opportunities, _, _ = (
            opportunity_services.get_translation_opportunities('hi', None))
        self.assertEqual(len(translation_opportunities), 0)
 def test_convert_states_v29_dict_to_v30_dict(self):
     draft_change_list = [
         exp_domain.ExplorationChange({
             'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
             'property_name': 'answer_groups',
             'state_name': 'State 1',
             'new_value': {
                 'rule_specs': [{
                     'rule_type': 'Equals',
                     'inputs': {'x': [
                         '<p>This is value1 for ItemSelection</p>'
                     ]}
                 }, {
                     'rule_type': 'Equals',
                     'inputs': {'x': [
                         '<p>This is value2 for ItemSelection</p>'
                     ]}
                 }],
                 'outcome': {
                     'dest': 'Introduction',
                     'feedback': {
                         'content_id': 'feedback',
                         'html': '<p>Outcome for state1</p>'
                     },
                     'param_changes': [],
                     'labelled_as_correct': False,
                     'refresher_exploration_id': None,
                     'missing_prerequisite_skill_id': None
                 },
                 'training_data': [],
                 'tagged_misconception_id': None
             }
         })]
     self.assertEqual(
         draft_upgrade_services.DraftUpgradeUtil._convert_states_v29_dict_to_v30_dict(  # pylint: disable=protected-access,line-too-long
             draft_change_list)[0].to_dict(),
         exp_domain.ExplorationChange({
             'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
             'property_name': 'answer_groups',
             'state_name': 'State 1',
             'new_value': {
                 'rule_specs': [{
                     'rule_type': 'Equals',
                     'inputs': {'x': [
                         '<p>This is value1 for ItemSelection</p>'
                     ]}
                 }, {
                     'rule_type': 'Equals',
                     'inputs': {'x': [
                         '<p>This is value2 for ItemSelection</p>'
                     ]}
                 }],
                 'outcome': {
                     'dest': 'Introduction',
                     'feedback': {
                         'content_id': 'feedback',
                         'html': '<p>Outcome for state1</p>'
                     },
                     'param_changes': [],
                     'labelled_as_correct': False,
                     'refresher_exploration_id': None,
                     'missing_prerequisite_skill_id': None
                 },
                 'training_data': [],
                 'tagged_skill_misconception_id': None
             }
         }).to_dict())
Exemplo n.º 29
0
    def _convert_html_in_draft_change_list(cls, draft_change_list,
                                           conversion_fn):
        """Applies a conversion function on all HTML fields in the provided
        draft change list.

        Args:
            draft_change_list: list(ExplorationChange). The list of
                ExplorationChange domain objects to upgrade.
            conversion_fn: function. The function to be used for converting the
                HTML.

        Returns:
            list(ExplorationChange). The converted draft_change_list.
        """
        for i, change in enumerate(draft_change_list):
            new_value = None
            if not change.cmd == exp_domain.CMD_EDIT_STATE_PROPERTY:
                continue
            # The change object has the key 'new_value' only if the
            # cmd is 'CMD_EDIT_STATE_PROPERTY' or
            # 'CMD_EDIT_EXPLORATION_PROPERTY'.
            new_value = change.new_value
            if change.property_name == exp_domain.STATE_PROPERTY_CONTENT:
                new_value['html'] = conversion_fn(new_value['html'])
            elif (change.property_name ==
                  exp_domain.STATE_PROPERTY_INTERACTION_CUST_ARGS):
                # Only customization args with the key 'choices' have HTML
                # content in them.
                if 'choices' in new_value.keys():
                    for value_index, value in enumerate(
                            new_value['choices']['value']):
                        if isinstance(value, dict) and 'html' in value:
                            new_value['choices']['value'][value_index][
                                'html'] = conversion_fn(value['html'])
                        elif isinstance(value, str):
                            new_value['choices']['value'][value_index] = (
                                conversion_fn(value))
            elif (change.property_name ==
                  exp_domain.STATE_PROPERTY_WRITTEN_TRANSLATIONS):
                for content_id, language_code_to_written_translation in (
                        new_value['translations_mapping'].items()):
                    for language_code in (
                            language_code_to_written_translation.keys()):
                        new_value['translations_mapping'][content_id][
                            language_code]['html'] = (conversion_fn(
                                new_value['translations_mapping'][content_id]
                                [language_code]['html']))
            elif (change.property_name
                  == exp_domain.STATE_PROPERTY_INTERACTION_DEFAULT_OUTCOME
                  and new_value is not None):
                new_value = (state_domain.Outcome.convert_html_in_outcome(
                    new_value, conversion_fn))
            elif (change.property_name ==
                  exp_domain.STATE_PROPERTY_INTERACTION_HINTS):
                new_value = [(state_domain.Hint.convert_html_in_hint(
                    hint_dict, conversion_fn)) for hint_dict in new_value]
            elif (change.property_name
                  == exp_domain.STATE_PROPERTY_INTERACTION_SOLUTION
                  and new_value is not None):
                new_value['explanation']['html'] = (conversion_fn(
                    new_value['explanation']['html']))
                # TODO(#9413): Find a way to include a reference to the
                # interaction type in the Draft change lists.
                # See issue: https://github.com/oppia/oppia/issues/9413.
                # currently, only DragAndDropSortInput interaction allows
                # solution correct answers having HTML in them.
                # This code below should be updated if any new interaction
                # is allowed to have HTML in the solution correct answer
                # The typecheckings below can be avoided once #9413 is fixed.
                if new_value['correct_answer']:
                    if isinstance(new_value['correct_answer'], list):
                        for list_index, html_list in enumerate(
                                new_value['correct_answer']):
                            if isinstance(html_list, list):
                                for answer_html_index, answer_html in enumerate(
                                        html_list):
                                    if isinstance(answer_html, str):
                                        new_value['correct_answer'][
                                            list_index][answer_html_index] = (
                                                conversion_fn(answer_html))
            elif (change.property_name ==
                  exp_domain.STATE_PROPERTY_INTERACTION_ANSWER_GROUPS):
                html_field_types_to_rule_specs = (
                    rules_registry.Registry.get_html_field_types_to_rule_specs(
                        state_schema_version=41))
                new_value = [
                    state_domain.AnswerGroup.convert_html_in_answer_group(
                        answer_group, conversion_fn,
                        html_field_types_to_rule_specs)
                    for answer_group in new_value
                ]
            if new_value is not None:
                draft_change_list[i] = exp_domain.ExplorationChange({
                    'cmd':
                    change.cmd,
                    'property_name':
                    change.property_name,
                    'state_name':
                    change.state_name,
                    'new_value':
                    new_value
                })
        return draft_change_list
class DraftUpgradeUnitTests(test_utils.GenericTestBase):
    """Test the draft upgrade services module."""
    EXP_ID = 'exp_id'
    USER_ID = 'user_id'
    OTHER_CHANGE_LIST = [exp_domain.ExplorationChange({
        'cmd': exp_domain.CMD_EDIT_EXPLORATION_PROPERTY,
        'property_name': 'title',
        'new_value': 'New title'
    })]
    EXP_MIGRATION_CHANGE_LIST = [exp_domain.ExplorationChange({
        'cmd': exp_domain.CMD_MIGRATE_STATES_SCHEMA_TO_LATEST_VERSION,
        'from_version': '0',
        'to_version': '1'
    })]
    DRAFT_CHANGELIST = [exp_domain.ExplorationChange({
        'cmd': 'edit_exploration_property',
        'property_name': 'title',
        'old_value': None,
        'new_value': 'Updated title'})]

    def setUp(self):
        super(DraftUpgradeUnitTests, self).setUp()
        self.save_new_valid_exploration(self.EXP_ID, self.USER_ID)

    def test_try_upgrade_with_no_version_difference(self):
        self.assertIsNone(
            draft_upgrade_services.try_upgrading_draft_to_exp_version(
                self.DRAFT_CHANGELIST, 1, 1, self.EXP_ID))

    def test_try_upgrade_raises_exception_if_versions_are_invalid(self):
        with self.assertRaisesRegexp(
            utils.InvalidInputException,
            'Current draft version is greater than the exploration version.'):
            draft_upgrade_services.try_upgrading_draft_to_exp_version(
                self.DRAFT_CHANGELIST, 2, 1, self.EXP_ID)

        exp_services.update_exploration(
            self.USER_ID, self.EXP_ID, self.OTHER_CHANGE_LIST,
            'Changed exploration title.')
        exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID)
        self.assertEqual(exploration.version, 2)
        self.assertIsNone(
            draft_upgrade_services.try_upgrading_draft_to_exp_version(
                self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID))

    def test_try_upgrade_failure_due_to_unsupported_commit_type(self):
        exp_services.update_exploration(
            self.USER_ID, self.EXP_ID, self.OTHER_CHANGE_LIST,
            'Changed exploration title.')
        exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID)
        self.assertEqual(exploration.version, 2)
        self.assertIsNone(
            draft_upgrade_services.try_upgrading_draft_to_exp_version(
                self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID))

    def test_try_upgrade_failure_due_to_unimplemented_upgrade_methods(self):
        exp_services.update_exploration(
            self.USER_ID, self.EXP_ID, self.EXP_MIGRATION_CHANGE_LIST,
            'Ran Exploration Migration job.')
        exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID)
        self.assertEqual(exploration.version, 2)
        self.assertIsNone(
            draft_upgrade_services.try_upgrading_draft_to_exp_version(
                self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID))

    def test_try_upgrade_success(self):
        exp_services.update_exploration(
            self.USER_ID, self.EXP_ID, self.EXP_MIGRATION_CHANGE_LIST,
            'Ran Exploration Migration job.')
        exploration = exp_fetchers.get_exploration_by_id(self.EXP_ID)
        draft_upgrade_services.DraftUpgradeUtil._convert_states_v0_dict_to_v1_dict = (  # pylint: disable=protected-access,line-too-long
            classmethod(lambda cls, changelist: changelist))
        self.assertEqual(exploration.version, 2)
        self.assertEqual(
            draft_upgrade_services.try_upgrading_draft_to_exp_version(
                self.DRAFT_CHANGELIST, 1, exploration.version, self.EXP_ID),
            self.DRAFT_CHANGELIST)