Skip to content

Commit 529cfe2

Browse files
authored
Merge pull request #1691 from Northeastern-Electric-Racing/#1592-CR-Slack-Bug
#1592 Fix Cr Bug
2 parents 0cdc977 + 3d2bdf4 commit 529cfe2

10 files changed

Lines changed: 126 additions & 85 deletions

File tree

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,19 @@ export default class ChangeRequestsController {
7575

7676
static async createStandardChangeRequest(req: Request, res: Response, next: NextFunction) {
7777
try {
78-
const { wbsNum, type, what, why } = req.body;
78+
const { wbsNum, type, what, why, proposedSolutions } = req.body;
7979
const submitter = await getCurrentUser(res);
80-
const id = await ChangeRequestsService.createStandardChangeRequest(
80+
const createdCR = await ChangeRequestsService.createStandardChangeRequest(
8181
submitter,
8282
wbsNum.carNumber,
8383
wbsNum.projectNumber,
8484
wbsNum.workPackageNumber,
8585
type,
8686
what,
87-
why
87+
why,
88+
proposedSolutions
8889
);
89-
return res.status(200).json({ message: `${id}` });
90+
return res.status(200).json(createdCR);
9091
} catch (error: unknown) {
9192
next(error);
9293
}

src/backend/src/integrations/slack.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { WebClient } from '@slack/web-api';
2+
import { HttpException } from '../utils/errors.utils';
23

34
const slack = new WebClient(process.env.SLACK_BOT_TOKEN);
45

@@ -40,13 +41,17 @@ export const sendMessage = async (slackId: string, message: string, link?: strin
4041
}
4142
};
4243

43-
await slack.chat.postMessage({
44-
token: SLACK_BOT_TOKEN,
45-
channel: slackId,
46-
text: message,
47-
blocks: [block],
48-
unfurl_links: false
49-
});
44+
try {
45+
await slack.chat.postMessage({
46+
token: SLACK_BOT_TOKEN,
47+
channel: slackId,
48+
text: message,
49+
blocks: [block],
50+
unfurl_links: false
51+
});
52+
} catch (error) {
53+
throw new HttpException(500, 'Error sending slack message, reason: ' + (error as any).data.error);
54+
}
5055
};
5156

5257
export default slack;

src/backend/src/prisma/seed.ts

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import ChangeRequestsService from '../services/change-requests.services';
2222
import projectQueryArgs from '../prisma-query-args/projects.query-args';
2323
import TeamsService from '../services/teams.services';
2424
import WorkPackagesService from '../services/work-packages.services';
25-
import { ClubAccount, validateWBS, WbsElementStatus, WorkPackageStage } from 'shared';
25+
import { ChangeRequest, ClubAccount, StandardChangeRequest, validateWBS, WbsElementStatus, WorkPackageStage } from 'shared';
2626
import TasksService from '../services/tasks.services';
2727
import DescriptionBulletsService from '../services/description-bullets.services';
2828
import { seedProject } from './seed-data/projects.seed';
@@ -129,7 +129,7 @@ const performSeed: () => Promise<void> = async () => {
129129
/**
130130
* Make an initial change request for car 1 using the wbs of the genesis project
131131
*/
132-
const changeRequest1Id: number = await ChangeRequestsService.createStandardChangeRequest(
132+
const changeRequest1: StandardChangeRequest = await ChangeRequestsService.createStandardChangeRequest(
133133
cyborg,
134134
genesisProject.wbsElement.carNumber,
135135
genesisProject.wbsElement.projectNumber,
@@ -141,21 +141,25 @@ const performSeed: () => Promise<void> = async () => {
141141
type: Scope_CR_Why_Type.INITIALIZATION,
142142
explain: 'need this to initialize all the seed data'
143143
}
144+
],
145+
[
146+
{
147+
description: 'Initialize seed data',
148+
scopeImpact: 'no scope impact',
149+
timelineImpact: 0,
150+
budgetImpact: 0
151+
}
144152
]
145153
);
146154

147-
// make a proposed solution for it
148-
const proposedSolution1Id: string = await ChangeRequestsService.addProposedSolution(
149-
cyborg,
150-
changeRequest1Id,
151-
0,
152-
'Initializing seed data',
153-
0,
154-
'no scope impact'
155-
);
156-
157155
// approve the change request
158-
await ChangeRequestsService.reviewChangeRequest(batman, changeRequest1Id, 'LGTM', true, proposedSolution1Id);
156+
await ChangeRequestsService.reviewChangeRequest(
157+
batman,
158+
changeRequest1.crId,
159+
'LGTM',
160+
true,
161+
changeRequest1.proposedSolutions[0].id
162+
);
159163

160164
/**
161165
* TEAMS
@@ -262,7 +266,7 @@ const performSeed: () => Promise<void> = async () => {
262266
/** Project 1 */
263267
const { projectWbsNumber: project1WbsNumber, projectId: project1Id } = await seedProject(
264268
thomasEmrax,
265-
changeRequest1Id,
269+
changeRequest1.crId,
266270
1,
267271
'Impact Attenuator',
268272
'Develop rules-compliant impact attenuator',
@@ -292,7 +296,7 @@ const performSeed: () => Promise<void> = async () => {
292296
/** Project 2 */
293297
const { projectWbsNumber: project2WbsNumber, projectId: project2Id } = await seedProject(
294298
thomasEmrax,
295-
changeRequest1Id,
299+
changeRequest1.crId,
296300
1,
297301
'Bodywork',
298302
'Develop rules-compliant bodywork',
@@ -322,7 +326,7 @@ const performSeed: () => Promise<void> = async () => {
322326
/** Project 3 */
323327
const { projectWbsNumber: project3WbsNumber, projectId: project3Id } = await seedProject(
324328
thomasEmrax,
325-
changeRequest1Id,
329+
changeRequest1.crId,
326330
1,
327331
'Battery Box',
328332
'Develop rules-compliant battery box.',
@@ -352,7 +356,7 @@ const performSeed: () => Promise<void> = async () => {
352356
/** Project 4 */
353357
const { projectWbsNumber: project4WbsNumber, projectId: project4Id } = await seedProject(
354358
thomasEmrax,
355-
changeRequest1Id,
359+
changeRequest1.crId,
356360
1,
357361
'Motor Controller Integration',
358362
'Develop rules-compliant motor controller integration.',
@@ -382,7 +386,7 @@ const performSeed: () => Promise<void> = async () => {
382386
/** Project 5 */
383387
const { projectWbsNumber: project5WbsNumber, projectId: project5Id } = await seedProject(
384388
thomasEmrax,
385-
changeRequest1Id,
389+
changeRequest1.crId,
386390
1,
387391
'Wiring Harness',
388392
'Develop rules-compliant wiring harness.',
@@ -417,7 +421,7 @@ const performSeed: () => Promise<void> = async () => {
417421
joeShmoe,
418422
project1WbsNumber,
419423
'Bodywork Concept of Design',
420-
changeRequest1Id,
424+
changeRequest1.crId,
421425
WorkPackageStage.Design,
422426
'01/01/2023',
423427
3,
@@ -458,7 +462,7 @@ const performSeed: () => Promise<void> = async () => {
458462
thomasEmrax,
459463
project1WbsNumber,
460464
'Adhesive Shear Strength Test',
461-
changeRequest1Id,
465+
changeRequest1.crId,
462466
WorkPackageStage.Research,
463467
'01/22/2023',
464468
5,
@@ -482,7 +486,7 @@ const performSeed: () => Promise<void> = async () => {
482486
thomasEmrax,
483487
project5WbsNumber,
484488
'Manufacture Wiring Harness',
485-
changeRequest1Id,
489+
changeRequest1.crId,
486490
WorkPackageStage.Manufacturing,
487491
'02/01/2023',
488492
3,
@@ -502,7 +506,7 @@ const performSeed: () => Promise<void> = async () => {
502506
thomasEmrax,
503507
project5WbsNumber,
504508
'Install Wiring Harness',
505-
changeRequest1Id,
509+
changeRequest1.crId,
506510
WorkPackageStage.Install,
507511
'04/01/2023',
508512
7,
@@ -527,7 +531,7 @@ const performSeed: () => Promise<void> = async () => {
527531
true
528532
);
529533

530-
const changeRequest2Id = await ChangeRequestsService.createStandardChangeRequest(
534+
const changeRequest2 = await ChangeRequestsService.createStandardChangeRequest(
531535
thomasEmrax,
532536
project2WbsNumber.carNumber,
533537
project2WbsNumber.projectNumber,
@@ -537,18 +541,23 @@ const performSeed: () => Promise<void> = async () => {
537541
[
538542
{ type: Scope_CR_Why_Type.DESIGN, explain: 'It would be really pretty' },
539543
{ type: Scope_CR_Why_Type.ESTIMATION, explain: 'I estimate that it would be really pretty' }
544+
],
545+
[
546+
{
547+
description: 'Buy hot pink paint',
548+
scopeImpact: 'n/a',
549+
timelineImpact: 1,
550+
budgetImpact: 50
551+
},
552+
{
553+
description: 'Buy slightly cheaper but lower quality hot pink paint',
554+
scopeImpact: 'n/a',
555+
timelineImpact: 1,
556+
budgetImpact: 40
557+
}
540558
]
541559
);
542-
await ChangeRequestsService.addProposedSolution(thomasEmrax, changeRequest2Id, 50, 'Buy hot pink paint', 1, 'n/a');
543-
await ChangeRequestsService.addProposedSolution(
544-
thomasEmrax,
545-
changeRequest2Id,
546-
40,
547-
'Buy slightly cheaper but lower quality hot pink paint',
548-
1,
549-
'n/a'
550-
);
551-
await ChangeRequestsService.reviewChangeRequest(joeShmoe, changeRequest2Id, 'What the hell Thomas', false, null);
560+
await ChangeRequestsService.reviewChangeRequest(joeShmoe, changeRequest2.crId, 'What the hell Thomas', false, null);
552561

553562
await ChangeRequestsService.createActivationChangeRequest(
554563
thomasEmrax,

src/backend/src/routes/change-requests.routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ changeRequestsRouter.post(
6161
body('why').isArray(),
6262
nonEmptyString(body('why.*.explain')),
6363
body('why.*.type').custom((value) => Object.values(ChangeRequestReason).includes(value)),
64+
body('proposedSolutions').isArray({ min: 1 }),
65+
nonEmptyString(body('proposedSolutions.*.description')),
66+
nonEmptyString(body('proposedSolutions.*.scopeImpact')),
67+
body('proposedSolutions.*.timelineImpact').isInt(),
68+
body('proposedSolutions.*.budgetImpact').isInt(),
6469
validateInputs,
6570
ChangeRequestsController.createStandardChangeRequest
6671
);

src/backend/src/routes/projects.routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ projectRouter.post(
1818
validateInputs,
1919
ProjectsController.createProject
2020
);
21+
2122
projectRouter.post(
2223
'/edit',
2324
intMinZero(body('projectId')),

src/backend/src/services/change-requests.services.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import { ChangeRequest, isAdmin, isGuest, isLeadership, isNotLeadership, wbsPipe } from 'shared';
1+
import {
2+
ChangeRequest,
3+
isAdmin,
4+
isGuest,
5+
isLeadership,
6+
isNotLeadership,
7+
ProposedSolution,
8+
ProposedSolutionCreateArgs,
9+
StandardChangeRequest,
10+
wbsPipe
11+
} from 'shared';
212
import prisma from '../prisma/prisma';
313
import changeRequestQueryArgs from '../prisma-query-args/change-requests.query-args';
414
import {
@@ -527,11 +537,15 @@ export default class ChangeRequestsService {
527537
workPackageNumber: number,
528538
type: CR_Type,
529539
what: string,
530-
why: { type: Scope_CR_Why_Type; explain: string }[]
531-
): Promise<number> {
540+
why: { type: Scope_CR_Why_Type; explain: string }[],
541+
proposedSolutions: ProposedSolutionCreateArgs[]
542+
): Promise<StandardChangeRequest> {
532543
// verify user is allowed to create standard change requests
533544
if (isGuest(submitter.role)) throw new AccessDeniedGuestException('create standard change requests');
534545

546+
//verify proposed solutions length is greater than 0
547+
if (proposedSolutions.length === 0) throw new HttpException(400, 'No proposed solutions provided');
548+
535549
// verify wbs element exists
536550
const wbsElement = await prisma.wBS_Element.findUnique({
537551
where: {
@@ -576,18 +590,38 @@ export default class ChangeRequestsService {
576590
}
577591
});
578592

593+
const proposedSolutionPromises = proposedSolutions.map(async (proposedSolution) => {
594+
return await this.addProposedSolution(
595+
submitter,
596+
createdCR.crId,
597+
proposedSolution.budgetImpact,
598+
proposedSolution.description,
599+
proposedSolution.timelineImpact,
600+
proposedSolution.scopeImpact
601+
);
602+
});
603+
604+
await Promise.all(proposedSolutionPromises);
605+
579606
const project = createdCR.wbsElement.workPackage?.project || createdCR.wbsElement.project;
580607
const teams = project?.teams;
581608
if (teams && teams.length > 0) {
582-
teams.forEach(async (team) => {
609+
const completion: Promise<void>[] = teams.map(async (team) => {
583610
const slackMsg =
584611
`${type} CR submitted by ${submitter.firstName} ${submitter.lastName} ` +
585612
`for the ${project.wbsElement.name} project`;
586613
await sendSlackChangeRequestNotification(team, slackMsg, createdCR.crId);
587614
});
615+
616+
await Promise.all(completion);
588617
}
589618

590-
return createdCR.crId;
619+
const finishedCR = await prisma.change_Request.findUnique({
620+
where: { crId: createdCR.crId },
621+
...changeRequestQueryArgs
622+
});
623+
624+
return changeRequestTransformer(finishedCR!) as StandardChangeRequest;
591625
}
592626

593627
/**
@@ -609,7 +643,7 @@ export default class ChangeRequestsService {
609643
description: string,
610644
timelineImpact: number,
611645
scopeImpact: string
612-
): Promise<string> {
646+
): Promise<ProposedSolution> {
613647
// verify user is allowed to add proposed solutions
614648
if (isGuest(submitter.role)) throw new AccessDeniedGuestException('add proposed solutions');
615649

@@ -635,10 +669,11 @@ export default class ChangeRequestsService {
635669
budgetImpact,
636670
changeRequest: { connect: { scopeCrId: foundScopeCR.scopeCrId } },
637671
createdBy: { connect: { userId: submitter.userId } }
638-
}
672+
},
673+
include: { createdBy: true }
639674
});
640675

641-
return createProposedSolution.proposedSolutionId;
676+
return { ...createProposedSolution, id: createProposedSolution.proposedSolutionId };
642677
}
643678

644679
/**

src/backend/tests/change-requests.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('Change Requests', () => {
4141
return undefined;
4242
});
4343
vi.spyOn(changeRequestUtils, 'sendSlackChangeRequestNotification').mockImplementation(async (_slackId, _crId) => {
44-
return undefined;
44+
return [];
4545
});
4646
vi.spyOn(changeRequestUtils, 'updateBlocking').mockImplementation(async () => {});
4747
vi.spyOn(prisma.user_Settings, 'findUnique').mockResolvedValueOnce(batmanSettings);
@@ -344,7 +344,7 @@ describe('Change Requests', () => {
344344
vi.spyOn(prisma.scope_CR, 'findUnique').mockResolvedValue(prismaScopeChangeRequest1);
345345
vi.spyOn(prisma.proposed_Solution, 'create').mockResolvedValue(prismaProposedSolution1);
346346
const response = await ChangeRequestsService.addProposedSolution(aquaman, crId, 1000, description, 10, 'huge');
347-
expect(response).toStrictEqual(prismaProposedSolution1.proposedSolutionId);
347+
expect(response).toStrictEqual({ ...prismaProposedSolution1, id: prismaProposedSolution1.proposedSolutionId });
348348
expect(prisma.change_Request.findUnique).toHaveBeenCalledTimes(1);
349349
expect(prisma.scope_CR.findUnique).toHaveBeenCalledTimes(1);
350350
expect(prisma.proposed_Solution.create).toHaveBeenCalledTimes(1);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { useMutation, useQuery, useQueryClient } from 'react-query';
7-
import { ChangeRequest, ChangeRequestReason, ChangeRequestType, WbsNumber } from 'shared';
7+
import { ChangeRequest, ChangeRequestReason, ChangeRequestType, ProposedSolutionCreateArgs, WbsNumber } from 'shared';
88
import {
99
createActivationChangeRequest,
1010
createStandardChangeRequest,
@@ -87,6 +87,7 @@ export type CreateStandardChangeRequestPayload = {
8787
wbsNum: WbsNumber;
8888
type: Exclude<ChangeRequestType, 'STAGE_GATE' | 'ACTIVATION'>;
8989
why: { explain: string; type: ChangeRequestReason }[];
90+
proposedSolutions: ProposedSolutionCreateArgs[];
9091
};
9192

9293
/**

0 commit comments

Comments
 (0)