- Fix bug where workflows were being created again after exam update
- Moved createWorkflows function into an helper file instead of a post request. - Moved the workflow creation logic into the post of exam creation instead of a seperate post in each exam module
This commit is contained in:
@@ -205,7 +205,12 @@ const LevelSettings: React.FC = () => {
|
|||||||
|
|
||||||
const result = await axios.post('/api/exam/level', exam);
|
const result = await axios.post('/api/exam/level', exam);
|
||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
// Successfully submitted exam
|
||||||
|
if (result.status === 200) {
|
||||||
|
toast.success(result.data.message);
|
||||||
|
} else if (result.status === 207) {
|
||||||
|
toast.warning(result.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
Array.from(audioMap.values()).forEach(url => {
|
Array.from(audioMap.values()).forEach(url => {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
@@ -214,36 +219,6 @@ const LevelSettings: React.FC = () => {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestBody = await (async () => {
|
|
||||||
const handledExam = await getExamById("level", result.data.id);
|
|
||||||
return {
|
|
||||||
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
|
||||||
examEntities: handledExam?.entities ?? [],
|
|
||||||
examId: handledExam?.id ?? "Unknown ID",
|
|
||||||
examModule: "level"
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
await axios
|
|
||||||
.post(`/api/approval-workflows`, requestBody)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
toast.success(`Approval Workflows for exam have been successfully created`);
|
|
||||||
} else if (response.status === 207) {
|
|
||||||
toast.warning(
|
|
||||||
`Approval Workflows were partially created. Exam author might not have a configured workflow for all its entities.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
if (reason.response?.status === 404) {
|
|
||||||
toast.error("No configured workflow found for examAuthor for any of its entities.");
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
"Something went wrong while creating approval workflow, please try again later."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error submitting exam:', error);
|
console.error('Error submitting exam:', error);
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|||||||
@@ -150,37 +150,12 @@ const ListeningSettings: React.FC = () => {
|
|||||||
|
|
||||||
const result = await axios.post('/api/exam/listening', exam);
|
const result = await axios.post('/api/exam/listening', exam);
|
||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
// Successfully submitted exam
|
||||||
|
if (result.status === 200) {
|
||||||
const requestBody = await (async () => {
|
toast.success(result.data.message);
|
||||||
const handledExam = await getExamById("listening", result.data.id);
|
} else if (result.status === 207) {
|
||||||
return {
|
toast.warning(result.data.message);
|
||||||
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
}
|
||||||
examEntities: handledExam?.entities ?? [],
|
|
||||||
examId: handledExam?.id ?? "Unknown ID",
|
|
||||||
examModule: "listening"
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
await axios
|
|
||||||
.post(`/api/approval-workflows`, requestBody)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
toast.success(`Approval Workflows for exam have been successfully created`);
|
|
||||||
} else if (response.status === 207) {
|
|
||||||
toast.warning(
|
|
||||||
`Approval Workflows were partially created. Exam author might not have a configured workflow for all its entities.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
if (reason.response?.status === 404) {
|
|
||||||
toast.error("No configured workflow found for examAuthor for any of its entities.");
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
"Something went wrong while creating approval workflow, please try again later."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
toast.error('No audio sections found in the exam! Please either import them or generate them.');
|
toast.error('No audio sections found in the exam! Please either import them or generate them.');
|
||||||
|
|||||||
@@ -89,38 +89,17 @@ const ReadingSettings: React.FC = () => {
|
|||||||
axios.post(`/api/exam/reading`, exam)
|
axios.post(`/api/exam/reading`, exam)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
// Successfully submitted exam
|
||||||
return getExamById("reading", result.data.id);
|
if (result.status === 200) {
|
||||||
})
|
toast.success(result.data.message);
|
||||||
.then((handledExam) => {
|
} else if (result.status === 207) {
|
||||||
const requestBody = {
|
toast.warning(result.data.message);
|
||||||
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
|
||||||
examEntities: handledExam?.entities ?? [],
|
|
||||||
examId: handledExam?.id ?? "Unknown ID",
|
|
||||||
examModule: "reading"
|
|
||||||
};
|
|
||||||
|
|
||||||
return axios.post(`/api/approval-workflows`, requestBody);
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
toast.success(`Approval Workflows for exam have been successfully created`);
|
|
||||||
} else if (response.status === 207) {
|
|
||||||
toast.warning(
|
|
||||||
`Approval Workflows were partially created. Exam author might not have a configured workflow for all its entities.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.response && error.response.status === 404) {
|
console.log(error);
|
||||||
toast.error("No configured workflow found for examAuthor for any of its entities.");
|
toast.error(error.response.data.error || "Something went wrong while submitting, please try again later.");
|
||||||
} else {
|
})
|
||||||
toast.error(
|
|
||||||
error.response?.data?.error ||
|
|
||||||
"Something went wrong, please try again later."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const preview = () => {
|
const preview = () => {
|
||||||
|
|||||||
@@ -190,42 +190,17 @@ const SpeakingSettings: React.FC = () => {
|
|||||||
|
|
||||||
const result = await axios.post('/api/exam/speaking', exam);
|
const result = await axios.post('/api/exam/speaking', exam);
|
||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
// Successfully submitted exam
|
||||||
|
if (result.status === 200) {
|
||||||
|
toast.success(result.data.message);
|
||||||
|
} else if (result.status === 207) {
|
||||||
|
toast.warning(result.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
Array.from(urlMap.values()).forEach(url => {
|
Array.from(urlMap.values()).forEach(url => {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestBody = await (async () => {
|
|
||||||
const handledExam = await getExamById("speaking", result.data.id);
|
|
||||||
return {
|
|
||||||
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
|
||||||
examEntities: handledExam?.entities ?? [],
|
|
||||||
examId: handledExam?.id ?? "Unknown ID",
|
|
||||||
examModule: "speaking"
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
await axios
|
|
||||||
.post(`/api/approval-workflows`, requestBody)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
toast.success(`Approval Workflows for exam have been successfully created`);
|
|
||||||
} else if (response.status === 207) {
|
|
||||||
toast.warning(
|
|
||||||
`Approval Workflows were partially created. Exam author might not have a configured workflow for all its entities.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
if (reason.response?.status === 404) {
|
|
||||||
toast.error("No configured workflow found for examAuthor for any of its entities.");
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
"Something went wrong while creating approval workflow, please try again later."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(
|
toast.error(
|
||||||
"Something went wrong while submitting, please try again later."
|
"Something went wrong while submitting, please try again later."
|
||||||
|
|||||||
@@ -140,37 +140,12 @@ const WritingSettings: React.FC = () => {
|
|||||||
|
|
||||||
const result = await axios.post(`/api/exam/writing`, exam)
|
const result = await axios.post(`/api/exam/writing`, exam)
|
||||||
playSound("sent");
|
playSound("sent");
|
||||||
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
// Successfully submitted exam
|
||||||
|
if (result.status === 200) {
|
||||||
const requestBody = await (async () => {
|
toast.success(result.data.message);
|
||||||
const handledExam = await getExamById("writing", result.data.id);
|
} else if (result.status === 207) {
|
||||||
return {
|
toast.warning(result.data.message);
|
||||||
examAuthor: handledExam?.createdBy ?? "Unknown Author",
|
}
|
||||||
examEntities: handledExam?.entities ?? [],
|
|
||||||
examId: handledExam?.id ?? "Unknown ID",
|
|
||||||
examModule: "writing"
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
await axios
|
|
||||||
.post(`/api/approval-workflows`, requestBody)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
toast.success(`Approval Workflows for exam have been successfully created`);
|
|
||||||
} else if (response.status === 207) {
|
|
||||||
toast.warning(
|
|
||||||
`Approval Workflows were partially created. Exam author might not have a configured workflow for all its entities.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
if (reason.response?.status === 404) {
|
|
||||||
toast.error("No configured workflow found for examAuthor for any of its entities.");
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
"Something went wrong while creating approval workflow, please try again later."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error submitting exam:', error);
|
console.error('Error submitting exam:', error);
|
||||||
|
|||||||
34
src/lib/createWorkflowsOnExamCreation.ts
Normal file
34
src/lib/createWorkflowsOnExamCreation.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Module } from "@/interfaces";
|
||||||
|
import { getApprovalWorkflowByFormIntaker, createApprovalWorkflow } from "@/utils/approval.workflows.be";
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (!configuredWorkflow) {
|
||||||
|
return { entity, created: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return { entity, created: true };
|
||||||
|
} catch (error: any) {
|
||||||
|
return { entity, created: false };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const successCount = results.filter((r) => r.created).length;
|
||||||
|
const totalCount = examEntities.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
successCount,
|
||||||
|
totalCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,24 +1,14 @@
|
|||||||
// 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 { sessionOptions } from "@/lib/session";
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
import { createApprovalWorkflow, getApprovalWorkflowByFormIntaker, getApprovalWorkflows } from "@/utils/approval.workflows.be";
|
import { getApprovalWorkflows } from "@/utils/approval.workflows.be";
|
||||||
import { withIronSessionApiRoute } from "iron-session/next";
|
import { withIronSessionApiRoute } from "iron-session/next";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||||
|
|
||||||
interface PostRequestBody {
|
|
||||||
examAuthor: string;
|
|
||||||
examEntities: string[];
|
|
||||||
examId: string;
|
|
||||||
examName: string;
|
|
||||||
examModule: Module;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (req.method === "GET") return await get(req, res);
|
if (req.method === "GET") return await get(req, res);
|
||||||
if (req.method === "POST") return await post(req, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(req: NextApiRequest, res: NextApiResponse) {
|
async function get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -31,48 +21,3 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
return res.status(200).json(await getApprovalWorkflows("active-workflows"));
|
return res.status(200).json(await getApprovalWorkflows("active-workflows"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function post(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
const user = await requestUser(req, res);
|
|
||||||
if (!user) return res.status(401).json({ ok: false });
|
|
||||||
|
|
||||||
if (!["admin", "developer", "corporate", "mastercorporate"].includes(user.type)) {
|
|
||||||
return res.status(403).json({ ok: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { examAuthor, examEntities, examId, examModule } = req.body as PostRequestBody;
|
|
||||||
|
|
||||||
const results = await Promise.all(
|
|
||||||
examEntities.map(async (entity) => {
|
|
||||||
const configuredWorkflow = await getApprovalWorkflowByFormIntaker(entity, examAuthor);
|
|
||||||
if (!configuredWorkflow) {
|
|
||||||
return { entity, created: false, error: "No configured workflow found for examAuthor." };
|
|
||||||
}
|
|
||||||
|
|
||||||
configuredWorkflow.modules.push(examModule);
|
|
||||||
configuredWorkflow.name = `${examId}`;
|
|
||||||
configuredWorkflow.examId = examId;
|
|
||||||
configuredWorkflow.entityId = entity;
|
|
||||||
configuredWorkflow.startDate = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const creationResponse = await createApprovalWorkflow("active-workflows", configuredWorkflow);
|
|
||||||
return { entity, created: true, creationResponse };
|
|
||||||
} catch (error) {
|
|
||||||
const err = error as Error;
|
|
||||||
return { entity, created: false, error: err.message };
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const successCount = results.filter((r) => r.created).length;
|
|
||||||
const totalCount = examEntities.length;
|
|
||||||
|
|
||||||
if (successCount === totalCount) {
|
|
||||||
return res.status(200).json({ ok: true, results });
|
|
||||||
} else if (successCount > 0) {
|
|
||||||
return res.status(207).json({ ok: true, results });
|
|
||||||
} else {
|
|
||||||
return res.status(404).json({ ok: false, message: "No workflows were created", results });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
// 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 type { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import client from "@/lib/mongodb";
|
|
||||||
import { withIronSessionApiRoute } from "iron-session/next";
|
|
||||||
import { sessionOptions } from "@/lib/session";
|
|
||||||
import { Exam, ExamBase, InstructorGender, Variant } from "@/interfaces/exam";
|
|
||||||
import { getExams } from "@/utils/exams.be";
|
|
||||||
import { Module } from "@/interfaces";
|
import { Module } from "@/interfaces";
|
||||||
import { getUserCorporate } from "@/utils/groups.be";
|
import { Exam, ExamBase, InstructorGender, Variant } from "@/interfaces/exam";
|
||||||
import { requestUser } from "@/utils/api";
|
import { createApprovalWorkflowsOnExamCreation } from "@/lib/createWorkflowsOnExamCreation";
|
||||||
import { isAdmin } from "@/utils/users";
|
import client from "@/lib/mongodb";
|
||||||
|
import { sessionOptions } from "@/lib/session";
|
||||||
import { mapBy } from "@/utils";
|
import { mapBy } from "@/utils";
|
||||||
|
import { requestUser } from "@/utils/api";
|
||||||
|
import { getExams } from "@/utils/exams.be";
|
||||||
|
import { isAdmin } from "@/utils/users";
|
||||||
|
import { withIronSessionApiRoute } from "iron-session/next";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
const db = client.db(process.env.MONGODB_DB);
|
const db = client.db(process.env.MONGODB_DB);
|
||||||
|
|
||||||
@@ -46,7 +46,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");
|
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.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exam = {
|
const exam = {
|
||||||
@@ -57,6 +57,9 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let responseStatus: number;
|
||||||
|
let responseMessage: string;
|
||||||
|
|
||||||
await session.withTransaction(async () => {
|
await session.withTransaction(async () => {
|
||||||
const docSnap = await db.collection(module).findOne<ExamBase>({ id: req.body.id }, { session });
|
const docSnap = await db.collection(module).findOne<ExamBase>({ id: req.body.id }, { session });
|
||||||
|
|
||||||
@@ -79,9 +82,38 @@ async function POST(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
session,
|
session,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).json(exam);
|
// 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);
|
||||||
|
|
||||||
|
if (successCount === totalCount) {
|
||||||
|
responseStatus = 200;
|
||||||
|
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`;
|
||||||
|
} else {
|
||||||
|
responseStatus = 207;
|
||||||
|
responseMessage = `Successfully created exam with ID: "${exam.id}" but was not able to find any configured Approval Workflow for the 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).`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(responseStatus).json({
|
||||||
|
message: responseMessage,
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Transaction failed: ", error);
|
console.error("Transaction failed: ", error);
|
||||||
res.status(500).json({ ok: false, error: (error as any).message });
|
res.status(500).json({ ok: false, error: (error as any).message });
|
||||||
|
|||||||
Reference in New Issue
Block a user