示例#1
0
文件: calnet.py 项目: ssilverm/boac
def _calnet_user_api_feed(person):
    def _get(key):
        return person and person[key]

    # Array of departments is compatible with BOAC user schema.
    departments = []
    dept_code = _get_dept_code(person)
    if dept_code:
        departments.append({
            'code':
            dept_code,
            'name':
            BERKELEY_DEPT_CODE_TO_NAME.get(dept_code)
            if dept_code in BERKELEY_DEPT_CODE_TO_NAME else dept_code,
        })
    return {
        'campusEmail': _get('campus_email'),
        'departments': departments,
        'email': _get('email'),
        'firstName': _get('first_name'),
        'isExpiredPerLdap': _get('expired'),
        'lastName': _get('last_name'),
        'name': _get('name'),
        'csid': _get('csid'),
        'title': _get('title'),
        'uid': _get('uid'),
    }
示例#2
0
 def to_api_json(self, current_user_id):
     topics = [t.to_api_json() for t in self.topics if not t.deleted_at]
     departments = None
     if self.advisor_dept_codes:
         departments = [{'code': c, 'name': BERKELEY_DEPT_CODE_TO_NAME.get(c, c)} for c in self.advisor_dept_codes]
     api_json = {
         'id': self.id,
         'advisorId': AuthorizedUser.get_id_per_uid(self.advisor_uid),
         'advisorName': self.advisor_name,
         'advisorRole': self.advisor_role,
         'advisorUid': self.advisor_uid,
         'advisorDepartments': departments,
         'appointmentType': self.appointment_type,
         'createdAt': _isoformat(self.created_at),
         'createdBy': self.created_by,
         'deptCode': self.dept_code,
         'details': self.details,
         'read': AppointmentRead.was_read_by(current_user_id, self.id),
         'student': {
             'sid': self.student_sid,
         },
         'topics': topics,
         'updatedAt': _isoformat(self.updated_at),
         'updatedBy': self.updated_by,
     }
     if self.appointment_type == 'Scheduled':
         api_json.update({
             'scheduledTime': _isoformat(self.scheduled_time),
             'studentContactInfo': self.student_contact_info,
             'studentContactType': self.student_contact_type,
         })
     return {
         **api_json,
         **appointment_event_to_json(self.id, self.status),
     }
示例#3
0
def _create_users():
    for test_user in _test_users:
        # This script can be run more than once. Do not create user if s/he exists in BOAC db.
        uid = test_user['uid']
        # Mock CSIDs and names are random unless we need them to correspond to test data elsewhere.
        csid = test_user['csid'] or datetime.now().strftime('%H%M%S%f')
        first_name = test_user.get(
            'firstName', ''.join(random.choices(string.ascii_uppercase, k=6)))
        last_name = test_user.get(
            'lastName', ''.join(random.choices(string.ascii_uppercase, k=6)))

        # Put mock CalNet data in our json_cache for all users EXCEPT the test "no_calnet_record" user.
        if uid != no_calnet_record_for_uid:
            calnet_feed = {
                'uid': uid,
                'csid': csid,
                'firstName': first_name,
                'lastName': last_name,
                'name': f'{first_name} {last_name}',
            }
            if 'calnetDeptCodes' in test_user:
                calnet_feed['departments'] = []
                for dept_code in test_user['calnetDeptCodes']:
                    calnet_feed['departments'].append({
                        'code':
                        dept_code,
                        'name':
                        BERKELEY_DEPT_CODE_TO_NAME.get(dept_code),
                    })
            if 'title' in test_user:
                calnet_feed['title'] = test_user['title']
            insert_in_json_cache(f'calnet_user_for_uid_{uid}', calnet_feed)

        # Add user to authorized_users table if not already present.
        user = AuthorizedUser.find_by_uid(uid=uid)
        if not user:
            user = AuthorizedUser(
                uid=uid,
                created_by='2040',
                is_admin=test_user['isAdmin'],
                in_demo_mode=test_user['inDemoMode'],
                can_access_advising_data=test_user['canAccessAdvisingData'],
                can_access_canvas_data=test_user['canAccessCanvasData'],
                degree_progress_permission=test_user.get(
                    'degreeProgressPermission'),
                search_history=test_user.get('searchHistory', []),
            )
            if test_user.get('deleted'):
                user.deleted_at = utc_now()
            db.session.add(user)

    AuthorizedUser.delete(delete_this_admin_uid)
    AuthorizedUser.delete(delete_this_uid)

    std_commit(allow_test_environment=True)
