def _room_change_alert(self): template_type = 'room_change_no_longer_eligible' all_scheduled = list( filter( lambda s: template_type not in (s.alerts or []), Scheduled.get_all_scheduled(term_id=self.term_id), ), ) if all_scheduled: email_template = EmailTemplate.get_template_by_type(template_type) courses = SisSection.get_courses( term_id=self.term_id, section_ids=[s.section_id for s in all_scheduled], include_deleted=True, ) courses_per_section_id = dict( (course['sectionId'], course) for course in courses) for scheduled in all_scheduled: course = courses_per_section_id.get(scheduled.section_id) if course: if self._has_moved_to_ineligible_room( course, scheduled) or course['deletedAt']: if email_template: for instructor in course['instructors']: def _get_interpolate_content(template): return interpolate_content( course=course, publish_type_name=course.get( 'scheduled', {}).get('publishTypeName'), recipient_name=instructor['name'], recording_type_name=course.get( 'scheduled', {}).get('recordingTypeName'), templated_string=template, ) QueuedEmail.create( message=_get_interpolate_content( email_template.message), recipient=instructor, section_id=course['sectionId'], subject_line=_get_interpolate_content( email_template.subject_line), template_type=template_type, term_id=self.term_id, ) Scheduled.add_alert( scheduled_id=course['scheduled']['id'], template_type=template_type) else: send_system_error_email(f""" No '{template_type}' email template available. We are unable to notify {course['label']} instructors of room change. """) else: subject = f'Scheduled course has no SIS data (section_id={scheduled.section_id})' message = f'{subject}\n\nScheduled:<pre>{scheduled}</pre>' app.logger.error(message) send_system_error_email(message=message, subject=subject)
def test_course_has_opted_out(self): """Do not send email to courses that have opted out.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 50000 CoursePreference.update_opt_out(term_id=term_id, section_id=section_id, opt_out=True) email_template_type = 'invitation' recipient = { 'name': 'William Peter Blatty', 'uid': '10001', } QueuedEmail.create(section_id, email_template_type, term_id, recipient=recipient) std_commit(allow_test_environment=True) emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True) # Expect no emails sent emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) assert list(map(lambda e: e.id, emails_sent_before)) == list( map(lambda e: e.id, emails_sent_after))
def test_no_email_template_available(self): """If email_template is not available then keep related emails in the queue.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 22287 email_template_type = 'waiting_for_approval' queued_email = QueuedEmail.create(section_id, email_template_type, term_id) std_commit(allow_test_environment=True) emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect no email sent emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) # Assert that email is still queued assert section_id in QueuedEmail.get_all_section_ids( template_type=email_template_type, term_id=term_id) # Clean up QueuedEmail.delete(queued_email)
def _run(self, args=None): term_id = app.config['CURRENT_TERM_ID'] for queued_email in QueuedEmail.get_all(term_id): course = SisSection.get_course(term_id, queued_email.section_id, include_deleted=True) if not course: app.logger.warn( f'Email will remain queued until course data is present: {queued_email}' ) continue if course['hasOptedOut']: QueuedEmail.delete(queued_email) continue if BConnected().send( message=queued_email.message, recipient=queued_email.recipient, section_id=queued_email.section_id, subject_line=queued_email.subject_line, template_type=queued_email.template_type, term_id=term_id, ): QueuedEmail.delete(queued_email) else: # If send() fails then report the error and DO NOT delete the queued item. app.logger.error(f'Failed to send email: {queued_email}')
def test_queued_email_for_admin(self): """Certain email template types are for admin recipients only.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 22287 email_template_type = 'admin_alert_room_change' QueuedEmail.create(section_id, email_template_type, term_id) std_commit(allow_test_environment=True) before = utc_now() emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect email to admin email address emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) + 1 sent_email = next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None) assert sent_email json_ = sent_email.to_api_json() assert json_['recipientUids'] == [app.config['EMAIL_DIABLO_ADMIN_UID']] assert json_['sectionId'] == section_id assert json_['templateType'] == email_template_type assert json_['termId'] == term_id assert json_['sentAt']
def test_currently_no_person_teaching_course(self): """If course does not have a proper instructor then the email remains queued.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 22460 email_template_type = 'invitation' # Courses with no proper instructor are excluded from query results. assert not SisSection.get_course(term_id=term_id, section_id=section_id) queued_email = QueuedEmail.create(section_id, email_template_type, term_id) std_commit(allow_test_environment=True) emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect no email sent emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) # Assert that email is still queued assert section_id in QueuedEmail.get_all_section_ids( template_type=email_template_type, term_id=term_id) # Clean up QueuedEmail.delete(queued_email)
def test_course_has_opted_out(self): """Do not send email to courses that have opted out.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 28602 CoursePreference.update_opt_out(term_id=term_id, section_id=section_id, opt_out=True) email_template_type = 'invitation' QueuedEmail.create(section_id, email_template_type, term_id) std_commit(allow_test_environment=True) before = utc_now() emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect no emails sent emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) assert not next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None)
def _notify(self, course, template_type): email_template = EmailTemplate.get_template_by_type(template_type) if email_template: def _get_interpolate_content(template): scheduled = course.get('scheduled', {}) return interpolate_content( course=course, publish_type_name=scheduled.get('publishTypeName'), recipient_name=recipient['name'], recording_type_name=scheduled.get('recordingTypeName'), templated_string=template, ) recipient = get_admin_alert_recipient() QueuedEmail.create( message=_get_interpolate_content(email_template.message), recipient=recipient, section_id=course['sectionId'], subject_line=_get_interpolate_content( email_template.subject_line), template_type=template_type, term_id=self.term_id, ) Scheduled.add_alert(scheduled_id=course['scheduled']['id'], template_type=template_type) else: send_system_error_email(f""" No email template of type {template_type} is available. Diablo admin NOT notified in regard to course {course['label']}. """)
def queue_emails(): params = request.get_json() term_id = params.get('termId') section_ids = params.get('sectionIds') template_type = params.get('emailTemplateType') if term_id and section_ids and template_type: section_ids_already_queued = QueuedEmail.get_all_section_ids( template_type=template_type, term_id=term_id) section_ids_to_queue = [ id_ for id_ in section_ids if id_ not in section_ids_already_queued ] for section_id in section_ids_to_queue: QueuedEmail.create(section_id=section_id, template_type=template_type, term_id=term_id) section_id_count = len(section_ids) queued_count = len(section_ids_to_queue) if queued_count < section_id_count: message = f""" {len(section_ids_already_queued)} '{template_type}' emails were already queued up for the {'course' if section_id_count == 1 else 'courses'} you submitted. Thus, {'no' if queued_count == 0 else f'only {queued_count}'} emails added to the queue. """ else: message = f'{queued_count} \'{template_type}\' emails will be sent.' return tolerant_jsonify({ 'message': message, }) else: raise BadRequestError('Required parameters are missing.')
def test_send_invitation_emails(self): """Send all email in 'queued_emails' table.""" def _emails_sent(section_id): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] courses = [ { 'sectionId': 28602, 'instructorUids': ['234567', '8765432'], }, { 'sectionId': 28165, 'instructorUids': ['8765432'], }, ] email_template_type = 'invitation' for course in courses: QueuedEmail.create(course['sectionId'], email_template_type, term_id) std_commit(allow_test_environment=True) def _get_emails_to_courses(): emails_sent_ = [] for course_ in courses: emails_sent_.extend(_emails_sent(course_['sectionId'])) return emails_sent_ before = utc_now() emails_sent_before = _get_emails_to_courses() # Run the job QueuedEmailsJob(app.app_context).run() std_commit(allow_test_environment=True) # Expect one email per instructor emails_sent_after = _get_emails_to_courses() assert len(emails_sent_after) == len(emails_sent_before) + 2 def _find_email(section_id): return next((e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None) for course in courses: sent_email = _find_email(section_id=course['sectionId']) assert sent_email json_ = sent_email.to_api_json() assert set(json_['recipientUids']) == set(course['instructorUids']) assert json_['sectionId'] == course['sectionId'] assert json_['templateType'] == email_template_type assert json_['termId'] == term_id assert json_['sentAt']
def test_currently_no_person_teaching_course(self): """Refuse to queue emails for a course without a proper instructor.""" term_id = app.config['CURRENT_TERM_ID'] section_id = 50006 email_template_type = 'invitation' # Courses with no proper instructor are excluded from query results. assert not SisSection.get_course(term_id=term_id, section_id=section_id) # Queued email creation fails. assert not QueuedEmail.create(section_id, email_template_type, term_id, recipient=None) assert section_id not in QueuedEmail.get_all_section_ids(template_type=email_template_type, term_id=term_id)
def email_new_invites(self): for course in SisSection.get_courses(term_id=self.term_id): if not course['hasOptedOut'] and len(course.get('meetings', {}).get('eligible', [])) == 1: for i in course['instructors']: if not i['wasSentInvite']: QueuedEmail.create( recipient=i, section_id=course['sectionId'], template_type='invitation', term_id=self.term_id, )
def test_send_invitation_emails(self): """Send all email in 'queued_emails' table.""" term_id = app.config['CURRENT_TERM_ID'] courses = SisSection.get_courses(section_ids=[50000, 50001], term_id=term_id) email_template_type = 'invitation' for course in courses: for instructor in course['instructors']: QueuedEmail.create(course['sectionId'], email_template_type, term_id, recipient=instructor) std_commit(allow_test_environment=True) def _get_emails_to_courses(): emails_sent = [] for c in courses: emails_sent.extend( _get_emails_sent( email_template_type=email_template_type, section_id=c['sectionId'], term_id=term_id, ), ) return emails_sent before = utc_now() emails_sent_before = _get_emails_to_courses() # Run the job QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True) # Expect one email per instructor emails_sent_after = _get_emails_to_courses() assert len(emails_sent_after) == len(emails_sent_before) + 3 def _find_email(section_id, uid): return next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before and uid == e.recipient_uid), None) for course in courses: for instructor in course['instructors']: sent_email = _find_email(section_id=course['sectionId'], uid=instructor['uid']) assert sent_email email_json = sent_email.to_api_json() assert email_json['recipientUid'] == instructor['uid'] assert email_json['sectionId'] == course['sectionId'] assert email_json['templateType'] == email_template_type assert email_json['termId'] == term_id assert email_json['sentAt']
def run(self, args=None): term_id = app.config['CURRENT_TERM_ID'] for queued_email in QueuedEmail.get_all(term_id): template_type = queued_email.template_type course = SisSection.get_course(term_id, queued_email.section_id) if course: if course['hasOptedOut']: # Do not send email; delete the item from queue. QueuedEmail.delete(queued_email) else: if template_type in [ 'invitation', 'notify_instructor_of_changes', 'recordings_scheduled', 'room_change_no_longer_eligible', 'waiting_for_approval', ]: recipients = course['instructors'] elif template_type in [ 'admin_alert_instructor_change', 'admin_alert_room_change' ]: recipients = get_admin_alert_recipients() else: raise BackgroundJobError( f'Email template type not supported: {template_type}' ) # If send() returns False then report the error and DO NOT delete the queued item. if send_course_related_email( course=course, recipients=recipients, template_type=template_type, term_id=term_id, ): QueuedEmail.delete(queued_email) else: app.logger.error( f'Failed to send email: {queued_email}') else: app.logger.warn( f'Email will remain queued until course gets proper instructor: {queued_email}' )
def test_no_email_queued(self): """Do nothing if 'queued_emails' table is empty.""" term_id = app.config['CURRENT_TERM_ID'] QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True) # Verify that the next job run will have zero queued emails. assert len(QueuedEmail.get_all(term_id=term_id)) == 0 QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True)
def test_queued_email_for_admin(self): """Certain email template types are for admin recipients only.""" def _emails_sent(): return _get_emails_sent(email_template_type=email_template_type, section_id=section_id, term_id=term_id) term_id = app.config['CURRENT_TERM_ID'] section_id = 50005 email_template_type = 'admin_alert_room_change' recipient = { 'name': 'Course Capture Admin', 'uid': app.config['EMAIL_DIABLO_ADMIN_UID'], } QueuedEmail.create(section_id, email_template_type, term_id, recipient=recipient) std_commit(allow_test_environment=True) before = utc_now() emails_sent_before = _emails_sent() # Run the job QueuedEmailsJob(simply_yield).run() std_commit(allow_test_environment=True) # Expect email to admin email address emails_sent_after = _emails_sent() assert len(emails_sent_after) == len(emails_sent_before) + 1 sent_email = next( (e for e in emails_sent_after if e.section_id == section_id and e.sent_at > before), None) assert sent_email email_json = sent_email.to_api_json() assert email_json['recipientUid'] == app.config[ 'EMAIL_DIABLO_ADMIN_UID'] assert email_json['sectionId'] == section_id assert email_json['templateType'] == email_template_type assert email_json['termId'] == term_id assert email_json['sentAt']
def queue_emails(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') template_type = params.get('emailTemplateType') if not (term_id and section_id and template_type): raise BadRequestError('Required parameters are missing.') course = SisSection.get_course(term_id=term_id, section_id=section_id) for instructor in course['instructors']: if not QueuedEmail.create(section_id=section_id, recipient=instructor, template_type=template_type, term_id=term_id): raise BadRequestError(f"Failed to queue email of type '{template_type}'.") return tolerant_jsonify({ 'message': f"An email of type '{template_type}' has been queued.", })