示例#1
0
def _curated_group_with_complete_student_profiles(curated_group_id,
                                                  order_by='last_name',
                                                  term_id=None,
                                                  offset=0,
                                                  limit=50):
    benchmark = get_benchmarker(
        f'curated group {curated_group_id} with student profiles')
    benchmark('begin')
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'Sorry, no curated group found with id {curated_group_id}.')
    if not _can_current_user_view_curated_group(curated_group):
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, cannot view curated group {curated_group.id}'
        )
    api_json = curated_group.to_api_json(order_by=order_by,
                                         offset=offset,
                                         limit=limit)
    sids = [s['sid'] for s in api_json['students']]
    benchmark('begin profile query')
    api_json['students'] = get_summary_student_profiles(
        sids, term_id=term_id, include_historical=True)
    benchmark('begin alerts query')
    Alert.include_alert_counts_for_students(
        viewer_user_id=current_user.get_id(), group=api_json)
    benchmark('end')
    benchmark('begin get_referencing_cohort_ids')
    api_json[
        'referencingCohortIds'] = curated_group.get_referencing_cohort_ids()
    benchmark('end')
    return api_json
示例#2
0
def _curated_group_with_complete_student_profiles(curated_group_id,
                                                  order_by='last_name',
                                                  offset=0,
                                                  limit=50):
    benchmark = get_benchmarker(
        f'curated group {curated_group_id} with student profiles')
    benchmark('begin')
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'Sorry, no curated group found with id {curated_group_id}.')
    if curated_group.owner_id != current_user.get_id():
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, does not own curated group {curated_group.id}'
        )
    api_json = curated_group.to_api_json(order_by=order_by,
                                         offset=offset,
                                         limit=limit)
    sids = [s['sid'] for s in api_json['students']]
    benchmark('begin profile query')
    api_json['students'] = get_summary_student_profiles(sids)
    benchmark('begin alerts query')
    Alert.include_alert_counts_for_students(
        viewer_user_id=current_user.get_id(), group=api_json)
    benchmark('end')
    return api_json
示例#3
0
def create_alerts(client, db_session):
    """Create assignment and midterm grade alerts."""
    # Create three canned alerts for the current term and one for the previous term.
    from boac.models.alert import Alert
    Alert.create(
        sid='11667051',
        alert_type='late_assignment',
        key='2172_100900300',
        message='Week 5 homework in LATIN 100 is late.',
    )
    Alert.create(
        sid='11667051',
        alert_type='late_assignment',
        key='2178_800900300',
        message='Week 5 homework in RUSSIAN 13 is late.',
    )
    Alert.create(
        sid='11667051',
        alert_type='missing_assignment',
        key='2178_500600700',
        message='Week 6 homework in PORTUGUESE 12 is missing.',
    )
    Alert.create(
        sid='2345678901',
        alert_type='late_assignment',
        key='2178_100200300',
        message='Week 5 homework in BOSCRSR 27B is late.',
    )
    # Load our usual student of interest into the cache and generate midterm alerts from fixture data.
    client.get('/api/student/by_uid/61889')
    Alert.update_all_for_term(2178)
示例#4
0
 def test_no_activity_percentile_cutoff(self, app):
     """Respect percentile cutoff for alert creation."""
     with override_config(app, 'ALERT_NO_ACTIVITY_PERCENTILE_CUTOFF', 10):
         Alert.update_all_for_term(2178)
         assert len(get_current_alerts('3456789012')) == 0
     with override_config(app, 'ALERT_NO_ACTIVITY_PERCENTILE_CUTOFF', 20):
         Alert.update_all_for_term(2178)
         assert len(get_current_alerts('3456789012')) == 1
示例#5
0
def dismiss_alert(alert_id):
    user_id = current_user.get_id()
    Alert.dismiss(alert_id, user_id)
    CohortFilter.refresh_alert_counts_for_owner(user_id)
    return tolerant_jsonify({
        'message':
        f'Alert {alert_id} dismissed by UID {current_user.get_uid()}'
    }), 200