示例#4
0
def appointment_to_compatible_json(appointment, topics=(), attachments=None, event=None):
    # We have legacy appointments and appointments created via BOA. The following sets a standard for the front-end.
    advisor_sid = appointment.get('advisor_sid')
    advisor_uid = appointment.get('advisor_uid')
    appointment_id = appointment.get('id')
    appointment_type = appointment.get('appointment_type')
    cancelled = appointment.get('cancelled')
    departments = []
    dept_codes = appointment.get('advisor_dept_codes') or []
    created_by = appointment.get('created_by') or 'YCBM'

    for dept_code in dept_codes:
        departments.append({
            'code': dept_code,
            'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code),
        })
    api_json = {
        'id': appointment_id,
        'advisor': {
            'id': AuthorizedUser.get_id_per_uid(advisor_uid) if advisor_uid else None,
            'name': appointment.get('advisor_name') or join_if_present(
                ' ',
                [appointment.get('advisor_first_name'), appointment.get('advisor_last_name')],
            ),
            'sid': advisor_sid,
            'title': appointment.get('advisor_role'),
            'uid': advisor_uid,
            'departments': departments,
        },
        'appointmentTitle': appointment.get('title'),
        'appointmentType': appointment_type,
        'attachments': attachments,
        'createdAt': resolve_sis_created_at(appointment) or appointment.get('starts_at').isoformat(),
        'createdBy': created_by,
        'deptCode': appointment.get('dept_code'),
        'details': appointment.get('details'),
        'endsAt': appointment.get('ends_at').isoformat() if created_by == 'YCBM' and appointment.get('ends_at') else None,
        'student': {
            'sid': appointment.get('student_sid'),
        },
        'topics': topics,
        'updatedAt': resolve_sis_updated_at(appointment),
        'updatedBy': appointment.get('updated_by'),
        'cancelReason': appointment.get('cancellation_reason') if created_by == 'YCBM' else None,
        'status': 'cancelled' if cancelled else None,
    }
    if appointment_type and appointment_type == 'Scheduled':
        api_json.update({
            'scheduledTime': _isoformat(appointment, 'scheduled_time'),
            'studentContactInfo': appointment.get('student_contact_info'),
            'studentContactType': appointment.get('student_contact_type'),
        })
    if event:
        api_json.update(event)
    return api_json
示例#5
0
 def _get_api_json(cls, user=None):
     calnet_profile = None
     departments = []
     if user:
         calnet_profile = calnet.get_calnet_user_for_uid(
             app,
             user.uid,
             force_feed=False,
             skip_expired_users=True,
         )
         for m in user.department_memberships:
             dept_code = m.university_dept.dept_code
             departments.append(
                 {
                     'code': dept_code,
                     'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code),
                     'role': get_dept_role(m),
                     'isAdvisor': m.is_advisor,
                     'isDirector': m.is_director,
                     'isScheduler': m.is_scheduler,
                 })
     is_active = False
     if user:
         if not calnet_profile:
             is_active = False
         elif user.is_admin:
             is_active = True
         elif len(user.department_memberships):
             for m in user.department_memberships:
                 is_active = m.is_advisor or m.is_director or m.is_scheduler
                 if is_active:
                     break
     is_admin = user and user.is_admin
     drop_in_advisor_status = []
     if user and len(user.drop_in_departments):
         drop_in_advisor_status = [d.to_api_json() for d in user.drop_in_departments]
     return {
         **(calnet_profile or {}),
         **{
             'id': user and user.id,
             'departments': departments,
             'isActive': is_active,
             'isAdmin': is_admin,
             'isAnonymous': not is_active,
             'isAuthenticated': is_active,
             'inDemoMode': user and user.in_demo_mode,
             'canAccessCanvasData': user and user.can_access_canvas_data,
             'uid': user and user.uid,
             'dropInAdvisorStatus': drop_in_advisor_status,
         },
     }
