def get_courses_ready_to_schedule(approvals, term_id): ready_to_schedule = [] scheduled_section_ids = [ s.section_id for s in Scheduled.get_all_scheduled(term_id=term_id) ] unscheduled_approvals = [ approval for approval in approvals if approval.section_id not in scheduled_section_ids ] if unscheduled_approvals: courses = SisSection.get_courses( section_ids=[a.section_id for a in unscheduled_approvals], term_id=term_id) courses_per_section_id = dict( (int(course['sectionId']), course) for course in courses) admin_user_uids = set([ user.uid for user in AdminUser.all_admin_users(include_deleted=True) ]) for section_id, uids in _get_uids_per_section_id( approvals=unscheduled_approvals).items(): if admin_user_uids.intersection(set(uids)): ready_to_schedule.append(courses_per_section_id[section_id]) else: course = courses_per_section_id[section_id] necessary_uids = [i['uid'] for i in course['instructors']] if all(uid in uids for uid in necessary_uids): ready_to_schedule.append( courses_per_section_id[section_id]) return ready_to_schedule
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 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 test_invite_new_instructors(self, app, db_session): """Invite all assigned instructors who haven't yet received an invitation.""" term_id = app.config['CURRENT_TERM_ID'] with test_approvals_workflow(app): # The job creates many new invitations. timestamp = utc_now() # Emails are queued but not sent. InvitationJob(simply_yield).run() assert len(_get_invitations_since(term_id, timestamp)) == 0 # Emails are sent. We have more emails than courses since some courses have multiple instructors. QueuedEmailsJob(simply_yield).run() invitations = _get_invitations_since(term_id, timestamp) assert len(invitations) == 14 # Each eligible course has an invitation. eligible_courses = [ c for c in SisSection.get_courses(term_id=term_id) if len(c['meetings']['eligible']) == 1 ] assert len(eligible_courses) == 11 for course in eligible_courses: for i in course['instructors']: sent_email = next((e for e in invitations if e.section_id == course['sectionId'] and i['uid'] == e.recipient_uid), None) assert sent_email email_json = sent_email.to_api_json() assert email_json['recipientUid'] == i['uid'] assert email_json['sectionId'] == course['sectionId'] assert email_json['templateType'] == 'invitation' assert email_json['termId'] == term_id assert email_json['sentAt'] # Add an instructor. section = SisSection.query.filter_by(term_id=term_id, section_id=50002).first() db_session.expunge(section) make_transient(section) section.id = None section.instructor_uid = '10008' db_session.add(section) std_commit(allow_test_environment=True) # Re-run the job. An email is sent to the new instructor only. timestamp = utc_now() InvitationJob(simply_yield).run() QueuedEmailsJob(simply_yield).run() invitations = _get_invitations_since(term_id, timestamp) assert len(invitations) == 1 invitation = invitations[0].to_api_json() assert invitation['sectionId'] == 50002 assert invitation['recipientUid'] == '10008'
def run(self): term_id = app.config['CURRENT_TERM_ID'] all_scheduled = Scheduled.get_all_scheduled(term_id=term_id) if all_scheduled: courses = SisSection.get_courses( term_id=term_id, section_ids=[s.section_id for s in all_scheduled]) _alert_admin_of_instructor_change( courses=courses, approval_uids_per_section_id=_approval_uids_per_section_id( scheduled=all_scheduled, term_id=term_id, ), ) _alert_admin_of_room_change( courses=courses, scheduled_rooms_per_section_id= _scheduled_locations_per_section_id(all_scheduled), )
def run(self): term_id = app.config['CURRENT_TERM_ID'] all_scheduled = Scheduled.get_all_scheduled(term_id=term_id) if all_scheduled: courses = SisSection.get_courses( term_id=term_id, section_ids=[s.section_id for s in all_scheduled]) courses_per_section_id = dict( (course['sectionId'], course) for course in courses) for scheduled in all_scheduled: course = courses_per_section_id[scheduled.section_id] if course: if scheduled.room_id != course['room']['id']: email_template = EmailTemplate.get_template_by_type( 'room_change_no_longer_eligible') for instructor in course['instructor']: BConnected().send( message=interpolate_email_content( templated_string=email_template.message, course=course, instructor_name=instructor['name'], recipient_name=instructor['name'], recording_type_name=scheduled. recording_type, ), recipients=course['instructors'], subject_line=interpolate_email_content( templated_string=email_template. subject_line, course=course, instructor_name=instructor['name'], recipient_name=instructor['name'], recording_type_name=scheduled. recording_type, ), ) else: error = f'section_id of scheduled recordings was not found in SIS data: {scheduled}' app.logger.error(error) send_system_error_email(message=error)
def get_courses_ready_to_schedule(approvals, term_id): ready_to_schedule = [] scheduled_section_ids = [ s.section_id for s in Scheduled.get_all_scheduled(term_id=term_id) ] unscheduled_approvals = [ approval for approval in approvals if approval.section_id not in scheduled_section_ids ] if unscheduled_approvals: courses = SisSection.get_courses( section_ids=[a.section_id for a in unscheduled_approvals], term_id=term_id) courses_per_section_id = dict( (int(course['sectionId']), course) for course in courses) admin_user_uids = set([ user.uid for user in AdminUser.all_admin_users(include_deleted=True) ]) for section_id, approved_by_uids in _get_uids_per_section_id( approvals=unscheduled_approvals).items(): course = courses_per_section_id.get(section_id) if not course: continue if len(course.get('meetings', {}).get('eligible', [])) != 1: app.logger.warn( f'Unique meeting pattern not found for section id {section_id}; will not schedule.' ) continue if admin_user_uids.intersection(set(approved_by_uids)): ready_to_schedule.append(course) else: necessary_uids = [i['uid'] for i in course['instructors']] if all(uid in approved_by_uids for uid in necessary_uids): ready_to_schedule.append(course) return ready_to_schedule
def _get_courses_per_filter(filter_, term_id): if filter_ not in get_search_filter_options() or not term_id: raise BadRequestError('One or more required params are missing or invalid') if filter_ == 'All': courses = SisSection.get_courses(term_id) elif filter_ == 'Do Not Email': courses = SisSection.get_courses_opted_out(term_id) elif filter_ == 'Invited': courses = SisSection.get_courses_invited(term_id) elif filter_ == 'Not Invited': courses = SisSection.get_eligible_courses_not_invited(term_id) elif filter_ == 'Partially Approved': courses = SisSection.get_courses_partially_approved(term_id) elif filter_ == 'Queued for Scheduling': courses = SisSection.get_courses_queued_for_scheduling(term_id) elif filter_ == 'Scheduled': courses = SisSection.get_courses_scheduled_standard_dates(term_id) elif filter_ == 'Scheduled (Nonstandard Dates)': courses = SisSection.get_courses_scheduled_nonstandard_dates(term_id) else: raise BadRequestError(f'Invalid filter: {filter_}') return courses