Repository: oppia/oppia

Issue:
[BUG]: Translations review page not loading for some explorations
### Describe the bug

Spanish translation reviewers are blocked from reviewing translations on the contributor dashboard because they don't load:

![Screenshot from 2022-11-13 12-45-36](https://user-images.githubusercontent.com/10575562/201543910-f16dd3fa-699f-4e67-94b2-8ac56debdd25.png)

On further investigation this appears to be due to a server error:

![Screenshot from 2022-11-13 12-45-36](https://user-images.githubusercontent.com/10575562/201544113-f01fd226-68b0-41b5-916d-763af9139ed4.png)

This issue appears on the production site and is causing the review workflow to be completely blocked.

### Steps To Reproduce

1. Visit https://www.oppia.org/contributor-dashboard as a Spanish reviewer.

### Expected Behavior

_No response_

### Screenshots/Videos

(Screenshots provided above)

### What device are you using?

Desktop

### Operating System

Linux

### What browsers are you seeing the problem on?

Other

### Browser version

98.0.4758.80 (Chromium)

### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/core/controllers/suggestion.py b/core/controllers/suggestion.py
--- a/core/controllers/suggestion.py
+++ b/core/controllers/suggestion.py
@@ -729,18 +729,21 @@ def get(self, target_type: str, suggestion_type: str) -> None:
         suggestions: Sequence[suggestion_registry.BaseSuggestion] = []
         next_offset = 0
         if suggestion_type == feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT:
-            suggestions, next_offset = (
-                suggestion_services.
-                get_reviewable_translation_suggestions_by_offset(
-                    self.user_id,
-                    exp_ids,
-                    limit, offset
-                )
-            )
+            reviewable_suggestions, next_offset = (
+                suggestion_services
+                .get_reviewable_translation_suggestions_by_offset(
+                    self.user_id, exp_ids, limit, offset))
+            # Filter out obsolete translation suggestions, i.e. suggestions with
+            # translations that no longer match the current exploration content
+            # text. See issue #16536 for more details.
+            suggestions = (
+                suggestion_services
+                .get_suggestions_with_translatable_explorations(
+                    reviewable_suggestions))
         elif suggestion_type == feconf.SUGGESTION_TYPE_ADD_QUESTION:
             suggestions, next_offset = (
-                suggestion_services.
-                get_reviewable_question_suggestions_by_offset(
+                suggestion_services
+                .get_reviewable_question_suggestions_by_offset(
                     self.user_id, limit, offset))
         self._render_suggestions(target_type, suggestions, next_offset)
 


Test patch:
diff --git a/core/controllers/suggestion_test.py b/core/controllers/suggestion_test.py
--- a/core/controllers/suggestion_test.py
+++ b/core/controllers/suggestion_test.py
@@ -2712,6 +2712,90 @@ def test_exploration_handler_returns_data_with_valid_exploration_id(
         )
         self.assertEqual(response['next_offset'], 1)
 
+    def test_exploration_handler_does_not_return_obsolete_suggestions(
+        self
+    ) -> None:
+        # Create a new exploration and linked story.
+        self.login(self.EDITOR_EMAIL)
+        continue_state_name = 'continue state'
+        exp_100 = self.save_new_linear_exp_with_state_names_and_interactions(
+            '100',
+            self.owner_id,
+            ['Introduction', continue_state_name, 'End state'],
+            ['TextInput', 'Continue'],
+            category='Algebra',
+            correctness_feedback_enabled=True
+        )
+        self.publish_exploration(self.owner_id, exp_100.id)
+        self.create_story_for_translation_opportunity(
+            self.owner_id, self.admin_id, 'story_id_100', self.TOPIC_ID,
+            exp_100.id)
+        self.logout()
+
+        # Create a translation suggestion for the Continue button text.
+        self.login(self.AUTHOR_EMAIL)
+        continue_state = exp_100.states['continue state']
+        content_id_of_continue_button_text = (
+            continue_state.interaction.customization_args[
+                'buttonText'].value.content_id)
+        change_dict = {
+            'cmd': 'add_translation',
+            'content_id': content_id_of_continue_button_text,
+            'language_code': 'hi',
+            'content_html': 'Continue',
+            'state_name': continue_state_name,
+            'translation_html': '<p>Translation for content.</p>'
+        }
+        suggestion_services.create_suggestion(
+            feconf.SUGGESTION_TYPE_TRANSLATE_CONTENT,
+            feconf.ENTITY_TYPE_EXPLORATION,
+            exp_100.id, 1, self.owner_id, change_dict, 'description')
+        self.logout()
+
+        self.login(self.REVIEWER_EMAIL)
+        # Handler should return the new suggestion.
+        response = self.get_json(
+            '/getreviewablesuggestions/exploration/translate_content', params={
+                'exploration_id': exp_100.id,
+                'limit': constants.OPPORTUNITIES_PAGE_SIZE,
+                'offset': 0
+            })
+        self.assertEqual(len(response['suggestions']), 1)
+        self.assertEqual(response['next_offset'], 1)
+        self.logout()
+
+        # Replace the Continue button text content ID.
+        self.login(self.EDITOR_EMAIL)
+        exp_services.update_exploration(
+            self.owner_id, exp_100.id, [
+                exp_domain.ExplorationChange({
+                    'cmd': exp_domain.CMD_EDIT_STATE_PROPERTY,
+                    'property_name':
+                        exp_domain.STATE_PROPERTY_INTERACTION_CUST_ARGS,
+                    'state_name': continue_state_name,
+                    'new_value': {
+                        'buttonText': {
+                            'value': {
+                                'content_id': 'new_content_id',
+                                'unicode_str': 'Continua'
+                            }
+                        }
+                    }
+                })], 'Change continue button content ID')
+        self.logout()
+
+        self.login(self.REVIEWER_EMAIL)
+        # After the new suggestion's content was deleted, handler should not
+        # return the obsolete suggestion.
+        response = self.get_json(
+            '/getreviewablesuggestions/exploration/translate_content', params={
+                'exploration_id': exp_100.id,
+                'limit': constants.OPPORTUNITIES_PAGE_SIZE,
+                'offset': 0
+            })
+        self.assertEqual(len(response['suggestions']), 0)
+        self.assertEqual(response['next_offset'], 1)
+
     def test_topic_translate_handler_returns_no_data(self) -> None:
         response = self.get_json(
             '/getreviewablesuggestions/topic/translate_content', {