Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ReviewDashboardService from "../../services/implementations/reviewDashboa
import {
ReviewDashboardRowDTO,
ReviewDashboardSidePanelDTO,
ReviewDashboardFilter,
} from "../../types";
import { getErrorMessage } from "../../utilities/errorUtils";

Expand All @@ -11,12 +12,17 @@ const reviewDashboardResolvers = {
Query: {
reviewDashboard: async (
_parent: undefined,
args: { pageNumber: number; resultsPerPage: number },
args: {
pageNumber: number;
resultsPerPage: number;
filter?: ReviewDashboardFilter;
},
): Promise<ReviewDashboardRowDTO[]> => {
try {
return await reviewDashboardService.getReviewDashboard(
args.pageNumber,
args.resultsPerPage,
args.filter,
);
} catch (error) {
throw new Error(getErrorMessage(error));
Expand Down
62 changes: 55 additions & 7 deletions backend/typescript/graphql/types/reviewDashboardType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,60 @@ const reviewDashboardType = gql`
totalScore: Int
}

type Review {
passionFSG: Int
teamPlayer: Int
desireToLearn: Int
skill: Int
skillCategory: String
comments: String
enum ApplicantRoleEnum {
pres
int_dir
ext_dir
vpe
vpd
vpp
vpt
vp_ext
vp_int
vp_comms
vp_scoping
vp_finance
pm
pl
design_mentor
graphic_design
product_design
uxr
dev
}

enum DepartmentEnum {
Engineering
Design
Product
Community
}

enum AdditionalFiltersEnum {
IN_REVIEW
REVIEWED
SELECTED
NOT_SELECTED
SENIOR
INTERMEDIATE
JUNIOR
GREATER_THAN_25
BETWEEN_20_AND_25
BETWEEN_15_AND_20
BETWEEN_10_AND_15
LESS_THAN_10
FIRST_YEAR
SECOND_YEAR
THIRD_YEAR
FOURTH_YEAR
FIFTH_YEAR
SIXTH_YEAR
}

input ReviewDashboardFilter {
department: DepartmentEnum
role: ApplicantRoleEnum
additionalFilters: [AdditionalFiltersEnum!]
}

type ReviewDetails {
Expand All @@ -47,6 +94,7 @@ const reviewDashboardType = gql`
reviewDashboard(
pageNumber: Int!
resultsPerPage: Int!
filter: ReviewDashboardFilter
): [ReviewDashboardRowDTO!]!

reviewDashboardSidePanel(applicantId: String!): ReviewDashboardSidePanelDTO!
Expand Down
3 changes: 3 additions & 0 deletions backend/typescript/models/applicantRecord.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,7 @@ export default class ApplicantRecord extends Model {

@HasMany(() => ReviewedApplicantRecord, "applicantRecordId")
reviewedApplicantRecords?: NonAttribute<ReviewedApplicantRecord[]>;

@BelongsTo(() => Position, { foreignKey: "position" })
appliedTo?: NonAttribute<Position>;
}
161 changes: 160 additions & 1 deletion backend/typescript/services/implementations/reviewDashboardService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { literal, Op, Sequelize } from "sequelize";
import {
AdditionalFilters,
Department,
PositionTitle,
ReviewDashboardFilter,
ReviewDashboardRowDTO,
ReviewDashboardSidePanelDTO,
ApplicantRole,
} from "../../types";
import IReviewDashboardService from "../interfaces/IReviewDashboardService";
import { getErrorMessage } from "../../utilities/errorUtils";
Expand All @@ -26,6 +31,142 @@ function toDTO(model: ApplicantRecord): ReviewDashboardRowDTO {
};
}

function buildWhereStatement(filter?: ReviewDashboardFilter) {
const exp: any = [];
const ranges: any = [];
const year: any = [];
const skill: any = [];
const status: any = [];
if (filter) {
if (filter.department) {
if (filter.department === Department.Community) {
exp.push({ "$appliedTo.department$": { [Op.eq]: "Community" } });
} else if (filter.department === Department.Design) {
exp.push({ "$appliedTo.department$": { [Op.eq]: "Design" } });
} else if (filter.department === Department.Engineering) {
exp.push({ "$appliedTo.department$": { [Op.eq]: "Engineering" } });
} else if (filter.department === Department.Product) {
exp.push({ "$appliedTo.department$": { [Op.eq]: "Product" } });
}
}

if (filter.role) {
if (filter.role.toString() === "int_dir") {
exp.push({ position: { [Op.eq]: "Internal Director" } });
} else if (filter.role.toString() === "ext_dir") {
exp.push({ position: { [Op.eq]: "External Director" } });
} else if (filter.role.toString() === "pres") {
exp.push({ position: { [Op.eq]: "President" } });
} else if (filter.role.toString() === "vpe") {
exp.push({ position: { [Op.eq]: "VP Engineering" } });
} else if (filter.role.toString() === "vpd") {
exp.push({ position: { [Op.eq]: "VP Design" } });
} else if (filter.role.toString() === "vpp") {
exp.push({ position: { [Op.eq]: "VP Product" } });
} else if (filter.role.toString() === "vpt") {
exp.push({ position: { [Op.eq]: "VP Talent" } });
} else if (filter.role.toString() === "vp_ext") {
exp.push({ position: { [Op.eq]: "VP External" } });
} else if (filter.role.toString() === "vp_int") {
exp.push({ position: { [Op.eq]: "VP Internal" } });
} else if (filter.role.toString() === "vp_comms") {
exp.push({ position: { [Op.eq]: "VP Community" } });
} else if (filter.role.toString() === "vp_scoping") {
exp.push({ position: { [Op.eq]: "VP Scoping" } });
} else if (filter.role.toString() === "vp_finance") {
exp.push({ position: { [Op.eq]: "VP Finance" } });
} else if (filter.role.toString() === "pm") {
exp.push({ position: { [Op.eq]: "Project Manager" } });
} else if (filter.role.toString() === "pl") {
exp.push({ position: { [Op.eq]: "Project Lead" } });
} else if (filter.role.toString() === "design_mentor") {
exp.push({ position: { [Op.eq]: "Design Mentor" } });
} else if (filter.role.toString() === "graphic_design") {
exp.push({ position: { [Op.eq]: "Graphic Design" } });
} else if (filter.role.toString() === "product_design") {
exp.push({ position: { [Op.eq]: "Product Design" } });
} else if (filter.role.toString() === "uxr") {
exp.push({ position: { [Op.eq]: "User Researcher" } });
} else if (filter.role.toString() === "dev") {
exp.push({ position: { [Op.eq]: "Developer" } });
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you feel if we just pass in a position string instead of making GraphQL guard with an enum. if we can't find anything entries with matching position string, we just return empty array. just looking at this term, we added i think 2 new positions, i think doing it without this enum check would make it easier to add new positions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think we can also do multi-select positions for filtering? i think i see that in the design right now, so it would be good to have that option if it's not too bad to implement 🙏


if (filter.additionalFilters) {
if (
filter.additionalFilters.includes(AdditionalFilters.GREATER_THAN_25)
) {
ranges.push({ combined_score: { [Op.gt]: 25 } });
}
if (
filter.additionalFilters.includes(AdditionalFilters.BETWEEN_20_AND_25)
) {
ranges.push({ combined_score: { [Op.between]: [20, 25] } });
}
if (
filter.additionalFilters.includes(AdditionalFilters.BETWEEN_15_AND_20)
) {
ranges.push({ combined_score: { [Op.between]: [15, 20] } });
}
if (
filter.additionalFilters.includes(AdditionalFilters.BETWEEN_10_AND_15)
) {
ranges.push({ combined_score: { [Op.between]: [10, 15] } });
}
if (filter.additionalFilters.includes(AdditionalFilters.LESS_THAN_10)) {
ranges.push({ combined_score: { [Op.lt]: 10 } });
}
if (filter.additionalFilters.includes(AdditionalFilters.SENIOR)) {
skill.push({ skillCategory: { [Op.eq]: "Senior" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.JUNIOR)) {
skill.push({ skillCategory: { [Op.eq]: "Junior" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.INTERMEDIATE)) {
skill.push({ skillCategory: { [Op.eq]: "Intermediate" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.FIRST_YEAR)) {
year.push({ "$applicant.academicYear$": { [Op.regexp]: "1(A|B)" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.SECOND_YEAR)) {
year.push({ "$applicant.academicYear$": { [Op.regexp]: "2(A|B)" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.THIRD_YEAR)) {
year.push({ "$applicant.academicYear$": { [Op.regexp]: "3(A|B)" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.FOURTH_YEAR)) {
year.push({ "$applicant.academicYear$": { [Op.regexp]: "4(A|B)" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.FIFTH_YEAR)) {
year.push({ "$applicant.academicYear$": { [Op.regexp]: "5(A|B)" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.SIXTH_YEAR)) {
year.push({
"$applicant.academicYear$": { [Op.iRegexp]: "graduate" },
});
}
if (filter.additionalFilters.includes(AdditionalFilters.IN_REVIEW)) {
status.push({ status: { [Op.eq]: "In Review" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.REVIEWED)) {
status.push({ status: { [Op.eq]: "Reviewed" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.SELECTED)) {
status.push({ status: { [Op.eq]: "Selected for Interview" } });
}
if (filter.additionalFilters.includes(AdditionalFilters.NOT_SELECTED)) {
status.push({ status: { [Op.eq]: "Not Considered" } });
}
}
}

if (ranges.length > 0) exp.push({ [Op.or]: ranges });
if (skill.length > 0) exp.push({ [Op.or]: skill });
if (year.length > 0) exp.push({ [Op.or]: year });
if (status.length > 0) exp.push({ [Op.or]: status });
return exp;
}

function toSidePanelDTO(model: ApplicantRecord): ReviewDashboardSidePanelDTO {
const reviewDetails =
model.reviewedApplicantRecords?.map((reviewRecord) => ({
Expand All @@ -51,6 +192,7 @@ class ReviewDashboardService implements IReviewDashboardService {
async getReviewDashboard(
pageNumber: number,
resultsPerPage: number,
filters?: ReviewDashboardFilter,
): Promise<ReviewDashboardRowDTO[]> {
try {
const perPage = Number.isFinite(Number(resultsPerPage))
Expand All @@ -61,6 +203,8 @@ class ReviewDashboardService implements IReviewDashboardService {
: 1;
const offsetRow = (currentPage - 1) * perPage;

const whereStatement = buildWhereStatement(filters);

// get applicant_record
// JOIN applicant ON applicant_id
// JOIN reviewed_applicant_record ON applicant_record_id
Expand All @@ -70,7 +214,9 @@ class ReviewDashboardService implements IReviewDashboardService {
attributes: { exclude: ["createdAt", "updatedAt"] },
include: [
{
attributes: { exclude: ["createdAt", "updatedAt"] },
attributes: {
exclude: ["createdAt", "updatedAt"],
},
association: "reviewedApplicantRecords",
include: [
{
Expand All @@ -82,8 +228,21 @@ class ReviewDashboardService implements IReviewDashboardService {
{
attributes: { exclude: ["createdAt", "updatedAt"] },
association: "applicant",
required: true,
},
{
attributes: { exclude: ["createdAt", "updatedAt"] },
association: "appliedTo",
required: true,
on: literal(
`"ApplicantRecord"."position"::text = "appliedTo"."title"::text`,
),
},
],
where:
whereStatement.length > 0
? { [Op.and]: whereStatement }
: undefined,
order: [["id", "ASC"]],
limit: perPage,
offset: offsetRow,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ReviewDashboardRowDTO,
ReviewDashboardFilter,
ReviewDashboardSidePanelDTO,
} from "../../types";

Expand All @@ -8,10 +9,12 @@ interface IReviewDashboardService {
* Pagination-supporting viewing of the virtual review dashboard
* @Param page the page the viewer is on
* @Param resultsPerPage the number of results per page
* @param filters the filters for the review dashboard results
*/
getReviewDashboard(
page: number,
resultsPerPage: number,
filters?: ReviewDashboardFilter,
): Promise<ReviewDashboardRowDTO[]>;

/**
Expand Down
49 changes: 49 additions & 0 deletions backend/typescript/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,28 @@ export type NodemailerConfig = {

export type SignUpMethod = "PASSWORD" | "GOOGLE";

export enum ApplicantRole {
pres = "president", // community tab
int_dir = "internal director",
ext_dir = "external director",
vpe = "vp engineering", // eng tab
vpd = "vp design", // design tab
vpp = "vp product", // prod tab
vpt = "vp talent", // community tab
vp_ext = "vp external", // community tab
vp_int = "vp internal", // community tab
vp_comms = "vp communications", // community tab
vp_scoping = "vp scoping", // community tab
vp_finance = "vp finance & operations", // community tab
pm = "project manager", // prod tab
pl = "project lead", // eng tab
design_mentor = "design mentor", // design tab
graphic_design = "graphic designer", // design tab
product_design = "product designer", // design tab
uxr = "user researcher", // design tab
dev = "project developer", // eng tab
}

export enum Department {
Engineering = "Engineering",
Design = "Design",
Expand Down Expand Up @@ -252,3 +274,30 @@ export type CreateAdminCommentDTO = Pick<
AdminCommentDTO,
"userId" | "applicantRecordId" | "comment"
>;

export enum AdditionalFilters {
IN_REVIEW = "IN_REVIEW",
REVIEWED = "REVIEWED",
SELECTED = "SELECTED",
NOT_SELECTED = "NOT_SELECTED",
SENIOR = "SENIOR",
INTERMEDIATE = "INTERMEDIATE",
JUNIOR = "JUNIOR",
GREATER_THAN_25 = "GREATER_THAN_25",
BETWEEN_20_AND_25 = "BETWEEN_20_AND_25",
BETWEEN_15_AND_20 = "BETWEEN_15_AND_20",
BETWEEN_10_AND_15 = "BETWEEN_10_AND_15",
LESS_THAN_10 = "LESS_THAN_10",
FIRST_YEAR = "FIRST_YEAR",
SECOND_YEAR = "SECOND_YEAR",
THIRD_YEAR = "THIRD_YEAR",
FOURTH_YEAR = "FOURTH_YEAR",
FIFTH_YEAR = "FIFTH_YEAR",
SIXTH_YEAR = "SIXTH_YEAR",
}

export type ReviewDashboardFilter = {
department?: Department;
role?: ApplicantRole;
additionalFilters?: AdditionalFilters[];
};
Loading