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
4 changes: 2 additions & 2 deletions apps/app/languine.lock
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,9 @@ files:
frameworks.controls.table.control: a1595abbb4c3a326636dd178757cd6c1
frameworks.controls.table.artifacts: 08004d9d4c599f44814a861568d200cb
frameworks.controls.table.actions: 06df33001c1d7187fdd81ea1f5b277aa
frameworks.controls.statuses.completed: 07ca5050e697392c9ed47e6453f1453f
frameworks.controls.statuses.in_progress: c9413dd7682a45da9ecc9efa8cea47d7
frameworks.controls.statuses.not_started: 10db56f8b10dc989c43fbe33202e5339
frameworks.controls.statuses.compliant: 952358fc53c0167a1ea7354f338ed5af
frameworks.controls.statuses.non_compliant: a0ecb19f7c90c0d5c1e919aec4d33aac
vendor.title: 2938c7f7e560ed972f8a4f68e80ff834
vendor.register_title: 97f6291ea1000272b34fdfaeb3a9e2d2
vendor.dashboard.title: 2938c7f7e560ed972f8a4f68e80ff834
Expand Down
2 changes: 2 additions & 0 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@bubba/notifications": "workspace:*",
"@date-fns/tz": "^1.2.0",
"@hookform/resolvers": "^3.10.0",
"@nangohq/frontend": "^0.53.1",
"@novu/headless": "^2.6.5",
"@novu/react": "^2.6.5",
"@prisma/instrumentation": "^6.3.1",
Expand Down Expand Up @@ -45,6 +46,7 @@
"next-intl": "^3.26.3",
"next-safe-action": "^7.10.3",
"next-themes": "^0.4.4",
"novel": "^1.0.2",
"novu": "^2.6.5",
"nuqs": "^2.3.2",
"playwright-core": "^1.50.1",
Expand Down
75 changes: 74 additions & 1 deletion apps/app/src/actions/framework/select-frameworks-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const selectFrameworksAction = authActionClient
await createOrganizationCategories(user as User, frameworkIds);

// Then create frameworks and controls
await Promise.all(
const organizationFrameworks = await Promise.all(
frameworkIds.map((frameworkId) =>
createOrganizationFramework(user as User, frameworkId)
)
Expand All @@ -43,6 +43,12 @@ export const selectFrameworksAction = authActionClient
// Finally create policies
await createOrganizationPolicy(user as User, frameworkIds);

// Finally create control requirements
await createOrganizationControlRequirements(
user as User,
organizationFrameworks.map((framework) => framework.id)
);

return {
data: true,
};
Expand Down Expand Up @@ -105,6 +111,8 @@ const createOrganizationFramework = async (user: User, frameworkId: string) => {
})),
});
}

return organizationFramework;
};

