implement initialization of approval workflows on exam creation.

This commit is contained in:
Joao Correia
2025-02-04 22:04:58 +00:00
parent d3385caaf8
commit de15eb5ee1
12 changed files with 175 additions and 20 deletions

View File

@@ -17,6 +17,7 @@ import ListeningComponents from "./listening/components";
import ReadingComponents from "./reading/components"; import ReadingComponents from "./reading/components";
import SpeakingComponents from "./speaking/components"; import SpeakingComponents from "./speaking/components";
import SectionPicker from "./Shared/SectionPicker"; import SectionPicker from "./Shared/SectionPicker";
import { getExamById } from "@/utils/exams";
const LevelSettings: React.FC = () => { const LevelSettings: React.FC = () => {
@@ -213,6 +214,28 @@ 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",
examId: handledExam?.id ?? "Unknown ID",
examModule: "level"
};
})();
await axios.post(`/api/approval-workflows`, requestBody)
.then(() => {
toast.success(`Approval Workflow for exam has been created`);
})
.catch((reason) => {
if (reason.response.status === 404) {
toast.error("No configured workflow found for examAuthor.");
}
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(

View File

@@ -17,6 +17,7 @@ import { usePersistentExamStore } from "@/stores/exam";
import { playSound } from "@/utils/sound"; import { playSound } from "@/utils/sound";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import ListeningComponents from "./components"; import ListeningComponents from "./components";
import { getExamById } from "@/utils/exams";
const ListeningSettings: React.FC = () => { const ListeningSettings: React.FC = () => {
const router = useRouter(); const router = useRouter();
@@ -151,6 +152,28 @@ const ListeningSettings: React.FC = () => {
playSound("sent"); playSound("sent");
toast.success(`Submitted Exam ID: ${result.data.id}`); toast.success(`Submitted Exam ID: ${result.data.id}`);
const requestBody = await (async () => {
const handledExam = await getExamById("listening", result.data.id);
return {
examAuthor: handledExam?.createdBy ?? "Unknown Author",
examId: handledExam?.id ?? "Unknown ID",
examModule: "listening"
};
})();
await axios.post(`/api/approval-workflows`, requestBody)
.then(() => {
toast.success(`Approval Workflow for exam has been created`);
})
.catch((reason) => {
if (reason.response.status === 404) {
toast.error("No configured workflow found for examAuthor.");
}
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.');
} }

View File

@@ -12,6 +12,7 @@ import axios from "axios";
import { playSound } from "@/utils/sound"; import { playSound } from "@/utils/sound";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import ReadingComponents from "./components"; import ReadingComponents from "./components";
import { getExamById } from "@/utils/exams";
const ReadingSettings: React.FC = () => { const ReadingSettings: React.FC = () => {
const router = useRouter(); const router = useRouter();
@@ -89,11 +90,29 @@ const ReadingSettings: React.FC = () => {
.then((result) => { .then((result) => {
playSound("sent"); playSound("sent");
toast.success(`Submitted Exam ID: ${result.data.id}`); toast.success(`Submitted Exam ID: ${result.data.id}`);
return getExamById("reading", result.data.id);
})
.then((handledExam) => {
const requestBody = {
examAuthor: handledExam?.createdBy ?? "Unknown Author",
examId: handledExam?.id ?? "Unknown ID",
examModule: "reading"
};
return axios.post(`/api/approval-workflows`, requestBody);
})
.then(() => {
toast.success(`Approval Workflow for exam has been created`);
}) })
.catch((error) => { .catch((error) => {
console.log(error); if (error.response && error.response.status === 404) {
toast.error(error.response.data.error || "Something went wrong while submitting, please try again later."); toast.error("No configured workflow found for examAuthor.");
}) } else {
// This error could come from either of the requests
toast.error(error.response?.data?.error || "Something went wrong, please try again later.");
}
});
} }
const preview = () => { const preview = () => {

View File

@@ -11,6 +11,7 @@ import openDetachedTab from "@/utils/popout";
import axios from "axios"; import axios from "axios";
import { playSound } from "@/utils/sound"; import { playSound } from "@/utils/sound";
import SpeakingComponents from "./components"; import SpeakingComponents from "./components";
import { getExamById } from "@/utils/exams";
export interface Avatar { export interface Avatar {
name: string; name: string;
@@ -195,6 +196,28 @@ const SpeakingSettings: React.FC = () => {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}); });
const requestBody = await (async () => {
const handledExam = await getExamById("speaking", result.data.id);
return {
examAuthor: handledExam?.createdBy ?? "Unknown Author",
examId: handledExam?.id ?? "Unknown ID",
examModule: "speaking"
};
})();
await axios.post(`/api/approval-workflows`, requestBody)
.then(() => {
toast.success(`Approval Workflow for exam has been created`);
})
.catch((reason) => {
if (reason.response.status === 404) {
toast.error("No configured workflow found for examAuthor.");
}
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."

View File

@@ -12,6 +12,8 @@ import axios from "axios";
import { playSound } from "@/utils/sound"; import { playSound } from "@/utils/sound";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import WritingComponents from "./components"; import WritingComponents from "./components";
import { getExamById } from "@/utils/exams";
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
const WritingSettings: React.FC = () => { const WritingSettings: React.FC = () => {
const router = useRouter(); const router = useRouter();
@@ -140,6 +142,28 @@ const WritingSettings: React.FC = () => {
playSound("sent"); playSound("sent");
toast.success(`Submitted Exam ID: ${result.data.id}`); toast.success(`Submitted Exam ID: ${result.data.id}`);
const requestBody = await (async () => {
const handledExam = await getExamById("writing", result.data.id);
return {
examAuthor: handledExam?.createdBy ?? "Unknown Author",
examId: handledExam?.id ?? "Unknown ID",
examModule: "writing"
};
})();
await axios.post(`/api/approval-workflows`, requestBody)
.then(() => {
toast.success(`Approval Workflow for exam has been created`);
})
.catch((reason) => {
if (reason.response.status === 404) {
toast.error("No configured workflow found for examAuthor.");
}
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(

View File

@@ -9,6 +9,7 @@ export interface ApprovalWorkflow {
requester: User["id"], requester: User["id"],
startDate: number, startDate: number,
modules: Module[], modules: Module[],
examId?: string,
status: ApprovalWorkflowStatus, status: ApprovalWorkflowStatus,
steps: WorkflowStep[], steps: WorkflowStep[],
} }

View File

@@ -25,7 +25,7 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
const { id } = req.query as { id?: string }; const { id } = req.query as { id?: string };
if (id) return res.status(200).json(await deleteApprovalWorkflow("configured-workflows", id)); if (id) return res.status(200).json(await deleteApprovalWorkflow("active-workflows", id));
} }
async function put(req: NextApiRequest, res: NextApiResponse) { async function put(req: NextApiRequest, res: NextApiResponse) {
@@ -41,7 +41,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) {
if (id && workflow) { if (id && workflow) {
workflow._id = new ObjectId(id); workflow._id = new ObjectId(id);
await updateApprovalWorkflow("configured-workflows", workflow); await updateApprovalWorkflow("active-workflows", workflow);
return res.status(204).end(); return res.status(204).end();
} }
} }
@@ -57,6 +57,6 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const { id } = req.query as { id?: string }; const { id } = req.query as { id?: string };
if (id) { if (id) {
return res.status(200).json(await getApprovalWorkflow("configured-workflows", id)); return res.status(200).json(await getApprovalWorkflow("active-workflows", id));
} }
} }

View File

@@ -33,5 +33,5 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
await replaceApprovalWorkflowsByEntities(configuredWorkflows, entitiesIds); await replaceApprovalWorkflowsByEntities(configuredWorkflows, entitiesIds);
return res.status(201).json({ ok: true }); return res.status(204).end();
} }

View File

@@ -1,14 +1,23 @@
// 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 { getApprovalWorkflows } from "@/utils/approval.workflows.be"; import { createApprovalWorkflow, getApprovalWorkflowByFormIntaker, 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,
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) {
@@ -19,5 +28,33 @@ 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("configured-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, examId, examModule } = req.body as PostRequestBody;
if (examAuthor) {
const configuredWorkflow = await getApprovalWorkflowByFormIntaker(examAuthor);
if(configuredWorkflow) {
configuredWorkflow.modules.push(examModule);
configuredWorkflow.name = `${examId}`;
configuredWorkflow.examId = examId;
configuredWorkflow.startDate = Date.now();
return res.status(201).json(await createApprovalWorkflow("active-workflows", configuredWorkflow));
} else {
return res.status(404).json("No configured workflow found for examAuthor.");
}
}
} }

View File

@@ -41,7 +41,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
const { id } = params as { id: string }; const { id } = params as { id: string };
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("configured-workflows", id); const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("active-workflows", id);
if (!workflow) if (!workflow)
return redirect("/approval-workflows") return redirect("/approval-workflows")
@@ -161,7 +161,11 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor
return; return;
}) })
if (selectedStepIndex + 1 < currentWorkflow.steps.length){
handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]); handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]);
} else {
setIsPanelOpen(false);
}
}; };
const handleRejectStep = () => { const handleRejectStep = () => {

View File

@@ -20,7 +20,6 @@ import clsx from "clsx";
import { withIronSessionSsr } from "iron-session/next"; import { withIronSessionSsr } from "iron-session/next";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { BsTrash } from "react-icons/bs"; import { BsTrash } from "react-icons/bs";
import { FaRegEdit } from "react-icons/fa"; import { FaRegEdit } from "react-icons/fa";
@@ -98,6 +97,8 @@ export default function ApprovalWorkflows({ user, initialWorkflows, workflowsAss
const {workflows, reload} = useApprovalWorkflows(); const {workflows, reload} = useApprovalWorkflows();
const currentWorkflows = workflows || initialWorkflows; const currentWorkflows = workflows || initialWorkflows;
console.log(currentWorkflows);
const [filteredWorkflows, setFilteredWorkflows] = useState<ApprovalWorkflow[]>([]); const [filteredWorkflows, setFilteredWorkflows] = useState<ApprovalWorkflow[]>([]);
const [statusFilter, setStatusFilter] = useState<CustomStatus>(undefined); const [statusFilter, setStatusFilter] = useState<CustomStatus>(undefined);

View File

@@ -22,9 +22,9 @@ export const getApprovalWorkflowsByEntities = async (collection: string, ids: st
.toArray(); .toArray();
}; };
export const getApprovalWorkflowByFormIntaker = async (entityId: string, formIntakerId: string) => { export const getApprovalWorkflowByFormIntaker = async (/* entityId: string, */ formIntakerId: string) => {
return await db.collection<ApprovalWorkflow>("configured-workflows").findOne({ return await db.collection<ApprovalWorkflow>("configured-workflows").findOne({
entityId, /* entityId, */
steps: { steps: {
$elemMatch: { $elemMatch: {
stepNumber: 1, stepNumber: 1,