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 SpeakingComponents from "./speaking/components";
import SectionPicker from "./Shared/SectionPicker";
import { getExamById } from "@/utils/exams";
const LevelSettings: React.FC = () => {
@@ -213,6 +214,28 @@ const LevelSettings: React.FC = () => {
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) {
console.error('Error submitting exam:', error);
toast.error(

View File

@@ -17,6 +17,7 @@ import { usePersistentExamStore } from "@/stores/exam";
import { playSound } from "@/utils/sound";
import { toast } from "react-toastify";
import ListeningComponents from "./components";
import { getExamById } from "@/utils/exams";
const ListeningSettings: React.FC = () => {
const router = useRouter();
@@ -151,6 +152,28 @@ const ListeningSettings: React.FC = () => {
playSound("sent");
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 {
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 { toast } from "react-toastify";
import ReadingComponents from "./components";
import { getExamById } from "@/utils/exams";
const ReadingSettings: React.FC = () => {
const router = useRouter();
@@ -46,15 +47,15 @@ const ReadingSettings: React.FC = () => {
{
label: "Preset: Reading Passage 1",
value: "Welcome to {part} of the {label}. You will read texts relating to everyday topics and situations. These may include advertisements, brochures, manuals, or official documents. Answer questions that test your ability to locate specific information and understand main ideas."
},
{
label: "Preset: Reading Passage 2",
},
{
label: "Preset: Reading Passage 2",
value: "Welcome to {part} of the {label}. You will read texts dealing with general interest topics that may include news articles, company policies, or workplace documents. Answer questions testing your understanding of main ideas, specific details, and the author's views."
},
{
},
{
label: "Preset: Reading Passage 3",
value: "Welcome to {part} of the {label}. You will read longer academic texts that may include journal articles, academic essays, or research papers. Answer questions testing your ability to understand complex arguments, identify key points, and follow the development of ideas."
}
}
];
const canPreviewOrSubmit = sections.some(
@@ -89,11 +90,29 @@ const ReadingSettings: React.FC = () => {
.then((result) => {
playSound("sent");
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) => {
console.log(error);
toast.error(error.response.data.error || "Something went wrong while submitting, please try again later.");
})
if (error.response && error.response.status === 404) {
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 = () => {

View File

@@ -11,6 +11,7 @@ import openDetachedTab from "@/utils/popout";
import axios from "axios";
import { playSound } from "@/utils/sound";
import SpeakingComponents from "./components";
import { getExamById } from "@/utils/exams";
export interface Avatar {
name: string;
@@ -194,6 +195,28 @@ const SpeakingSettings: React.FC = () => {
Array.from(urlMap.values()).forEach(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) {
toast.error(

View File

@@ -12,6 +12,8 @@ import axios from "axios";
import { playSound } from "@/utils/sound";
import { toast } from "react-toastify";
import WritingComponents from "./components";
import { getExamById } from "@/utils/exams";
import { ApprovalWorkflow } from "@/interfaces/approval.workflow";
const WritingSettings: React.FC = () => {
const router = useRouter();
@@ -140,6 +142,28 @@ const WritingSettings: React.FC = () => {
playSound("sent");
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) {
console.error('Error submitting exam:', error);
toast.error(

View File

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

View File

@@ -25,7 +25,7 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
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) {
@@ -41,7 +41,7 @@ async function put(req: NextApiRequest, res: NextApiResponse) {
if (id && workflow) {
workflow._id = new ObjectId(id);
await updateApprovalWorkflow("configured-workflows", workflow);
await updateApprovalWorkflow("active-workflows", workflow);
return res.status(204).end();
}
}
@@ -57,6 +57,6 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const { id } = req.query as { id?: string };
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);
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
import { Module } from "@/interfaces";
import { sessionOptions } from "@/lib/session";
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 type { NextApiRequest, NextApiResponse } from "next";
export default withIronSessionApiRoute(handler, sessionOptions);
interface PostRequestBody {
examAuthor: string,
examId: string,
examName: string,
examModule: Module,
}
async function handler(req: NextApiRequest, res: NextApiResponse) {
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) {
@@ -19,5 +28,33 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
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 workflow: ApprovalWorkflow | null = await getApprovalWorkflow("configured-workflows", id);
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("active-workflows", id);
if (!workflow)
return redirect("/approval-workflows")
@@ -161,7 +161,11 @@ export default function Home({ user, initialWorkflow, id, workflowAssignees, wor
return;
})
handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]);
if (selectedStepIndex + 1 < currentWorkflow.steps.length){
handleStepClick(selectedStepIndex + 1, currentWorkflow.steps[selectedStepIndex + 1]);
} else {
setIsPanelOpen(false);
}
};
const handleRejectStep = () => {

View File

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

View File

@@ -22,9 +22,9 @@ export const getApprovalWorkflowsByEntities = async (collection: string, ids: st
.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({
entityId,
/* entityId, */
steps: {
$elemMatch: {
stepNumber: 1,