예제 #1
0
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))
예제 #2
0
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}.
        """)
예제 #3
0
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,
        )
예제 #4
0
    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)
예제 #5
0
    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']}.
            """)
예제 #6
0
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
예제 #7
0
 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
예제 #8
0
 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})',
         )
예제 #9
0
    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')
예제 #10
0
 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}',
         )
예제 #11
0
 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)
예제 #12
0
 def _report_error(subject):
     message = f'{subject}\n\n<pre>{course}</pre>'
     app.logger.error(message)
     send_system_error_email(message=message, subject=subject)
예제 #13
0
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