Repository: oppia/oppia

Issue:
Send email to selected recipients when ML training job fails.
In Oppia ML training jobs are trained separately on Oppia-ml. It may happen that some jobs will fail by exceeding TTL. To keep record of these failed jobs we need facility to send email to selected recipients whenever a training job fails. To implement this feature:

- [x] Add config property on admin page from where admin can add selected recipients' emails.
- [x] Implement feature for sending email to these selected recipients and Oppia admin.


Discussion:

Resolving patch:
diff --git a/core/domain/email_manager.py b/core/domain/email_manager.py
--- a/core/domain/email_manager.py
+++ b/core/domain/email_manager.py
@@ -37,6 +37,21 @@
 def log_new_error(*args, **kwargs):
     logging.error(*args, **kwargs)
 
+NOTIFICATION_EMAIL_LIST_SCHEMA = {
+    'type': 'list',
+    'items': {
+        'type': 'unicode',
+        'validators': [{
+            'id': 'is_valid_email',
+        }]
+    },
+    'validators': [{
+        'id': 'has_length_at_most',
+        'max_value': 5
+    }, {
+        'id': 'is_uniquified',
+    }]
+}
 
 EMAIL_HTML_BODY_SCHEMA = {
     'type': 'unicode',
@@ -117,6 +132,13 @@ def log_new_error(*args, **kwargs):
     'I\'m writing to inform you that I have unpublished the above '
     'exploration.')
 
+NOTIFICATION_EMAILS_FOR_FAILED_TASKS = config_domain.ConfigProperty(
+    'notification_emails_for_failed_tasks',
+    NOTIFICATION_EMAIL_LIST_SCHEMA,
+    'Email(s) to notify if an ML training task fails',
+    []
+)
+
 SENDER_VALIDATORS = {
     feconf.EMAIL_INTENT_SIGNUP: (lambda x: x == feconf.SYSTEM_COMMITTER_ID),
     feconf.EMAIL_INTENT_UNPUBLISH_EXPLORATION: (
diff --git a/schema_utils.py b/schema_utils.py
--- a/schema_utils.py
+++ b/schema_utils.py
@@ -25,6 +25,7 @@
 """
 
 import numbers
+import re
 import urllib
 import urlparse
 
@@ -265,3 +266,8 @@ def is_regex(obj):
     def matches_regex(obj, regex):
         """Ensures that `obj` (a string) matches the given regex."""
         raise NotImplementedError
+
+    @staticmethod
+    def is_valid_email(obj):
+        """Ensures that `obj` (a string) is a valid email."""
+        return bool(re.search(r"^[\w\.\+\-]+\@[\w]+\.[a-z]{2,3}$", obj))


Test patch:
diff --git a/schema_utils_test.py b/schema_utils_test.py
--- a/schema_utils_test.py
+++ b/schema_utils_test.py
@@ -20,6 +20,7 @@
 
 import inspect
 
+from core.domain import email_manager
 from core.tests import test_utils
 import schema_utils
 
@@ -160,6 +161,7 @@
         },
         'is_nonempty': {},
         'is_regex': {},
+        'is_valid_email': {},
     },
 }
 
@@ -568,3 +570,14 @@ def test_dict_schema(self):
         ], None, 123, 'abc']
 
         self.check_normalization(schema, mappings, invalid_vals)
+
+    def test_notification_email_list_validator(self):
+        schema = email_manager.NOTIFICATION_EMAIL_LIST_SCHEMA
+        valid_email_list = [u'user{}@oppia.com'.format(i) for i in xrange(0, 5)]
+        big_email_list = [u'user{}@oppia.com'.format(i) for i in xrange(0, 7)]
+        mappings = [
+            ([u'admin@oppia.com'], [u'admin@oppia.com']),
+            (valid_email_list, valid_email_list)]
+        invalid_vals = [[u'admin@oppia'], big_email_list,
+                        [u'admin@oppia.commmm'], [u'a@.com']]
+        self.check_normalization(schema, mappings, invalid_vals)