Skip to content

Commit a2dc17c

Browse files
Maggie ChenMaggie Chen
authored andcommitted
resolve merge conflicts
2 parents 48ff423 + 8107e58 commit a2dc17c

File tree

12 files changed

+284
-5
lines changed

12 files changed

+284
-5
lines changed

backend/typescript/graphql/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import simpleEntityResolvers from "./resolvers/simpleEntityResolvers";
1515
import simpleEntityType from "./types/simpleEntityType";
1616
import userResolvers from "./resolvers/userResolvers";
1717
import userType from "./types/userType";
18+
import reviewDashboardResolvers from "./resolvers/reviewDashboardResolvers";
19+
import reviewDashboardType from "./types/reviewDashboardType";
1820

1921
const query = gql`
2022
type Query {
@@ -29,12 +31,21 @@ const mutation = gql`
2931
`;
3032

3133
const executableSchema = makeExecutableSchema({
32-
typeDefs: [query, mutation, authType, entityType, simpleEntityType, userType],
34+
typeDefs: [
35+
query,
36+
mutation,
37+
authType,
38+
entityType,
39+
simpleEntityType,
40+
userType,
41+
reviewDashboardType,
42+
],
3343
resolvers: merge(
3444
authResolvers,
3545
entityResolvers,
3646
simpleEntityResolvers,
3747
userResolvers,
48+
reviewDashboardResolvers,
3849
),
3950
});
4051

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import ReviewDashboardService from "../../services/implementations/reviewDashboardService";
2+
import { ReviewDashboardRowDTO } from "../../types";
3+
import { getErrorMessage } from "../../utilities/errorUtils";
4+
5+
const reviewDashboardService = new ReviewDashboardService();
6+
7+
const reviewDashboardResolvers = {
8+
Query: {
9+
reviewDashboard: async (
10+
_parent: undefined,
11+
args: { pageNumber: number; resultsPerPage: number },
12+
): Promise<ReviewDashboardRowDTO[]> => {
13+
try {
14+
return await reviewDashboardService.getReviewDashboard(
15+
args.pageNumber,
16+
args.resultsPerPage,
17+
);
18+
} catch (error) {
19+
throw new Error(getErrorMessage(error));
20+
}
21+
},
22+
},
23+
};
24+
25+
export default reviewDashboardResolvers;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { gql } from "apollo-server-express";
2+
import { ApplicationStatus, PositionTitle, ReviewerDTO } from "../../types";
3+
4+
const reviewDashboardType = gql`
5+
type ReviewerDTO {
6+
firstName: String!
7+
lastName: String!
8+
}
9+
10+
type ReviewDashboardRowDTO {
11+
firstName: String!
12+
lastName: String!
13+
position: String!
14+
timesApplied: String!
15+
applicationStatus: String!
16+
choice: Int!
17+
reviewers: [ReviewerDTO!]!
18+
totalScore: Int
19+
}
20+
21+
extend type Query {
22+
reviewDashboard(
23+
pageNumber: Int!
24+
resultsPerPage: Int!
25+
): [ReviewDashboardRowDTO!]!
26+
}
27+
`;
28+
29+
export default reviewDashboardType;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { DataType } from "sequelize-typescript";
2+
import { Migration } from "../umzug";
3+
4+
const TABLE_NAME = "reviewed_applicant_records";
5+
6+
export const up: Migration = async ({ context: sequelize }) => {
7+
await sequelize
8+
.getQueryInterface()
9+
.addColumn(TABLE_NAME, "reviewerHasConflict", {
10+
type: DataType.BOOLEAN,
11+
allowNull: false,
12+
defaultValue: false,
13+
});
14+
};
15+
16+
export const down: Migration = async ({ context: sequelize }) => {
17+
await sequelize
18+
.getQueryInterface()
19+
.removeColumn(TABLE_NAME, "reviewerHasConflict");
20+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { DataType } from "sequelize-typescript";
2+
import { Migration } from "../umzug";
3+
4+
const REV_APP_TABLE = "reviewed_applicant_records";
5+
const APP_TABLE = "applicant_records";
6+
7+
export const up: Migration = async ({ context: sequelize }) => {
8+
await sequelize.getQueryInterface().addColumn(REV_APP_TABLE, "score", {
9+
type: DataType.INTEGER,
10+
allowNull: true,
11+
defaultValue: null,
12+
});
13+
14+
await sequelize.getQueryInterface().addColumn(APP_TABLE, "combined_score", {
15+
type: DataType.INTEGER,
16+
allowNull: true,
17+
defaultValue: null,
18+
});
19+
};
20+
21+
export const down: Migration = async ({ context: sequelize }) => {
22+
await sequelize
23+
.getQueryInterface()
24+
.removeColumn("reviewed_applicant_records", "score");
25+
26+
await sequelize
27+
.getQueryInterface()
28+
.removeColumn("applicant_records", "combined_score");
29+
};

backend/typescript/models/applicant.model.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/* eslint import/no-cycle: 0 */
22

3-
import { Column, DataType, Model, Table } from "sequelize-typescript";
3+
import { Column, DataType, HasMany, Model, Table } from "sequelize-typescript";
4+
import { NonAttribute } from "sequelize";
5+
import ApplicantRecord from "./applicantRecord.model";
46

57
@Table({ tableName: "applicants" })
68
export default class Applicant extends Model {
@@ -10,7 +12,7 @@ export default class Applicant extends Model {
1012
unique: true,
1113
autoIncrement: true,
1214
})
13-
id!: string;
15+
id!: number;
1416

1517
@Column({ type: DataType.STRING })
1618
academicOrCoop!: string;
@@ -53,4 +55,10 @@ export default class Applicant extends Model {
5355

5456
@Column({ type: DataType.DATE })
5557
submittedAt!: Date;
58+
59+
@HasMany(() => ApplicantRecord, {
60+
foreignKey: "id",
61+
as: "applicant",
62+
})
63+
applicantRecords?: NonAttribute<ApplicantRecord[]>;
5664
}

backend/typescript/models/applicantRecord.model.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
/* eslint import/no-cycle: 0 */
22

33
import {
4+
BelongsTo,
45
Column,
56
DataType,
67
ForeignKey,
8+
HasMany,
79
Model,
810
Table,
911
} from "sequelize-typescript";
12+
import { NonAttribute } from "sequelize";
1013
import {
1114
ApplicantRecordExtraInfo,
1215
ApplicationStatus,
1316
SkillCategory,
1417
} from "../types";
1518
import Applicant from "./applicant.model";
1619
import Position from "./position.model";
20+
import ReviewedApplicantRecord from "./reviewedApplicantRecord.model";
1721

1822
@Table({ tableName: "applicant_records" })
1923
export default class ApplicantRecord extends Model {
@@ -23,7 +27,7 @@ export default class ApplicantRecord extends Model {
2327
unique: true,
2428
autoIncrement: true,
2529
})
26-
id!: string;
30+
id!: number;
2731

2832
@ForeignKey(() => Applicant)
2933
@Column({ type: DataType.STRING })
@@ -48,6 +52,19 @@ export default class ApplicantRecord extends Model {
4852
@Column({ type: DataType.JSONB, allowNull: true })
4953
extraInfo!: ApplicantRecordExtraInfo;
5054

55+
@Column({
56+
type: DataType.INTEGER,
57+
allowNull: true,
58+
defaultValue: null,
59+
})
60+
combined_score!: number;
61+
5162
@Column({ type: DataType.BOOLEAN, defaultValue: false })
5263
isApplicantFlagged!: boolean;
64+
65+
@BelongsTo(() => Applicant, "applicantId")
66+
applicant?: NonAttribute<Applicant>;
67+
68+
@HasMany(() => ReviewedApplicantRecord, "applicantRecordId")
69+
reviewedApplicantRecords?: NonAttribute<ReviewedApplicantRecord[]>;
5370
}

backend/typescript/models/reviewedApplicantRecord.model.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import {
66
ForeignKey,
77
Model,
88
Table,
9+
BelongsTo,
910
} from "sequelize-typescript";
10-
import User from "./user.model";
11+
import { NonAttribute } from "sequelize";
1112
import { Review, ReviewStatus, ReviewStatusEnum } from "../types";
1213
import ApplicantRecord from "./applicantRecord.model";
14+
import User from "./user.model";
1315

1416
@Table({ tableName: "reviewed_applicant_records" })
1517
export default class ReviewedApplicantRecord extends Model {
@@ -29,4 +31,20 @@ export default class ReviewedApplicantRecord extends Model {
2931
defaultValue: ReviewStatusEnum.TODO,
3032
})
3133
status!: ReviewStatus;
34+
35+
@Column({
36+
type: DataType.INTEGER,
37+
allowNull: true,
38+
defaultValue: null,
39+
})
40+
score!: number;
41+
42+
@Column({
43+
type: DataType.BOOLEAN,
44+
defaultValue: false,
45+
})
46+
reviewerHasConflict!: boolean;
47+
48+
@BelongsTo(() => User, { foreignKey: "reviewerId", targetKey: "id" })
49+
user?: NonAttribute<User>;
3250
}

backend/typescript/models/user.model.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import {
88
Model,
99
Table,
1010
} from "sequelize-typescript";
11+
import { NonAttribute } from "sequelize";
1112
import { PositionTitle, PositionTitles, Role } from "../types";
1213
import Position from "./position.model";
14+
import ReviewedApplicantRecord from "./reviewedApplicantRecord.model";
1315

1416
@Table({ tableName: "users" })
1517
export default class User extends Model {
@@ -34,4 +36,13 @@ export default class User extends Model {
3436
@ForeignKey(() => Position)
3537
@Column({ type: DataType.ENUM(...Object.values(PositionTitles)) })
3638
position?: PositionTitle;
39+
40+
@HasMany(() => ApplicationDashboardTable)
41+
applicationDashboards?: ApplicationDashboardTable[];
42+
43+
@HasMany(() => ReviewedApplicantRecord, {
44+
foreignKey: "reviewerId",
45+
as: "user",
46+
})
47+
reviewedApplicantRecords?: NonAttribute<ReviewedApplicantRecord[]>;
3748
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { PositionTitle, ReviewDashboardRowDTO } from "../../types";
2+
import IReviewDashboardService from "../interfaces/IReviewDashboardService";
3+
import { getErrorMessage } from "../../utilities/errorUtils";
4+
import logger from "../../utilities/logger";
5+
import ApplicantRecord from "../../models/applicantRecord.model";
6+
7+
const Logger = logger(__filename);
8+
9+
function toDTO(model: ApplicantRecord): ReviewDashboardRowDTO {
10+
return {
11+
firstName: model.applicant!.firstName,
12+
lastName: model.applicant!.lastName,
13+
position: model.position as PositionTitle,
14+
timesApplied: model.applicant!.timesApplied.toString(),
15+
applicationStatus: model.status,
16+
choice: model.choice,
17+
reviewers: model.reviewedApplicantRecords!.map((r) => ({
18+
firstName: r.user!.first_name,
19+
lastName: r.user!.last_name,
20+
})),
21+
totalScore: model.combined_score,
22+
};
23+
}
24+
25+
class ReviewDashboardService implements IReviewDashboardService {
26+
/* eslint-disable class-methods-use-this */
27+
async getReviewDashboard(
28+
pageNumber: number,
29+
resultsPerPage: number,
30+
): Promise<ReviewDashboardRowDTO[]> {
31+
try {
32+
const perPage = Number.isFinite(Number(resultsPerPage))
33+
? Number(resultsPerPage)
34+
: 1;
35+
const currentPage = Number.isFinite(Number(pageNumber))
36+
? Number(pageNumber)
37+
: 1;
38+
const offsetRow = (currentPage - 1) * perPage;
39+
40+
// get applicant_record
41+
// JOIN applicant ON applicant_id
42+
// JOIN reviewed_applicant_record ON applicant_record_id
43+
// JOIN user ON reviewer_id
44+
const applicants: Array<ApplicantRecord> | null =
45+
await ApplicantRecord.findAll({
46+
attributes: { exclude: ["createdAt", "updatedAt"] },
47+
include: [
48+
{
49+
attributes: { exclude: ["createdAt", "updatedAt"] },
50+
association: "reviewedApplicantRecords",
51+
include: [
52+
{
53+
attributes: { exclude: ["createdAt", "updatedAt"] },
54+
association: "user",
55+
},
56+
],
57+
},
58+
{
59+
attributes: { exclude: ["createdAt", "updatedAt"] },
60+
association: "applicant",
61+
},
62+
],
63+
order: [["id", "ASC"]],
64+
limit: perPage,
65+
offset: offsetRow,
66+
});
67+
return applicants.map(toDTO);
68+
} catch (error: unknown) {
69+
Logger.error(
70+
`Failed to get dashboard. Reason = ${getErrorMessage(error)}`,
71+
);
72+
throw error;
73+
}
74+
}
75+
}
76+
77+
export default ReviewDashboardService;

0 commit comments

Comments
 (0)