Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IDOL: Update coffee chat bingo board for SP25 #835

Merged
merged 7 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
140 changes: 13 additions & 127 deletions backend/src/API/coffeeChatAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { Request } from 'express';
import CoffeeChatDao from '../dao/CoffeeChatDao';
import PermissionsManager from '../utils/permissionsManager';
import { BadRequestError, PermissionError } from '../utils/errors';
import { getMember, allMembers } from './memberAPI';
import { LEAD_ROLES } from '../consts';
import { getGeneralRoleFromLeadType } from '../utils/memberUtil';
import { getMember } from './memberAPI';
import { ADVISOR_ROLES } from '../consts';
import { sendCoffeeChatReminder } from './mailAPI';

const coffeeChatDao = new CoffeeChatDao();
Expand Down Expand Up @@ -66,12 +65,15 @@ export const createCoffeeChat = async (
'approved',
coffeeChat.otherMember
);
const prevChats = [...pendingChats, ...approvedChats];
const prevChats = [
...pendingChats.filter((chat) => !chat.isArchived),
...approvedChats.filter((chat) => !chat.isArchived)
];
const chatExists = prevChats.length > 0;

if (chatExists) {
throw new Error(
'Cannot create coffee chat with member. Previous coffee chats from previous semesters exist.'
'Cannot create coffee chat with member. Coffee chats from current or previous semesters exist.'
);
}

Expand Down Expand Up @@ -203,138 +205,22 @@ export const checkMemberMeetsCategory = async (
submitterEmail: string,
category: string
): Promise<{ status: MemberMeetsCategoryStatus; message: string }> => {
const otherMemberProperties = await CoffeeChatDao.getMemberProperties(otherMemberEmail);
const submitterProperties = await CoffeeChatDao.getMemberProperties(submitterEmail);
const otherMember = await getMember(otherMemberEmail);
const submitter = await getMember(submitterEmail);
const haveNoCommonSubteams = (member1: IdolMember, member2: IdolMember): boolean =>
member2.subteams.every((team) => !member1.subteams.includes(team)) &&
member1.subteams.every((team) => !member2.subteams.includes(team));
let status: MemberMeetsCategoryStatus = 'no data';
let message: string = '';

// If otherMember doesn't exist in the DB, assume they are an alumni
if (!otherMember && category === 'an alumni') {
return { status: 'pass', message };
}

// If otherMember and submitter don't exist, status should stay undefined
if (otherMember && submitter) {
if (category === 'an alumni') {
status = (await allMembers()).every((member) => member.email !== otherMember.email)
? 'pass'
: 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not an alumni`;
}
} else if (category === 'courseplan member') {
status = otherMember.subteams.includes('courseplan') ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember?.lastName} is not a CoursePlan member`;
}
} else if (category === 'business member') {
status = otherMember.role === 'business' ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not a business member`;
}
} else if (category === 'idol member') {
status = otherMember.subteams.includes('idol') ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not an IDOL member`;
}
} else if (category === 'curaise member') {
status = otherMember.subteams.includes('curaise') ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not a CURaise member`;
}
} else if (category === 'cornellgo member') {
status = otherMember.subteams.includes('cornellgo') ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not a CornellGo member`;
}
} else if (category === 'carriage member') {
status = otherMember.subteams.includes('carriage') ? 'pass' : 'fail';
if (category === 'a newbie') {
status = otherMember.semesterJoined === 'Spring 2025' ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not a Carriage member`;
message = `${otherMember.firstName} ${otherMember.lastName} is not a newbie`;
}
} else if (category === 'qmi member') {
status = otherMember.subteams.includes('queuemein') ? 'pass' : 'fail';
} else if (category === 'is an advisor') {
status = ADVISOR_ROLES.includes(otherMember.role) ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not a QMI member`;
}
} else if (category === 'cuapts member') {
status = otherMember.subteams.includes('cuapts') ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not a CUApts member`;
}
} else if (category === 'a pm (not your team)') {
const isPm = otherMember.role === 'pm';
const notSameTeam = haveNoCommonSubteams(submitter, otherMember);
status = isPm && notSameTeam ? 'pass' : 'fail';
if (status === 'fail') {
if (!isPm) {
message = `${otherMember.firstName} ${otherMember.lastName} is not a PM`;
} else {
message = `${otherMember.firstName} ${otherMember.lastName} is a PM, but is on the same team as ${submitter?.firstName} ${submitter?.lastName}`;
}
}
} else if (category === 'a tpm (not your team)') {
const isTpm = otherMember.role === 'tpm';
const notSameTeam = haveNoCommonSubteams(submitter, otherMember);
status = isTpm && notSameTeam ? 'pass' : 'fail';
if (status === 'fail') {
if (!isTpm) {
message = `${otherMember.firstName} ${otherMember.lastName} is not a TPM`;
} else {
message = `${otherMember.firstName} ${otherMember.lastName} is a TPM, but is on the same team as ${submitter?.firstName} ${submitter?.lastName}`;
}
}
}

// If otherMemberProperties doesn't exist, status should stay undefined
if (otherMemberProperties) {
if (category === 'a newbie') {
status = otherMemberProperties.newbie ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is not a newbie`;
}
} else if (category === 'is/was a TA') {
status = otherMemberProperties.ta ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} was never a TA`;
}
} else if (category === 'major/minor that is not cs/infosci') {
status = otherMemberProperties.notCsOrInfosci ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is a CS or Infosci major`;
}
}