示例#6
0
 def _put(_dept_code, _user):
     if _dept_code not in depts:
         if _dept_code == 'ADMIN':
             dept_name = 'Admins'
         elif _dept_code == 'GUEST':
             dept_name = 'Guest Access'
         else:
             dept_name = BERKELEY_DEPT_CODE_TO_NAME.get(_dept_code)
         depts[_dept_code] = {
             'code': _dept_code,
             'name': dept_name,
             'users': [],
         }
     depts[_dept_code]['users'].append(_user)
示例#7
0
def load_development_data():
    for name, code in BERKELEY_DEPT_NAME_TO_CODE.items():
        UniversityDept.create(code, name, False)
    for test_user in _test_users:
        # This script can be run more than once. Do not create user if s/he exists in BOAC db.
        uid = test_user[0]
        csid = test_user[1]
        user = AuthorizedUser.find_by_uid(uid=uid)
        if uid != no_calnet_record_for_uid:
            # Put mock CalNet data in our json_cache for all users EXCEPT the test "no_calnet_record" user.
            first_name = ''.join(random.choices(string.ascii_uppercase, k=6))
            last_name = ''.join(random.choices(string.ascii_uppercase, k=6))
            calnet_feed = {
                'uid': uid,
                # Mock CSIDs are random unless we need them to correspond to test data elsewhere.
                'csid': csid or datetime.now().strftime('%H%M%S%f'),
                'firstName': first_name,
                'lastName': last_name,
                'name': f'{first_name} {last_name}',
            }
            if len(test_user) > 4:
                calnet_feed['departments'] = [
                    {
                        'code': test_user[4],
                        'name': BERKELEY_DEPT_CODE_TO_NAME.get(test_user[4]),
                    },
                ]
            insert_in_json_cache(f'calnet_user_for_uid_{uid}', calnet_feed)
        if not user:
            user = AuthorizedUser(uid=uid,
                                  is_admin=test_user[2],
                                  in_demo_mode=test_user[3])
            db.session.add(user)
    for dept_code, dept_membership in _university_depts.items():
        university_dept = UniversityDept.find_by_dept_code(dept_code)
        university_dept.automate_memberships = dept_membership[
            'automate_memberships']
        db.session.add(university_dept)
        for user in dept_membership['users']:
            authorized_user = AuthorizedUser.find_by_uid(user['uid'])
            UniversityDeptMember.create_membership(
                university_dept,
                authorized_user,
                user['is_advisor'],
                user['is_director'],
            )
    std_commit(allow_test_environment=True)
示例#8
0
def note_to_compatible_json(note,
                            topics=(),
                            attachments=None,
                            note_read=False):
    # We have legacy notes and notes created via BOAC. The following sets a standard for the front-end.
    departments = []
    dept_codes = note.get('dept_code') if 'dept_code' in note else note.get(
        'author_dept_codes') or []
    for dept_code in dept_codes:
        departments.append({
            'code':
            dept_code,
            'name':
            BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code),
        })
    return {
        'id': note.get('id'),
        'sid': note.get('sid'),
        'author': {
            'id': note.get('author_id'),
            'uid': note.get('author_uid'),
            'sid': note.get('advisor_sid'),
            'name': note.get('author_name'),
            'role': note.get('author_role'),
            'departments': departments,
            'email': note.get('advisor_email'),
        },
        'subject': note.get('subject'),
        'body': note.get('body') or note.get('note_body'),
        'category': note.get('note_category'),
        'subcategory': note.get('note_subcategory'),
        'appointmentId': note.get('appointmentId'),
        'createdBy': note.get('created_by'),
        'createdAt': resolve_sis_created_at(note),
        'updatedBy': note.get('updated_by'),
        'updatedAt': resolve_sis_updated_at(note),
        'read': True if note_read else False,
        'topics': topics,
        'attachments': attachments,
        'eForm': _eform_to_json(note),
    }
