Skip to content

Commit 64aab85

Browse files
authored
Merge branch 'develop' into #1293-PeterMoise-Refund-Sources
2 parents b2c7089 + 98746f9 commit 64aab85

27 files changed

Lines changed: 405 additions & 189 deletions

File tree

src/backend/src/controllers/reimbursement-requests.controllers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ export default class ReimbursementRequestsController {
182182

183183
const receipt = await ReimbursementRequestService.uploadReceipt(requestId, file, user);
184184

185-
res.header('Access-Control-Allow-Origin', 'true');
185+
const isProd = process.env.NODE_ENV === 'production';
186+
const origin = isProd ? 'https://finishlinebyner.com' : 'http://localhost:3000';
187+
188+
res.header('Access-Control-Allow-Origin', origin);
186189
res.status(200).json(receipt);
187190
} catch (error: unknown) {
188191
next(error);

src/backend/src/services/tasks.services.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ export default class TasksService {
9494
*/
9595
static async editTask(user: User, taskId: string, title: string, notes: string, priority: Task_Priority, deadline: Date) {
9696
const hasPermission = await hasPermissionToEditTask(user, taskId);
97-
if (!hasPermission) throw new AccessDeniedException();
97+
if (!hasPermission)
98+
throw new AccessDeniedException(
99+
'Only admins, app admins, heads, task creators, project leads, project managers, or project assignees can edit a task'
100+
);
98101

99102
const originalTask = await prisma.task.findUnique({ where: { taskId } });
100103
if (!originalTask) throw new NotFoundException('Task', taskId);
@@ -129,7 +132,7 @@ export default class TasksService {
129132
const hasPermission = await hasPermissionToEditTask(user, taskId);
130133
if (!hasPermission)
131134
throw new AccessDeniedException(
132-
'Only admins, app admins, task creators, project leads, project managers, or project assignees can edit a task'
135+
'Only admins, app admins, heads, task creators, project leads, project managers, or project assignees can edit a task'
133136
);
134137

135138
const updatedTask = await prisma.task.update({ where: { taskId }, data: { status }, ...taskQueryArgs });
@@ -158,7 +161,7 @@ export default class TasksService {
158161
const hasPermission = await hasPermissionToEditTask(user, taskId);
159162
if (!hasPermission)
160163
throw new AccessDeniedException(
161-
'Only admins, app admins, task creators, project leads, project managers, or project assignees can edit a task'
164+
'Only admins, app admins, heads, task creators, project leads, project managers, or project assignees can edit a task'
162165
);
163166

164167
// this throws if any of the users aren't found

src/backend/src/services/work-packages.services.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ export default class WorkPackagesService {
154154
throw new HttpException(400, 'A Work Package cannot have its own project as a blocker');
155155
}
156156

157+
blockedBy.forEach((dep: WbsNumber) => {
158+
if (dep.workPackageNumber === 0) {
159+
throw new HttpException(400, 'A Project cannot be a Blocker');
160+
}
161+
});
162+
157163
const wbsElem = await prisma.wBS_Element.findUnique({
158164
where: {
159165
wbsNumber: {
@@ -287,6 +293,12 @@ export default class WorkPackagesService {
287293
// verify user is allowed to edit work packages
288294
if (isGuest(user.role)) throw new AccessDeniedGuestException('edit work packages');
289295

296+
blockedBy.forEach((dep: WbsNumber) => {
297+
if (dep.workPackageNumber === 0) {
298+
throw new HttpException(400, 'A Project cannot be a Blocker');
299+
}
300+
});
301+
290302
const { userId } = user;
291303

292304
// get the original work package so we can compare things

src/backend/tests/tasks.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ describe('Tasks', () => {
201201
// Try updating from IN_PROGRESS to IN_BACKLOG
202202
await expect(() => TasksService.editTaskStatus(aquaman, taskId, Task_Status.IN_BACKLOG)).rejects.toThrow(
203203
new AccessDeniedException(
204-
'Only admins, app admins, task creators, project leads, project managers, or project assignees can edit a task'
204+
'Only admins, app admins, heads, task creators, project leads, project managers, or project assignees can edit a task'
205205
)
206206
);
207207
});
@@ -216,7 +216,7 @@ describe('Tasks', () => {
216216
// Aquaman is a leader, but did not create this task
217217
await expect(() => TasksService.editTaskStatus(aquaman, taskId, Task_Status.IN_BACKLOG)).rejects.toThrow(
218218
new AccessDeniedException(
219-
'Only admins, app admins, task creators, project leads, project managers, or project assignees can edit a task'
219+
'Only admins, app admins, heads, task creators, project leads, project managers, or project assignees can edit a task'
220220
)
221221
);
222222
});
@@ -288,7 +288,7 @@ describe('Tasks', () => {
288288
TasksService.editTaskAssignees(aquaman, taskId, [superman.userId, wonderwoman.userId])
289289
).rejects.toThrow(
290290
new AccessDeniedException(
291-
'Only admins, app admins, task creators, project leads, project managers, or project assignees can edit a task'
291+
'Only admins, app admins, heads, task creators, project leads, project managers, or project assignees can edit a task'
292292
)
293293
);
294294
});
@@ -391,7 +391,11 @@ describe('Tasks', () => {
391391
vi.spyOn(taskUtils, 'hasPermissionToEditTask').mockResolvedValue(false);
392392
await expect(() =>
393393
TasksService.editTask(wonderwoman, taskId, fakeTitle, fakeNotes, fakePriority, fakeDeadline)
394-
).rejects.toThrow(new AccessDeniedException());
394+
).rejects.toThrow(
395+
new AccessDeniedException(
396+
'Only admins, app admins, heads, task creators, project leads, project managers, or project assignees can edit a task'
397+
)
398+
);
395399
});
396400

397401
test('Task not found', async () => {

src/frontend/src/apis/change-requests.api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,12 @@ export const addProposedSolution = (
141141
budgetImpact
142142
});
143143
};
144+
145+
/**
146+
* Request reviewers in change request
147+
* @param crId The ID of the associated change request.
148+
* @param crReviewData The data to request reviewers
149+
*/
150+
export const requestCRReview = (crId: string, crReviewData: { userIds: number[] }) => {
151+
return axios.post<{ message: string }>(apiUrls.changeRequestRequestReviewer(crId), crReviewData);
152+
};

src/frontend/src/components/ActionsMenu.tsx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@ import { Box } from '@mui/system';
22
import { ReactElement, useState } from 'react';
33
import { NERButton } from './NERButton';
44
import { ArrowDropDown } from '@mui/icons-material';
5-
import { ListItemIcon, Menu, MenuItem } from '@mui/material';
5+
import { Divider, ListItemIcon, Menu, MenuItem } from '@mui/material';
66

77
export type ButtonInfo = {
88
title: string;
99
onClick: () => void;
1010
disabled?: boolean;
1111
icon?: ReactElement;
12+
dividerTop?: boolean;
1213
};
1314

1415
interface ActionsMenuProps {
1516
buttons: ButtonInfo[];
17+
title?: string;
1618
}
1719

18-
const ActionsMenu: React.FC<ActionsMenuProps> = ({ buttons }) => {
20+
const ActionsMenu: React.FC<ActionsMenuProps> = ({ buttons, title = 'Actions' }) => {
1921
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
2022

2123
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
@@ -36,22 +38,25 @@ const ActionsMenu: React.FC<ActionsMenuProps> = ({ buttons }) => {
3638
id="reimbursement-request-actions-dropdown"
3739
onClick={handleClick}
3840
>
39-
Actions
41+
{title}
4042
</NERButton>
4143
<Menu open={dropdownOpen} anchorEl={anchorEl} onClose={handleDropdownClose}>
42-
{buttons.map((button, index) => (
43-
<MenuItem
44-
key={index}
45-
onClick={() => {
46-
handleDropdownClose();
47-
button.onClick();
48-
}}
49-
disabled={button.disabled}
50-
>
51-
<ListItemIcon>{button.icon}</ListItemIcon>
52-
{button.title}
53-
</MenuItem>
54-
))}
44+
{buttons.flatMap((button, index) => {
45+
return [
46+
button.dividerTop && <Divider key={`${index}-divider`} />,
47+
<MenuItem
48+
key={index}
49+
onClick={() => {
50+
handleDropdownClose();
51+
button.onClick();
52+
}}
53+
disabled={button.disabled}
54+
>
55+
<ListItemIcon>{button.icon}</ListItemIcon>
56+
{button.title}
57+
</MenuItem>
58+
];
59+
})}
5560
</Menu>
5661
</Box>
5762
);

src/frontend/src/hooks/change-requests.hooks.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
getSingleChangeRequest,
1414
reviewChangeRequest,
1515
addProposedSolution,
16-
deleteChangeRequest
16+
deleteChangeRequest,
17+
requestCRReview
1718
} from '../apis/change-requests.api';
1819

1920
/**
@@ -153,3 +154,22 @@ export const useCreateProposeSolution = () => {
153154
}
154155
);
155156
};
157+
158+
/**
159+
* Custom React hook to request cr reviewers
160+
*/
161+
export const useRequestCRReview = (crId: string) => {
162+
const queryClient = useQueryClient();
163+
return useMutation<{ message: string }, Error, any>(
164+
['change requests', 'review'],
165+
async (crReviewPayload: { userIds: number[] }) => {
166+
const { data } = await requestCRReview(crId, crReviewPayload);
167+
return data;
168+
},
169+
{
170+
onSuccess: () => {
171+
queryClient.invalidateQueries(['change requests']);
172+
}
173+
}
174+
);
175+
};

src/frontend/src/hooks/work-packages.hooks.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,6 @@ export const useSingleWorkPackage = (wbsNum: WbsNumber) => {
3737
});
3838
};
3939

40-
/**
41-
* Custom React Hook to supply multiple work packages
42-
*
43-
* @param wbsNums WBS numbers of the requested work packages
44-
*/
45-
export const useManyWorkPackages = (wbsNums: WbsNumber[]) => {
46-
return useQuery<WorkPackage[], Error>(['work packages', wbsNums], async () => {
47-
const workPackagePromises = wbsNums.map(async (wbsNum) => {
48-
const { data } = await getSingleWorkPackage(wbsNum);
49-
return data;
50-
});
51-
const workPackages = await Promise.all(workPackagePromises);
52-
return workPackages;
53-
});
54-
};
55-
5640
/**
5741
* Custom React Hook to create a new work package.
5842
*

0 commit comments

Comments
 (0)