const createOrganizationPolicy = async (user: User, frameworkIds: string[]) => {
Expand Down Expand Up @@ -171,3 +179,68 @@ const createOrganizationCategories = async (

return organizationCategories;
};

const createOrganizationControlRequirements = async (
user: User,
organizationFrameworkIds: string[]
) => {
if (!user.organizationId) {
throw new Error("Not authorized - no organization found");
}

const controls = await db.organizationControl.findMany({
where: {
organizationId: user.organizationId!,
organizationFrameworkId: {
in: organizationFrameworkIds,
},
},
include: {
control: true,
},
});

// Create control requirements for each control
const controlRequirements = await db.controlRequirement.findMany({
where: {
controlId: { in: controls.map((control) => control.controlId) },
},
include: {
policy: true, // Include the policy to get its ID
},
});

// Get all organization policies for this organization
const organizationPolicies = await db.organizationPolicy.findMany({
where: {
organizationId: user.organizationId,
},
});

for (const control of controls) {
const requirements = controlRequirements.filter(
(req) => req.controlId === control.controlId
);

await db.organizationControlRequirement.createMany({
data: requirements.map((requirement) => {
// Find the corresponding organization policy if this is a policy requirement
const policyId =
requirement.type === "policy" ? requirement.policy?.id : null;
const organizationPolicy = policyId
? organizationPolicies.find((op) => op.policyId === policyId)
: null;

return {
organizationControlId: control.id,
controlRequirementId: requirement.id,
type: requirement.type,
description: requirement.description,
organizationPolicyId: organizationPolicy?.id || null,
};
}),
});
}

return controlRequirements;
};
Comment on lines +183 to +246

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling and type safety.

Consider adding error handling for database operations and improving type safety.

 const createOrganizationControlRequirements = async (
   user: User,
   organizationFrameworkIds: string[]
 ) => {
   if (!user.organizationId) {
     throw new Error("Not authorized - no organization found");
   }

+  try {
     const controls = await db.organizationControl.findMany({
       where: {
         organizationId: user.organizationId!,
         organizationFrameworkId: {
           in: organizationFrameworkIds,
         },
       },
       include: {
         control: true,
       },
     });

+    if (controls.length === 0) {
+      console.warn("No controls found for the given organization frameworks");
+      return [];
+    }

     const controlRequirements = await db.controlRequirement.findMany({
       where: {
         controlId: { in: controls.map((control) => control.controlId) },
       },
       include: {
         policy: true,
       },
     });

+    if (controlRequirements.length === 0) {
+      console.warn("No control requirements found for the given controls");
+      return [];
+    }

     const organizationPolicies = await db.organizationPolicy.findMany({
       where: {
         organizationId: user.organizationId,
       },
     });

     for (const control of controls) {
       const requirements = controlRequirements.filter(
         (req) => req.controlId === control.controlId
       );

+      if (requirements.length === 0) {
+        console.warn(`No requirements found for control ${control.controlId}`);
+        continue;
+      }

       await db.organizationControlRequirement.createMany({
         data: requirements.map((requirement) => {
           const policyId =
             requirement.type === "policy" ? requirement.policy?.id : null;
           const organizationPolicy = policyId
             ? organizationPolicies.find((op) => op.policyId === policyId)
             : null;

           return {
             organizationControlId: control.id,
             controlRequirementId: requirement.id,
             type: requirement.type,
             description: requirement.description,
             organizationPolicyId: organizationPolicy?.id || null,
           };
         }),
       });
     }

     return controlRequirements;
+  } catch (error) {
+    console.error("Error creating organization control requirements:", error);
+    throw new Error("Failed to create organization control requirements");
+  }
 };
📝 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 createOrganizationControlRequirements = async (
user: User,
organizationFrameworkIds: string[]
) => {
if (!user.organizationId) {
throw new Error("Not authorized - no organization found");
}
const controls = await db.organizationControl.findMany({
where: {
organizationId: user.organizationId!,
organizationFrameworkId: {
in: organizationFrameworkIds,
},
},
include: {
control: true,
},
});
// Create control requirements for each control
const controlRequirements = await db.controlRequirement.findMany({
where: {
controlId: { in: controls.map((control) => control.controlId) },
},
include: {
policy: true, // Include the policy to get its ID
},
});
// Get all organization policies for this organization
const organizationPolicies = await db.organizationPolicy.findMany({
where: {
organizationId: user.organizationId,
},
});
for (const control of controls) {
const requirements = controlRequirements.filter(
(req) => req.controlId === control.controlId
);
await db.organizationControlRequirement.createMany({
data: requirements.map((requirement) => {
// Find the corresponding organization policy if this is a policy requirement
const policyId =
requirement.type === "policy" ? requirement.policy?.id : null;
const organizationPolicy = policyId
? organizationPolicies.find((op) => op.policyId === policyId)
: null;
return {
organizationControlId: control.id,
controlRequirementId: requirement.id,
type: requirement.type,
description: requirement.description,
organizationPolicyId: organizationPolicy?.id || null,
};
}),
});
}
return controlRequirements;
};
const createOrganizationControlRequirements = async (
user: User,
organizationFrameworkIds: string[]
) => {
if (!user.organizationId) {
throw new Error("Not authorized - no organization found");
}
try {
const controls = await db.organizationControl.findMany({
where: {
organizationId: user.organizationId!,
organizationFrameworkId: {
in: organizationFrameworkIds,
},
},
include: {
control: true,
},
});
if (controls.length === 0) {
console.warn("No controls found for the given organization frameworks");
return [];
}
const controlRequirements = await db.controlRequirement.findMany({
where: {
controlId: { in: controls.map((control) => control.controlId) },
},
include: {
policy: true, // Include the policy to get its ID
},
});
if (controlRequirements.length === 0) {
console.warn("No control requirements found for the given controls");
return [];
}
const organizationPolicies = await db.organizationPolicy.findMany({
where: {
organizationId: user.organizationId,
},
});
for (const control of controls) {
const requirements = controlRequirements.filter(
(req) => req.controlId === control.controlId
);
if (requirements.length === 0) {
console.warn(`No requirements found for control ${control.controlId}`);
continue;
}
await db.organizationControlRequirement.createMany({
data: requirements.map((requirement) => {
const policyId =
requirement.type === "policy" ? requirement.policy?.id : null;
const organizationPolicy = policyId
? organizationPolicies.find((op) => op.policyId === policyId)
: null;
return {
organizationControlId: control.id,
controlRequirementId: requirement.id,
type: requirement.type,
description: requirement.description,
organizationPolicyId: organizationPolicy?.id || null,
};
}),
});
}
return controlRequirements;
} catch (error) {
console.error("Error creating organization control requirements:", error);
throw new Error("Failed to create organization control requirements");
}
};

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use server";

import { db, OrganizationControl, Control } from "@bubba/db";
import { authActionClient } from "@/actions/safe-action";
import { db, type Control, type OrganizationControl } from "@bubba/db";
import { z } from "zod";

export interface OrganizationControlResponse {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"use server";

import { authActionClient } from "@/actions/safe-action";
import { db } from "@bubba/db";
import { z } from "zod";

export interface ControlProgressResponse {
total: number;
completed: number;
progress: number;
byType: {
[key: string]: {
total: number;
completed: number;
};
};
}

export const getOrganizationControlProgress = authActionClient
.schema(z.object({ controlId: z.string() }))
.metadata({
name: "getOrganizationControlProgress",
track: {
event: "get-organization-control-progress",
channel: "server",
},
})
.action(async ({ ctx, parsedInput }) => {
const { user } = ctx;
const { controlId } = parsedInput;

if (!user.organizationId) {
return {
error: "Not authorized - no organization found",
};
}

try {
const requirements = await db.organizationControlRequirement.findMany({
where: {
organizationControlId: controlId,
},
include: {
organizationPolicy: true,
},
});

const progress: ControlProgressResponse = {
total: requirements.length,
completed: 0,
progress: 0,
byType: {},
};

for (const requirement of requirements) {
// Initialize type counters if not exists
if (!progress.byType[requirement.type]) {
progress.byType[requirement.type] = {
total: 0,
completed: 0,
};
}

progress.byType[requirement.type].total++;

// Check completion based on requirement type
let isCompleted = false;
switch (requirement.type) {
case "policy":
isCompleted =
requirement.organizationPolicy?.status === "published";
break;
case "file":
isCompleted = !!requirement.fileUrl;
break;
case "evidence":
isCompleted = !!requirement.content;
break;
default:
isCompleted = requirement.published;
}

if (isCompleted) {
progress.completed++;
progress.byType[requirement.type].completed++;
}
}

// Calculate overall progress percentage
progress.progress =
progress.total > 0
? Math.round((progress.completed / progress.total) * 100)
: 0;

return {
data: {
progress,
},
};
} catch (error) {
console.error("Error fetching control progress:", error);
return {
error: "Failed to fetch control progress",
};
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use server";

import { authActionClient } from "@/actions/safe-action";
import type {
ControlRequirement,
OrganizationControlRequirement,
Policy,
} from "@bubba/db";
import { db } from "@bubba/db";
import { z } from "zod";

export const getOrganizationControlRequirements = authActionClient
.schema(z.object({ controlId: z.string() }))
.metadata({
name: "getOrganizationControlRequirements",
track: {
event: "get-organization-control-requirements",
channel: "server",
},
})
.action(async ({ ctx, parsedInput }) => {
const { user } = ctx;
const { controlId } = parsedInput;

if (!user.organizationId) {
return {
error: "Not authorized - no organization found",
};
}
Comment on lines +26 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Standardize error response structure.

The error response structure is inconsistent with other functions in the codebase which use { success: boolean, error?: string }. Also, the error message in the catch block is incorrect as it mentions "organization control" instead of "organization control requirements".

Apply this diff to standardize error responses:

       return {
+        success: false,
         error: "Not authorized - no organization found",
       };

 // ... and in catch block:
       return {
+        success: false,
-        error: "Failed to fetch organization control",
+        error: "Failed to fetch organization control requirements",
       };

Also applies to: 58-62


try {
const organizationControlRequirements =
await db.organizationControlRequirement.findMany({
where: {
organizationControlId: controlId,
},
include: {
organizationPolicy: {
include: {
policy: true,
},
},
},
});

if (!organizationControlRequirements) {
return {
error: "Organization control requirements not found",
};
}

return {
data: {
organizationControlRequirements,
},
};
} catch (error) {
console.error("Error fetching organization control:", error);
return {
error: "Failed to fetch organization control",
};
}
});
Loading