Skip to content

Commit

Permalink
Hotfix hiring for rostering management (#583)
Browse files Browse the repository at this point in the history
* Add API endpoint for getting a course site's hiring selections and assignments

* Load course data in hiring admin on expansion

* Prepopulate default level

* Add all TA applications to PhD CSV export for Fall 2024
  • Loading branch information
KrisJordan authored Aug 15, 2024
1 parent f6007a9 commit 9c80cb4
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 170 deletions.
12 changes: 12 additions & 0 deletions backend/api/academics/hiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ def get_hiring_admin_overview(
return hiring_service.get_hiring_admin_overview(subject, term_id)


@api.get("/admin/course/{course_site_id}", tags=["Hiring"])
def get_hiring_admin_course_overview(
course_site_id: int,
subject: User = Depends(registered_user),
hiring_service: HiringService = Depends(),
) -> HiringAdminCourseOverview:
"""
Returns the state of hiring to the admin.
"""
return hiring_service.get_hiring_admin_course_overview(subject, course_site_id)


@api.post("/assignment", tags=["Hiring"])
def create_hiring_assignment(
assignment: HiringAssignmentDraft,
Expand Down
8 changes: 6 additions & 2 deletions backend/models/academics/hiring/hiring_assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,13 @@ class HiringCourseSiteOverview(BaseModel):
total_cost: float
coverage: float
assignments: list[HiringAssignmentOverview]
# reviews: list[ApplicationReviewOverview]
# instructor_preferences: list[PublicUser]


class HiringAdminOverview(BaseModel):
sites: list[HiringCourseSiteOverview]


class HiringAdminCourseOverview(BaseModel):
assignments: list[HiringAssignmentOverview]
reviews: list[ApplicationReviewOverview]
instructor_preferences: list[PublicUser]
91 changes: 62 additions & 29 deletions backend/services/academics/hiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def get_phd_applicants(
query = select(ApplicationEntity).where(
ApplicationEntity.term_id == term_id,
ApplicationEntity.type == "gta",
ApplicationEntity.program_pursued.in_({"PhD", "PhD (ABD)"}),
ApplicationEntity.program_pursued.in_({"PhD", "PhD (ABD)", "MS", "BS/MS"}),
)
all = self._session.scalars(query).all()

Expand Down Expand Up @@ -237,12 +237,13 @@ def get_phd_applicants(
select(ApplicationReviewEntity)
.where(ApplicationReviewEntity.application_id.in_(application_ids))
.where(ApplicationReviewEntity.status == ApplicationReviewStatus.PREFERRED)
.order_by(ApplicationReviewEntity.preference)
.options(joinedload(ApplicationReviewEntity.course_site))
)
instructor_preferences = self._session.scalars(instructor_review_query).all()
for review in instructor_preferences:
phd_applications[review.application_id].instructor_preferences.append(
f"{review.course_site.sections[0].course_id}.{review.course_site.sections[0].number}"
f"({review.preference}) {review.course_site.sections[0].course_id}.{review.course_site.sections[0].number}"
)

return list(phd_applications.values())
Expand Down Expand Up @@ -588,12 +589,6 @@ def get_hiring_admin_overview(
.where(TermEntity.id == term_id)
.options(
joinedload(CourseSiteEntity.sections),
# # .joinedload(SectionEntity.staff)
# # .joinedload(SectionMemberEntity.user),
# joinedload(CourseSiteEntity.application_reviews)
# .joinedload(ApplicationReviewEntity.application),
# # .joinedload(ApplicationEntity.user),
# joinedload(CourseSiteEntity.hiring_assignments),
)
)
course_site_entities = self._session.scalars(course_site_query).unique().all()
Expand All @@ -616,25 +611,6 @@ def get_hiring_admin_overview(
]
total_enrollment += section_entity.enrolled

# preferred_review_query = (
# select(ApplicationReviewEntity)
# .where(
# ApplicationReviewEntity.course_site_id == course_site_entity.id,
# ApplicationReviewEntity.status == ApplicationReviewStatus.PREFERRED,
# )
# .order_by(ApplicationReviewEntity.preference)
# )
# preferred_review_entities = self._session.scalars(
# preferred_review_query
# ).all()
# reviews = [
# application_review.to_overview_model()
# for application_review in preferred_review_entities
# ]
# instructor_preferences = [
# application_review.application.user.to_public_model()
# for application_review in preferred_review_entities
# ]
assignments = sorted(
[
assignment.to_overview_model()
Expand All @@ -656,8 +632,6 @@ def get_hiring_admin_overview(
total_cost=total_cost,
coverage=coverage,
assignments=assignments,
# reviews=reviews,
# instructor_preferences=instructor_preferences,
)

# Add overview to the list
Expand All @@ -666,6 +640,65 @@ def get_hiring_admin_overview(
# 4. Return hiring adming overview object
return HiringAdminOverview(sites=hiring_course_site_overviews)

def get_hiring_admin_course_overview(
self, subject: User, course_site_id: str
) -> HiringAdminCourseOverview:
self._permission.enforce(subject, "hiring.admin", "*")
course_site_entity = self._session.get(CourseSiteEntity, course_site_id)
if course_site_entity is None:
raise ResourceNotFoundException()

preferred_review_query = (
select(ApplicationReviewEntity)
.where(
ApplicationReviewEntity.course_site_id == course_site_entity.id,
ApplicationReviewEntity.status == ApplicationReviewStatus.PREFERRED,
)
.order_by(ApplicationReviewEntity.preference)
.options(
joinedload(ApplicationReviewEntity.application).joinedload(
ApplicationEntity.user
)
)
)
preferred_review_entities = self._session.scalars(preferred_review_query).all()

def to_overview(review: ApplicationReviewEntity) -> ApplicationReviewOverview:
return ApplicationReviewOverview(
id=review.id,
application_id=review.application_id,
course_site_id=course_site_entity.id,
status=review.status,
preference=review.preference,
notes=review.notes,
application=review.application.to_review_overview_model(),
applicant_id=review.application.user_id,
applicant_course_ranking=0,
)

reviews = [
to_overview(application_review)
for application_review in preferred_review_entities
]

instructor_preferences = [review.application.applicant for review in reviews]

assignments_query = (
select(HiringAssignmentEntity)
.where(HiringAssignmentEntity.course_site_id == course_site_id)
.options(joinedload(HiringAssignmentEntity.user))
)
assignment_entities = self._session.scalars(assignments_query).all()
assignments = [
assignment.to_overview_model() for assignment in assignment_entities
]

return HiringAdminCourseOverview(
assignments=assignments,
reviews=reviews,
instructor_preferences=instructor_preferences,
)

def create_hiring_assignment(
self, subject: User, assignment: HiringAssignmentDraft
) -> HiringAssignmentOverview:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ <h2 mat-dialog-title>Create Assignment</h2>
}}</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle formControlName="i9">
This student has submitted an I9.
</mat-slide-toggle>
<mat-form-field appearance="outline">
<mat-label>Position Number</mat-label>
<input matInput formControlName="position_number" />
Expand All @@ -36,9 +39,6 @@ <h2 mat-dialog-title>Create Assignment</h2>
<mat-label>Notes</mat-label>
<textarea matInput formControlName="notes" name="notes"></textarea>
</mat-form-field>
<mat-slide-toggle formControlName="i9">
This student has submitted an I9.
</mat-slide-toggle>
</form>
</mat-dialog-content>
<mat-dialog-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { HiringService } from '../../hiring.service';
import { PublicProfile } from 'src/app/profile/profile.service';
import {
ApplicationReviewOverview,
HiringAdminCourseOverview,
HiringAssignmentDraft,
HiringAssignmentStatus,
HiringCourseSiteOverview,
Expand All @@ -21,6 +22,7 @@ import { MatSnackBar } from '@angular/material/snack-bar';
export interface CreateAssignmentDialogData {
termId: string;
courseSite: HiringCourseSiteOverview;
courseAdmin: HiringAdminCourseOverview;
}

@Component({
Expand Down Expand Up @@ -100,7 +102,7 @@ export class CreateAssignmentDialog {
}

getApplication(): ApplicationReviewOverview | undefined {
return this.data.courseSite.reviews.find(
return this.data.courseAdmin.reviews.find(
(a) => a.applicant_id === this.users[0].id
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ <h2 mat-dialog-title>Edit Assignment</h2>
}}</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle formControlName="i9">
This student has submitted an I9.
</mat-slide-toggle>
<mat-form-field appearance="outline">
<mat-label>Position Number</mat-label>
<input matInput formControlName="position_number" />
Expand All @@ -46,9 +49,6 @@ <h2 mat-dialog-title>Edit Assignment</h2>
<mat-label>Notes</mat-label>
<textarea matInput formControlName="notes" name="notes"></textarea>
</mat-form-field>
<mat-slide-toggle formControlName="i9">
This student has submitted an I9.
</mat-slide-toggle>
</form>
</mat-dialog-content>
<mat-dialog-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { HiringService } from '../../hiring.service';
import { PublicProfile } from 'src/app/profile/profile.service';
import {
ApplicationReviewOverview,
HiringAdminCourseOverview,
HiringAssignmentDraft,
HiringAssignmentOverview,
HiringAssignmentStatus,
Expand All @@ -28,6 +29,7 @@ export interface EditAssignmentDialogData {
assignment: HiringAssignmentOverview;
termId: string;
courseSite: HiringCourseSiteOverview;
courseAdmin: HiringAdminCourseOverview;
}

@Component({
Expand Down Expand Up @@ -116,7 +118,7 @@ export class EditAssignmentDialog {
}

getApplication(): ApplicationReviewOverview | undefined {
return this.data.courseSite.reviews.find(
return this.data.courseAdmin.reviews.find(
(a) => a.applicant_id === this.data.assignment.user.id
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ <h2 mat-dialog-title>Quick Create Assignment</h2>
}}</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle formControlName="i9">
This student has submitted an I9.
</mat-slide-toggle>
<mat-form-field appearance="outline">
<mat-label>Position Number</mat-label>
<input matInput formControlName="position_number" />
Expand All @@ -46,9 +49,6 @@ <h2 mat-dialog-title>Quick Create Assignment</h2>
<mat-label>Notes</mat-label>
<textarea matInput formControlName="notes" name="notes"></textarea>
</mat-form-field>
<mat-slide-toggle formControlName="i9">
This student has submitted an I9.
</mat-slide-toggle>
</form>
</mat-dialog-content>
<mat-dialog-actions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
import { HiringService } from '../../hiring.service';
import { PublicProfile } from 'src/app/profile/profile.service';
import {
ApplicationOverview,
ApplicationReviewOverview,
HiringAdminCourseOverview,
HiringAssignmentDraft,
HiringAssignmentStatus,
HiringCourseSiteOverview,
Expand All @@ -28,6 +28,7 @@ export interface QuickCreateAssignmentDialogData {
user: PublicProfile;
termId: string;
courseSite: HiringCourseSiteOverview;
courseAdmin: HiringAdminCourseOverview;
}

@Component({
Expand Down Expand Up @@ -62,6 +63,35 @@ export class QuickCreateAssignmentDialog {
@Inject(MAT_DIALOG_DATA) public data: QuickCreateAssignmentDialogData
) {
this.user = data.user;

// Simple hack to automatically populate the level...
let review = data.courseAdmin.reviews.find(
(r) => r.applicant_id == data.user.id
)!;
let program = review.application.program_pursued!;
let defaultLevelSearch: string | null;
switch (program) {
case 'PhD':
defaultLevelSearch = '1.0 PhD TA';
break;
case 'PhD (ABD)':
defaultLevelSearch = '1.0 PhD (ABD) TA';
break;
case 'BS/MS':
case 'MS':
defaultLevelSearch = '1.0 MS TA';
break;
default:
defaultLevelSearch = '10h UTA';
break;
}

const level = this.hiringService
.hiringLevels()
.find((level) => level.title == defaultLevelSearch);
if (level) {
this.createAssignmentForm.get('level')?.setValue(level);
}
}

/** Determines if the form is valid and can be submitted. */
Expand Down Expand Up @@ -110,7 +140,7 @@ export class QuickCreateAssignmentDialog {
}

getApplication(): ApplicationReviewOverview | undefined {
return this.data.courseSite.reviews.find(
return this.data.courseAdmin.reviews.find(
(a) => a.applicant_id === this.data.user.id
);
}
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/app/hiring/hiring-admin/hiring-admin.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,12 @@
[@detailExpand]="
element === expandedElement ? 'expanded' : 'collapsed'
">
<course-hiring-card
[termId]="selectedTermId()!"
[itemInput]="element"
(updateData)="reloadData()" />
@if (element === expandedElement) {
<course-hiring-card
[termId]="selectedTermId()!"
[itemInput]="element"
(updateData)="reloadData()" />
}
</div>
</td>
</ng-container>
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/app/hiring/hiring.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,20 @@ export interface HiringCourseSiteOverview {
total_cost: number;
coverage: number;
assignments: HiringAssignmentOverview[];
reviews: ApplicationReviewOverview[];
instructor_preferences: PublicProfile[];
// reviews: ApplicationReviewOverview[];
// instructor_preferences: PublicProfile[];
}

export interface HiringAdminOverview {
sites: HiringCourseSiteOverview[];
}

export interface HiringAdminCourseOverview {
assignments: HiringAssignmentOverview[];
reviews: ApplicationReviewOverview[];
instructor_preferences: PublicProfile[];
}

export interface HiringAssignmentSummaryOverview {
id: number | null;
application_review_id: number | null;
Expand Down
Loading

0 comments on commit 9c80cb4

Please sign in to comment.