Repository: oppia/oppia

Issue:
Unable to delete a user account from the preferences page
<!--
  - 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**
Unable to delete a user account from the preferences page

**To Reproduce**
Steps to reproduce the behavior:
 1. Go to 'Preferences'
 2. Click on the 'Delete account' button at the bottom of the page.
 3. Click on the 'Delete my account button' at the bottom of the page.
 4. Fill up the 'username' in the modal and click on 'Delete my account' 

**Observed behavior**
modal disappear and nothing happens after clicking the 'Delete my account' button

**Screenshots**
![image](https://user-images.githubusercontent.com/55298925/129392731-9ac07afc-e046-46ce-9142-fef924452516.png)


**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**
 - Browser [firefox]

[Music and Fraction Interactions] Certain interaction from old exploration are no longer working as expected
<!--
  - 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**
Fraction Input does not accept answers.
Music input does not play a note when "Play Target Sequence" is clicked.

**To Reproduce**
Steps to reproduce fraction input the behavior:
 1. Go to https://www.oppiatestserver.org/create/16#/preview/Fraction%20Input
 2. Enter 1/6 or 1/8
 3. See warning "Please enter an answer in simplest form (e.g., 1/3 instead of 2/6)."

Steps to reproduce Music input the behavior:
1. Go to https://www.oppiatestserver.org/create/16#/preview/Music%20Notes%20Input
2. Click on "Play Target Sequence"
3. C5 Note is not played

**Observed behavior**
Fraction input interaction not accepting answer
Music input not playing C5 note

**Expected behavior**
Fraction input interaction should accept answer
Music input should play C5 note

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Fraction input**

https://user-images.githubusercontent.com/30050862/129175703-8a79b916-782e-46cd-a02a-ffcab54e26bc.mp4

**Music input**

https://user-images.githubusercontent.com/30050862/129175582-0fa2cfd4-80c8-4a20-bf0a-9de9f218050b.mp4


**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**
 - OS: Ubuntu 20.04
 - Browser Firefox
 - Browser-version 90

**Additional context**
Add any other context about the problem here.
Please **Note** This occurs only in old exploration and not in the new ones
[Music and Fraction Interactions] Certain interaction from old exploration are no longer working as expected
<!--
  - 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**
Fraction Input does not accept answers.
Music input does not play a note when "Play Target Sequence" is clicked.

**To Reproduce**
Steps to reproduce fraction input the behavior:
 1. Go to https://www.oppiatestserver.org/create/16#/preview/Fraction%20Input
 2. Enter 1/6 or 1/8
 3. See warning "Please enter an answer in simplest form (e.g., 1/3 instead of 2/6)."

Steps to reproduce Music input the behavior:
1. Go to https://www.oppiatestserver.org/create/16#/preview/Music%20Notes%20Input
2. Click on "Play Target Sequence"
3. C5 Note is not played

**Observed behavior**
Fraction input interaction not accepting answer
Music input not playing C5 note

**Expected behavior**
Fraction input interaction should accept answer
Music input should play C5 note

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Fraction input**

https://user-images.githubusercontent.com/30050862/129175703-8a79b916-782e-46cd-a02a-ffcab54e26bc.mp4

**Music input**

https://user-images.githubusercontent.com/30050862/129175582-0fa2cfd4-80c8-4a20-bf0a-9de9f218050b.mp4


**Desktop (please complete the following information; delete this section if the issue does not arise on desktop):**
 - OS: Ubuntu 20.04
 - Browser Firefox
 - Browser-version 90

**Additional context**
Add any other context about the problem here.
Please **Note** This occurs only in old exploration and not in the new ones


Discussion:
@vojtechjelinek could you look into this regression in wipeout?
Looked into the test server logs and it seems to be related to Mailchimp.
```

Exception raised: Mailchimp API key is not available. (/core/controllers/base.py:643)
Traceback (most recent call last):
  File "third_party/python_libs/webapp2.py", line 604, in dispatch
    return method(*args, **kwargs)
  File "core/controllers/acl_decorators.py", line 898, in test_can_manage_account
    return handler(self, **kwargs)
  File "core/controllers/profile.py", line 422, in delete
    wipeout_service.pre_delete_user(self.user_id)
  File "core/domain/wipeout_service.py", line 175, in pre_delete_user
    user_settings.email)
  File "core/platform/bulk_email/mailchimp_bulk_email_services.py", line 135, in permanently_delete_user_from_list
    client = _get_mailchimp_class()
  File "core/platform/bulk_email/mailchimp_bulk_email_services.py", line 67, in _get_mailchimp_class
    raise Exception('Mailchimp API key is not available.')
Exception: Mailchimp API key is not available.
```

@aks681 PTAL
[Note] Unchecking the first option here makes the interaction work correctly. But this still doesn't seem to be a correct behaviour.
![Screenshot from 2021-08-13 18-01-26](https://user-images.githubusercontent.com/60519303/129357573-a28f4e77-80a3-4685-8c4e-d0e40f7a2399.png)

Since this looks like a learner and creator experience issue, @aks681 could you please either take up this issue or reassign it? I think the fraction input problem is particularly severe since we use that in the math classroom explorations
I've confirmed that this happens with the fraction input interaction even on a brand-new exploration. Therefore, this is not an issue with old data on the testing server
[Note] Unchecking the first option here makes the interaction work correctly. But this still doesn't seem to be a correct behaviour.
![Screenshot from 2021-08-13 18-01-26](https://user-images.githubusercontent.com/60519303/129357573-a28f4e77-80a3-4685-8c4e-d0e40f7a2399.png)

Since this looks like a learner and creator experience issue, @aks681 could you please either take up this issue or reassign it? I think the fraction input problem is particularly severe since we use that in the math classroom explorations
I've confirmed that this happens with the fraction input interaction even on a brand-new exploration. Therefore, this is not an issue with old data on the testing server

Resolving patch:
diff --git a/core/platform/bulk_email/mailchimp_bulk_email_services.py b/core/platform/bulk_email/mailchimp_bulk_email_services.py
--- a/core/platform/bulk_email/mailchimp_bulk_email_services.py
+++ b/core/platform/bulk_email/mailchimp_bulk_email_services.py
@@ -21,6 +21,7 @@
 
 import ast
 import hashlib
+import logging
 
 import feconf
 import python_utils
@@ -59,16 +60,18 @@ def _get_mailchimp_class() -> mailchimp3.MailChimp:
     Returns:
         Mailchimp. A mailchimp class instance with the API key and username
         initialized.
-
-    Raises:
-        Exception. Mailchimp API key is not available.
-        Exception. Mailchimp username is not set.
     """
+    # The return value ignore pragma is required for this. This is
+    # because adding a Union[] type annotation to handle both None and
+    # mailchimp3.MailChimp causes errors where the return value is called
+    # (for eg: client.lists), since NoneType does not have an attribute lists.
     if not feconf.MAILCHIMP_API_KEY:
-        raise Exception('Mailchimp API key is not available.')
+        logging.exception('Mailchimp API key is not available.')
+        return None # type: ignore[return-value]
 
     if not feconf.MAILCHIMP_USERNAME:
-        raise Exception('Mailchimp username is not set.')
+        logging.exception('Mailchimp username is not set.')
+        return None
 
     # The following is a class initialized in the library with the API key and
     # username and hence cannot be tested directly. The mailchimp functions are
@@ -134,6 +137,8 @@ def permanently_delete_user_from_list(user_email: str) -> None:
         Exception. Any error raised by the mailchimp API.
     """
     client = _get_mailchimp_class()
+    if not client:
+        return None
     subscriber_hash = _get_subscriber_hash(user_email)
 
     try:
@@ -180,6 +185,8 @@ def add_or_update_user_status(
             deleted earlier) raised by the mailchimp API.
     """
     client = _get_mailchimp_class()
+    if not client:
+        return False
     subscriber_hash = _get_subscriber_hash(user_email)
 
     subscribed_mailchimp_data = {


Test patch:
diff --git a/core/platform/bulk_email/mailchimp_bulk_email_services_test.py b/core/platform/bulk_email/mailchimp_bulk_email_services_test.py
--- a/core/platform/bulk_email/mailchimp_bulk_email_services_test.py
+++ b/core/platform/bulk_email/mailchimp_bulk_email_services_test.py
@@ -17,6 +17,8 @@
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
+import logging
+
 from core.platform.bulk_email import mailchimp_bulk_email_services
 from core.tests import test_utils
 import feconf
@@ -24,7 +26,7 @@
 
 from mailchimp3 import mailchimpclient
 
-from typing import Dict
+from typing import Any, Dict
 
 
 class MailchimpServicesUnitTests(test_utils.GenericTestBase):
@@ -163,15 +165,42 @@ def test_get_subscriber_hash(self) -> None:
             mailchimp_bulk_email_services._get_subscriber_hash(sample_email_2) # type: ignore[arg-type]  # pylint: disable=protected-access
 
     def test_get_mailchimp_class_error(self) -> None:
-        with self.assertRaisesRegexp( # type: ignore[no-untyped-call]
-            Exception, 'Mailchimp API key is not available.'):
+        observed_log_messages = []
+
+        def _mock_logging_function(
+                msg: str, *args: Any, **unused_kwargs: Any) -> None:
+            """Mocks logging.exception().
+
+            Args:
+                msg: str. The logging message.
+                *args: list(*). A list of arguments.
+                **unused_kwargs: *. Keyword arguments.
+            """
+            observed_log_messages.append(msg % args)
+
+        logging_swap = self.swap(logging, 'exception', _mock_logging_function)
+        with logging_swap:
             mailchimp_bulk_email_services._get_mailchimp_class() # pylint: disable=protected-access
+            self.assertItemsEqual( # type: ignore[no-untyped-call]
+                observed_log_messages, ['Mailchimp API key is not available.'])
 
-        swap_api = self.swap(feconf, 'MAILCHIMP_API_KEY', 'key')
-        with swap_api:
-            with self.assertRaisesRegexp( # type: ignore[no-untyped-call]
-                Exception, 'Mailchimp username is not set.'):
+            observed_log_messages = []
+            swap_api = self.swap(feconf, 'MAILCHIMP_API_KEY', 'key')
+            with swap_api:
                 mailchimp_bulk_email_services._get_mailchimp_class() # pylint: disable=protected-access
+                self.assertItemsEqual( # type: ignore[no-untyped-call]
+                    observed_log_messages, ['Mailchimp username is not set.'])
+
+            # For the tests below, the email ID for the user doesn't matter
+            # since the function should return earlier if mailchimp api key or
+            # username is not set.
+            # Permanently deletes returns None when mailchimp keys are not set.
+            self.assertIsNone(
+                mailchimp_bulk_email_services.permanently_delete_user_from_list( # type: ignore[func-returns-value]
+                    'sample_email'))
+            self.assertFalse(
+                mailchimp_bulk_email_services.add_or_update_user_status(
+                    'sample_email', True))
 
     def test_add_or_update_mailchimp_user_status(self) -> None:
         mailchimp = self.MockMailchimpClass()