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 notify_instructors_recordings_scheduled(course, scheduled): template_type = 'recordings_scheduled' email_template = EmailTemplate.get_template_by_type(template_type) if email_template: publish_type_name = NAMES_PER_PUBLISH_TYPE[scheduled.publish_type] recording_type_name = NAMES_PER_RECORDING_TYPE[scheduled.recording_type] for instructor in course['instructors']: message = interpolate_content( templated_string=email_template.message, course=course, recipient_name=instructor['name'], publish_type_name=publish_type_name, recording_type_name=recording_type_name, ) subject_line = interpolate_content( templated_string=email_template.subject_line, course=course, recipient_name=instructor['name'], ) QueuedEmail.create( message=message, subject_line=subject_line, recipient=instructor, section_id=course['sectionId'], template_type=email_template.template_type, term_id=course['termId'], ) else: send_system_error_email(f""" No email template of type {template_type} is available. {course['label']} instructors were NOT notified of scheduled: {scheduled}. """)
def ping(): b_connected_ping = None canvas_ping = None db_ping = None kaltura_ping = None status = 200 try: b_connected_ping = BConnected().ping() canvas_ping = _ping_canvas() db_ping = _db_status() kaltura_ping = Kaltura().ping() except Exception as e: status = 500 subject = str(e) subject = f'{subject[:50]}...' if len(subject) > 50 else subject message = f'Error during /api/ping: {subject}' app.logger.error(message) app.logger.exception(e) if app.config['EMAIL_IF_PING_HAS_ERROR']: send_system_error_email( message=f'{message}\n\n<pre>{traceback.format_exc()}</pre>', subject=message, ) finally: return tolerant_jsonify( { 'app': True, 'bConnected': b_connected_ping, 'canvas': canvas_ping, 'db': db_ping, 'kaltura': kaltura_ping, }, status=status, )
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 _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 _get_email_template(course, template_type): template = EmailTemplate.get_template_by_type(template_type) if not template: subject = f"No {template_type} email template found; failed to queue email for section_id {course['sectionId']}" send_system_error_email( message=f'{subject}\n\n<pre>{course}</pre>', subject=subject, ) return template
def handle_exception(e): subject = str(e) if isinstance(e, HTTPException): # No Ops notification when HTTP error app.logger.exception(e) else: # Notify Diablo Ops teams send_system_error_email( message=f'{subject}\n\n<pre>{traceback.format_exc()}</pre>', subject=f'{subject[:50]}...' if len(subject) > 50 else subject, ) return {'message': subject}, 500
def _run(self): canvas_term_id = app.config['CANVAS_ENROLLMENT_TERM_ID'] canvas_course_sites = get_canvas_course_sites(canvas_term_id) if canvas_course_sites: CanvasCourseSite.refresh_term_data( term_id=app.config['CURRENT_TERM_ID'], canvas_course_sites=canvas_course_sites, ) else: send_system_error_email( message='Please verify Canvas settings in Diablo config.', subject=f'Canvas API call returned zero courses (canvas_term_id = {canvas_term_id})', )
def run(self, force_run=False): with self.app_context(): job = Job.get_job_by_key(self.key()) if job: current_instance_id = os.environ.get('EC2_INSTANCE_ID') job_runner_id = fetch_job_runner_id() if job.disabled and not force_run: app.logger.warn( f'Job {self.key()} is disabled. It will not run.') elif current_instance_id and current_instance_id != job_runner_id: app.logger.warn( f'Skipping job because current instance {current_instance_id} is not job runner {job_runner_id}' ) elif JobHistory.is_job_running(job_key=self.key()): app.logger.warn( f'Skipping job {self.key()} because an older instance is still running' ) else: app.logger.info(f'Job {self.key()} is starting.') job_tracker = JobHistory.job_started(job_key=self.key()) try: self._run() JobHistory.job_finished(id_=job_tracker.id) app.logger.info( f'Job {self.key()} finished successfully.') except Exception as e: JobHistory.job_finished(id_=job_tracker.id, failed=True) summary = f'Job {self.key()} failed due to {str(e)}' 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: raise BackgroundJobError( f'Job {self.key()} is not registered in the database')
def _run(self): sis_term_id = app.config['CURRENT_TERM_ID'] canvas_enrollment_term = get_canvas_enrollment_term(sis_term_id) if canvas_enrollment_term: canvas_course_sites = get_canvas_course_sites(canvas_enrollment_term.id) if canvas_course_sites: CanvasCourseSite.refresh_term_data( term_id=sis_term_id, canvas_course_sites=canvas_course_sites, ) else: send_system_error_email( message='Please verify Canvas settings in Diablo config.', subject=f'Canvas API call returned zero courses (canvas_term_id = {canvas_enrollment_term.id})', ) else: send_system_error_email( message=f'No matching Canvas enrollment_term for sis_term_id = {sis_term_id}', subject=f'Canvas API returned no enrollment_term for sis_term_id = {sis_term_id}', )
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 _report_error(subject): message = f'{subject}\n\n<pre>{course}</pre>' app.logger.error(message) send_system_error_email(message=message, subject=subject)
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