Skip to content
This repository has been archived by the owner on Sep 12, 2023. It is now read-only.

Commit

Permalink
Merge pull request #740 from Northeastern-Electric-Racing/#729-timeli…
Browse files Browse the repository at this point in the history
…ne-status-home

#729 timeline status home
  • Loading branch information
jamescd18 authored Jun 19, 2022
2 parents fe8dce4 + a253ce0 commit 20dfca9
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 62 deletions.
94 changes: 41 additions & 53 deletions src/backend/functions/work-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {

const prisma = new PrismaClient();

const manyRelationArgs = Prisma.validator<Prisma.Work_PackageArgs>()({
const wpQueryArgs = Prisma.validator<Prisma.Work_PackageArgs>()({
include: {
wbsElement: {
include: {
Expand All @@ -48,15 +48,6 @@ const manyRelationArgs = Prisma.validator<Prisma.Work_PackageArgs>()({
}
});

const uniqueRelationArgs = Prisma.validator<Prisma.WBS_ElementArgs>()({
include: {
workPackage: { include: { expectedActivities: true, deliverables: true, dependencies: true } },
projectLead: true,
projectManager: true,
changes: { include: { implementer: true } }
}
});

const convertStatus = (status: WBS_Element_Status): WbsElementStatus =>
({
INACTIVE: WbsElementStatus.Inactive,
Expand All @@ -77,55 +68,54 @@ const descriptionBulletTransformer = (descBullet: Description_Bullet) => ({
dateDeleted: descBullet.dateDeleted ?? undefined
});

const workPackageTransformer = (
payload:
| Prisma.Work_PackageGetPayload<typeof manyRelationArgs>
| Prisma.WBS_ElementGetPayload<typeof uniqueRelationArgs>
): WorkPackage => {
if (payload === null) throw new TypeError('WBS_Element not found');
const wbsElement = 'wbsElement' in payload ? payload.wbsElement : payload;
const workPackage = 'workPackage' in payload ? payload.workPackage! : payload;

const workPackageTransformer = (wpInput: Prisma.Work_PackageGetPayload<typeof wpQueryArgs>) => {
const expectedProgress = calculatePercentExpectedProgress(
workPackage.startDate,
workPackage.duration,
wbsElement.status
wpInput.startDate,
wpInput.duration,
wpInput.wbsElement.status
);

const wbsNum = wbsNumOf(wbsElement);
const wbsNum = wbsNumOf(wpInput.wbsElement);
return {
id: workPackage.workPackageId,
dateCreated: wbsElement.dateCreated,
name: wbsElement.name,
orderInProject: workPackage.orderInProject,
progress: workPackage.progress,
startDate: workPackage.startDate,
duration: workPackage.duration,
expectedActivities: workPackage.expectedActivities.map(descriptionBulletTransformer),
deliverables: workPackage.deliverables.map(descriptionBulletTransformer),
dependencies: workPackage.dependencies.map(wbsNumOf),
projectManager: wbsElement.projectManager ?? undefined,
projectLead: wbsElement.projectLead ?? undefined,
status: convertStatus(wbsElement.status),
id: wpInput.workPackageId,
dateCreated: wpInput.wbsElement.dateCreated,
name: wpInput.wbsElement.name,
orderInProject: wpInput.orderInProject,
progress: wpInput.progress,
startDate: wpInput.startDate,
duration: wpInput.duration,
expectedActivities: wpInput.expectedActivities.map(descriptionBulletTransformer),
deliverables: wpInput.deliverables.map(descriptionBulletTransformer),
dependencies: wpInput.dependencies.map(wbsNumOf),
projectManager: wpInput.wbsElement.projectManager ?? undefined,
projectLead: wpInput.wbsElement.projectLead ?? undefined,
status: convertStatus(wpInput.wbsElement.status),
wbsNum,
endDate: calculateEndDate(workPackage.startDate, workPackage.duration),
endDate: calculateEndDate(wpInput.startDate, wpInput.duration),
expectedProgress,
timelineStatus: calculateTimelineStatus(workPackage.progress, expectedProgress),
changes: wbsElement.changes.map((change) => ({
timelineStatus: calculateTimelineStatus(wpInput.progress, expectedProgress),
changes: wpInput.wbsElement.changes.map((change) => ({
wbsNum,
changeId: change.changeId,
changeRequestId: change.changeRequestId,
implementer: change.implementer,
detail: change.detail,
dateImplemented: change.dateImplemented
}))
};
} as WorkPackage;
};

// Fetch all work packages
const getAllWorkPackages: ApiRouteFunction = async () => {
const workPackages = await prisma.work_Package.findMany(manyRelationArgs);
return buildSuccessResponse(workPackages.map(workPackageTransformer));
const getAllWorkPackages: ApiRouteFunction = async (_, event) => {
const { queryStringParameters: eQSP } = event;
const workPackages = await prisma.work_Package.findMany(wpQueryArgs);
return buildSuccessResponse(
workPackages.map(workPackageTransformer).filter((wp) => {
let passes = true;
if (eQSP?.status) passes &&= wp.status === eQSP?.status;
if (eQSP?.timelineStatus) passes &&= wp.timelineStatus === eQSP?.timelineStatus;
return passes;
})
);
};

// Fetch the work package for the specified WBS number
Expand All @@ -134,31 +124,29 @@ const getSingleWorkPackage: ApiRouteFunction = async (params: { wbsNum: string }
if (isProject(parsedWbs)) {
return buildClientFailureResponse('WBS Number is a project WBS#, not a Work Package WBS#');
}
const wbsEle = await prisma.wBS_Element.findUnique({
const wp = await prisma.work_Package.findFirst({
where: {
wbsNumber: {
wbsElement: {
carNumber: parsedWbs.carNumber,
projectNumber: parsedWbs.projectNumber,
workPackageNumber: parsedWbs.workPackageNumber
}
},
...uniqueRelationArgs
...wpQueryArgs
});
if (wbsEle === null) {
return buildNotFoundResponse('work package', `WBS # ${params.wbsNum}`);
}
return buildSuccessResponse(workPackageTransformer(wbsEle));
if (wp === null) return buildNotFoundResponse('work package', `WBS # ${params.wbsNum}`);
return buildSuccessResponse(workPackageTransformer(wp));
};

// Fetch all work packages with an end date in the next 2 weeks
const getAllWorkPackagesUpcomingDeadlines: ApiRouteFunction = async () => {
const workPackages = await prisma.work_Package.findMany({
where: {
wbsElement: {
status: 'ACTIVE'
status: WBS_Element_Status.ACTIVE
}
},
...manyRelationArgs
...wpQueryArgs
});
const outputWorkPackages = workPackages
.filter((wp) => {
Expand Down
32 changes: 31 additions & 1 deletion src/frontend/pages/HomePage/home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import { render, screen, routerWrapperBuilder } from '../../../test-support/test-utils';
import { routes } from '../../../shared/routes';
import Home from './home';
import { useAuth } from '../../../services/auth.hooks';
import { Auth } from '../../../shared/types';
import { exampleAdminUser } from '../../../test-support/test-data/users.stub';
import { mockAuth } from '../../../test-support/test-data/test-utils.stub';

jest.mock('./useful-links/useful-links', () => {
return {
Expand All @@ -25,6 +29,23 @@ jest.mock('./upcoming-deadlines/upcoming-deadlines', () => {
};
});

jest.mock('./work-packages-by-timeline-status/work-packages-by-timeline-status', () => {
return {
__esModule: true,
default: () => {
return <div>work-packages-by-timeline-status</div>;
}
};
});

jest.mock('../../../services/auth.hooks');

const mockedUseAuth = useAuth as jest.Mock<Auth>;

const mockAuthHook = (user = exampleAdminUser) => {
mockedUseAuth.mockReturnValue(mockAuth(false, user));
};

/**
* Sets up the component under test with the desired values and renders it.
*/
Expand All @@ -38,9 +59,13 @@ const renderComponent = () => {
};

describe('home component', () => {
beforeEach(() => {
mockAuthHook();
});

it('renders welcome', () => {
renderComponent();
expect(screen.getByText(/Welcome/i)).toBeInTheDocument();
expect(screen.getByText(`Welcome, ${exampleAdminUser.firstName}!`)).toBeInTheDocument();
});

it('renders useful links', () => {
Expand All @@ -52,4 +77,9 @@ describe('home component', () => {
renderComponent();
expect(screen.getByText('upcoming-deadlines')).toBeInTheDocument();
});

it('renders work packages by timeline status', () => {
renderComponent();
expect(screen.getByText('work-packages-by-timeline-status')).toBeInTheDocument();
});
});
2 changes: 2 additions & 0 deletions src/frontend/pages/HomePage/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { Container } from 'react-bootstrap';
import { useAuth } from '../../../services/auth.hooks';
import UsefulLinks from './useful-links/useful-links';
import WorkPackagesByTimelineStatus from './work-packages-by-timeline-status/work-packages-by-timeline-status';
import UpcomingDeadlines from './upcoming-deadlines/upcoming-deadlines';
import styles from './home.module.css';

Expand All @@ -16,6 +17,7 @@ const Home: React.FC = () => {
<h1 className={styles.title}>Welcome, {auth.user?.firstName}!</h1>
<UsefulLinks />
<UpcomingDeadlines />
<WorkPackagesByTimelineStatus />
</Container>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* This file is part of NER's PM Dashboard and licensed under GNU AGPLv3.
* See the LICENSE file in the repository root folder for details.
*/

.workPackageCard {
min-width: fit-content !important;
margin: 0px 10px 10px 0px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* This file is part of NER's PM Dashboard and licensed under GNU AGPLv3.
* See the LICENSE file in the repository root folder for details.
*/

import { UseQueryResult } from 'react-query';
import { WorkPackage } from 'utils';
import { useAllWorkPackages } from '../../../../services/work-packages.hooks';
import { datePipe, fullNamePipe } from '../../../../shared/pipes';
import { mockUseQueryResult } from '../../../../test-support/test-data/test-utils.stub';
import { exampleAllWorkPackages } from '../../../../test-support/test-data/work-packages.stub';
import { render, routerWrapperBuilder, screen } from '../../../../test-support/test-utils';
import WorkPackagesByTimelineStatus from './work-packages-by-timeline-status';

jest.mock('../../../../services/work-packages.hooks');

const mockedUseAllWPs = useAllWorkPackages as jest.Mock<UseQueryResult<WorkPackage[]>>;

const mockHook = (isLoading: boolean, isError: boolean, data?: WorkPackage[], error?: Error) => {
mockedUseAllWPs.mockReturnValue(
mockUseQueryResult<WorkPackage[]>(isLoading, isError, data, error)
);
};

/**
* Sets up the component under test with the desired values and renders it.
*/
const renderComponent = () => {
const RouterWrapper = routerWrapperBuilder({});
return render(
<RouterWrapper>
<WorkPackagesByTimelineStatus />
</RouterWrapper>
);
};

describe('upcoming deadlines component', () => {
it('renders headers', () => {
mockHook(false, false, []);
renderComponent();
expect(screen.getByText('Work Packages By Timeline Status')).toBeInTheDocument();
});

it('renders loading indicator', () => {
mockHook(true, false);
renderComponent();
expect(screen.getByTestId('loader')).toBeInTheDocument();
});

it('handles the api throwing an error', () => {
mockHook(false, true, undefined, new Error('out of bounds error'));
renderComponent();
expect(screen.getByText('Oops, sorry!')).toBeInTheDocument();
expect(screen.getByText('out of bounds error')).toBeInTheDocument();
});

it('renders the work packages', () => {
mockHook(false, false, exampleAllWorkPackages);
renderComponent();
expect(screen.getByText(exampleAllWorkPackages[0].name, { exact: false })).toBeInTheDocument();
expect(
screen.getByText(fullNamePipe(exampleAllWorkPackages[0].projectLead), { exact: false })
).toBeInTheDocument();
expect(screen.getByText(exampleAllWorkPackages[1].name, { exact: false })).toBeInTheDocument();
expect(
screen.getByText(fullNamePipe(exampleAllWorkPackages[2].projectManager), { exact: false })
).toBeInTheDocument();
expect(screen.getAllByText(/Expected Activities/).length).toEqual(3);
expect(
screen.getByText(datePipe(exampleAllWorkPackages[1].endDate), { exact: false })
).toBeInTheDocument();
});

it('renders when no work packages', () => {
mockHook(false, false, []);
renderComponent();
expect(screen.getByText('No VERY_BEHIND work packages')).toBeInTheDocument();
});

it('renders timeline status selector', () => {
mockHook(false, false, exampleAllWorkPackages);
renderComponent();
expect(screen.getByText('Status:')).toBeInTheDocument();
expect(screen.getByText('VERY_BEHIND')).toBeInTheDocument();
});
});
Loading

0 comments on commit 20dfca9

Please sign in to comment.