示例#6
0
 def test_update_withdrawal_alerts(self, app):
     """Can be created from SIS feeds."""
     with override_config(app, 'ALERT_WITHDRAWAL_ENABLED', True):
         Alert.update_all_for_term(2178)
         alerts = get_current_alerts('2345678901')
         assert len(alerts) == 1
         assert alerts[0]['key'] == '2178_withdrawal'
         assert alerts[0]['message'] == 'Student is no longer enrolled in the Fall 2017 term.'
示例#7
0
 def test_infrequent_activity_percentile_cutoff(self, app):
     """Respect percentile cutoff for alert creation."""
     with override_config(app, 'ALERT_INFREQUENT_ACTIVITY_ENABLED', True):
         with override_config(app, 'ALERT_INFREQUENT_ACTIVITY_PERCENTILE_CUTOFF', 10):
             Alert.update_all_for_term(2178)
             assert len(get_current_alerts('5678901234')) == 0
         with override_config(app, 'ALERT_INFREQUENT_ACTIVITY_PERCENTILE_CUTOFF', 20):
             Alert.update_all_for_term(2178)
             assert len(get_current_alerts('5678901234')) == 1
示例#8
0
 def test_update_no_activity_alerts(self):
     """Can be created from bCourses analytics feeds, at most one per enrollment."""
     Alert.update_all_for_term(2178)
     alerts = get_current_alerts('3456789012')
     assert len(alerts) == 1
     assert alerts[0]['id'] > 0
     assert alerts[0]['alertType'] == 'no_activity'
     assert alerts[0]['key'] == '2178_MED ST 205'
     assert alerts[0]['message'] == 'No activity! Student has never visited the MED ST 205 bCourses site for Fall 2017.'
示例#9
0
 def test_update_infrequent_activity_alerts(self, app):
     """Can be created from bCourses analytics feeds, at most one per enrollment."""
     with override_config(app, 'ALERT_INFREQUENT_ACTIVITY_ENABLED', True):
         Alert.update_all_for_term(2178)
         alerts = get_current_alerts('5678901234')
         assert len(alerts) == 1
         assert alerts[0]['id'] > 0
         assert alerts[0]['alertType'] == 'infrequent_activity'
         assert alerts[0]['key'] == '2178_MED ST 205'
         assert alerts[0]['message'].startswith('Infrequent activity! Last MED ST 205 bCourses activity')
示例#10
0
 def test_update_assignment_alerts(self):
     """Can be created from assignment data."""
     assert len(get_current_alerts('11667051')) == 0
     Alert.update_assignment_alerts(**alert_props)
     alerts = get_current_alerts('11667051')
     assert len(alerts) == 1
     assert alerts[0]['id'] > 0
     assert alerts[0]['alertType'] == 'missing_assignment'
     assert alerts[0]['key'] == '2178_987654321'
     assert alerts[0]['message'] == 'MED ST 205 assignment due on Oct 31, 2017.'
示例#11
0
 def test_no_duplicate_alerts(self):
     """If an alert exists with the same key, updates the message rather than creating a duplicate."""
     assert len(get_current_alerts('11667051')) == 0
     Alert.update_assignment_alerts(**alert_props)
     updated_alert_props = dict(alert_props, due_at='2017-12-25T12:00:00Z')
     Alert.update_assignment_alerts(**updated_alert_props)
     alerts = get_current_alerts('11667051')
     assert len(alerts) == 1
     assert alerts[0]['key'] == '2178_987654321'
     assert alerts[0]['message'] == 'MED ST 205 assignment due on Dec 25, 2017.'
示例#12
0
 def test_activation_deactivation_all_students(self):
     """Can activate and deactive across entire population for term."""
     assert len(get_current_alerts('11667051')) == 0
     assert len(get_current_alerts('3456789012')) == 0
     Alert.update_all_for_term(2178)
     assert len(get_current_alerts('11667051')) == 2
     assert len(get_current_alerts('3456789012')) == 1
     Alert.deactivate_all_for_term(2178)
     assert len(get_current_alerts('11667051')) == 0
     assert len(get_current_alerts('3456789012')) == 0
