Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
} from "@mui/material";
import { ReactComponent as CloseIcon } from "../../../assets/icons/close.svg";
import DropDowns from "../../Inputs/Dropdowns";
import { useState, useContext, useEffect } from "react";
import { useState, useContext } from "react";
import AuditorFeedback from "../ComplianceFeedback/ComplianceFeedback";
import { getEntityById, updateEntityById } from "../../../../application/repository/entity.repository";
import { updateEntityById } from "../../../../application/repository/entity.repository";
import { Subcontrol } from "../../../../domain/types/Subcontrol";
import { Control } from "../../../../domain/types/Control";
import { FileData } from "../../../../domain/types/File";
Expand All @@ -22,7 +22,6 @@ import VWToast from "../../../vw-v2-components/Toast";
import SaveIcon from "@mui/icons-material/Save";
import VWButton from "../../../vw-v2-components/Buttons";
import { VerifyWiseContext } from "../../../../application/contexts/VerifyWise.context";
import VWSkeleton from "../../../vw-v2-components/Skeletons";

const tabStyle = {
textTransform: "none",
Expand All @@ -37,29 +36,25 @@ const tabStyle = {
};

const NewControlPane = ({
_data,
data,
isOpen,
handleClose,
controlCategoryId,
OnSave,
OnError,
onComplianceUpdate,
projectId,
projectFrameworkId,
}: {
_data: Control;
data: Control;
isOpen: boolean;
handleClose: () => void;
controlCategoryId?: string;
OnSave?: (state: Control) => void;
OnError?: () => void;
onComplianceUpdate?: () => void;
projectId: number;
projectFrameworkId: number;
}) => {
const theme = useTheme();
const [data, setData] = useState<Control>(_data);
const [loading, setLoading] = useState<boolean>(true);
const [selectedTab, setSelectedTab] = useState<number>(0);
const [activeSection, setActiveSection] = useState<string>("Overview");
const [alert, setAlert] = useState<{
Expand All @@ -77,43 +72,14 @@ const NewControlPane = ({
}>({});
const context = useContext(VerifyWiseContext);

useEffect(() => {
const fetchControls = async () => {
setLoading(true);
const response = await getEntityById({
routeUrl: `eu-ai-act/controlById?controlId=${_data.id}&projectFrameworkId=${projectFrameworkId}`,
});
setData(response.data);
setLoading(false);
setState({
order_no: response.data.order_no,
id: response.data.id,
title: response.data.title,
description: response.data.description,
status: response.data.status,
approver: response.data.approver,
risk_review: response.data.risk_review,
owner: response.data.owner,
reviewer: response.data.reviewer,
implementation_details: response.data.implementation_details,
due_date: response.data.due_date,
control_category_id: response.data.control_category_id, // Added missing property

subControls: response.data.subControls,
})
};

fetchControls();
}, [isOpen])

const sanitizeField = (value: string | undefined | null): string => {
if (!value || value === "undefined") {
return "";
}
return value;
};

const initialSubControlState = (data.subControls || []).length > 0 && data
const initialSubControlState = data
.subControls!.slice()
.sort((a, b) => a.order_no! - b.order_no!)
.map((subControl: Subcontrol) => ({
Expand Down Expand Up @@ -366,16 +332,6 @@ const NewControlPane = ({
handleClose();
};

if (loading) {
return (
<Stack spacing={2}>
<VWSkeleton variant="rectangular" width="100%" height={36} />
<VWSkeleton variant="rectangular" width="100%" height={36} />
<VWSkeleton variant="rectangular" width="100%" height={36} />
</Stack>
);
}

return (
<>
{alert && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const ControlsTable: React.FC<ControlsTableProps> = ({
const { users } = dashboardValues;
const currentProjectId = projectId;
const [controls, setControls] = useState<Control[]>([]);
const [selectedControl, setSelectedControl] = useState<Control | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<unknown>(null);
const [selectedRow, setSelectedRow] = useState<number | null>(null);
Expand All @@ -72,7 +73,11 @@ const ControlsTable: React.FC<ControlsTableProps> = ({
setAlert(null);
}, [currentProjectId]);

const handleRowClick = (id: number) => {
const handleRowClick = async (id: number) => {
const subControlsResponse = await getEntityById({
routeUrl: `eu-ai-act/controlById?controlId=${id}&projectFrameworkId=${projectFrameworkId}`,
});
setSelectedControl(subControlsResponse.data);
setSelectedRow(id);
setModalOpen(true);
};
Expand Down Expand Up @@ -125,7 +130,7 @@ const ControlsTable: React.FC<ControlsTableProps> = ({
setLoading(true);
try {
const response = await getEntityById({
routeUrl: `/eu-ai-act/controls/byControlCategoryId/${controlCategoryId}`,
routeUrl: `/eu-ai-act/controls/byControlCategoryId/${controlCategoryId}?projectFrameworkId=${projectFrameworkId}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work. Now we could move the logic inside this useEffect to its hook, useControls, and call it here to obtain controls.

});
setControls(response);
} catch (err) {
Expand Down Expand Up @@ -278,15 +283,14 @@ const ControlsTable: React.FC<ControlsTableProps> = ({
</TableContainer>
{modalOpen && selectedRow !== null && (
<NewControlPane
_data={controls.find((c) => c.id === selectedRow)!}
data={selectedControl!}
isOpen={modalOpen}
handleClose={handleCloseModal}
OnSave={handleSaveSuccess}
OnError={handleSaveError}
controlCategoryId={controlCategoryIndex?.toString()}
onComplianceUpdate={onComplianceUpdate}
projectId={currentProjectId}
projectFrameworkId={projectFrameworkId}
/>
)}
</>
Expand Down
13 changes: 7 additions & 6 deletions Servers/controllers/eu.ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getAllProjectsQuery, updateProjectUpdatedByIdQuery } from "../utils/pro
import { RequestWithFile, UploadedFile } from "../utils/question.utils";
import { STATUS_CODE } from "../utils/statusCode.utils";
import { QuestionStructEU } from "../models/EU/questionStructEU.model";
import { countAnswersEUByProjectId, countSubControlsEUByProjectId, deleteAssessmentEUByProjectIdQuery, deleteComplianeEUByProjectIdQuery, getAllControlCategoriesQuery, getAllTopicsQuery, getAssessmentsEUByProjectIdQuery, getComplianceEUByProjectIdQuery, getControlByIdForProjectQuery, getControlStructByControlCategoryIdQuery, getTopicByIdForProjectQuery, updateControlEUByIdQuery, updateQuestionEUByIdQuery, updateSubcontrolEUByIdQuery } from "../utils/eu.utils";
import { countAnswersEUByProjectId, countSubControlsEUByProjectId, deleteAssessmentEUByProjectIdQuery, deleteComplianeEUByProjectIdQuery, getAllControlCategoriesQuery, getAllTopicsQuery, getAssessmentsEUByProjectIdQuery, getComplianceEUByProjectIdQuery, getControlByIdForProjectQuery, getControlStructByControlCategoryIdForAProjectQuery, getControlStructByControlCategoryIdQuery, getTopicByIdForProjectQuery, updateControlEUByIdQuery, updateQuestionEUByIdQuery, updateSubcontrolEUByIdQuery } from "../utils/eu.utils";
import { AnswerEU } from "../models/EU/answerEU.model";
import { sequelize } from "../database/db";
import { Project, ProjectModel } from "../models/project.model";
Expand Down Expand Up @@ -292,8 +292,8 @@ export async function getProjectAssessmentProgress(req: Request, res: Response)
const { totalAssessments, answeredAssessments } = await countAnswersEUByProjectId(projectFrameworkId);
return res.status(200).json(
STATUS_CODE[200]({
totalQuestions: totalAssessments,
answeredQuestions: answeredAssessments,
totalQuestions: parseInt(totalAssessments),
answeredQuestions: parseInt(answeredAssessments),
})
);
} catch (error) {
Expand All @@ -313,8 +313,8 @@ export async function getProjectComplianceProgress(req: Request, res: Response)
const { totalSubcontrols, doneSubcontrols } = await countSubControlsEUByProjectId(projectFrameworkId);
return res.status(200).json(
STATUS_CODE[200]({
allsubControls: totalSubcontrols,
allDonesubControls: doneSubcontrols,
allsubControls: parseInt(totalSubcontrols),
allDonesubControls: parseInt(doneSubcontrols),
})
);
} catch (error) {
Expand Down Expand Up @@ -412,7 +412,8 @@ export async function getControlsByControlCategoryId(
): Promise<any> {
try {
const controlCategoryId = parseInt(req.params.id);
const controls = await getControlStructByControlCategoryIdQuery(controlCategoryId);
const projectFrameworkId = parseInt(req.query.projectFrameworkId as string);
const controls = await getControlStructByControlCategoryIdForAProjectQuery(controlCategoryId, projectFrameworkId);
return res.status(200).json(controls);
Comment on lines 414 to 417
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Added project framework filtering to control retrieval.

The function now accepts a projectFrameworkId query parameter and uses it to filter controls by both category and project framework. This resolves the issue where sub-controls were undefined due to missing project context.

You should add validation for the projectFrameworkId parameter to handle cases where it might be missing or invalid:

  try {
    const controlCategoryId = parseInt(req.params.id);
    const projectFrameworkId = parseInt(req.query.projectFrameworkId as string);
+   if (isNaN(projectFrameworkId)) {
+     return res.status(400).json(STATUS_CODE[400]("Invalid projectFrameworkId"));
+   }
    const controls = await getControlStructByControlCategoryIdForAProjectQuery(controlCategoryId, projectFrameworkId);
    return res.status(200).json(controls);
  } catch (error) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const controlCategoryId = parseInt(req.params.id);
const controls = await getControlStructByControlCategoryIdQuery(controlCategoryId);
const projectFrameworkId = parseInt(req.query.projectFrameworkId as string);
const controls = await getControlStructByControlCategoryIdForAProjectQuery(controlCategoryId, projectFrameworkId);
return res.status(200).json(controls);
try {
const controlCategoryId = parseInt(req.params.id);
const projectFrameworkId = parseInt(req.query.projectFrameworkId as string);
if (isNaN(projectFrameworkId)) {
return res.status(400).json(STATUS_CODE[400]("Invalid projectFrameworkId"));
}
const controls = await getControlStructByControlCategoryIdForAProjectQuery(
controlCategoryId,
projectFrameworkId
);
return res.status(200).json(controls);
} catch (error) {
next(error);
}

} catch (error) {
return res.status(500).json(STATUS_CODE[500]((error as Error).message));
Expand Down
47 changes: 45 additions & 2 deletions Servers/utils/eu.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@ export const getTopicByIdForProjectQuery = async (
return topic;
}

const getSubControlsCalculations = async (
controlId: number,
) => {
const result = await sequelize.query(
`SELECT COUNT(*) AS "numberOfSubcontrols", COUNT(CASE WHEN sc.status = 'Done' THEN 1 END) AS "numberOfDoneSubcontrols" FROM
controls_eu c JOIN subcontrols_eu sc ON c.id = sc.control_id WHERE c.id = :control_id;`,
{
replacements: { control_id: controlId },
}
) as [{ numberOfSubcontrols: string; numberOfDoneSubcontrols: string }[], number];
return result[0][0] as {
numberOfSubcontrols: string;
numberOfDoneSubcontrols: string;
};
}

export const getControlByIdForProjectQuery = async (
controlStructId: number,
projectFrameworkId: number
Expand All @@ -139,6 +155,9 @@ export const getControlByIdForProjectQuery = async (
for (let subControl of subControls) {
(control as any).subControls.push({ ...subControl });
}
const subControlsCalculations = await getSubControlsCalculations(control.id!);
(control as any).numberOfSubcontrols = parseInt(subControlsCalculations.numberOfSubcontrols);
(control as any).numberOfDoneSubcontrols = parseInt(subControlsCalculations.numberOfDoneSubcontrols);
return control;
}

Expand Down Expand Up @@ -251,6 +270,27 @@ export const getControlStructByControlCategoryIdQuery = async (
return controlsStruct;
}

export const getControlStructByControlCategoryIdForAProjectQuery = async (
controlCategoryId: number,
projectFrameworkId: number,
) => {
const controlsStruct = await sequelize.query(
`SELECT cs.*, c.owner FROM controls_struct_eu cs JOIN controls_eu c ON cs.id = c.control_meta_id
WHERE cs.control_category_id = :control_category_id AND c.projects_frameworks_id = :projects_frameworks_id;`,
{
replacements: {
control_category_id: controlCategoryId, projects_frameworks_id: projectFrameworkId
}
}
) as [Partial<ControlStructEUModel & ControlEUModel>[], number];
for (let control of controlsStruct[0]) {
const subControlsCalculations = await getSubControlsCalculations(control.id!);
(control as any).numberOfSubcontrols = parseInt(subControlsCalculations.numberOfSubcontrols);
(control as any).numberOfDoneSubcontrols = parseInt(subControlsCalculations.numberOfDoneSubcontrols);
}
return controlsStruct[0];
}

export const getControlByIdQuery = async (
controlId: number,
transaction: Transaction | null = null
Expand Down Expand Up @@ -571,6 +611,9 @@ export const updateControlEUByIdQuery = async (
return true
}
}).map(f => `${f} = :${f}`).join(", ");
if (!setClause) {
return control as ControlEU;
}

const query = `UPDATE controls_eu SET ${setClause} WHERE id = :id RETURNING *;`;

Expand All @@ -593,7 +636,7 @@ export const updateSubcontrolEUByIdQuery = async (
feedbackUploadedFiles: { id: string; fileName: string, project_id: number, uploaded_by: number, uploaded_time: Date }[] = [],
deletedFiles: number[] = [],
transaction: Transaction
): Promise<SubcontrolEU | null> => {
): Promise<SubcontrolEU> => {
const files = await sequelize.query(
`SELECT evidence_files, feedback_files FROM subcontrols_eu WHERE id = :id`,
{
Expand Down Expand Up @@ -649,7 +692,7 @@ export const updateSubcontrolEUByIdQuery = async (
}).join(", ");

if (setClause.length === 0) {
return null;
return subcontrol as SubcontrolEU;
}

const query = `UPDATE subcontrols_eu SET ${setClause} WHERE id = :id RETURNING *;`;
Expand Down