// If submitterProperties doesn't exist, status should stay undefined
if (submitterProperties) {
if (category === 'from a different college') {
status = otherMemberProperties.college !== submitterProperties.college ? 'pass' : 'fail';
if (status === 'fail') {
message = `${otherMember.firstName} ${otherMember.lastName} is from the same college as ${submitter.firstName} ${submitter.lastName} (${otherMemberProperties.college})`;
}
}
}
}

if (category === 'a lead (not your role)') {
const isLead = LEAD_ROLES.includes(otherMember.role);
if (!isLead) {
status = 'fail';
message = `${otherMember.firstName} ${otherMember.lastName} is not a lead`;
} else {
const diffRole = !LEAD_ROLES.includes(submitter.role)
? getGeneralRoleFromLeadType(otherMember.role) !== submitter.role
: getGeneralRoleFromLeadType(otherMember.role) !==
getGeneralRoleFromLeadType(submitter.role);
status = diffRole ? 'pass' : 'fail';
if (!diffRole) {
message = `${otherMember.firstName} ${otherMember.lastName} is a lead, but from the same role (${submitter.role}) as ${submitter.firstName} ${submitter.lastName}`;
}
message = `${otherMember.firstName} ${otherMember.lastName} is not an advisor`;
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions backend/src/consts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const COFFEE_CHAT_BINGO_BOARD = [
['an alumni', 'courseplan member', 'a pm (not your team)', 'business member'],
['is/was a TA', 'major/minor that is not cs/infosci', 'idol member', 'a newbie'],
['from a different college', 'curaise member', 'cornellgo member', 'a tpm (not your team)'],
['carriage member', 'qmi member', 'a lead (not your role)', 'cuapts member']
['has studied abroad', 'a newbie', 'plays DTI on roblox', 'sleeps before 12am'],
['double major', 'uses windows', 'is an advisor', 'loves to cook'],
['attended a hackathon', 'collects blindboxes', 'lives in ctown', 'plays a sport'],
['lives on west', 'is left handed', 'has a digital camera', 'loves pineapple on pizza']
];

export default COFFEE_CHAT_BINGO_BOARD;
Expand Down
1 change: 1 addition & 0 deletions backend/src/types/DataTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export type DBCoffeeChat = {
category: string;
status: Status;
date: number;
isArchived: boolean;
memberMeetsCategory: MemberMeetsCategoryStatus;
reason?: string;
errorMessage?: string;
Expand Down
103 changes: 17 additions & 86 deletions backend/tests/CoffeeChatAPI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from '../src/API/coffeeChatAPI';
import { setMember, deleteMember } from '../src/API/memberAPI';
import { PermissionError } from '../src/utils/errors';
import { getGeneralRoleFromLeadType } from '../src/utils/memberUtil';

const user = fakeIdolMember();
const user2 = fakeIdolMember();
Expand Down Expand Up @@ -50,7 +49,7 @@ describe('User is not lead or admin', () => {
test('createCoffeeChat should throw error if previous chats exist', async () => {
await expect(createCoffeeChat(coffeeChat, user)).rejects.toThrow(
new Error(
'Cannot create coffee chat with member. Previous coffee chats from previous semesters exist.'
'Cannot create coffee chat with member. Coffee chats from current or previous semesters exist.'
)
);
});
Expand Down Expand Up @@ -191,110 +190,42 @@ describe('User is lead or admin', () => {

describe('More complicated member meets category checks', () => {
const admin = { ...fakeIdolLead() };
const user1 = { ...fakeIdolMember(), subteams: ['team1'], role: 'developer' as Role };
const user2 = { ...fakeIdolMember(), role: 'pm' as Role, subteams: ['team2'] };
const user3 = { ...fakeIdolMember(), role: 'pm' as Role, subteams: ['team1'] };
const user4 = { ...fakeIdolMember(), role: 'business' as Role };
const user5 = { ...fakeIdolMember(), role: 'tpm' as Role, subteams: ['team3'] };
const user6 = { ...fakeIdolMember(), role: 'tpm' as Role, subteams: ['team1'] };
const user7 = { ...fakeIdolMember(), role: 'product-lead' as Role };
const user8 = { ...fakeIdolMember(), role: 'dev-lead' as Role };
const user9 = { ...fakeIdolMember(), role: 'ops-lead' as Role };
const user10 = { ...fakeIdolMember(), role: 'ops-lead' as Role };
const user1 = fakeIdolMember();
const user2 = { ...fakeIdolMember(), role: 'dev-advisor' as Role };
const user3 = { ...fakeIdolMember(), role: 'tpm' as Role };
const user4 = { ...fakeIdolMember(), semesterJoined: 'Spring 2025' };

beforeAll(async () => {
const users = [user1, user2, user3, user4, user5, user6, user7, user8, user9, user10];
const users = [user1, user2, user3, user4];
await Promise.all(users.map((user) => setMember(user, admin)));
});

afterAll(async () => {
const users = [user1, user2, user3, user4, user5, user6, user7, user8, user9, user10];
const users = [user1, user2, user3, user4];
await Promise.all(users.map((user) => deleteMember(user.email, admin)));
await CoffeeChatDao.deleteMemberProperties(user7.email);
await CoffeeChatDao.deleteMemberProperties(user8.email);
});

test('pm that is not on same team', async () => {
const result = await checkMemberMeetsCategory(user2.email, user1.email, 'a pm (not your team)');
test('is an advisor', async () => {
const result = await checkMemberMeetsCategory(user2.email, user1.email, 'is an advisor');
expect(result.status).toBe('pass');
expect(result.message).toBe('');
});

test('pm that is on same team', async () => {
const result = await checkMemberMeetsCategory(user3.email, user1.email, 'a pm (not your team)');
test('is not an advisor', async () => {
const result = await checkMemberMeetsCategory(user3.email, user1.email, 'is an advisor');
expect(result.status).toBe('fail');
expect(result.message).toBe(
`${user3.firstName} ${user3.lastName} is a PM, but is on the same team as ${user1.firstName} ${user1.lastName}`
);
});

test('not a pm', async () => {
const result = await checkMemberMeetsCategory(user4.email, user1.email, 'a pm (not your team)');
expect(result.status).toBe('fail');
expect(result.message).toBe(`${user4.firstName} ${user4.lastName} is not a PM`);
expect(result.message).toBe(`${user3.firstName} ${user3.lastName} is not an advisor`);
});

test('tpm that is not on same team', async () => {
const result = await checkMemberMeetsCategory(
user5.email,
user1.email,
'a tpm (not your team)'
);
test('is newbie', async () => {
const result = await checkMemberMeetsCategory(user4.email, user1.email, 'a newbie');
expect(result.status).toBe('pass');
expect(result.message).toBe('');
});

test('tpm that is on same team', async () => {
const result = await checkMemberMeetsCategory(
user6.email,
user1.email,
'a tpm (not your team)'
);
expect(result.status).toBe('fail');
expect(result.message).toBe(
`${user6.firstName} ${user6.lastName} is a TPM, but is on the same team as ${user1.firstName} ${user1.lastName}`
);
});

test('not a tpm', async () => {
const result = await checkMemberMeetsCategory(
user4.email,
user1.email,
'a tpm (not your team)'
);
expect(result.status).toBe('fail');
expect(result.message).toBe(`${user4.firstName} ${user4.lastName} is not a TPM`);
});

test('a lead that is not same role', async () => {
const result = await checkMemberMeetsCategory(
user7.email,
user1.email,
'a lead (not your role)'
);
expect(result.status).toBe('pass');
expect(result.message).toBe('');
});

test('a lead that is the same role', async () => {
const result = await checkMemberMeetsCategory(
user8.email,
user1.email,
'a lead (not your role)'
);
expect(result.status).toBe('fail');
expect(result.message).toBe(
`${user8.firstName} ${user8.lastName} is a lead, but from the same role (${getGeneralRoleFromLeadType(user8.role)}) as ${user1.firstName} ${user1.lastName}`
);
});

test('not a lead', async () => {
const result = await checkMemberMeetsCategory(
user4.email,
user1.email,
'a lead (not your role)'
);
test('is newbie', async () => {
const result = await checkMemberMeetsCategory(user2.email, user1.email, 'a newbie');
expect(result.status).toBe('fail');
expect(result.message).toBe(`${user4.firstName} ${user4.lastName} is not a lead`);
expect(result.message).toBe(`${user2.firstName} ${user2.lastName} is not a newbie`);
});
});
1 change: 1 addition & 0 deletions backend/tests/data/createData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export const fakeCoffeeChat = (): CoffeeChat => {
category: 'test',
status: 'pending' as Status,
date: Date.now(),
isArchived: false,
memberMeetsCategory: 'no data' as MemberMeetsCategoryStatus
};
return CC;
Expand Down
1 change: 1 addition & 0 deletions common-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ interface CoffeeChat {
readonly category: string;
readonly status: Status;
readonly date: number;
readonly isArchived: boolean;
readonly memberMeetsCategory: MemberMeetsCategoryStatus;
readonly reason?: string;
readonly errorMessage?: string;
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/Admin/CoffeeChats/CoffeeChats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ const CoffeeChats: React.FC = () => {
setSelectedMember(member);

CoffeeChatAPI.getCoffeeChatsByUser(member).then((coffeeChats) => {
setSpecificApprovedChats(coffeeChats.filter((chat) => chat.status === 'approved'));
setSpecificPendingChats(coffeeChats.filter((chat) => chat.status === 'pending'));
setSpecificRejectedChats(coffeeChats.filter((chat) => chat.status === 'rejected'));
const filteredChats = coffeeChats.filter((chat) => !chat.isArchived);
setSpecificApprovedChats(filteredChats.filter((chat) => chat.status === 'approved'));
setSpecificPendingChats(filteredChats.filter((chat) => chat.status === 'pending'));
setSpecificRejectedChats(filteredChats.filter((chat) => chat.status === 'rejected'));
setIsChatLoading(false);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ const CoffeeChatsForm: React.FC = () => {
useEffect(() => {
MembersAPI.getAllMembers().then((members) => setMembersList(members));
CoffeeChatAPI.getCoffeeChatsByUser(userInfo).then((coffeeChats) => {
setApprovedChats(coffeeChats.filter((chat) => chat.status === 'approved'));
setPendingChats(coffeeChats.filter((chat) => chat.status === 'pending'));
setRejectedChats(coffeeChats.filter((chat) => chat.status === 'rejected'));
const filteredChats = coffeeChats.filter((chat) => !chat.isArchived);
setApprovedChats(filteredChats.filter((chat) => chat.status === 'approved'));
setPendingChats(filteredChats.filter((chat) => chat.status === 'pending'));
setRejectedChats(filteredChats.filter((chat) => chat.status === 'rejected'));
setIsChatLoading(false);
});
CoffeeChatAPI.getCoffeeChatBingoBoard().then((board) => setBingoBoard(board));
Expand Down Expand Up @@ -118,6 +119,7 @@ const CoffeeChatsForm: React.FC = () => {
category,
status: 'pending' as Status,
date: new Date().getTime(),
isArchived: false,
memberMeetsCategory,
errorMessage
};
Expand Down
Loading