示例#9
0
def get_users_report(dept_code):
    dept_code = dept_code.upper()
    dept_name = BERKELEY_DEPT_CODE_TO_NAME.get(dept_code)
    if dept_name:
        if current_user.is_admin or _current_user_is_director_of(dept_code):
            users, total_user_count = AuthorizedUser.get_users(
                deleted=False, dept_code=dept_code)
            users = authorized_users_api_feed(users, sort_by='lastName')
            note_count_per_user = get_note_count_per_user(dept_code)
            for user in users:
                user['notesCreated'] = note_count_per_user.get(user['uid'], 0)
            return tolerant_jsonify({
                'users': users,
                'totalUserCount': total_user_count,
            })
        else:
            raise ForbiddenRequestError(
                f'You are unauthorized to view {dept_name} reports')
    else:
        raise ResourceNotFoundError(
            f'Unrecognized department code {dept_code}')
示例#10
0
def get_notes_report_by_dept(dept_code):
    dept_code = dept_code.upper()
    dept_name = BERKELEY_DEPT_CODE_TO_NAME.get(dept_code)
    if dept_name:
        if current_user.is_admin or _current_user_is_director_of(dept_code):
            total_note_count = get_note_count()
            return tolerant_jsonify({
                'asc': get_asc_advising_note_count(),
                'ei': get_e_and_i_advising_note_count(),
                'sis': get_sis_advising_note_count(),
                'boa': {
                    'total': total_note_count,
                    'authors': get_note_author_count(),
                    'withAttachments': get_note_with_attachments_count(),
                    'withTopics': get_note_with_topics_count(),
                },
            })
        else:
            raise ForbiddenRequestError(
                f'You are unauthorized to view {dept_name} reports')
    else:
        raise ResourceNotFoundError(
            f'Unrecognized department code {dept_code}')
示例#11
0
    def _get_api_json(cls, user=None):
        calnet_profile = None
        departments = []
        if user:
            calnet_profile = calnet.get_calnet_user_for_uid(
                app,
                user.uid,
                force_feed=False,
                skip_expired_users=True,
            )
            for m in user.department_memberships:
                dept_code = m.university_dept.dept_code
                departments.append(
                    {
                        'code': dept_code,
                        'isDropInEnabled': dept_code in app.config['DEPARTMENTS_SUPPORTING_DROP_INS'],
                        'isSameDayEnabled': dept_code in app.config['DEPARTMENTS_SUPPORTING_SAME_DAY_APPTS'],
                        'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code, dept_code),
                        'role': m.role,
                    })
        drop_in_advisor_status = []
        same_day_advisor_status = []
        is_active = False
        if user:
            if not calnet_profile:
                is_active = False
            elif user.is_admin:
                is_active = True
            elif len(user.department_memberships):
                for m in user.department_memberships:
                    is_active = True if m.role else False
                    if is_active:
                        break
            drop_in_advisor_status = [
                d.to_api_json() for d in user.drop_in_departments if d.dept_code in app.config['DEPARTMENTS_SUPPORTING_DROP_INS']
            ]
            same_day_advisor_status = [
                d.to_api_json() for d in user.same_day_departments if d.dept_code in app.config['DEPARTMENTS_SUPPORTING_SAME_DAY_APPTS']
            ]

        is_admin = user and user.is_admin
        if app.config['FEATURE_FLAG_DEGREE_CHECK']:
            degree_progress_permission = 'read_write' if is_admin else (user and user.degree_progress_permission)
        else:
            degree_progress_permission = None

        return {
            **(calnet_profile or {}),
            **{
                'id': user and user.id,
                'canAccessAdvisingData': user and user.can_access_advising_data,
                'canAccessCanvasData': user and user.can_access_canvas_data,
                'canEditDegreeProgress': degree_progress_permission == 'read_write',
                'canReadDegreeProgress': degree_progress_permission in ['read', 'read_write'],
                'degreeProgressPermission': degree_progress_permission,
                'departments': departments,
                'dropInAdvisorStatus': drop_in_advisor_status,
                'inDemoMode': user and user.in_demo_mode,
                'isActive': is_active,
                'isAdmin': is_admin,
                'isAnonymous': not is_active,
                'isAuthenticated': is_active,
                'sameDayAdvisorStatus': same_day_advisor_status,
                'uid': user and user.uid,
            },
        }