示例#13
0
文件: test_alert.py 项目: lyttam/boac
 def test_update_hold_alerts(self, app):
     """Can be created from SIS feeds."""
     with override_config(app, 'ALERT_HOLDS_ENABLED', True):
         Alert.update_all_for_term(2178)
         alerts = get_current_alerts('5678901234')
         assert len(alerts) == 2
         assert alerts[0]['key'] == '2178_S01_CSBAL'
         assert alerts[0]['message'].startswith(
             'Hold: Past due balance! Your student account has a past due balance.'
         )
         assert alerts[1]['key'] == '2178_V00_SMOUT'
         assert alerts[1]['message'].startswith(
             'Hold: Semester Out! You are not eligible to register')
示例#14
0
def get_section(term_id, section_id):
    offset = util.get(request.args, 'offset', None)
    if offset:
        offset = int(offset)
    limit = util.get(request.args, 'limit', None)
    if limit:
        limit = int(limit)
    section = get_sis_section(term_id, section_id)
    if not section:
        raise ResourceNotFoundError(f'No section {section_id} in term {term_id}')
    student_profiles = get_course_student_profiles(term_id, section_id, offset=offset, limit=limit)
    section.update(student_profiles)
    Alert.include_alert_counts_for_students(viewer_uid=current_user.uid, cohort=student_profiles)
    return tolerant_jsonify(section)
示例#15
0
def get_curated_cohort(curated_cohort_id):
    cohort = CuratedCohort.find_by_id(curated_cohort_id)
    if not cohort:
        raise ResourceNotFoundError(
            f'Sorry, no cohort found with id {curated_cohort_id}.')
    if cohort.owner_id != current_user.id:
        raise ForbiddenRequestError(
            f'Current user, {current_user.uid}, does not own cohort {cohort.id}'
        )
    cohort = cohort.to_api_json(sids_only=True)
    sids = [s['sid'] for s in cohort['students']]
    cohort['students'] = get_summary_student_profiles(sids)
    cohort['students'] = api_util.sort_students_by_name(cohort['students'])
    Alert.include_alert_counts_for_students(viewer_uid=current_user.uid,
                                            cohort=cohort)
    return tolerant_jsonify(cohort)
示例#16
0
def search_students():
    params = request.get_json()
    if is_unauthorized_search(params):
        raise ForbiddenRequestError(
            'You are unauthorized to access student data managed by other departments'
        )
    search_phrase = util.get(params, 'searchPhrase', '').strip()
    if not len(search_phrase):
        raise BadRequestError('Invalid or empty search input')
    results = search_for_students(
        include_profiles=True,
        search_phrase=search_phrase.replace(',', ' '),
        is_active_asc=_convert_asc_inactive_arg(
            util.get(params, 'isInactiveAsc')),
        order_by=util.get(params, 'orderBy', None),
        offset=util.get(params, 'offset', 0),
        limit=util.get(params, 'limit', 50),
    )
    alert_counts = Alert.current_alert_counts_for_viewer(current_user.id)
    students = results['students']
    add_alert_counts(alert_counts, students)
    return tolerant_jsonify({
        'students': students,
        'totalStudentCount': results['totalStudentCount'],
    })
def get_students_with_alerts(curated_group_id):
    offset = get_param(request.args, 'offset', 0)
    limit = get_param(request.args, 'limit', 50)
    benchmark = get_benchmarker(
        f'curated group {curated_group_id} students_with_alerts')
    benchmark('begin')
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'Sorry, no curated group found with id {curated_group_id}.')
    if not _can_current_user_view_curated_group(curated_group):
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, cannot view curated group {curated_group.id}'
        )
    students = Alert.include_alert_counts_for_students(
        benchmark=benchmark,
        viewer_user_id=current_user.get_id(),
        group={'sids': CuratedGroup.get_all_sids(curated_group_id)},
        count_only=True,
        offset=offset,
        limit=limit,
    )
    alert_count_per_sid = {}
    for s in list(filter(lambda s: s.get('alertCount') > 0, students)):
        sid = s.get('sid')
        alert_count_per_sid[sid] = s.get('alertCount')
    sids = list(alert_count_per_sid.keys())
    benchmark('begin profile query')
    students_with_alerts = get_student_profile_summaries(sids=sids)
    benchmark('end profile query')
    for student in students_with_alerts:
        student['alertCount'] = alert_count_per_sid[student['sid']]
    benchmark('end')
    return tolerant_jsonify(students_with_alerts)
