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

#2149-work-package-template-endpoint #2361

Merged
merged 25 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
31 changes: 31 additions & 0 deletions src/backend/src/controllers/work-packages.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,37 @@ export default class WorkPackagesController {
}
}

// Create a work package template with the given details
static async createWorkPackageTemplate(req: Request, res: Response, next: NextFunction) {
try {
const { templateName, templateNotes, workPackageName, duration, descriptionBullets, blockedBy } = req.body;

let { stage } = req.body;
if (stage === 'NONE') {
stage = null;
}

const user = await getCurrentUser(res);
const organizationId = getOrganizationId(req.headers);

const workPackageTemplate: WorkPackageTemplate = await WorkPackagesService.createWorkPackageTemplate(
user,
templateName,
templateNotes,
workPackageName,
stage,
duration,
descriptionBullets,
blockedBy,
organizationId
);

res.status(200).json(workPackageTemplate);
} catch (error: unknown) {
next(error);
}
}

// Edit a work package to the given specifications
static async editWorkPackage(req: Request, res: Response, next: NextFunction) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ CREATE TABLE "Work_Package_Template" (
"workPackageName" TEXT,
"stage" "Work_Package_Stage",
"duration" INTEGER,
"dateCreated" TIMESTAMP(3) NOT NULL,
"dateCreated" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userCreatedId" INTEGER NOT NULL,
"dateDeleted" TIMESTAMP(3),
"userDeletedId" INTEGER,
Expand Down
2 changes: 1 addition & 1 deletion src/backend/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ model Work_Package_Template {
blockedBy Work_Package_Template[] @relation("blocking")
blocking Work_Package_Template[] @relation("blocking")
descriptionBullets Description_Bullet[]
dateCreated DateTime
dateCreated DateTime @default(now())
userCreated User @relation(fields: [userCreatedId], references: [userId], name: "workPackageTemplateCreator")
userCreatedId Int
dateDeleted DateTime?
Expand Down
38 changes: 38 additions & 0 deletions src/backend/src/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import DesignReviewsService from '../services/design-reviews.services';
import BillOfMaterialsService from '../services/boms.services';
import UsersService from '../services/users.services';
import { transformDate } from '../utils/datetime.utils';
import WorkPackagesService from '../services/work-packages.services';

const prisma = new PrismaClient();

Expand Down Expand Up @@ -278,6 +279,7 @@ const performSeed: () => Promise<void> = async () => {
/** If the .env file exists, set the FINANCE_TEAM_ID */
if (currentEnv) {
currentEnv.FINANCE_TEAM_ID = financeTeam.teamId;
currentEnv.DEV_ORGANIZATION_ID = organizationId;

/** Write the new .env file */
let stringifiedEnv = '';
Expand Down Expand Up @@ -1924,6 +1926,42 @@ const performSeed: () => Promise<void> = async () => {
links: []
}
);

const workPackageTemplate1 = await WorkPackagesService.createWorkPackageTemplate(
batman,
'Batmobile Config 1',
'This is the first Batmobile configuration',
null,
null,
5,
[],
[],
organizationId
);

const schematicWpTemplate = await WorkPackagesService.createWorkPackageTemplate(
batman,
'Schematic',
'This is the schematic template',
null,
WorkPackageStage.Design,
4,
[],
[],
organizationId
);

const layoutWpTemplate = await WorkPackagesService.createWorkPackageTemplate(
batman,
'Layout ',
'This is the Layout template',
null,
WorkPackageStage.Design,
4,
[],
[schematicWpTemplate.workPackageTemplateId],
organizationId
);
};

performSeed()
Expand Down
14 changes: 14 additions & 0 deletions src/backend/src/routes/work-package-templates.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,18 @@ workPackageTemplatesRouter.post(
WorkPackagesController.editWorkPackageTemplate
);

workPackageTemplatesRouter.post(
'/create',
nonEmptyString(body('templateName')),
nonEmptyString(body('templateNotes')),
nonEmptyString(body('workPackageName').optional()),
isWorkPackageStageOrNone(body('stage').optional()),
intMinZero(body('duration').optional()),
body('blockedBy').isArray(),
nonEmptyString(body('blockedBy.*')),
...descriptionBulletsValidators,
validateInputs,
WorkPackagesController.createWorkPackageTemplate
);

export default workPackageTemplatesRouter;
1 change: 1 addition & 0 deletions src/backend/src/routes/work-packages.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ workPackagesRouter.post(
validateInputs,
WorkPackagesController.createWorkPackage
);

workPackagesRouter.post(
'/edit',
intMinZero(body('workPackageId')),
Expand Down
84 changes: 80 additions & 4 deletions src/backend/src/services/work-packages.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import {
descriptionBulletToChangeListValue,
descriptionBulletsToChangeListValues,
editDescriptionBullets,
markDescriptionBulletsAsDeleted
markDescriptionBulletsAsDeleted,
validateDescriptionBullets
} from '../utils/description-bullets.utils';
import { getBlockingWorkPackages, validateBlockedByTemplates, validateBlockedBys } from '../utils/work-packages.utils';
import { getBlockingWorkPackages, validateBlockedBys, validateBlockedByTemplates } from '../utils/work-packages.utils';
import { workPackageTemplateTransformer } from '../transformers/work-package-template.transformer';
import { getWorkPackageTemplateQueryArgs } from '../prisma-query-args/work-package-template.query-args';
import { getDescriptionBulletQueryArgs } from '../prisma-query-args/description-bullets.query-args';
Expand Down Expand Up @@ -605,6 +606,81 @@ export default class WorkPackagesService {
return workPackageTemplates.map(workPackageTemplateTransformer);
}

/**
* Creates a Work_Package_Template in the database
*
* @param user the user creating the work package template
* @param templateName the template name
* @param templateNotes the template notes
* @param workPackageName the name of the work packge
* @param stage the stage
* @param duration the duration of the work package template in weeks
* @param expectedActivities the expected activities descriptions for this WPT
* @param deliverables the expected deliverables descriptions for this WPT
* @param blockedByIds the WBS elements that need to be completed before this WPT
* @param organizationId the id of the organization that the user is currently in
* @returns the created work package template
* @throws if the work package template could not be created
*/
static async createWorkPackageTemplate(
user: User,
templateName: string,
templateNotes: string,
workPackageName: string | null,
stage: WorkPackageStage | null,
duration: number,
descriptionBullets: DescriptionBulletPreview[],
blockedByIds: string[],
organizationId: string
): Promise<WorkPackageTemplate> {
if (!(await userHasPermission(user.userId, organizationId, isAdmin)))
throw new AccessDeniedAdminOnlyException('create work package templates');

// get the corresponding IDs of all work package templates in BlockedBy,
// and throw an errror if the template doesn't exist
await Promise.all(
blockedByIds.map(async (workPackageTemplateId) => {
const template = await prisma.work_Package_Template.findFirst({
where: { workPackageTemplateId }
});

if (!template) {
throw new NotFoundException('Work Package Template', workPackageTemplateId);
}
return template.workPackageTemplateId;
})
);

await validateDescriptionBullets(descriptionBullets, organizationId);

// add to the db
const created = await prisma.work_Package_Template.create({
data: {
templateName,
templateNotes,
workPackageName,
stage,
duration,
userCreatedId: user.userId,
organizationId,
blockedBy: {
connect: blockedByIds.map((blockedById) => ({ workPackageTemplateId: blockedById }))
}
},

...getWorkPackageTemplateQueryArgs(organizationId)
});

bderbs30 marked this conversation as resolved.
Show resolved Hide resolved
await addRawDescriptionBullets(
descriptionBullets,
DescriptionBulletDestination.TEMPLATE,
created.workPackageTemplateId,
created.organizationId
);

return workPackageTemplateTransformer(created);
}

/**
* Edits a work package template given the specified parameters
* @param submitter user who is submitting the edit
Expand All @@ -613,12 +689,12 @@ export default class WorkPackagesService {
* @param templateNotes notes about the work package template
* @param duration duration value on the template
* @param stage stage value on the template
* @param blockedByInfo array of templates blocking this
* @param blockedByIds array of templates blocking this
* @param expectedActivities array of expected activity values on the template
* @param deliverables array of deliverable values on the template
* @param workPackageName name value on the template
* @param organizationId id of the organization that the user is currently in
* @returns
* @returns the updated work package template
*/
static async editWorkPackageTemplate(
submitter: User,
Expand Down
8 changes: 7 additions & 1 deletion src/backend/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ export const convertStatus = (status: WBS_Element_Status): WbsElementStatus =>
}[status]);

export const getOrganizationId = (headers: IncomingHttpHeaders): string => {
const { organizationid } = headers;
let { organizationid } = headers;

const isProd = process.env.NODE_ENV === 'production';

if (organizationid === undefined && !isProd) {
organizationid = process.env.DEV_ORGANIZATION_ID;
}

if (organizationid === undefined) {
throw new AccessDeniedException('Organization not provided');
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const workPackageTemplates = () => `${API_URL}/templates`;
const workPackageTemplatesById = (workPackageTemplateId: string) => `${workPackageTemplates()}/${workPackageTemplateId}`;
const workPackageTemplatesEdit = (workPackageTemplateId: string) =>
`${workPackageTemplatesById(workPackageTemplateId)}/edit`;
const workPackageTemplatesCreate = () => `${workPackageTemplates()}/create`;

/**************** Other Endpoints ****************/
const version = () => `https://api.github.com/repos/Northeastern-Electric-Racing/FinishLine/releases/latest`;
Expand Down Expand Up @@ -281,7 +282,9 @@ export const apiUrls = {
designReviewDelete,

workPackageTemplates,
workPackageTemplatesById,
workPackageTemplatesEdit,
workPackageTemplatesCreate,

version
};
6 changes: 0 additions & 6 deletions src/shared/src/types/work-package-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,3 @@ export interface WorkPackageTemplate {
userDeleted?: User;
userDeletedId?: Number;
}

export interface BlockedByCreateArgs {
blockedByInfoId?: string;
stage?: WorkPackageStage;
name: string;
}
Loading