Skip to content

Commit

Permalink
Show Applicant Course Preferences on Hiring Page (#520)
Browse files Browse the repository at this point in the history
* Attempted Solution

* Fix Issue where application reviews not created if one existed for a different course

* Add preference UI to frontend

* Fix broken tests and test data

* Fix Ordering Issues, Change Card Design
  • Loading branch information
ajaygandecha authored Jul 17, 2024
1 parent 7be5cea commit ce9a1aa
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 14 deletions.
40 changes: 40 additions & 0 deletions backend/entities/academics/hiring/application_review_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing import Self
from sqlalchemy import Enum as SQLAlchemyEnum
from itertools import groupby

from ...entity_base import EntityBase
from ....models.academics.hiring.application_review import (
Expand Down Expand Up @@ -83,6 +84,43 @@ def to_overview_model(self) -> ApplicationReviewOverview:
Returns:
ApplicationReviewOverview: `ApplicationReviewOverview` object from the entity
"""
# Determine the ranking of the applicant for a given course
# 1. Find all of the applicant's preferred sections
applicant_section_preferences = self.application.preferred_sections
# 2. Find the course sites that the applicant's preferred sections are attached to.
preferences_course_site_ids = [
section.course_site_id for section in applicant_section_preferences
]
# 3. Remove duplicates in the list above while preserving an ordering.
# (i.e., keep only the first occurrence of each ID)
# Note: Cannot just use a set since sets are unordered.
found_course_site_ids = set()
preferences_course_site_ids_no_duplicates = []
for course_site_id in preferences_course_site_ids:
if course_site_id not in found_course_site_ids:
preferences_course_site_ids_no_duplicates.append(course_site_id)
found_course_site_ids.add(course_site_id)

# 3. Find the applicant's preference for the sections included in the course site by
# determining the first index where the course site ID occurs in the list above.
# Example:
# Assume a student applied to 110-001 --> 110-002 --> 210-001 --> 210-002 --> 301-001 --> 110-003
# Assume that the all COMP 110 sections are in course site ID #8, 210 in #7, and 301 in #6.
# Then, preferences_course_site_ids = [8, 8, 8, 7, 7, 6, 8]
# So, preferences_course_site_ids_no_duplicates = [8, 7, 6]
# Therefore, this is the user's preference order:
# 1. Sections in course site 8 (COMP 110s)
# 2. Sections in course site 7 (COMP 210s)
# 3. Sections in course site 6 (COMP 301)
applicant_preference_for_course = (
preferences_course_site_ids_no_duplicates.index(
self.course_site_id
) # Find the min index.
# This case should never be reached, but it prevents an error if so.
if self.course_site_id in preferences_course_site_ids_no_duplicates
else -1
)

return ApplicationReviewOverview(
id=self.id,
application_id=self.application_id,
Expand All @@ -91,4 +129,6 @@ def to_overview_model(self) -> ApplicationReviewOverview:
preference=self.preference,
notes=self.notes,
application=self.application.to_overview_model(),
applicant_course_ranking=applicant_preference_for_course
+ 1, # Increment since starting index is 0.
)
1 change: 1 addition & 0 deletions backend/entities/application_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class UTAApplicationEntity(ApplicationEntity):
"SectionEntity",
secondary=section_application_table,
back_populates="preferred_applicants",
order_by=section_application_table.c.preference,
)

# Any row in the main Application table that is an instance of UTAApplicationEntity will override
Expand Down
1 change: 1 addition & 0 deletions backend/models/academics/hiring/application_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class ApplicationReviewOverview(ApplicationReview):
status: ApplicationReviewStatus = ApplicationReviewStatus.NOT_PROCESSED
preference: int
notes: str
applicant_course_ranking: int


class HiringStatus(BaseModel):
Expand Down
5 changes: 4 additions & 1 deletion backend/services/academics/hiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ def get_status(self, subject: User, course_site_id: int) -> HiringStatus:

for application in applications:
# Check if the application is missing reviews
if len(application.reviews) == 0:
reviews_course_site_ids = [
review.course_site_id for review in application.reviews
]
if course_site_id not in reviews_course_site_ids:
applications_missing_review.append(application)

# Step 4: Create reviews for applications missing reviews, if any.
Expand Down
2 changes: 1 addition & 1 deletion backend/test/services/academics/course_site_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_get_user_course_sites(course_site_svc: CourseSiteService):
assert isinstance(term_overview[0], TermOverview)
assert len(term_overview) == 2
assert term_overview[-1].id == term_data.current_term.id
assert len(term_overview[-1].sites) == 1
assert len(term_overview[-1].sites) == 2


def test_get_course_site_roster(course_site_svc: CourseSiteService):
Expand Down
6 changes: 4 additions & 2 deletions backend/test/services/academics/hiring/hiring_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@
applications = [application_one, application_two, application_three, application_four]

application_associations = [
(application_one, section_data.comp_110_001_current_term, 0),
(application_one, section_data.comp_301_001_current_term, 0),
(application_one, section_data.comp_110_001_current_term, 1),
(application_one, section_data.comp_110_002_current_term, 2),
(application_two, section_data.comp_110_001_current_term, 0),
(application_three, section_data.comp_110_001_current_term, 0),
(application_four, section_data.comp_110_001_current_term, 0),
Expand Down Expand Up @@ -160,7 +162,7 @@ def insert_fake_data(session: Session):
}
)
)
session.commit()
session.commit()

for review in reviews:
entity = ApplicationReviewEntity.from_model(review)
Expand Down
2 changes: 1 addition & 1 deletion backend/test/services/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
OrganizationService,
EventService,
RoomService,
HiringService,
)
from ...services.academics import HiringService

__authors__ = ["Kris Jordan", "Ajay Gandecha"]
__copyright__ = "Copyright 2023"
Expand Down
18 changes: 11 additions & 7 deletions backend/test/services/office_hours/office_hours_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

# Site
comp_110_site = CourseSite(id=1, title="COMP 110", term_id=term_data.current_term.id)
comp_301_site = CourseSite(id=2, title="COMP 301", term_id=term_data.current_term.id)

# Sections
comp_110_sections = (
Expand All @@ -55,6 +56,11 @@
],
comp_110_site.id,
)
comp_301_sections = (
[section_data.comp_301_001_current_term],
comp_301_site.id,
)


# Office Hours
comp_110_current_office_hours = OfficeHours(
Expand Down Expand Up @@ -152,8 +158,8 @@
]

# All
sites = [comp_110_site]
section_pairings = [comp_110_sections]
sites = [comp_110_site, comp_301_site]
section_pairings = [comp_110_sections, comp_301_sections]

office_hours = [
comp_110_current_office_hours,
Expand Down Expand Up @@ -260,7 +266,6 @@ def fake_data_fixture(session: Session):
title="Ina's COMP 301",
term_id=term_data.current_term.id,
section_ids=[
section_data.comp_301_001_current_term.id,
section_data.comp_301_002_current_term.id,
],
)
Expand All @@ -269,7 +274,6 @@ def fake_data_fixture(session: Session):
title="Ina's COMP 301",
term_id=term_data.f_23.id,
section_ids=[
section_data.comp_301_001_current_term.id,
section_data.comp_301_002_current_term.id,
],
)
Expand All @@ -279,15 +283,15 @@ def fake_data_fixture(session: Session):
title="Ina's COMP 3x1",
term_id=term_data.current_term.id,
section_ids=[
section_data.comp_301_001_current_term.id,
section_data.comp_301_002_current_term.id,
section_data.comp_311_001_current_term.id,
],
)
new_course_site_term_noninstructor = NewCourseSite(
title="Ina's COMP 3x1",
term_id=term_data.current_term.id,
section_ids=[
section_data.comp_301_001_current_term.id,
section_data.comp_301_002_current_term.id,
section_data.comp_311_002_current_term.id,
],
)
Expand All @@ -297,7 +301,7 @@ def fake_data_fixture(session: Session):
title="Ina's COMP courses",
term_id=term_data.current_term.id,
section_ids=[
section_data.comp_301_001_current_term.id,
section_data.comp_301_002_current_term.id,
section_data.comp_110_001_current_term.id,
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,18 @@ mat-form-field {

.end-align {
margin-left: auto;
}
}

.text-badge {
font-size: 10px;
height: 22px;
width: 32px;
text-align: center;
padding-left: 6px;
padding-right: 6px;
padding-top: 2px;
padding-bottom: 2px;
border-radius: 11px;
line-height: 22px;
color: white;
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ <h2 mat-dialog-title>

<mat-divider />

<p mat-card-subtitle>Applicant Preferences</p>

<p>
{{ data.review.application.applicant_name }} ranked this course as their
<span class="text-badge primary-background"
>#{{ data.review.applicant_course_ranking }}</span
>
choice.
</p>

<mat-divider />

<div class="row">
<p mat-card-subtitle>Your Notes</p>
<!-- <p class="end-align"><em>Changes saved.</em></p> -->
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/hiring/hiring.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface ApplicationReviewOverview {
status: ApplicationReviewStatus;
preference: number;
notes: string;
applicant_course_ranking: number;
}

export interface HiringStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,20 @@
.semibold {
font-weight: 500;
}

.mat-mdc-card-subtitle {
display: flex;
flex-direction: row;
align-items: center;
width: 96%;
}

.accessories {
margin-left: auto;

p {
margin-bottom: 0;
text-align: center;
font-size: 10px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@
<mat-card-header>
<mat-card-subtitle>
<span class="preference">{{ item.preference + 1 }}. </span>
<span>{{ item.application.applicant_name }}</span>
<span> {{ item.application.applicant_name }}</span>
@if (item.notes.length > 0) {
<mat-icon class="notes" matListItemIcon>tooltip</mat-icon>
}
<div class="accessories">
<p class="font-variant">
Course rank:
<span
[class]="
'text-badge ' +
(item.applicant_course_ranking === 1
? 'primary-background'
: 'secondary-background')
"
>#{{ item.applicant_course_ranking }}</span
>
</p>
</div>
</mat-card-subtitle>
</mat-card-header>
</mat-card>
3 changes: 3 additions & 0 deletions frontend/src/styles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ html, body {
.font-secondary {
color: mat.get-theme-color($theme, secondary) !important;
}
.font-variant {
color: mat.get-theme-color($theme, on-surface-variant) !important;
}
}

/// Defines a mixin that applies custom widgets to material text field components
Expand Down

0 comments on commit ce9a1aa

Please sign in to comment.