示例#18
0
 def test_assignment_alerts_change_updated_at_timestamp(self):
     Alert.update_all_for_term(2178)
     alerts = Alert.current_alerts_for_sid(sid='3456789012', viewer_id='2040')
     assert alerts[0]['updatedAt'] == alerts[0]['createdAt']
     sleep(1.0)
     Alert.deactivate_all_for_term(2178)
     Alert.update_all_for_term(2178)
     alerts = Alert.current_alerts_for_sid(sid='3456789012', viewer_id='2040')
     assert alerts[0]['updatedAt'] > alerts[0]['createdAt']
示例#19
0
def get_section(term_id, section_id):
    if not current_user.can_access_canvas_data:
        raise ForbiddenRequestError('Unauthorized to view course data')
    offset = util.get(request.args, 'offset', None)
    if offset:
        offset = int(offset)
    limit = util.get(request.args, 'limit', None)
    if limit:
        limit = int(limit)
    featured = util.get(request.args, 'featured', None)
    section = get_sis_section(term_id, section_id)
    if not section:
        raise ResourceNotFoundError(f'No section {section_id} in term {term_id}')
    student_profiles = get_course_student_profiles(term_id, section_id, offset=offset, limit=limit, featured=featured)
    section.update(student_profiles)
    Alert.include_alert_counts_for_students(viewer_user_id=current_user.get_id(), group=student_profiles)
    return tolerant_jsonify(section)
示例#20
0
 def test_alert_timezones(self):
     """For purposes of displaying due dates, loves LA."""
     Alert.update_assignment_alerts(**dict(alert_props, due_at='2017-02-03T07:59:01Z'))
     assert get_current_alerts('11667051')[0]['message'] == 'MED ST 205 assignment due on Feb 2, 2017.'
     Alert.update_assignment_alerts(**dict(alert_props, due_at='2017-02-03T08:00:01Z'))
     assert get_current_alerts('11667051')[0]['message'] == 'MED ST 205 assignment due on Feb 3, 2017.'
     Alert.update_assignment_alerts(**dict(alert_props, due_at='2017-06-17T06:59:59Z'))
     assert get_current_alerts('11667051')[0]['message'] == 'MED ST 205 assignment due on Jun 16, 2017.'
     Alert.update_assignment_alerts(**dict(alert_props, due_at='2017-06-17T07:00:01Z'))
     assert get_current_alerts('11667051')[0]['message'] == 'MED ST 205 assignment due on Jun 17, 2017.'
示例#21
0
 def test_creates_alert_for_midterm_grade(self, app):
     from boac.api.cache_utils import refresh_alerts
     refresh_alerts(2178)
     alerts = Alert.current_alerts_for_sid(sid='11667051', viewer_id='2040')
     alert = next((a for a in alerts if a['alertType'] == 'midterm'), None)
     assert alert
     assert 'midterm' == alert['alertType']
     assert '2178_90100' == alert['key']
     assert 'BURMESE 1A midpoint deficient grade of D+.' == alert['message']
示例#22
0
 def test_creates_alert_for_midterm_grade(self, app):
     from boac.api.cache_utils import refresh_alerts
     refresh_alerts(2178)
     alerts = Alert.current_alerts_for_sid(sid='11667051', viewer_id='2040')['shown']
     assert 1 == len(alerts)
     assert 0 < alerts[0]['id']
     assert 'midterm' == alerts[0]['alertType']
     assert '2178_90100' == alerts[0]['key']
     assert 'BURMESE 1A midterm grade of D+.' == alerts[0]['message']
