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 unschedule(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') course = SisSection.get_course(term_id, section_id, include_deleted=True) if (term_id and section_id) else None if not course: raise BadRequestError('Required params missing or invalid') if not (course['scheduled'] or course['hasNecessaryApprovals']): raise BadRequestError(f'Section id {section_id}, term id {term_id} is not currently scheduled or queued for scheduling') Approval.delete(term_id=term_id, section_id=section_id) Scheduled.delete(term_id=term_id, section_id=section_id) event_id = (course.get('scheduled') or {}).get('kalturaScheduleId') if event_id: try: Kaltura().delete(event_id) except (KalturaClientException, KalturaException) as e: message = f'Failed to delete Kaltura schedule: {event_id}' app.logger.error(message) app.logger.exception(e) send_system_error_email( message=f'{message}\n\n<pre>{traceback.format_exc()}</pre>', subject=message, ) CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=True, ) return tolerant_jsonify(SisSection.get_course(term_id, section_id, include_deleted=True))
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 test_do_not_email_filter(self, client, db, admin_session): """Do Not Email filter: Courses in eligible room; "opt out" is true; all stages of approval; not scheduled.""" with test_approvals_workflow(app): # Send invites them opt_out. for section_id in (section_1_id, section_in_ineligible_room, section_3_id, section_4_id): CoursePreference.update_opt_out(section_id=section_id, term_id=self.term_id, opt_out=True) in_enabled_room = _is_course_in_enabled_room(section_id=section_id, term_id=self.term_id) if section_id == section_in_ineligible_room: # Courses in ineligible rooms will be excluded from the feed. assert not in_enabled_room else: assert in_enabled_room SentEmail.create( section_id=section_id, recipient_uids=_get_instructor_uids(section_id=section_id, term_id=self.term_id), template_type='invitation', term_id=self.term_id, ) # If course has approvals but not scheduled then it will show up in the feed. Approval.create( approved_by_uid=_get_instructor_uids(section_id=section_1_id, term_id=self.term_id)[0], approver_type_='instructor', cross_listed_section_ids=[], publish_type_='canvas', recording_type_='presentation_audio', room_id=Room.get_room_id(section_id=section_1_id, term_id=self.term_id), section_id=section_1_id, term_id=self.term_id, ) # Feed will exclude scheduled. _schedule_recordings( section_id=section_3_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Do Not Email') for section_id in (section_1_id, section_4_id): # The 'Do Not Email' course is in the feed assert _find_course(api_json=api_json, section_id=section_id) for section_id in (section_3_id, section_in_ineligible_room): # Excluded courses assert not _find_course(api_json=api_json, section_id=section_id)
def update_opt_out(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') opt_out = params.get('optOut') preferences = CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=opt_out, ) return tolerant_jsonify(preferences.to_api_json())
def test_course_opted_out(self, app): """Do not send email to courses that have opted out.""" term_id = app.config['CURRENT_TERM_ID'] with test_approvals_workflow(app): section_id = 50006 CoursePreference.update_opt_out(term_id=term_id, section_id=section_id, opt_out=True) std_commit(allow_test_environment=True) 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. QueuedEmailsJob(simply_yield).run() invitations = _get_invitations_since(term_id, timestamp) assert len(invitations) == 15 assert not next( (e for e in invitations if e.section_id == section_id), None)
def _after_approval(course): section_id = course['sectionId'] term_id = course['termId'] approvals = Approval.get_approvals(section_id=section_id, term_id=term_id) if get_courses_ready_to_schedule(approvals=approvals, term_id=term_id): # Queuing course for scheduling wipes any opt-out preference. if course['hasOptedOut']: CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=False, ) if app.config['FEATURE_FLAG_SCHEDULE_RECORDINGS_SYNCHRONOUSLY']: # Feature flag intended for dev workstation ONLY. Do not enable in diablo-dev|qa|prod. schedule_recordings( all_approvals=approvals, course=course, ) return SisSection.get_course(section_id=course['sectionId'], term_id=course['termId']) else: return course
def test_do_not_email_filter(self, client, admin_session): """Do Not Email filter: Courses in eligible room; "opt out" is true; all stages of approval; not scheduled.""" with test_approvals_workflow(app): # Send invites them opt_out. for section_id in (section_1_id, section_in_ineligible_room, section_3_id, section_4_id): CoursePreference.update_opt_out(section_id=section_id, term_id=self.term_id, opt_out=True) in_enabled_room = _is_course_in_enabled_room(section_id=section_id, term_id=self.term_id) if section_id == section_in_ineligible_room: # Courses in ineligible rooms will be excluded from the feed. assert not in_enabled_room else: assert in_enabled_room self._send_invitation_email(section_id) # If course has approvals but not scheduled then it will show up in the feed. self._create_approval(section_4_id) # Feed will exclude scheduled. mock_scheduled( section_id=section_3_id, term_id=self.term_id, ) std_commit(allow_test_environment=True) api_json = self._api_courses(client, term_id=self.term_id, filter_='Do Not Email') # Opted-out courses are in the feed, whether approved or not course_1 = _find_course(api_json=api_json, section_id=section_1_id) assert course_1['approvalStatus'] == 'Invited' assert course_1['schedulingStatus'] == 'Not Scheduled' course_4 = _find_course(api_json=api_json, section_id=section_4_id) assert course_4['approvalStatus'] == 'Approved' assert course_4['schedulingStatus'] == 'Queued for Scheduling' for section_id in (section_3_id, section_in_ineligible_room): # Excluded courses assert not _find_course(api_json=api_json, section_id=section_id)
def update_opt_out(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') course = SisSection.get_course(term_id, section_id) if (term_id and section_id) else None opt_out = params.get('optOut') if not course or opt_out is None: raise BadRequestError('Required params missing or invalid') if course['scheduled']: raise BadRequestError('Cannot update opt-out on scheduled course') preferences = CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=opt_out, ) return tolerant_jsonify(preferences.to_api_json())
def schedule_recordings(all_approvals, course): def _report_error(subject): message = f'{subject}\n\n<pre>{course}</pre>' app.logger.error(message) send_system_error_email(message=message, subject=subject) meetings = course.get('meetings', {}).get('eligible', []) meeting = meetings[0] if len(meetings) == 1 else None if not meeting: _report_error( subject= f"{course['label']} not scheduled. Unique eligible meeting pattern not found." ) return None all_approvals.sort(key=lambda a: a.created_at.isoformat()) latest_approval = all_approvals[-1] room = Room.get_room(latest_approval.room_id) if room.location != meeting['location']: _report_error( subject= f"{course['label']} not scheduled. Room change: {room.location} to {meeting['location']}" ) return None has_admin_approval = next( (a for a in all_approvals if a.approver_type == 'admin'), None) approved_by_uids = set(a.approved_by_uid for a in all_approvals) instructor_uids = set([i['uid'] for i in course['instructors']]) if not has_admin_approval and not instructor_uids.issubset( approved_by_uids): _report_error( subject= f"{course['label']} not scheduled. We are missing instructor approval(s)." ) return None term_id = course['termId'] section_id = int(course['sectionId']) scheduled = None if room.kaltura_resource_id: try: kaltura_schedule_id = Kaltura().schedule_recording( canvas_course_site_ids=[ c['courseSiteId'] for c in course['canvasCourseSites'] ], course_label=course['label'], instructors=course['instructors'], meeting=meeting, publish_type=latest_approval.publish_type, recording_type=latest_approval.recording_type, room=room, term_id=term_id, ) scheduled = Scheduled.create( course_display_name=course['label'], instructor_uids=instructor_uids, kaltura_schedule_id=kaltura_schedule_id, meeting_days=meeting['days'], meeting_end_date=get_recording_end_date(meeting), meeting_end_time=meeting['endTime'], meeting_start_date=get_recording_start_date( meeting, return_today_if_past_start=True), meeting_start_time=meeting['startTime'], publish_type_=latest_approval.publish_type, recording_type_=latest_approval.recording_type, room_id=room.id, section_id=section_id, term_id=term_id, ) # Turn off opt-out setting if present. if section_id in CoursePreference.get_section_ids_opted_out( term_id=term_id): CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=False, ) notify_instructors_recordings_scheduled(course=course, scheduled=scheduled) uids = [approval.approved_by_uid for approval in all_approvals] app.logger.info( f'Recordings scheduled for course {section_id} per approvals: {", ".join(uids)}' ) except (KalturaClientException, KalturaException) as e: # Error codes: https://developer.kaltura.com/api-docs/Error_Codes summary = f"Failed to schedule recordings {course['label']} (section_id: {course['sectionId']})" app.logger.error(summary) app.logger.exception(e) send_system_error_email( message=f'{summary}\n\n<pre>{traceback.format_exc()}</pre>', subject=f'{summary[:50]}...' if len(summary) > 50 else summary, ) else: app.logger.warn(f""" SKIP schedule recordings because room has no 'kaltura_resource_id'. Course: {course['label']} Room: {room.location} Latest approved_by_uid: {latest_approval.approved_by_uid} """) return scheduled