示例#12
0
def _load_users_and_departments():
    for code, name in BERKELEY_DEPT_CODE_TO_NAME.items():
        UniversityDept.create(code, name)
    _create_users()
    _create_department_memberships()
示例#13
0
 def _to_json(row):
     dept_code = row.upper()
     return {
         'code': dept_code,
         'name': BERKELEY_DEPT_CODE_TO_NAME.get(dept_code),
     }
示例#14
0
def _get_filter_options(scope, cohort_owner_uid):
    all_dept_codes = list(BERKELEY_DEPT_CODE_TO_NAME.keys())
    categories = [
        [
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'enteringTerms',
                'label': {
                    'primary': 'Entering Term',
                },
                'options': _entering_terms,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'expectedGradTerms',
                'label': {
                    'primary': 'Expected Graduation Term',
                },
                'options': _grad_terms,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'gpaRanges',
                'options': None,
                'label': {
                    'primary': 'GPA (Cumulative)',
                    'range': ['', '-'],
                    'rangeMinEqualsMax': '',
                },
                'type': {
                    'db': 'json[]',
                    'ux': 'range',
                },
                'validation': 'gpa',
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'lastTermGpaRanges',
                'options': None,
                'label': {
                    'primary': 'GPA (Last Term)',
                    'range': ['', '-'],
                    'rangeMinEqualsMax': '',
                },
                'type': {
                    'db': 'json[]',
                    'ux': 'range',
                },
                'validation': 'gpa',
            },
            {
                'availableTo':
                all_dept_codes,
                'defaultValue':
                None,
                'key':
                'levels',
                'label': {
                    'primary': 'Level',
                },
                'options': [
                    {
                        'name': 'Freshman (0-29 Units)',
                        'value': 'Freshman'
                    },
                    {
                        'name': 'Sophomore (30-59 Units)',
                        'value': 'Sophomore'
                    },
                    {
                        'name': 'Junior (60-89 Units)',
                        'value': 'Junior'
                    },
                    {
                        'name': 'Senior (90+ Units)',
                        'value': 'Senior'
                    },
                ],
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'majors',
                'label': {
                    'primary': 'Major',
                },
                'options': _majors,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'midpointDeficient',
                'label': {
                    'primary': 'Midpoint Deficient Grade',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'transfer',
                'label': {
                    'primary': 'Transfer Student',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
            {
                'availableTo':
                all_dept_codes,
                'defaultValue':
                None,
                'key':
                'unitRanges',
                'label': {
                    'primary': 'Units Completed',
                },
                'options': [
                    {
                        'name': '0 - 29',
                        'value': 'numrange(NULL, 30, \'[)\')'
                    },
                    {
                        'name': '30 - 59',
                        'value': 'numrange(30, 60, \'[)\')'
                    },
                    {
                        'name': '60 - 89',
                        'value': 'numrange(60, 90, \'[)\')'
                    },
                    {
                        'name': '90 - 119',
                        'value': 'numrange(90, 120, \'[)\')'
                    },
                    {
                        'name': '120 +',
                        'value': 'numrange(120, NULL, \'[)\')'
                    },
                ],
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
        ],
        [
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'ethnicities',
                'label': {
                    'primary': 'Ethnicity',
                },
                'options': _ethnicities,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'genders',
                'label': {
                    'primary': 'Gender',
                },
                'options': _genders,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'underrepresented',
                'label': {
                    'primary': 'Underrepresented Minority',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
        ],
        [
            {
                'availableTo': ['UWASC'],
                'defaultValue': False if 'UWASC' in scope else None,
                'key': 'isInactiveAsc',
                'label': {
                    'primary': 'Inactive (ASC)',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
            {
                'availableTo': ['UWASC'],
                'defaultValue': None,
                'key': 'inIntensiveCohort',
                'label': {
                    'primary': 'Intensive',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
            {
                'availableTo': ['UWASC'],
                'defaultValue': None,
                'key': 'groupCodes',
                'label': {
                    'primary': 'Team',
                },
                'options': _team_groups,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
        ],
        [
            {
                'availableTo': ['COENG'],
                'defaultValue': None,
                'key': 'coeAdvisorLdapUids',
                'label': {
                    'primary': 'Advisor (COE)',
                },
                'options': _get_coe_profiles,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': ['COENG'],
                'defaultValue': None,
                'key': 'coeEthnicities',
                'label': {
                    'primary': 'Ethnicity (COE)',
                },
                'options': _coe_ethnicities,
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': ['COENG'],
                'defaultValue':
                None,
                'key':
                'coeGenders',
                'label': {
                    'primary': 'Gender (COE)',
                },
                'options': [
                    {
                        'name': 'Female',
                        'value': 'F'
                    },
                    {
                        'name': 'Male',
                        'value': 'M'
                    },
                ],
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': ['COENG'],
                'defaultValue': False if 'COENG' in scope else None,
                'key': 'isInactiveCoe',
                'label': {
                    'primary': 'Inactive (COE)',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'lastNameRanges',
                'label': {
                    'primary': 'Last Name',
                    'range': ['Initials', 'through'],
                    'rangeMinEqualsMax': 'Starts with',
                },
                'type': {
                    'db': 'json[]',
                    'ux': 'range',
                },
                'validation': 'char',
            },
            {
                'availableTo': all_dept_codes,
                'defaultValue': None,
                'key': 'cohortOwnerAcademicPlans',
                'label': {
                    'primary': 'My Students',
                },
                'options': _academic_plans_for_cohort_owner(cohort_owner_uid),
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': ['COENG'],
                'defaultValue':
                None,
                'key':
                'coePrepStatuses',
                'label': {
                    'primary': 'PREP',
                },
                'options': [
                    {
                        'name': 'PREP',
                        'value': 'did_prep'
                    },
                    {
                        'name': 'PREP eligible',
                        'value': 'prep_eligible'
                    },
                    {
                        'name': 'T-PREP',
                        'value': 'did_tprep'
                    },
                    {
                        'name': 'T-PREP eligible',
                        'value': 'tprep_eligible'
                    },
                ],
                'type': {
                    'db': 'string[]',
                    'ux': 'dropdown',
                },
            },
            {
                'availableTo': ['COENG'],
                'defaultValue': None,
                'key': 'coeProbation',
                'label': {
                    'primary': 'Probation',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
            {
                'availableTo': ['COENG'],
                'defaultValue': None,
                'key': 'coeUnderrepresented',
                'label': {
                    'primary': 'Underrepresented Minority (COE)',
                },
                'type': {
                    'db': 'boolean',
                    'ux': 'boolean',
                },
            },
        ],
    ]
    available_categories = []

    def is_available(d):
        available = 'ADMIN' in scope or next(
            (dept_code
             for dept_code in d['availableTo'] if dept_code in scope), False)
        if available and 'options' in d:
            # If it is available then populate menu options
            options = d.pop('options')
            d['options'] = options() if callable(options) else options
        return available

    for category in categories:
        available_categories.append(
            list(filter(lambda d: is_available(d), category)))
    # Remove unavailable (ie, empty) categories
    return list(filter(lambda g: len(g), available_categories))