示例#23
0
 def test_midpoint_deficient_grade_alerts_preserve_updated_at_timestamp(self):
     Alert.update_all_for_term(2178)
     alerts = Alert.current_alerts_for_sid(sid='11667051', viewer_id='2040')
     alert = next((a for a in alerts if a['alertType'] == 'midterm'), None)
     assert alert['updatedAt'] == alert['createdAt']
     sleep(0.5)
     Alert.deactivate_all_for_term(2178)
     Alert.update_all_for_term(2178)
     alerts = Alert.current_alerts_for_sid(sid='11667051', viewer_id='2040')
     alert = next((a for a in alerts if a['alertType'] == 'midterm'), None)
     assert alert['updatedAt'] == alert['createdAt']
示例#24
0
    def test_my_cohorts_includes_students_with_alert_counts(self, asc_advisor_session, client, create_alerts, db_session):
        # Pre-load students into cache for consistent alert data.
        client.get('/api/student/61889/analytics')
        client.get('/api/student/98765/analytics')
        from boac.models.alert import Alert
        Alert.update_all_for_term(2178)

        cohorts = client.get('/api/filtered_cohorts/my').json
        assert len(cohorts[0]['alerts']) == 3

        deborah = cohorts[0]['alerts'][0]
        assert deborah['sid'] == '11667051'
        assert deborah['alertCount'] == 3
        # Summary student data is included with alert counts, but full term and analytics feeds are not.
        assert deborah['cumulativeGPA'] == 3.8
        assert deborah['cumulativeUnits'] == 101.3
        assert deborah['level'] == 'Junior'
        assert len(deborah['majors']) == 2
        assert deborah['term']['enrolledUnits'] == 12.5
        assert 'analytics' not in deborah
        assert 'enrollments' not in deborah['term']

        dave_doolittle = cohorts[0]['alerts'][1]
        assert dave_doolittle['sid'] == '2345678901'
        assert dave_doolittle['uid']
        assert dave_doolittle['firstName']
        assert dave_doolittle['lastName']
        assert dave_doolittle['alertCount'] == 1

        other_alerts = cohorts[1]['alerts']
        assert len(other_alerts) == 1
        assert other_alerts[0]['sid'] == '2345678901'
        assert other_alerts[0]['alertCount'] == 1

        alert_to_dismiss = client.get('/api/alerts/current/11667051').json['shown'][0]['id']
        client.get('/api/alerts/' + str(alert_to_dismiss) + '/dismiss')
        alert_to_dismiss = client.get('/api/alerts/current/2345678901').json['shown'][0]['id']
        client.get('/api/alerts/' + str(alert_to_dismiss) + '/dismiss')

        cohorts = client.get('/api/filtered_cohorts/my').json
        assert len(cohorts[0]['alerts']) == 2
        assert cohorts[0]['alerts'][0]['sid'] == '11667051'
        assert cohorts[0]['alerts'][0]['alertCount'] == 2
        assert len(cohorts[1]['alerts']) == 0
示例#25
0
def get_my_curated_groups():
    curated_groups = []
    user_id = current_user.get_id()
    for curated_group in CuratedGroup.get_curated_groups_by_owner_id(user_id):
        api_json = curated_group.to_api_json(include_students=False)
        students = [{'sid': sid} for sid in CuratedGroup.get_all_sids(curated_group.id)]
        students_with_alerts = Alert.include_alert_counts_for_students(
            viewer_user_id=user_id,
            group={'students': students},
            count_only=True,
        )
        api_json['alertCount'] = sum(s['alertCount'] for s in students_with_alerts)
        api_json['totalStudentCount'] = len(students)
        curated_groups.append(api_json)
    return curated_groups
示例#26
0
    def test_academic_standing_action_date_as_created_at(self):
        actual_action_date = '2017-12-30'
        Alert.update_all_for_term(2178)
        alerts = Alert.current_alerts_for_sid(sid='11667051', viewer_id='2040')
        alert = next((a for a in alerts if a['alertType'] == 'academic_standing'), None)
        created_at = alert['createdAt']
        assert created_at.startswith(actual_action_date)
        assert alert['updatedAt'] == created_at

        sleep(1.0)
        Alert.deactivate_all_for_term(2178)
        Alert.update_all_for_term(2178)
        alerts = Alert.current_alerts_for_sid(sid='11667051', viewer_id='2040')
        academic_standing_alert = next((a for a in alerts if a['alertType'] == 'academic_standing'), None)
        created_at = academic_standing_alert['createdAt']
        assert created_at.startswith(actual_action_date)
        assert academic_standing_alert['updatedAt'] == created_at
