Merged develop into addedAccess-bugfixes

This commit is contained in:
Francisco Lima
2025-02-09 04:32:42 +00:00
6 changed files with 99 additions and 32 deletions

View File

@@ -2,7 +2,7 @@ import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
import axios from "axios"; import axios from "axios";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
export default function useApprovalWorkflows() { export default function useApprovalWorkflows(entitiesString?: string) {
const [workflows, setWorkflows] = useState<ApprovalWorkflow[]>([]); const [workflows, setWorkflows] = useState<ApprovalWorkflow[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false); const [isError, setIsError] = useState(false);
@@ -10,7 +10,7 @@ export default function useApprovalWorkflows() {
const getData = useCallback(() => { const getData = useCallback(() => {
setIsLoading(true); setIsLoading(true);
axios axios
.get<ApprovalWorkflow[]>(`/api/approval-workflows`) .get<ApprovalWorkflow[]>(`/api/approval-workflows`, {params: { entityIds: entitiesString }})
.then((response) => setWorkflows(response.data)) .then((response) => setWorkflows(response.data))
.catch((error) => { .catch((error) => {
setIsError(true); setIsError(true);

View File

@@ -1,7 +1,10 @@
import { Module } from "@/interfaces"; import { Module } from "@/interfaces";
import { getApprovalWorkflowByFormIntaker, createApprovalWorkflow } from "@/utils/approval.workflows.be"; 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( const results = await Promise.all(
examEntities.map(async (entity) => { examEntities.map(async (entity) => {
const configuredWorkflow = await getApprovalWorkflowByFormIntaker(entity, examAuthor); 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 successCount = results.filter((r) => r.created).length;
const totalCount = examEntities.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 { return {
successCount, successCount,
totalCount, totalCount,

View File

@@ -19,5 +19,9 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
return res.status(403).json({ ok: false }); return res.status(403).json({ ok: false });
} }
return res.status(200).json(await getApprovalWorkflows("active-workflows")); const entityIdsString = req.query.entityIds as string;
}
const entityIdsArray = entityIdsString.split(",");
return res.status(200).json(await getApprovalWorkflows("active-workflows", entityIdsArray));
}

View File

@@ -1,7 +1,7 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { Module } from "@/interfaces"; import { Module } from "@/interfaces";
import { Exam, ExamBase, InstructorGender, Variant } from "@/interfaces/exam"; import { Exam, ExamBase, InstructorGender, Variant } from "@/interfaces/exam";
import { createApprovalWorkflowsOnExamCreation } from "@/lib/createWorkflowsOnExamCreation"; import { createApprovalWorkflowOnExamCreation } from "@/lib/createWorkflowsOnExamCreation";
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import { sessionOptions } from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import { mapBy } from "@/utils"; import { mapBy } from "@/utils";
@@ -49,7 +49,7 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
const { module } = req.query as { module: string }; const { module } = req.query as { module: string };
const session = client.startSession(); 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 { try {
const exam = { const exam = {
@@ -78,6 +78,10 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
throw new Error("Name already exists"); throw new Error("Name already exists");
} }
if (isAdmin(user)) {
exam.isDiagnostic = false;
}
await db.collection(module).updateOne( await db.collection(module).updateOne(
{ id: req.body.id }, { id: req.body.id },
{ $set: { id: req.body.id, ...exam } }, { $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. // 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; responseStatus = 200;
responseMessage = `Successfully updated exam with ID: "${exam.id}"`; 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 // create workflow only if exam is being created for the first time
if (docSnap === null) { if (docSnap === null) {
try { 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; 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) { } else if (successCount > 0) {
responseStatus = 207; 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 { } else {
responseStatus = 207; 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) { } catch (error) {
console.error("Workflow creation error:", error); console.error("Workflow creation error:", error);
responseStatus = 207; responseStatus = 207;
responseMessage = `Successfully created exam with ID: "${exam.id}" but something went wrong while creating the Approval Workflow(s).`; 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); const approvalWorkflows = await getApprovalWorkflowsByExamId(exam.id);
if (approvalWorkflows) { if (approvalWorkflows) {
const differences = generateExamDifferences(docSnap as Exam, exam as Exam); const differences = generateExamDifferences(docSnap as Exam, exam as Exam);
if (differences) { if (differences) {
approvalWorkflows.forEach((workflow) => { 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) { if (workflow.steps[currentStepIndex].examChanges === undefined) {
workflow.steps[currentStepIndex].examChanges = [...differences]; workflow.steps[currentStepIndex].examChanges = [...differences];
} else { } else {
@@ -131,7 +139,7 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
} }
} }
} }
res.status(responseStatus).json({ res.status(responseStatus).json({
message: responseMessage, message: responseMessage,
}); });

View File

@@ -69,7 +69,11 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/"); 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[] = [ const allAssigneeIds: string[] = [
...new Set( ...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 { return {
props: serialize({ props: serialize({
user, user,
@@ -103,7 +103,8 @@ interface Props {
} }
export default function ApprovalWorkflows({ user, initialWorkflows, workflowsAssignees, userEntitiesWithLabel }: 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 currentWorkflows = workflows || initialWorkflows;
const [filteredWorkflows, setFilteredWorkflows] = useState<ApprovalWorkflow[]>([]); const [filteredWorkflows, setFilteredWorkflows] = useState<ApprovalWorkflow[]>([]);

View File

@@ -4,11 +4,18 @@ import { ObjectId } from "mongodb";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
export const getApprovalWorkflows = async (collection: string, ids?: string[]) => { export const getApprovalWorkflows = async (collection: string, entityIds?: string[], ids?: string[]) => {
return await db const filters: any = {};
.collection<ApprovalWorkflow>(collection)
.find(ids ? { _id: { $in: ids.map((id) => new ObjectId(id)) } } : {}) if (ids && ids.length > 0) {
.toArray(); filters.id = { $in: ids };
}
if (entityIds && entityIds.length > 0) {
filters.entityId = { $in: entityIds };
}
return await db.collection<ApprovalWorkflow>(collection).find(filters).toArray();
}; };
export const getApprovalWorkflow = async (collection: string, id: string) => { 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) => { export const getApprovalWorkflowsByExamId = async (examId: string) => {
return await db return await db
.collection<ApprovalWorkflow>("active-workflows") .collection<ApprovalWorkflow>("active-workflows")
.find({ .find({
examId, examId,
status: { $in: ["pending"] } status: { $in: ["pending"] },
}) })
.toArray(); .toArray();
}; };