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

Optimize Change Requests Queries #3156

Merged
merged 4 commits into from
Jan 22, 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
27 changes: 27 additions & 0 deletions src/backend/src/controllers/change-requests.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,33 @@ export default class ChangeRequestsController {
}
}

static async getToReviewChangeRequests(req: Request, res: Response, next: NextFunction) {
try {
const changeRequests = await ChangeRequestsService.getToReviewChangeRequests(req.currentUser, req.organization);
res.status(200).json(changeRequests);
} catch (error: unknown) {
next(error);
}
}

static async getUnreviewedChangeRequests(req: Request, res: Response, next: NextFunction) {
try {
const changeRequests = await ChangeRequestsService.getUnreviewedChangeRequests(req.currentUser, req.organization);
res.status(200).json(changeRequests);
} catch (error: unknown) {
next(error);
}
}

static async getApprovedChangeRequests(req: Request, res: Response, next: NextFunction) {
try {
const changeRequests = await ChangeRequestsService.getApprovedChangeRequests(req.currentUser, req.organization);
res.status(200).json(changeRequests);
} catch (error: unknown) {
next(error);
}
}

static async reviewChangeRequest(req: Request, res: Response, next: NextFunction) {
try {
const { crId, reviewNotes, accepted, psId } = req.body;
Expand Down
49 changes: 49 additions & 0 deletions src/backend/src/prisma-query-args/change-requests.query-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,56 @@ import { getWorkPackageQueryArgs } from './work-packages.query-args';

export type ChangeRequestQueryArgs = ReturnType<typeof getChangeRequestQueryArgs>;

export type ChangeRequestWithProjectAndWorkPackageQueryArgs = ReturnType<
typeof getChangeRequestWithProjectAndWorkPackageQueryArgs
>;

export type ChangeRequestManyQueryArgs = ReturnType<typeof getManyChangeRequestQueryArgs>;

export const getChangeRequestQueryArgs = (organizationId: string) =>
Prisma.validator<Prisma.Change_RequestDefaultArgs>()({
include: {
submitter: getUserQueryArgs(organizationId),
wbsElement: true,
reviewer: getUserQueryArgs(organizationId),
changes: {
where: {
wbsElement: {
dateDeleted: null
}
},
include: {
implementer: getUserQueryArgs(organizationId),
wbsElement: true
}
},
scopeChangeRequest: getScopeChangeRequestQueryArgs(organizationId),
stageGateChangeRequest: true,
activationChangeRequest: {
include: { lead: getUserQueryArgs(organizationId), manager: getUserQueryArgs(organizationId) }
},
deletedBy: getUserQueryArgs(organizationId),
requestedReviewers: getUserQueryArgs(organizationId)
}
});

export const getManyChangeRequestQueryArgs = (organizationId: string) =>
Prisma.validator<Prisma.Change_RequestDefaultArgs>()({
include: {
submitter: getUserQueryArgs(organizationId),
wbsElement: true,
reviewer: getUserQueryArgs(organizationId),
stageGateChangeRequest: true,
changes: true,
activationChangeRequest: {
include: { lead: getUserQueryArgs(organizationId), manager: getUserQueryArgs(organizationId) }
},
deletedBy: getUserQueryArgs(organizationId),
requestedReviewers: getUserQueryArgs(organizationId)
}
});

export const getChangeRequestWithProjectAndWorkPackageQueryArgs = (organizationId: string) =>
Prisma.validator<Prisma.Change_RequestDefaultArgs>()({
include: {
submitter: getUserQueryArgs(organizationId),
Expand Down
4 changes: 4 additions & 0 deletions src/backend/src/routes/change-requests.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const changeRequestsRouter = express.Router();

changeRequestsRouter.get('/', ChangeRequestsController.getAllChangeRequests);

changeRequestsRouter.get('/to-review', ChangeRequestsController.getToReviewChangeRequests);
changeRequestsRouter.get('/unreviewed', ChangeRequestsController.getUnreviewedChangeRequests);
changeRequestsRouter.get('/approved', ChangeRequestsController.getApprovedChangeRequests);

changeRequestsRouter.get('/:crId', ChangeRequestsController.getChangeRequestByID);

changeRequestsRouter.post(
Expand Down
123 changes: 114 additions & 9 deletions src/backend/src/services/change-requests.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
DeletedException,
InvalidOrganizationException
} from '../utils/errors.utils';
import changeRequestTransformer from '../transformers/change-requests.transformer';
import changeRequestTransformer, { changeRequestManyTransformer } from '../transformers/change-requests.transformer';
import {
allChangeRequestsReviewed,
validateProposedChangesFields,
Expand All @@ -43,7 +43,12 @@ import {
sendSlackCRStatusToThread,
sendSlackRequestedReviewNotification
} from '../utils/slack.utils';
import { ChangeRequestQueryArgs, getChangeRequestQueryArgs } from '../prisma-query-args/change-requests.query-args';
import {
ChangeRequestWithProjectAndWorkPackageQueryArgs,
getChangeRequestQueryArgs,
getChangeRequestWithProjectAndWorkPackageQueryArgs,
getManyChangeRequestQueryArgs
} from '../prisma-query-args/change-requests.query-args';
import proposedSolutionTransformer from '../transformers/proposed-solutions.transformer';
import { getProposedSolutionQueryArgs } from '../prisma-query-args/proposed-solutions.query-args';
import { sendCrRequestReviewPopUp, sendCrReviewedPopUp } from '../utils/pop-up.utils';
Expand Down Expand Up @@ -77,11 +82,111 @@ export default class ChangeRequestsService {
*/
static async getAllChangeRequests(organization: Organization): Promise<ChangeRequest[]> {
const changeRequests = await prisma.change_Request.findMany({
where: { dateDeleted: null, wbsElement: { organizationId: organization.organizationId ?? null } },
...getChangeRequestQueryArgs(organization.organizationId)
where: { dateDeleted: null, organizationId: organization.organizationId },
...getManyChangeRequestQueryArgs(organization.organizationId)
});

return changeRequests.map(changeRequestManyTransformer);
}

/**
* Gets a users change requests that they have been requested reviewer for or, if they are leadership, their teams change requests as well
*
* @param user The user to get their to review change requests for
* @param organization The organization the user is in
* @returns The user's change requests for them to review
*/
static async getToReviewChangeRequests(user: User, organization: Organization): Promise<ChangeRequest[]> {
const wbsOr: Prisma.WBS_ElementWhereInput[] = [{ managerId: user.userId }, { leadId: user.userId }];

if (await userHasPermission(user.userId, organization.organizationId, isLeadership)) {
wbsOr.push({
project: {
teams: {
some: {
OR: [
{ headId: user.userId },
{ leads: { some: { userId: user.userId } } },
{ members: { some: { userId: user.userId } } }
]
}
}
}
});
}

const queryOr: Prisma.Change_RequestWhereInput[] = [
{
requestedReviewers: {
some: {
userId: user.userId
}
}
},
{
wbsElement: {
OR: wbsOr
}
}
];

const changeRequests = await prisma.change_Request.findMany({
where: {
dateDeleted: null,
dateReviewed: null,
organizationId: organization.organizationId,
OR: queryOr
},
...getManyChangeRequestQueryArgs(organization.organizationId)
});

return changeRequests.map(changeRequestManyTransformer);
}

/**
* Gets all the unreviewed change requests for the current user
*
* @param user The user to get the change requests for
* @param organization The organization the user is currently in
* @returns The users unreviewed change requests
*/
static async getUnreviewedChangeRequests(user: User, organization: Organization): Promise<ChangeRequest[]> {
const changeRequests = await prisma.change_Request.findMany({
where: {
submitterId: user.userId,
organizationId: organization.organizationId,
dateReviewed: null,
dateDeleted: null
},
...getManyChangeRequestQueryArgs(organization.organizationId)
});

return changeRequests.map(changeRequestManyTransformer);
}

/**
* Gets the users approved change requests from the last five days
*
* @param user The user to get their approved change requests for
* @param organization The organization the user is currently in
* @returns The users approved change requests
*/
static async getApprovedChangeRequests(user: User, organization: Organization): Promise<ChangeRequest[]> {
const currentDate = new Date();

const changeRequests = await prisma.change_Request.findMany({
where: {
submitterId: user.userId,
organizationId: organization.organizationId,
dateReviewed: {
gte: new Date(currentDate.getTime() - 1000 * 60 * 60 * 24 * 5) // Change requests that were reviewed less than five days ago
},
dateDeleted: null
},
...getManyChangeRequestQueryArgs(organization.organizationId)
});

return changeRequests.map(changeRequestTransformer);
return changeRequests.map(changeRequestManyTransformer);
}

/**
Expand Down Expand Up @@ -110,7 +215,7 @@ export default class ChangeRequestsService {
// ensure existence of change request
const foundCR = await prisma.change_Request.findUnique({
where: { crId },
include: getChangeRequestQueryArgs(organization.organizationId).include
...getChangeRequestWithProjectAndWorkPackageQueryArgs(organization.organizationId)
});

if (!foundCR) throw new NotFoundException('Change Request', crId);
Expand Down Expand Up @@ -167,7 +272,7 @@ export default class ChangeRequestsService {
* @param organization the organization the user is currently in
*/
static async reviewScopeChangeRequest(
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestQueryArgs>,
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestWithProjectAndWorkPackageQueryArgs>,
reviewer: User,
psId: string | null,
organization: Organization
Expand Down Expand Up @@ -285,7 +390,7 @@ export default class ChangeRequestsService {
* @param reviewer the user reviewing the change request
*/
static async reviewStageGateChangeRequest(
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestQueryArgs>,
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestWithProjectAndWorkPackageQueryArgs>,
reviewer: User
): Promise<void> {
if (!foundCR.wbsElement.workPackage) {
Expand Down Expand Up @@ -328,7 +433,7 @@ export default class ChangeRequestsService {
* @param reviewer the user reviewing the change request
*/
static async reviewActivationChangeRequest(
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestQueryArgs>,
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestWithProjectAndWorkPackageQueryArgs>,
reviewer: User
): Promise<void> {
const { activationChangeRequest } = foundCR;
Expand Down
50 changes: 49 additions & 1 deletion src/backend/src/transformers/change-requests.transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
WorkPackageProposedChangesQueryArgs
} from '../prisma-query-args/scope-change-requests.query-args';
import { HttpException } from '../utils/errors.utils';
import { ChangeRequestQueryArgs } from '../prisma-query-args/change-requests.query-args';
import { ChangeRequestManyQueryArgs, ChangeRequestQueryArgs } from '../prisma-query-args/change-requests.query-args';

const projectProposedChangesTransformer = (
wbsProposedChanges: Prisma.Wbs_Proposed_ChangesGetPayload<WbsProposedChangeQueryArgs>
Expand Down Expand Up @@ -69,6 +69,54 @@ const workPackageProposedChangesTransformer = (
};
};

export const changeRequestManyTransformer = (
changeRequest: Prisma.Change_RequestGetPayload<ChangeRequestManyQueryArgs>
): ChangeRequest | StandardChangeRequest | ActivationChangeRequest | StageGateChangeRequest => {
const status = calculateChangeRequestStatus(changeRequest);

return {
// all cr fields
crId: changeRequest.crId,
identifier: changeRequest.identifier,
wbsNum: wbsNumOf(changeRequest.wbsElement),
wbsName: changeRequest.wbsElement.name,
submitter: userTransformer(changeRequest.submitter),
dateSubmitted: changeRequest.dateSubmitted,
type: changeRequest.type,
reviewer: changeRequest.reviewer ? userTransformer(changeRequest.reviewer) : undefined,
dateReviewed: changeRequest.dateReviewed ?? undefined,
accepted: changeRequest.accepted ?? undefined,
reviewNotes: changeRequest.reviewNotes ?? undefined,
dateImplemented: getDateImplemented(changeRequest),
implementedChanges: [],
status,
// scope cr fields
projectProposedChanges: undefined,
workPackageProposedChanges: undefined,
what: undefined,
why: undefined,
scopeImpact: undefined,
budgetImpact: undefined,
timelineImpact: undefined,
proposedSolutions: undefined,
originalProjectData: undefined,
originalWorkPackageData: undefined,
// activation cr fields
lead: changeRequest.activationChangeRequest?.lead
? userTransformer(changeRequest.activationChangeRequest.lead)
: undefined,
manager: changeRequest.activationChangeRequest?.manager
? userTransformer(changeRequest.activationChangeRequest.manager)
: undefined,
startDate: changeRequest.activationChangeRequest?.startDate ?? undefined,
confirmDetails: changeRequest.activationChangeRequest?.confirmDetails ?? undefined,
// stage gate cr fields
leftoverBudget: changeRequest.stageGateChangeRequest?.leftoverBudget ?? undefined,
confirmDone: changeRequest.stageGateChangeRequest?.confirmDone ?? undefined,
requestedReviewers: changeRequest.requestedReviewers.map(userTransformer) ?? []
};
};

const changeRequestTransformer = (
changeRequest: Prisma.Change_RequestGetPayload<ChangeRequestQueryArgs>
): ChangeRequest | StandardChangeRequest | ActivationChangeRequest | StageGateChangeRequest => {
Expand Down
10 changes: 7 additions & 3 deletions src/backend/src/utils/change-requests.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import { HttpException, NotFoundException } from './errors.utils';
import { ChangeRequestStatus } from 'shared';
import { buildChangeDetail, createChange } from './changes.utils';
import { WorkPackageQueryArgs, getWorkPackageQueryArgs } from '../prisma-query-args/work-packages.query-args';
import { ChangeRequestQueryArgs } from '../prisma-query-args/change-requests.query-args';
import {
ChangeRequestManyQueryArgs,
ChangeRequestQueryArgs,
ChangeRequestWithProjectAndWorkPackageQueryArgs
} from '../prisma-query-args/change-requests.query-args';
import {
ProjectProposedChangesQueryArgs,
WbsProposedChangeQueryArgs,
Expand Down Expand Up @@ -153,7 +157,7 @@ export const validateChangeRequestAccepted = async (crId: string) => {
* @returns The status of the change request. Can either be Open, Accepted, Denied, or Implemented
*/
export const calculateChangeRequestStatus = (
changeRequest: Prisma.Change_RequestGetPayload<ChangeRequestQueryArgs>
changeRequest: Prisma.Change_RequestGetPayload<ChangeRequestManyQueryArgs>
): ChangeRequestStatus => {
if (changeRequest.changes.length) {
return ChangeRequestStatus.Implemented;
Expand Down Expand Up @@ -454,7 +458,7 @@ export const applyWorkPackageProposedChanges = async (
*/
export const reviewProposedSolution = async (
psId: string,
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestQueryArgs>,
foundCR: Prisma.Change_RequestGetPayload<ChangeRequestWithProjectAndWorkPackageQueryArgs>,
reviewer: User,
organizationId: string
) => {
Expand Down
18 changes: 18 additions & 0 deletions src/frontend/src/apis/change-requests.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ export const getAllChangeRequests = () => {
});
};

export const getToReviewChangeRequests = () => {
return axios.get<ChangeRequest[]>(apiUrls.toReviewChangeRequests(), {
transformResponse: (data) => JSON.parse(data).map(changeRequestTransformer)
});
};

export const getUnreviewedChangeRequests = () => {
return axios.get<ChangeRequest[]>(apiUrls.unreviewedChangeRequests(), {
transformResponse: (data) => JSON.parse(data).map(changeRequestTransformer)
});
};

export const getApprovedChangeRequests = () => {
return axios.get<ChangeRequest[]>(apiUrls.approvedChangeRequests(), {
transformResponse: (data) => JSON.parse(data).map(changeRequestTransformer)
});
};

/**
* Fetches a single change request.
*
Expand Down
Loading
Loading