Repository: oppia/oppia

Issue:
[Contributor Dashboard] Review screen does not show the latest original content
<!--
  - Thanks for taking the time to report a bug in the Oppia project.
  - Before filing a new issue, please do a quick search to check that it hasn't
  - already been filed on the [issue tracker](https://github.com/oppia/oppia/issues)._
  -->

**Describe the bug**
Review screen does not show the latest original content.

**To Reproduce**
Steps to reproduce the behavior:
 1. Sign up as User A (admin) and assign contributor review rights to review translations in language X.
 2. Login as User B.
 3. Submit translation suggestions for Exploration X via the contributor dashboard.
 4. Go to Exploration X and edit the content that was translated.
 5. Sign in as User A and review the translation suggestion.
 6. Observe that the "original content" shown does not reflect the update in step 4.

**Observed behavior**
Review screen does not show the latest original content.

**Expected behavior**
Review screen must *always* show the latest original content.

See discussion [here](https://docs.google.com/document/d/15iaCn7XEchKNgAVZ0Nn1-gyg42HL9k86n-DR_WLQR_s/edit?disco=AAAALh9sfTk) for details.

## Note

Can we remove content check as part of the accept flow? If the review view shows the updated content, the translation reviewer can decide whether to accept/reject the translation even if the translation is slightly old. It may not be desirable to reject an entire translation because there were minor updates to the original content.
[Contributor Dashboard] Translation editor not resetting when clicking "Save and translate another"
Observed only locally on develop:

![Screen Shot 2021-09-27 at 6 36 55 PM](https://user-images.githubusercontent.com/9004726/134994972-a30e0e6e-cfd9-4495-ba4b-e4e97d5266df.png)

Note the "Save and translate another" button is also disabled when it shouldn't be.


Discussion:
/cc @sagangwee 
I believe the fix here would be to update any pending corresponding suggestions when updating an exploration, i.e. in `update_exploration` in core/domain/exp_services.py
The `change` object stored in a translation suggestion has the following structure:

```
change: {
            cmd: 'add_translation',
            content_id: activeContentId,
            state_name: activeStateName,
            language_code: languageCode,
            content_html: stateWiseContents[activeStateName][activeContentId],
            translation_html: translationHtml
          }
```

The `content_html` is what is displayed as the original content text in the translation suggestion review modal (core/templates/pages/contributor-dashboard-page/modal-templates/translation-suggestion-review-modal.controller.ts).

It may not make sense to update the original `content_html` when an exploration is updated. Instead, we could indicate to the reviewer that the original content is outdated (which would require fetching the corresponding exploration upon loading the review modal). 
Suggested changes:

1. Remove the original content check in `pre_accept_validate` in core/domain/suggestion_registry.py. 
2. Add a note in the review modal if the original content has changed.
3. Add a diff of old content and new content so reviewer can quickly update if needed.
@kevintab95 Could you take a look at this?
Ah, I believe the issue is the order in which we update `status` and `translation` in https://github.com/oppia/oppia/blame/develop/core/templates/pages/contributor-dashboard-page/services/translate-text.service.ts.

Resolving patch:
diff --git a/core/controllers/suggestion.py b/core/controllers/suggestion.py
--- a/core/controllers/suggestion.py
+++ b/core/controllers/suggestion.py
@@ -24,6 +24,7 @@
 from constants import constants
 from core.controllers import acl_decorators
 from core.controllers import base
+from core.domain import exp_fetchers
 from core.domain import fs_services
 from core.domain import html_cleaner
 from core.domain import image_validation_services
@@ -223,7 +224,7 @@ def _render_suggestions(self, target_type, suggestions):
             target_id_to_opportunity_dict = (
                 _get_target_id_to_exploration_opportunity_dict(suggestions))
             self.render_json({
-                'suggestions': [s.to_dict() for s in suggestions],
+                'suggestions': _construct_exploration_suggestions(suggestions),
                 'target_id_to_opportunity_dict':
                     target_id_to_opportunity_dict
             })
@@ -465,6 +466,29 @@ def _get_target_id_to_skill_opportunity_dict(suggestions):
     return opportunity_id_to_opportunity_dict
 
 
+def _construct_exploration_suggestions(suggestions):
+    """Returns exploration suggestions with current exploration content.
+
+    Args:
+        suggestions: list(BaseSuggestion). A list of suggestions.
+
+    Returns:
+        list(dict). List of suggestion dicts with an additional
+        exploration_content_html field representing the target
+        exploration's current content.
+    """
+    suggestion_dicts = []
+    for suggestion in suggestions:
+        exploration = exp_fetchers.get_exploration_by_id(
+            suggestion.target_id)
+        content_html = exploration.get_content_html(
+            suggestion.change.state_name, suggestion.change.content_id)
+        suggestion_dict = suggestion.to_dict()
+        suggestion_dict['exploration_content_html'] = content_html
+        suggestion_dicts.append(suggestion_dict)
+    return suggestion_dicts
+
+
 def _upload_suggestion_images(request, suggestion, filenames):
     """Saves a suggestion's images to storage.
 
diff --git a/core/domain/suggestion_registry.py b/core/domain/suggestion_registry.py
--- a/core/domain/suggestion_registry.py
+++ b/core/domain/suggestion_registry.py
@@ -638,12 +638,6 @@ def pre_accept_validate(self):
         if self.change.state_name not in exploration.states:
             raise utils.ValidationError(
                 'Expected %s to be a valid state name' % self.change.state_name)
-        content_html = exploration.get_content_html(
-            self.change.state_name, self.change.content_id)
-        if content_html != self.change.content_html:
-            raise utils.ValidationError(
-                'The Exploration content has changed since this translation '
-                'was submitted.')
 
     def accept(self, commit_message):
         """Accepts the suggestion.


Test patch:
diff --git a/core/domain/suggestion_registry_test.py b/core/domain/suggestion_registry_test.py
--- a/core/domain/suggestion_registry_test.py
+++ b/core/domain/suggestion_registry_test.py
@@ -1417,46 +1417,6 @@ def test_pre_accept_validate_state_name(self):
         ):
             suggestion.pre_accept_validate()
 
-    def test_pre_accept_validate_content_html(self):
-        self.save_new_default_exploration('exp1', self.author_id)
-        expected_suggestion_dict = self.suggestion_dict
-        suggestion = suggestion_registry.SuggestionTranslateContent(
-            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'],
-            expected_suggestion_dict['language_code'], False, self.fake_date)
-
-        exp_services.update_exploration(
-            self.author_id, 'exp1', [
-                exp_domain.ExplorationChange({
-                    'cmd': exp_domain.CMD_ADD_STATE,
-                    'state_name': 'State A',
-                }),
-                exp_domain.ExplorationChange({
-                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
-                    'property_name': exp_domain.STATE_PROPERTY_CONTENT,
-                    'new_value': {
-                        'content_id': 'content',
-                        'html': '<p>This is a content.</p>'
-                    },
-                    'state_name': 'State A',
-                })
-            ], 'Added state')
-        suggestion.change.state_name = 'State A'
-
-        suggestion.pre_accept_validate()
-
-        suggestion.change.content_html = 'invalid content_html'
-        with self.assertRaisesRegexp(
-            utils.ValidationError,
-            'The Exploration content has changed since this translation '
-            'was submitted.'
-        ):
-            suggestion.pre_accept_validate()
-
     def test_accept_suggestion_adds_translation_in_exploration(self):
         self.save_new_default_exploration('exp1', self.author_id)
         exploration = exp_fetchers.get_exploration_by_id('exp1')