diff --git a/src/hooks/useApprovalWorkflows.tsx b/src/hooks/useApprovalWorkflows.tsx index 1b950c2f..7bfec920 100644 --- a/src/hooks/useApprovalWorkflows.tsx +++ b/src/hooks/useApprovalWorkflows.tsx @@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow"; import axios from "axios"; import { useCallback, useEffect, useState } from "react"; -export default function useApprovalWorkflows() { +export default function useApprovalWorkflows(entitiesString?: string) { const [workflows, setWorkflows] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); @@ -10,7 +10,7 @@ export default function useApprovalWorkflows() { const getData = useCallback(() => { setIsLoading(true); axios - .get(`/api/approval-workflows`) + .get(`/api/approval-workflows`, {params: { entityIds: entitiesString }}) .then((response) => setWorkflows(response.data)) .catch((error) => { setIsError(true); diff --git a/src/lib/createWorkflowsOnExamCreation.ts b/src/lib/createWorkflowsOnExamCreation.ts index 732a1f18..f72d9db7 100644 --- a/src/lib/createWorkflowsOnExamCreation.ts +++ b/src/lib/createWorkflowsOnExamCreation.ts @@ -1,7 +1,10 @@ import { Module } from "@/interfaces"; import { getApprovalWorkflowByFormIntaker, createApprovalWorkflow } from "@/utils/approval.workflows.be"; +import client from "@/lib/mongodb"; -export async function createApprovalWorkflowsOnExamCreation(examAuthor: string, examEntities: string[], examId: string, examModule: string) { +const db = client.db(process.env.MONGODB_DB); + +/* export async function createApprovalWorkflowsOnExamCreation(examAuthor: string, examEntities: string[], examId: string, examModule: string) { const results = await Promise.all( examEntities.map(async (entity) => { const configuredWorkflow = await getApprovalWorkflowByFormIntaker(entity, examAuthor); @@ -27,6 +30,50 @@ export async function createApprovalWorkflowsOnExamCreation(examAuthor: string, const successCount = results.filter((r) => r.created).length; const totalCount = examEntities.length; + return { + successCount, + totalCount, + }; +} */ + +// TEMPORARY BEHAVIOUR! ONLY THE FIRST CONFIGURED WORKFLOW FOUND IS STARTED +export async function createApprovalWorkflowOnExamCreation(examAuthor: string, examEntities: string[], examId: string, examModule: string) { + let successCount = 0; + let totalCount = 0; + + for (const entity of examEntities) { + const configuredWorkflow = await getApprovalWorkflowByFormIntaker(entity, examAuthor); + + if (!configuredWorkflow) { + continue; + } + + totalCount = 1; // a workflow was found + + configuredWorkflow.modules.push(examModule as Module); + configuredWorkflow.name = examId; + configuredWorkflow.examId = examId; + configuredWorkflow.entityId = entity; + configuredWorkflow.startDate = Date.now(); + + try { + await createApprovalWorkflow("active-workflows", configuredWorkflow); + successCount = 1; + break; // Stop after the first success + } catch (error: any) { + break; + } + } + + // prettier-ignore + if (totalCount === 0) { // current behaviour: if no workflow was found skip approval process + await db.collection(examModule).updateOne( + { id: examId }, + { $set: { id: examId, isDiagnostic: false }}, + { upsert: true } + ); + } + return { successCount, totalCount, diff --git a/src/pages/api/approval-workflows/index.ts b/src/pages/api/approval-workflows/index.ts index ff381a5e..a2cd2ea4 100644 --- a/src/pages/api/approval-workflows/index.ts +++ b/src/pages/api/approval-workflows/index.ts @@ -19,5 +19,9 @@ async function get(req: NextApiRequest, res: NextApiResponse) { return res.status(403).json({ ok: false }); } - return res.status(200).json(await getApprovalWorkflows("active-workflows")); -} \ No newline at end of file + const entityIdsString = req.query.entityIds as string; + + const entityIdsArray = entityIdsString.split(","); + + return res.status(200).json(await getApprovalWorkflows("active-workflows", entityIdsArray)); +} diff --git a/src/pages/api/exam/[module]/index.ts b/src/pages/api/exam/[module]/index.ts index e90e356e..cedda8f1 100644 --- a/src/pages/api/exam/[module]/index.ts +++ b/src/pages/api/exam/[module]/index.ts @@ -1,7 +1,7 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction import { Module } from "@/interfaces"; import { Exam, ExamBase, InstructorGender, Variant } from "@/interfaces/exam"; -import { createApprovalWorkflowsOnExamCreation } from "@/lib/createWorkflowsOnExamCreation"; +import { createApprovalWorkflowOnExamCreation } from "@/lib/createWorkflowsOnExamCreation"; import client from "@/lib/mongodb"; import { sessionOptions } from "@/lib/session"; import { mapBy } from "@/utils"; @@ -49,7 +49,7 @@ async function POST(req: NextApiRequest, res: NextApiResponse) { const { module } = req.query as { module: string }; const session = client.startSession(); - const entities = isAdmin(user) ? [] : mapBy(user.entities, "id"); // might need to change this with new approval workflows logic.. if an admin creates an exam no workflow is started because workflows must have entities configured. + const entities = isAdmin(user) ? [] : mapBy(user.entities, "id"); try { const exam = { @@ -78,6 +78,10 @@ async function POST(req: NextApiRequest, res: NextApiResponse) { throw new Error("Name already exists"); } + if (isAdmin(user)) { + exam.isDiagnostic = false; + } + await db.collection(module).updateOne( { id: req.body.id }, { $set: { id: req.body.id, ...exam } }, @@ -90,37 +94,41 @@ async function POST(req: NextApiRequest, res: NextApiResponse) { // if it doesn't enter the next if condition it means the exam was updated and not created, so we can send this response. responseStatus = 200; responseMessage = `Successfully updated exam with ID: "${exam.id}"`; - // TODO maybe find a way to start missing approval workflows in case they were only configured after exam creation. // create workflow only if exam is being created for the first time if (docSnap === null) { try { - const { successCount, totalCount } = await createApprovalWorkflowsOnExamCreation(exam.createdBy, exam.entities, exam.id, module); + const { successCount, totalCount } = await createApprovalWorkflowOnExamCreation(exam.createdBy, exam.entities, exam.id, module); - if (successCount === totalCount) { + if (isAdmin(user)) { responseStatus = 200; - responseMessage = `Successfully created exam "${exam.id}" and started its Approval Workflow(s)`; + responseMessage = `Successfully created exam "${exam.id}" and skipped Approval Workflow due to admin rights.`; + } else if (successCount === totalCount) { + responseStatus = 200; + responseMessage = `Successfully created exam "${exam.id}" and started its Approval Workflow.`; + /* responseMessage = `Successfully created exam "${exam.id}" and started its Approval Workflow(s).`; */ } else if (successCount > 0) { responseStatus = 207; - responseMessage = `Successfully created exam with ID: "${exam.id}" but was not able to start/find an Approval Workflow for all the author's entities`; + responseMessage = `Successfully created exam with ID: "${exam.id}" but was not able to start/find an Approval Workflow for all the author's entities.`; } else { responseStatus = 207; - responseMessage = `Successfully created exam with ID: "${exam.id}" but was not able to find any configured Approval Workflow for the author.`; + responseMessage = `Successfully created exam with ID: "${exam.id}" but skipping approval process because no approval workflow was found configured for the exam author.`; } } catch (error) { console.error("Workflow creation error:", error); responseStatus = 207; responseMessage = `Successfully created exam with ID: "${exam.id}" but something went wrong while creating the Approval Workflow(s).`; } - } else { // if exam was updated, log the updates + } else { + // if exam was updated, log the updates const approvalWorkflows = await getApprovalWorkflowsByExamId(exam.id); - + if (approvalWorkflows) { const differences = generateExamDifferences(docSnap as Exam, exam as Exam); if (differences) { approvalWorkflows.forEach((workflow) => { - const currentStepIndex = workflow.steps.findIndex(step => !step.completed || step.rejected); - + const currentStepIndex = workflow.steps.findIndex((step) => !step.completed || step.rejected); + if (workflow.steps[currentStepIndex].examChanges === undefined) { workflow.steps[currentStepIndex].examChanges = [...differences]; } else { @@ -131,7 +139,7 @@ async function POST(req: NextApiRequest, res: NextApiResponse) { } } } - + res.status(responseStatus).json({ message: responseMessage, }); diff --git a/src/pages/approval-workflows/index.tsx b/src/pages/approval-workflows/index.tsx index a33d2967..58d4e54e 100644 --- a/src/pages/approval-workflows/index.tsx +++ b/src/pages/approval-workflows/index.tsx @@ -69,7 +69,11 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/"); - const workflows = await getApprovalWorkflows("active-workflows"); + const entityIDS = mapBy(user.entities, "id"); + const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS); + const allowedEntities = findAllowedEntities(user, entities, "view_workflows"); + + const workflows = await getApprovalWorkflows("active-workflows", allowedEntities.map(entity => entity.id)); const allAssigneeIds: string[] = [ ...new Set( @@ -81,10 +85,6 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { ) ]; - const entityIDS = mapBy(user.entities, "id"); - const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS); - const allowedEntities = findAllowedEntities(user, entities, "view_workflows"); - return { props: serialize({ user, @@ -103,7 +103,8 @@ interface Props { } export default function ApprovalWorkflows({ user, initialWorkflows, workflowsAssignees, userEntitiesWithLabel }: Props) { - const { workflows, reload } = useApprovalWorkflows(); + const entitiesString = userEntitiesWithLabel.map(entity => entity.id).join(","); + const { workflows, reload } = useApprovalWorkflows(entitiesString); const currentWorkflows = workflows || initialWorkflows; const [filteredWorkflows, setFilteredWorkflows] = useState([]); diff --git a/src/utils/approval.workflows.be.ts b/src/utils/approval.workflows.be.ts index 8b610b54..9998a06e 100644 --- a/src/utils/approval.workflows.be.ts +++ b/src/utils/approval.workflows.be.ts @@ -4,11 +4,18 @@ import { ObjectId } from "mongodb"; const db = client.db(process.env.MONGODB_DB); -export const getApprovalWorkflows = async (collection: string, ids?: string[]) => { - return await db - .collection(collection) - .find(ids ? { _id: { $in: ids.map((id) => new ObjectId(id)) } } : {}) - .toArray(); +export const getApprovalWorkflows = async (collection: string, entityIds?: string[], ids?: string[]) => { + const filters: any = {}; + + if (ids && ids.length > 0) { + filters.id = { $in: ids }; + } + + if (entityIds && entityIds.length > 0) { + filters.entityId = { $in: entityIds }; + } + + return await db.collection(collection).find(filters).toArray(); }; export const getApprovalWorkflow = async (collection: string, id: string) => { @@ -37,9 +44,9 @@ export const getApprovalWorkflowByFormIntaker = async (entityId: string, formInt export const getApprovalWorkflowsByExamId = async (examId: string) => { return await db .collection("active-workflows") - .find({ - examId, - status: { $in: ["pending"] } + .find({ + examId, + status: { $in: ["pending"] }, }) .toArray(); };