From f9b2b5268faffb0496f35fc5c1f51ac16646270d Mon Sep 17 00:00:00 2001 From: mat-ng Date: Wed, 27 Mar 2024 20:17:27 -0400 Subject: [PATCH 1/7] update users table --- .../2023.02.18T17.43.41.create-user-table.ts | 16 ++++++++++++---- backend/typescript/models/user.model.ts | 7 +++++-- backend/typescript/types.ts | 4 +++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/backend/typescript/migrations/2023.02.18T17.43.41.create-user-table.ts b/backend/typescript/migrations/2023.02.18T17.43.41.create-user-table.ts index 83ef00b..1a54982 100644 --- a/backend/typescript/migrations/2023.02.18T17.43.41.create-user-table.ts +++ b/backend/typescript/migrations/2023.02.18T17.43.41.create-user-table.ts @@ -10,21 +10,24 @@ const SEEDED_DATA = [ last_name: "Doe", email: "johndoe@gmail.com", auth_id: "bide", - role: "User", + permission: "Reviewers", + role: "Developer", }, { first_name: "Jane", last_name: "Doe", email: "janedoe@gmail.ca", auth_id: "none", - role: "Admin", + permission: "Reviewers", + role: "Developer", }, { first_name: "UW", last_name: "Blueprint", email: "recruitmenttools@uwblueprint.org", auth_id: "1Z4wyuonu9MhAi4VoAEiTMVj1iT2", - role: "Admin", + permission: "Reviewers", + role: "Developer", }, ]; // recruitmenttools@uwblueprint.org @@ -55,8 +58,13 @@ export const up: Migration = async ({ context: sequelize }) => { primaryKey: true, allowNull: false, }, + permission: { + type: DataType.ENUM("VP Talent", "Eteam", "Engineering", "Product", "Design", "Reviewers"), + allowNull: false, + defaultValue: "Reviewers" + }, role: { - type: DataType.ENUM("User", "Admin"), + type: DataType.ENUM("Developer", "Designer"), allowNull: false, }, createdAt: DataType.DATE, diff --git a/backend/typescript/models/user.model.ts b/backend/typescript/models/user.model.ts index 92fd943..ba71b32 100644 --- a/backend/typescript/models/user.model.ts +++ b/backend/typescript/models/user.model.ts @@ -1,7 +1,7 @@ /* eslint import/no-cycle: 0 */ import { Column, DataType, HasMany, Model, Table } from "sequelize-typescript"; -import { Role } from "../types"; +import { Permission, Role } from "../types"; import ApplicationDashboardTable from "./applicationDashboard.model"; @Table({ tableName: "users" }) @@ -21,7 +21,10 @@ export default class User extends Model { @Column({ type: DataType.INTEGER, primaryKey: true, autoIncrement: true }) id!: number; - @Column({ type: DataType.ENUM("User", "Admin") }) + @Column({ type: DataType.ENUM("VP Talent", "Eteam", "Engineering", "Product", "Design", "Reviewers"), allowNull: false, defaultValue: 'Reviewers' }) + permission!: Permission; + + @Column({ type: DataType.ENUM("Developer, Designer"), allowNull: false }) role!: Role; @HasMany(() => ApplicationDashboardTable) diff --git a/backend/typescript/types.ts b/backend/typescript/types.ts index d1299dd..3894da7 100644 --- a/backend/typescript/types.ts +++ b/backend/typescript/types.ts @@ -1,4 +1,6 @@ -export type Role = "User" | "Admin"; +export type Permission = "VP Talent" | "Eteam" | "Engineering" | "Product" | "Design" | "Reviewers"; + +export type Role = "Developer" | "Designer"; export enum StatusType { ACCEPTED = "accepted", From 9b7a750ba8038128b5d0abfd3628d345f7b47f58 Mon Sep 17 00:00:00 2001 From: mat-ng Date: Wed, 20 Mar 2024 19:59:41 -0400 Subject: [PATCH 2/7] create delegation algorithm --- .../utilities/delegation-algorithm/index.ts | 49 ++++++++++++++++++ .../delegation-algorithm/roundRobinPairs.ts | 51 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 backend/typescript/utilities/delegation-algorithm/index.ts create mode 100644 backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts diff --git a/backend/typescript/utilities/delegation-algorithm/index.ts b/backend/typescript/utilities/delegation-algorithm/index.ts new file mode 100644 index 0000000..3e53653 --- /dev/null +++ b/backend/typescript/utilities/delegation-algorithm/index.ts @@ -0,0 +1,49 @@ +import { sequelize } from "../../models"; + +import Application from '../../models/application.model'; +import ApplicationDashboardModel from '../../models/applicationDashboard.model'; +import User from '../../models/user.model'; + +import { roundRobinPairs, assignApplicationsToPairs } from './roundRobinPairs'; + + +async function delegationAlgorithm() { + sequelize.authenticate(); + + const applications = await loadApplications(); + const reviewers = await loadReviewers(); + + const uniquePairs = roundRobinPairs(reviewers); + const totalPairs = assignApplicationsToPairs(uniquePairs, applications); + + await Promise.all(applications.map(async function (application, i) { + return Promise.all(totalPairs[i].map(async function (reviewer) { + await ApplicationDashboardModel.create({ + applicationId: application.id, + reviewerId: reviewer.id, + reviewerEmail: reviewer.email, + passionFSG: 0, + teamPlayer: 0, + desireToLearn: 0, + skill: 0, + skillCategory: 'junior', + reviewerComments: '', + recommendedSecondChoice: 'N/A' + }); + })) + })) +} + +async function loadReviewers(): Promise { + return await User.findAll({ + attributes: ['id', 'email'], + }); +} + +async function loadApplications(): Promise { + return await Application.findAll({ + attributes: ['id'] + }); +} + +delegationAlgorithm(); diff --git a/backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts b/backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts new file mode 100644 index 0000000..2e53fab --- /dev/null +++ b/backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts @@ -0,0 +1,51 @@ +import { isEqual } from 'lodash'; + +import Application from '../../models/application.model'; +import User from '../../models/user.model'; + + +// Generate a list of all unique pairs of users — from a given list of users +// Uses Round Robin algorithm for optimized time complexity +export function roundRobinPairs(developers: (User | {})[]): [User, User][] { + if (developers.length % 2 !== 0) { + developers.push({}); + } + + const fixedDeveloper = developers[0]; + let rotatingDevelopers = developers.slice(1); + const pairs: [(User | {}), (User | {})][] = []; + + for (let i = 0; i < developers.length - 1; i++) { + for (let j = 0; j < developers.length / 2 - 1; j++) { + if (!isEqual(rotatingDevelopers[j], {}) && !isEqual(rotatingDevelopers[developers.length - j - 2], {})) { + pairs.push([rotatingDevelopers[j], rotatingDevelopers[developers.length - j - 2]]); + } + } + if (!isEqual(fixedDeveloper, {}) && !isEqual(rotatingDevelopers[Math.floor(developers.length / 2) - 1], {})) { + pairs.push([fixedDeveloper, rotatingDevelopers[Math.floor(developers.length / 2) - 1]]); + } + rotatingDevelopers = rotatingDevelopers.slice(1).concat(rotatingDevelopers.slice(0, 1)); //rotate list + } + + shuffleArray(pairs); + return pairs as [User, User][]; +} + +// Multiply pairs to equal the number of applications +export function assignApplicationsToPairs(pairs: [User, User][], applications: Application[]): [User, User][] { + const totalPairsNeeded = applications.length; + while (pairs.length < totalPairsNeeded) { + pairs.push(...pairs.slice(0, totalPairsNeeded - pairs.length)); + } + + shuffleArray(pairs); + return pairs; +} + +// Shuffle a list of items +function shuffleArray(array: T[]): void { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +} From 5ef3d1197cbe8c702b915f5bbbc9c872f90e551e Mon Sep 17 00:00:00 2001 From: mat-ng Date: Wed, 20 Mar 2024 20:06:44 -0400 Subject: [PATCH 3/7] create README for delegation algorithm --- .../utilities/delegation-algorithm/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 backend/typescript/utilities/delegation-algorithm/README.md diff --git a/backend/typescript/utilities/delegation-algorithm/README.md b/backend/typescript/utilities/delegation-algorithm/README.md new file mode 100644 index 0000000..a867387 --- /dev/null +++ b/backend/typescript/utilities/delegation-algorithm/README.md @@ -0,0 +1,17 @@ +## Delegation Algorithm + +Run the delegation algorithm from the command line of the running application: + +Install ts-node: + +``` +npm install -g ts-node +``` + +Run the algorithm: + +``` +ts-node utilities/delegation-algorithm/index.ts +``` + +The `applicationdashboardtable` should be populated with the review data. \ No newline at end of file From 17dca9666a364e69214f24dc18816481c2adbf37 Mon Sep 17 00:00:00 2001 From: mat-ng Date: Wed, 20 Mar 2024 20:19:44 -0400 Subject: [PATCH 4/7] fix formatting --- backend/typescript/utilities/delegation-algorithm/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/typescript/utilities/delegation-algorithm/index.ts b/backend/typescript/utilities/delegation-algorithm/index.ts index 3e53653..dc96f3b 100644 --- a/backend/typescript/utilities/delegation-algorithm/index.ts +++ b/backend/typescript/utilities/delegation-algorithm/index.ts @@ -30,8 +30,8 @@ async function delegationAlgorithm() { reviewerComments: '', recommendedSecondChoice: 'N/A' }); - })) - })) + })); + })); } async function loadReviewers(): Promise { From de642a577b958a40c3e5e991e857d035f90e5b74 Mon Sep 17 00:00:00 2001 From: mat-ng Date: Wed, 20 Mar 2024 20:24:06 -0400 Subject: [PATCH 5/7] reorganize directory --- .../typescript/{utilities => }/delegation-algorithm/README.md | 2 +- .../delegation-algorithm => delegation-algorithm/src}/index.ts | 0 .../src}/roundRobinPairs.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename backend/typescript/{utilities => }/delegation-algorithm/README.md (84%) rename backend/typescript/{utilities/delegation-algorithm => delegation-algorithm/src}/index.ts (100%) rename backend/typescript/{utilities/delegation-algorithm => delegation-algorithm/src}/roundRobinPairs.ts (100%) diff --git a/backend/typescript/utilities/delegation-algorithm/README.md b/backend/typescript/delegation-algorithm/README.md similarity index 84% rename from backend/typescript/utilities/delegation-algorithm/README.md rename to backend/typescript/delegation-algorithm/README.md index a867387..b2974a1 100644 --- a/backend/typescript/utilities/delegation-algorithm/README.md +++ b/backend/typescript/delegation-algorithm/README.md @@ -11,7 +11,7 @@ npm install -g ts-node Run the algorithm: ``` -ts-node utilities/delegation-algorithm/index.ts +ts-node /delegation-algorithm/src/index.ts ``` The `applicationdashboardtable` should be populated with the review data. \ No newline at end of file diff --git a/backend/typescript/utilities/delegation-algorithm/index.ts b/backend/typescript/delegation-algorithm/src/index.ts similarity index 100% rename from backend/typescript/utilities/delegation-algorithm/index.ts rename to backend/typescript/delegation-algorithm/src/index.ts diff --git a/backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts b/backend/typescript/delegation-algorithm/src/roundRobinPairs.ts similarity index 100% rename from backend/typescript/utilities/delegation-algorithm/roundRobinPairs.ts rename to backend/typescript/delegation-algorithm/src/roundRobinPairs.ts From 8b4b455f752a2d93bc5aa9dbbae64ff1c1dcf4a9 Mon Sep 17 00:00:00 2001 From: mat-ng Date: Wed, 20 Mar 2024 20:30:58 -0400 Subject: [PATCH 6/7] fix linting issues --- .../delegation-algorithm/src/index.ts | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/backend/typescript/delegation-algorithm/src/index.ts b/backend/typescript/delegation-algorithm/src/index.ts index dc96f3b..1702b46 100644 --- a/backend/typescript/delegation-algorithm/src/index.ts +++ b/backend/typescript/delegation-algorithm/src/index.ts @@ -1,4 +1,4 @@ -import { sequelize } from "../../models"; +import { sequelize } from '../../models'; import Application from '../../models/application.model'; import ApplicationDashboardModel from '../../models/applicationDashboard.model'; @@ -16,22 +16,26 @@ async function delegationAlgorithm() { const uniquePairs = roundRobinPairs(reviewers); const totalPairs = assignApplicationsToPairs(uniquePairs, applications); - await Promise.all(applications.map(async function (application, i) { - return Promise.all(totalPairs[i].map(async function (reviewer) { - await ApplicationDashboardModel.create({ - applicationId: application.id, - reviewerId: reviewer.id, - reviewerEmail: reviewer.email, - passionFSG: 0, - teamPlayer: 0, - desireToLearn: 0, - skill: 0, - skillCategory: 'junior', - reviewerComments: '', - recommendedSecondChoice: 'N/A' - }); - })); - })); + await Promise.all( + applications.map(async function (application, i) { + return Promise.all( + totalPairs[i].map(async function (reviewer) { + await ApplicationDashboardModel.create({ + applicationId: application.id, + reviewerId: reviewer.id, + reviewerEmail: reviewer.email, + passionFSG: 0, + teamPlayer: 0, + desireToLearn: 0, + skill: 0, + skillCategory: 'junior', + reviewerComments: '', + recommendedSecondChoice: 'N/A' + }); + }) + ); + }) + ); } async function loadReviewers(): Promise { From 6d9a5784ca12192e0a2a46aae9245c387772f717 Mon Sep 17 00:00:00 2001 From: mat-ng Date: Wed, 27 Mar 2024 20:42:17 -0400 Subject: [PATCH 7/7] update delegation algorithm to be run for each role --- .../delegation-algorithm/src/index.ts | 43 ++++++++++++------- .../src/roundRobinPairs.ts | 30 ++++++------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/backend/typescript/delegation-algorithm/src/index.ts b/backend/typescript/delegation-algorithm/src/index.ts index 1702b46..29e3f24 100644 --- a/backend/typescript/delegation-algorithm/src/index.ts +++ b/backend/typescript/delegation-algorithm/src/index.ts @@ -1,17 +1,26 @@ -import { sequelize } from '../../models'; +import Sequelize from "sequelize" -import Application from '../../models/application.model'; -import ApplicationDashboardModel from '../../models/applicationDashboard.model'; -import User from '../../models/user.model'; +import Application from "../../models/application.model"; +import ApplicationDashboardModel from "../../models/applicationDashboard.model"; +import User from "../../models/user.model"; +import { sequelize } from "../../models"; -import { roundRobinPairs, assignApplicationsToPairs } from './roundRobinPairs'; +import { roundRobinPairs, assignApplicationsToPairs } from "./roundRobinPairs"; -async function delegationAlgorithm() { +const roles = ["Developer", "Designer"]; + +async function runDelegationAlgorithms() { + await Promise.all(roles.map(async function (role) { + await delegationAlgorithm(role); + })) +} + +async function delegationAlgorithm(role: string) { sequelize.authenticate(); - const applications = await loadApplications(); - const reviewers = await loadReviewers(); + const applications = await loadApplications(role); + const reviewers = await loadReviewers(role); const uniquePairs = roundRobinPairs(reviewers); const totalPairs = assignApplicationsToPairs(uniquePairs, applications); @@ -28,9 +37,9 @@ async function delegationAlgorithm() { teamPlayer: 0, desireToLearn: 0, skill: 0, - skillCategory: 'junior', - reviewerComments: '', - recommendedSecondChoice: 'N/A' + skillCategory: "junior", + reviewerComments: "", + recommendedSecondChoice: "N/A" }); }) ); @@ -38,16 +47,18 @@ async function delegationAlgorithm() { ); } -async function loadReviewers(): Promise { +async function loadReviewers(role: string): Promise { return await User.findAll({ - attributes: ['id', 'email'], + attributes: ["id", "email"], + where: { role: role } }); } -async function loadApplications(): Promise { +async function loadApplications(role: string): Promise { return await Application.findAll({ - attributes: ['id'] + attributes: ["id"], + where: { firstChoiceRole: { [Sequelize.Op.like]: `%${role}%` } } }); } -delegationAlgorithm(); +runDelegationAlgorithms() diff --git a/backend/typescript/delegation-algorithm/src/roundRobinPairs.ts b/backend/typescript/delegation-algorithm/src/roundRobinPairs.ts index 2e53fab..60f8fbc 100644 --- a/backend/typescript/delegation-algorithm/src/roundRobinPairs.ts +++ b/backend/typescript/delegation-algorithm/src/roundRobinPairs.ts @@ -1,30 +1,30 @@ -import { isEqual } from 'lodash'; +import { isEqual } from "lodash"; -import Application from '../../models/application.model'; -import User from '../../models/user.model'; +import Application from "../../models/application.model"; +import User from "../../models/user.model"; // Generate a list of all unique pairs of users — from a given list of users // Uses Round Robin algorithm for optimized time complexity -export function roundRobinPairs(developers: (User | {})[]): [User, User][] { - if (developers.length % 2 !== 0) { - developers.push({}); +export function roundRobinPairs(reviewers: (User | {})[]): [User, User][] { + if (reviewers.length % 2 !== 0) { + reviewers.push({}); } - const fixedDeveloper = developers[0]; - let rotatingDevelopers = developers.slice(1); + const fixedReviewer = reviewers[0]; + let rotatingReviewers = reviewers.slice(1); const pairs: [(User | {}), (User | {})][] = []; - for (let i = 0; i < developers.length - 1; i++) { - for (let j = 0; j < developers.length / 2 - 1; j++) { - if (!isEqual(rotatingDevelopers[j], {}) && !isEqual(rotatingDevelopers[developers.length - j - 2], {})) { - pairs.push([rotatingDevelopers[j], rotatingDevelopers[developers.length - j - 2]]); + for (let i = 0; i < reviewers.length - 1; i++) { + for (let j = 0; j < reviewers.length / 2 - 1; j++) { + if (!isEqual(rotatingReviewers[j], {}) && !isEqual(rotatingReviewers[reviewers.length - j - 2], {})) { + pairs.push([rotatingReviewers[j], rotatingReviewers[reviewers.length - j - 2]]); } } - if (!isEqual(fixedDeveloper, {}) && !isEqual(rotatingDevelopers[Math.floor(developers.length / 2) - 1], {})) { - pairs.push([fixedDeveloper, rotatingDevelopers[Math.floor(developers.length / 2) - 1]]); + if (!isEqual(fixedReviewer, {}) && !isEqual(rotatingReviewers[Math.floor(reviewers.length / 2) - 1], {})) { + pairs.push([fixedReviewer, rotatingReviewers[Math.floor(reviewers.length / 2) - 1]]); } - rotatingDevelopers = rotatingDevelopers.slice(1).concat(rotatingDevelopers.slice(0, 1)); //rotate list + rotatingReviewers = rotatingReviewers.slice(1).concat(rotatingReviewers.slice(0, 1)); //rotate list } shuffleArray(pairs);