示例#27
0
def _student_search(search_phrase, params, order_by):
    student_results = search_for_students(
        search_phrase=search_phrase.replace(',', ' '),
        order_by=order_by,
        offset=util.get(params, 'offset', 0),
        limit=util.get(params, 'limit', 50),
    )
    students = student_results['students']
    sids = [s['sid'] for s in students]
    alert_counts = Alert.current_alert_counts_for_sids(current_user.get_id(),
                                                       sids)
    add_alert_counts(alert_counts, students)
    return {
        'students': students,
        'totalStudentCount': student_results['totalStudentCount'],
    }
示例#28
0
def alerts_log_export():
    def _param_to_utc_date(key):
        value = (params.get(key) or '').strip()
        return localized_timestamp_to_utc(
            value, date_format='%m/%d/%Y') if value else None

    params = request.get_json()
    from_date_utc = _param_to_utc_date('fromDate')
    to_date_utc = _param_to_utc_date('toDate') + timedelta(days=1)
    if from_date_utc and to_date_utc:

        def _to_api_json(alert):
            term_id_match = re.search(r'^2[012]\d[0258]', alert.key[0:4])
            active_until = alert.deleted_at or utc_now()
            return {
                'sid':
                alert.sid,
                'term':
                term_name_for_sis_id(term_id_match.string)
                if term_id_match else None,
                'key':
                alert.key,
                'type':
                alert.alert_type,
                'is_active':
                not alert.deleted_at,
                'active_duration_hours':
                round(
                    (active_until - alert.created_at).total_seconds() / 3600),
                'created_at':
                alert.created_at,
                'deleted_at':
                alert.deleted_at,
            }

        alerts = Alert.get_alerts_per_date_range(from_date_utc, to_date_utc)
        return response_with_csv_download(
            rows=[_to_api_json(a) for a in alerts],
            filename_prefix='alerts_log',
            fieldnames=[
                'sid', 'term', 'key', 'type', 'is_active',
                'active_duration_hours', 'created_at', 'deleted_at'
            ],
        )
    else:
        raise BadRequestError('Invalid arguments')
示例#29
0
    def test_deactivate_reactivate_alerts(self):
        """Can be deactivated and reactivated, preserving id."""
        assert len(get_current_alerts('11667051')) == 0
        Alert.update_assignment_alerts(**alert_props)
        alerts = get_current_alerts('11667051')
        assert len(alerts) == 1
        alert_id = alerts[0]['id']

        Alert.deactivate_all(sid='11667051', term_id='2178', alert_types=['missing_assignment'])
        assert len(get_current_alerts('11667051')) == 0

        Alert.update_assignment_alerts(**alert_props)
        alerts = get_current_alerts('11667051')
        assert len(alerts) == 1
        assert alerts[0]['id'] == alert_id
示例#30
0
def my_curated_cohorts():
    alert_counts = Alert.current_alert_counts_for_viewer(current_user.id)
    curated_cohorts = CuratedCohort.get_curated_cohorts_by_owner_id(
        current_user.id)
    curated_cohorts = sorted(curated_cohorts,
                             key=lambda curated_cohort: curated_cohort.name)
    curated_cohorts = [c.to_api_json(sids_only=True) for c in curated_cohorts]
    for curated_cohort in curated_cohorts:
        students = curated_cohort['students']
        api_util.add_alert_counts(alert_counts, students)
        # Only get detailed data for students with alerts.
        sids_with_alerts = [s['sid'] for s in students if s.get('alertCount')]
        students_with_alerts = get_summary_student_profiles(sids_with_alerts)
        for data in students_with_alerts:
            data['alertCount'] = next(
                s.get('alertCount') for s in students
                if s['sid'] == data['sid'])
            api_util.strip_analytics(data)
        curated_cohort['students'] = api_util.sort_students_by_name(
            students_with_alerts)
    return tolerant_jsonify(curated_cohorts)