Skip to content

Add statuses and keep track of policies across controls - #64

Merged
Marfuen merged 13 commits into
mainfrom
mariano/stuff
Feb 18, 2025
Merged

Marfuen merged 13 commits into
mainfrom
mariano/stuff

Conversation

@Marfuen

@Marfuen Marfuen commented Feb 18, 2025 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Introduced enhanced control dashboards with detailed progress tracking and requirement status displays, including sortable views and visual indicators.
    • Added improved localization of compliance statuses across multiple languages.
  • Refactor

    • Optimized data fetching and UI rendering for a smoother, more responsive experience.
  • Chores

    • Implemented several database migrations, cleanup routines, and dependency updates to support robust control tracking and seamless operations.

@vercel

vercel Bot commented Feb 18, 2025 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
app ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 18, 2025 10:36pm
comp-portal ❌ Failed (Inspect) Feb 18, 2025 10:36pm
web ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 18, 2025 10:36pm

@coderabbitai

coderabbitai Bot commented Feb 18, 2025 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

Walkthrough

This pull request introduces several new server‐side actions and hooks for handling organization control requirements and progress. New functions are added to create control requirements based on user frameworks, and endpoints are provided to fetch progress and detailed requirement data. The UI components have been enhanced to display control statuses and additional requirement details. Additionally, status representations, localization entries, database migration scripts, and schema updates have been modified to support the new data structures and relationships. Package dependencies were updated accordingly.

Changes

File(s) Change Summary
apps/app/src/actions/framework/select-frameworks-action.ts Added createOrganizationControlRequirements and updated selectFrameworksAction to incorporate organization control requirements creation.
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/{Actions,getOrganizationControlProgress.ts,getOrganizationControlRequirements.ts}
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/{hooks/useOrganizationControlProgress.ts, hooks/useOrganizationControlRequirements.ts}
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Components/SingleControl.tsx
Introduced new server actions and SWR hooks to fetch control progress and requirement data; enhanced the SingleControl component to display detailed status, requirements, and a navigation button.
apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/{Actions/getOrganizationCategories.ts,Actions/getOrganizationControlsProgress.ts,Components/FrameworkControls.ts}
apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/hooks/useOrganizationCategories.ts
Enhanced organization categories and controls progress with updated queries, memoized data processing, and refined data structures for improved display in frameworks.
apps/app/src/app/[locale]/(app)/(dashboard)/policies/{[id]/actions/publish-policy.ts, [id]/page.tsx, actions/get-policy.ts} Updated publish-policy logic with pre-update existence checks and debugging logs; modified policy fetching to use policyId for consistency in queries.
apps/app/src/components/frameworks/{framework-status.tsx,tables/frameworks/{columns.tsx,data-table-header.tsx}} Revised status constants and types (introducing “completed” and “in_progress”), added a helper to determine control status, and included a sortable "category" column in tables.
apps/app/src/env.mjs Added an optional NODE_ENV variable for both server-side validation and runtime configuration.
apps/app/src/locales/{en.ts,es.ts,fr.ts,no.ts,pt.ts} Updated available status strings for controls/frameworks, adding “completed” and “in_progress” while removing older values.
packages/data/controls/soc2.json Updated SOC2 controls by adding named requirements for policies, procedures, and evidence across various control categories.
packages/db/prisma/{migrations/*, schema.prisma, seed.ts, seedTypes.ts, seed.js} Introduced a new OrganizationControlRequirement table with additional columns and foreign key constraints; updated the ControlRequirement and related models; revised seed operations to clear and upsert new fields.
apps/app/package.json
package.json
Added new dependencies: @nangohq/frontend, novel, and @nangohq/node.
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Actions/getOrganizationControl.ts Updated import statements to use type-only imports for Control and OrganizationControl from @bubba/db.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant FrameworkAction as selectFrameworksAction
    participant OrgCtrlReq as createOrganizationControlRequirements
    participant DB

    User->>FrameworkAction: Initiates framework selection
    FrameworkAction->>DB: Create frameworks and policies
    FrameworkAction->>OrgCtrlReq: Call to create control requirements
    OrgCtrlReq->>DB: Insert organization control requirements
    DB-->>OrgCtrlReq: Confirmation
    OrgCtrlReq-->>FrameworkAction: Requirements created
    FrameworkAction-->>User: Completion response
Loading
sequenceDiagram
    participant UI as SingleControl Component
    participant Hook1 as useOrganizationControlProgress
    participant Hook2 as useOrganizationControlRequirements
    participant Server as Server Actions
    participant DB

    UI->>Hook1: Request control progress (controlId)
    Hook1->>Server: Call getOrganizationControlProgress
    Server->>DB: Query progress data
    DB-->>Server: Return progress data
    Server-->>Hook1: Return progress result
    Hook1-->>UI: Provide progress details

    UI->>Hook2: Request control requirements (controlId)
    Hook2->>Server: Call getOrganizationControlRequirements
    Server->>DB: Query requirement data
    DB-->>Server: Return requirement details
    Server-->>Hook2: Return requirement result
    Hook2-->>UI: Provide requirement details
Loading

Poem

Oh, what a hop and a leap in our code,
Where functions and hooks lighten our load.
Organization controls bloom like spring flowers,
New statuses gleam with fresh, vibrant powers.
I’m just a rabbit, dancing with glee,
Celebrating each commit so playfully!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0e8804 and 1399996.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • apps/app/package.json (2 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Actions/getOrganizationControl.ts (1 hunks)
  • packages/db/prisma/schema.prisma (4 hunks)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (24)
apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Actions/getOrganizationControlsProgress.ts (6)

7-18: Consider adding JSDoc/TSDoc for the ControlProgress interface.

This interface is an excellent abstraction for reporting control completion progress. Documenting its properties with descriptions (e.g., what "byType" tracks or how "progress" is derived) helps ensure clarity and easier maintenance.


20-28: Validate the input size.

The schema ensures controlIds is an array of strings, but there is no check on array length. If the client sends an extremely large array, the query might become expensive or even exceed parameter limits. Consider adding a length constraint or chunking to handle large volumes gracefully.

 .schema(z.object({
-  controlIds: z.array(z.string())
+  controlIds: z.array(z.string()).max(1000, "Too many IDs. Please limit to 1000.")
 }))

33-37: Improve authorization error reporting.

This block correctly checks for user.organizationId but simply returns a string for the error. Returning structured error data or throwing a dedicated error object might be more consistent with the rest of your codebase. You could also consider logging the user's ID for diagnostics.


39-49: Consider handling large result sets.

Retrieving many organization control requirements in one query could affect performance. If there's a chance the list is long, consider pagination, filtering, or batched processing to reduce load on the database.


64-101: Consider future extensibility for requirement completion logic.

Different requirement types (policy, file, evidence, etc.) are handled with a switch statement. If new types are introduced later, you’ll need to extend this logic. Centralizing or delegating each type’s "completion" condition—for instance, via a pluggable strategy object—can make future maintenance simpler.


116-122: Refine error handling.

Using console.error is fine for immediate debugging, but you may want to include more structured logging (e.g., using a logger library) to provide better observability. Consider returning consistent error objects or status codes to your consumers for clarity.

apps/app/src/components/tables/frameworks/columns.tsx (4)

22-34: Document the additional properties in OrganizationControlType.

The added fields name, id, category, and requirements expand the data model significantly. Adding inline comments or a dedicated docstring clarifies their purpose, possible values, and behaviors for collaborators.


37-59: Avoid duplicating completion logic in multiple places.

getControlStatus computes total vs. completed requirements to return a status. Later in the file, lines 103-115 repeat similar logic to derive completed vs. total. Returning both the status and some numeric details from getControlStatus would help centralize logic and reduce duplication.

-function getControlStatus(requirements: OrganizationControlType["requirements"]): StatusType {
+function getControlStatus(requirements: OrganizationControlType["requirements"]):
+  { status: StatusType; completed: number; total: number } {
   if (!requirements || requirements.length === 0) {
-    return "not_started";
+    return { status: "not_started", completed: 0, total: 0 };
   }
   ...
   return {
-    "in_progress";
+    status: "in_progress",
+    completed: completedRequirements,
+    total: totalRequirements
   };
 }

100-115: Consolidate the requirement filtering logic.

You are repeating the condition to filter completed requirements. Considering you already extracted a helper above (getControlStatus), you can unify this approach. It prevents any mismatch if you add or modify requirement types later.


118-141: Consider localizing the tooltip content.

"Progress:", "Completed:", and "requirements" are currently hardcoded strings, which might not be localized for users in other locales. If you have multi-language support, wrapping them in translation calls provides a more consistent experience.

apps/app/src/components/frameworks/framework-status.tsx (1)

16-17: Map all status types in STATUS_COLORS carefully.

draft and published are excluded from StatusType using Exclude, but if they're ever reintroduced or used in a similar color map, it could cause confusion. Keep an eye on this to avoid any unhandled status scenario in the future.

apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/hooks/useOrganizationControlProgress.ts (2)

13-13: Consider destructuring nested data property.

The return statement accesses a nested data property twice (result.data.data). This could be simplified for better readability.

-  return result.data.data;
+  const { data: { data: progressData } } = result;
+  return progressData;

9-11: Consider more specific error message.

The error message could be more descriptive by including the controlId in the error message for better debugging.

-    throw new Error("Failed to fetch control progress");
+    throw new Error(`Failed to fetch control progress for controlId: ${controlId}`);
apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/hooks/useOrganizationControlsProgress.ts (2)

32-38: Consider memoizing the transformed data.

The data transformation using reduce runs on every render. Consider using useMemo to optimize performance.

+import { useMemo } from 'react';

 export function useOrganizationControlsProgress(controlIds: string[]) {
   const { data, error, isLoading, mutate } = useSWR<ControlProgress[]>(
     controlIds.length > 0
       ? ["organization-controls-progress", controlIds]
       : null,
     () => fetchOrganizationControlsProgress(controlIds),
     {
       revalidateOnFocus: false,
       revalidateOnReconnect: false,
     }
   );

+  const transformedData = useMemo(() => 
+    data?.reduce(
+      (acc, curr) => {
+        acc[curr.controlId] = curr;
+        return acc;
+      },
+      {} as Record<string, ControlProgress>
+    ),
+    [data]
+  );

   return {
-    data: data?.reduce(
-      (acc, curr) => {
-        acc[curr.controlId] = curr;
-        return acc;
-      },
-      {} as Record<string, ControlProgress>
-    ),
+    data: transformedData,
     isLoading,
     error,
     mutate,
   };
 }

12-14: Consider more specific error message.

The error message could be more descriptive by including the controlIds in the error message for better debugging.

-    throw new Error("Failed to fetch controls progress");
+    throw new Error(`Failed to fetch controls progress for controlIds: ${controlIds.join(', ')}`);
apps/app/src/app/[locale]/(app)/(dashboard)/policies/actions/get-policy.ts (1)

53-56: Update error message to match function's purpose.

The error message "Failed to fetch policy statistics" is misleading as this function fetches a policy, not policy statistics.

Apply this diff to improve the error message:

       return {
         success: false,
-        error: "Failed to fetch policy statistics",
+        error: "Failed to fetch policy",
       };
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Actions/getOrganizationControlRequirements.ts (1)

4-8: Remove unused type imports.

The types ControlRequirement, OrganizationControlRequirement, and Policy are imported but never used.

Apply this diff to remove unused imports:

 import type {
-  ControlRequirement,
-  OrganizationControlRequirement,
-  Policy,
 } from "@bubba/db";
apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/actions/publish-policy.ts (1)

37-45: Simplify policy query.

The select clause is unnecessary since we only need the id. Also, the non-null assertion on organizationId is redundant due to the prior check.

Apply this diff to simplify the query:

     const policy = await db.organizationPolicy.findFirst({
       where: {
         policyId: id,
-        organizationId: user.organizationId!,
+        organizationId: user.organizationId,
-      },
-      select: {
-        id: true,
       },
     });
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Actions/getOrganizationControlProgress.ts (1)

67-81: Consider refactoring the completion check logic for better maintainability.

The switch statement could be simplified and the "published" status could be defined as a constant.

+const POLICY_PUBLISHED_STATUS = "published";
+const COMPLETION_CRITERIA = {
+  policy: (req) => req.organizationPolicy?.status === POLICY_PUBLISHED_STATUS,
+  file: (req) => !!req.fileUrl,
+  evidence: (req) => !!req.content,
+  default: (req) => req.published
+};
+
 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;
-}
+isCompleted = (COMPLETION_CRITERIA[requirement.type] || COMPLETION_CRITERIA.default)(requirement);
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Components/SingleControl.tsx (3)

29-34: Improve progress status calculation.

The current implementation can be simplified using a more concise approach.

-  const progressStatus =
-    controlProgress?.progress?.completed > 0
-      ? "in_progress"
-      : controlProgress?.progress?.completed === 0
-        ? "not_started"
-        : "completed";
+  const progressStatus = controlProgress?.progress?.completed === 0
+    ? "not_started"
+    : controlProgress?.progress?.completed === controlProgress?.progress?.total
+      ? "completed"
+      : "in_progress";

79-82: Improve URL generation for requirements.

Consider extracting the URL generation logic into a separate function for better maintainability.

-              const url =
-                requirement.type === "policy"
-                  ? `/policies/${requirement.organizationPolicy?.policy?.id}`
-                  : "_blank";
+              const getRequirementUrl = (requirement: any) => {
+                if (requirement.type === "policy" && requirement.organizationPolicy?.policy?.id) {
+                  return `/policies/${requirement.organizationPolicy.policy.id}`;
+                }
+                return "_blank";
+              };
+              const url = getRequirementUrl(requirement);

84-87: Improve completion status check.

Consider extracting the completion status check into a separate function for better readability and reusability.

-              const isCompleted =
-                requirement.type === "policy"
-                  ? requirement.organizationPolicy?.status === "published"
-                  : false;
+              const isRequirementCompleted = (requirement: any) => {
+                if (requirement.type === "policy") {
+                  return requirement.organizationPolicy?.status === "published";
+                }
+                return false;
+              };
+              const isCompleted = isRequirementCompleted(requirement);
packages/db/prisma/migrations/20250218014206_add_name_column/migration.sql (1)

1-2: Consider making the name column required.

The name column seems like an essential field for ControlRequirement. Consider making it NOT NULL with a default value to ensure data integrity.

-ALTER TABLE "ControlRequirement" ADD COLUMN     "name" TEXT;
+ALTER TABLE "ControlRequirement" ADD COLUMN     "name" TEXT NOT NULL DEFAULT 'Unnamed Requirement';
packages/db/prisma/migrations/20250218002311_add_missing_columns_to_org_control_req/migration.sql (1)

1-7: Migration Header and Warning Comments
The header comments appropriately warn that adding non-nullable columns (type and updatedAt) without a default value can cause issues if the table already contains data. Please ensure that either the table is empty or a suitable migration strategy (such as a temporary default value and a subsequent update) is in place.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bcfe13 and 2fbc3f6.

⛔ Files ignored due to path filters (3)
  • apps/app/languine.lock is excluded by !**/*.lock
  • bun.lockb is excluded by !**/bun.lockb
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (32)
  • apps/app/src/actions/framework/select-frameworks-action.ts (4 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Actions/getOrganizationControlProgress.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Actions/getOrganizationControlRequirements.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Components/SingleControl.tsx (2 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/hooks/useOrganizationControlProgress.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/hooks/useOrganizationControlRequirements.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Actions/getOrganizationCategories.ts (2 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Actions/getOrganizationControlsProgress.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Components/FrameworkControls.tsx (3 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/hooks/useOrganizationCategories.ts (2 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/hooks/useOrganizationControlsProgress.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/actions/publish-policy.ts (2 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/actions/get-policy.ts (1 hunks)
  • apps/app/src/components/frameworks/framework-status.tsx (2 hunks)
  • apps/app/src/components/tables/frameworks/columns.tsx (2 hunks)
  • apps/app/src/components/tables/frameworks/data-table-header.tsx (2 hunks)
  • apps/app/src/env.mjs (2 hunks)
  • apps/app/src/locales/en.ts (1 hunks)
  • apps/app/src/locales/es.ts (1 hunks)
  • apps/app/src/locales/fr.ts (1 hunks)
  • apps/app/src/locales/no.ts (1 hunks)
  • apps/app/src/locales/pt.ts (1 hunks)
  • packages/data/controls/soc2.json (28 hunks)
  • packages/db/prisma/migrations/20250217233112_add_organization_control_requirement_table/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250218002311_add_missing_columns_to_org_control_req/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250218002808_removed_policy_id_column/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250218014206_add_name_column/migration.sql (1 hunks)
  • packages/db/prisma/migrations/20250218202303_add_organization_policy_to_org_control_req/migration.sql (1 hunks)
  • packages/db/prisma/schema.prisma (4 hunks)
  • packages/db/prisma/seed.ts (3 hunks)
  • packages/db/prisma/seedTypes.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • apps/app/src/app/[locale]/(app)/(dashboard)/policies/[id]/page.tsx
  • packages/db/prisma/migrations/20250218002808_removed_policy_id_column/migration.sql
🔇 Additional comments (23)
apps/app/src/components/tables/frameworks/columns.tsx (1)

85-94: Verify correct i18n usage for the Category label.

You're using t("risk.vendor.table.category") for the header but referencing row.original.category for the data. Confirm that the same localized text is appropriate here, or rename the i18n key for clarity and consistency.

apps/app/src/components/frameworks/framework-status.tsx (1)

5-6: Check references to removed statuses.

You replaced "compliant" and "non_compliant" with "completed" and "in_progress" in STATUS_TYPES. Ensure you’ve removed or updated references to the old statuses (compliant/non_compliant) across the codebase (e.g., older migrations, older components).

apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Components/FrameworkControls.tsx (1)

26-41: Well-implemented memoization and data transformation!

The use of useMemo for flattening and transforming the controls array is a good optimization that prevents unnecessary recalculations. The transformation logic is clean and preserves all necessary data fields.

apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Actions/getOrganizationCategories.ts (1)

58-66: Well-structured database query with proper policy status inclusion!

The enhanced query properly includes organization policy status while maintaining a clean and type-safe structure.

apps/app/src/env.mjs (1)

23-23:

Details

❓ Verification inconclusive

Verify NODE_ENV configuration in deployment environments.

While the NODE_ENV configuration is correctly added, ensure it's properly set in all deployment environments as it often affects various runtime behaviors.

Also applies to: 55-55


🏁 Script executed:

#!/bin/bash
# Check if NODE_ENV is configured in deployment files
rg -g '*.{json,yaml,yml}' 'NODE_ENV' 

Length of output: 36


Action Required: Please verify that the NODE_ENV variable is correctly set in your deployment configuration files.

  • In apps/app/src/env.mjs, at lines 23 and 55, NODE_ENV is defined as an optional string.
  • The initial search in deployment-related files (JSON/YAML/YML) did not yield any instances of NODE_ENV. This might indicate that your deployment manifests (e.g., Docker Compose files, Kubernetes configurations, or other environment configuration files) might not explicitly set NODE_ENV.
  • Next Step: Manually inspect your deployment configuration to ensure NODE_ENV is provided where necessary since it affects runtime behavior.
apps/app/src/app/[locale]/(app)/(dashboard)/controls/[id]/Actions/getOrganizationControlProgress.ts (1)

48-93: Well-implemented progress tracking with type-specific handling!

The progress calculation is robust with proper handling of edge cases and type-specific completion criteria.

apps/app/src/components/tables/frameworks/data-table-header.tsx (1)

75-91: LGTM! The new category column is well-implemented.

The new category column follows the same pattern as existing columns, with proper sorting functionality and visibility control.

packages/db/prisma/seed.ts (2)

32-33: LGTM! Cleanup for organizationControlRequirement is properly added.

The cleanup is correctly added within the development environment check.


280-281: LGTM! The name field is properly handled in upsert operations.

The name field is correctly added to both create and update operations for control requirements.

Also applies to: 289-290

apps/app/src/locales/no.ts (1)

510-513: LGTM!

The Norwegian translations for the new statuses are accurate and maintain consistency with other language files.

apps/app/src/locales/pt.ts (1)

510-513: LGTM!

The Portuguese translations for the new statuses are accurate and maintain consistency with other language files.

apps/app/src/locales/es.ts (1)

510-513: LGTM!

The Spanish translations for the new statuses are accurate and maintain consistency with other language files.

apps/app/src/locales/fr.ts (1)

510-512: LGTM! French translations are accurate.

The new status translations "Terminé" for "completed" and "En cours" for "in_progress" are correctly implemented in French.

packages/db/prisma/migrations/20250218202303_add_organization_policy_to_org_control_req/migration.sql (1)

1-5: LGTM! Well-structured foreign key relationship.

The migration correctly sets up the foreign key relationship with appropriate cascade behavior for maintaining referential integrity.

packages/db/prisma/migrations/20250217233112_add_organization_control_requirement_table/migration.sql (1)

1-17: LGTM! Well-designed table structure with proper constraints.

The migration:

  • Creates a robust table structure with appropriate primary key
  • Prevents duplicates with composite unique index
  • Maintains referential integrity with cascading foreign keys
packages/db/prisma/migrations/20250218002311_add_missing_columns_to_org_control_req/migration.sql (2)

8-16: ALTER TABLE: Adding New Columns
The ALTER TABLE statement is correctly formulated to add the new columns (content, createdAt, description, fileUrl, policyId, published, type, and updatedAt) to the OrganizationControlRequirement table. However, note that the columns type and updatedAt are added as non-nullable without defaults. Double-check that this aligns with the data migration plan if existing records are present.


18-20: Foreign Key Constraint Addition
The foreign key on policyId is added with ON DELETE SET NULL and ON UPDATE CASCADE, which fits the expected behavior. Confirm that the data types for policyId in both tables match and that this change is consistent with the overall data model expectations.

packages/db/prisma/schema.prisma (5)

7-10: Datasource Configuration
The datasource block now uses PostgreSQL with the database URL sourced from an environment variable. This update adheres to common best practices for configuration management.


287-293: OrganizationControl Model Updates
New relations added to the OrganizationControl model—including optional fields for organizationFrameworkId and organizationCategoryId as well as the relation to OrganizationControlRequirement—are well defined. Please review that these relationships accurately support the intended business logic.


920-934: ControlRequirement Model Enhancements
The ControlRequirement model now includes createdAt (with a default value), updatedAt (managed by Prisma’s @updatedAt), and a relation to OrganizationControlRequirement. These enhancements improve auditability and data integrity. No issues were found.


936-958: OrganizationControlRequirement Model Definition
The new model for OrganizationControlRequirement is comprehensively defined with fields corresponding directly to the migration changes. The inclusion of non-nullable fields (like type) alongside relations, and the unique constraint on [organizationControlId, controlRequirementId], provides a robust structure. Just ensure that creation logic in the application handles these non-nullable fields properly.


966-984: OrganizationPolicy Model Enhancements
The OrganizationPolicy model now includes a content field (as an array of JSON objects) and a relation to OrganizationControlRequirement. These changes support the new functionality for tracking and associating policies with controls. It would be beneficial to verify compatibility with existing queries and application logic.

packages/data/controls/soc2.json (1)

1-1254: Comprehensive Update of SOC2 Controls Data
The JSON file has been significantly updated across multiple control categories (e.g., CC1.x, CC2.x, CC3.x, etc.) by explicitly adding a "name" property to each requirement object. This addition improves clarity, consistency, and maintainability in the control definitions and ensures that policies and procedures are clearly labeled. Please verify that the new names (such as "Corporate Governance Policy", "Board Oversight Procedures", etc.) align with your business terminology and map correctly to the corresponding database records in your application.

Comment on lines 9 to 11
if (!result) {
throw new Error("Failed to fetch frameworks");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect error message.

The error message references "frameworks" instead of "categories".

-    throw new Error("Failed to fetch frameworks");
+    throw new Error(`Failed to fetch categories for frameworkId: ${frameworkId}`);
📝 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
if (!result) {
throw new Error("Failed to fetch frameworks");
}
if (!result) {
- throw new Error("Failed to fetch frameworks");
+ throw new Error(`Failed to fetch categories for frameworkId: ${frameworkId}`);
}

Comment on lines +9 to +11
if (!result) {
throw new Error("Failed to fetch control");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect error message.

The error message references "control" instead of "control requirements" and could include the controlId.

-    throw new Error("Failed to fetch control");
+    throw new Error(`Failed to fetch control requirements for controlId: ${controlId}`);
📝 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
if (!result) {
throw new Error("Failed to fetch control");
}
if (!result) {
throw new Error(`Failed to fetch control requirements for controlId: ${controlId}`);
}

type: string;
description: string;
policyId?: string;
name?: string;

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

Add name property to ControlRequirement interface for consistency.

The name property has been added to the Requirement interface but is missing in the ControlRequirement interface. Since both interfaces represent similar entities, they should maintain consistency in their properties.

Apply this diff to add the name property to the ControlRequirement interface:

 export interface ControlRequirement {
   id: string;
   type: string;
   description: string;
   policyId?: string;
+  name?: string;
 }

Also applies to: 32-37

Comment on lines +26 to +29
return {
error: "Not authorized - no organization found",
};
}

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

Comment on lines +183 to +246
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;
};

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");
}
};

Comment on lines +638 to 640
completed: "Completed",
in_progress: "In Progress",
not_started: "Not Started",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure consistent status options across all languages.

The English translations are missing the "non_compliant" status that exists in other language files (es.ts, no.ts, pt.ts). This inconsistency could lead to missing translations or undefined behavior.

Apply this diff to maintain consistency with other language files:

 statuses: {
   completed: "Completed",
   in_progress: "In Progress",
+  non_compliant: "Non Compliant",
   not_started: "Not Started",
 }
📝 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
completed: "Completed",
in_progress: "In Progress",
not_started: "Not Started",
completed: "Completed",
in_progress: "In Progress",
non_compliant: "Non Compliant",
not_started: "Not Started",

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Components/FrameworkControls.tsx (1)

24-39: Consider improving type safety.

The type assertion could be replaced with proper type inference, and the requirements field might need null safety handling.

Consider this improvement:

-  const allControls = useMemo(() => {
+  const allControls = useMemo<OrganizationControlType[]>(() => {
     if (!organizationCategories) return [];

     return organizationCategories.flatMap((category) =>
       category.organizationControl.map((control) => ({
         code: control.control.code,
         description: control.control.description,
         name: control.control.name,
         status: control.status,
         id: control.id,
         frameworkId,
         category: category.name,
-        requirements: control.OrganizationControlRequirement,
+        requirements: control.OrganizationControlRequirement ?? [],
       }))
     );
-  }, [organizationCategories, frameworkId]) as OrganizationControlType[];
+  }, [organizationCategories, frameworkId]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2fbc3f6 and 0686504.

⛔ Files ignored due to path filters (3)
  • bun.lock is excluded by !**/*.lock
  • bun.lockb is excluded by !**/bun.lockb
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Components/FrameworkControls.tsx (3 hunks)
  • packages/db/prisma/seed.js (2 hunks)
🔇 Additional comments (5)
apps/app/src/app/[locale]/(app)/(dashboard)/frameworks/[frameworkId]/Components/FrameworkControls.tsx (3)

13-14: LGTM! Necessary imports added correctly.

The new imports support the memoization optimization and type safety improvements.


24-39: LGTM! Efficient memoization of control data.

The implementation correctly prevents unnecessary recomputations and handles the null case appropriately.


52-52: LGTM! Clean DataTable implementation.

The simplified DataTable usage with memoized data improves component clarity and performance.

packages/db/prisma/seed.js (2)

25-25: LGTM! Proper placement of the new deletion operation.

The deletion of organization control requirements is correctly positioned after related tables, maintaining referential integrity during cleanup.


208-208:

Details

❓ Verification inconclusive

Consider adding validation for the name field.

While the addition of the name field is correct, consider validating it before use to prevent potential issues with null or invalid values.

Also applies to: 216-216


🏁 Script executed:

#!/bin/bash
# Description: Check if any control requirements in the data files have missing or invalid names

# Search for control requirements without a name field or with invalid names
echo "Checking control requirements in data files..."
fd . "data/controls" -e json -x jq -r '.[] | select(.requirements != null) | .requirements[] | select(.name == null or .name == "" or .name == "undefined") | "Control requirement \(.id) has missing or invalid name"'

Length of output: 419


Action Required: Validate the 'name' field and verify data source paths

  • The addition of name: requirement.name to both create and update operations correctly aligns with the schema changes.
  • However, there is no validation in place to ensure that the name field is neither null nor invalid.
  • The provided shell script attempt failed because the search path "data/controls" is not a directory. Please verify whether the control requirements data exists at a different path or update the script accordingly.
  • Consider adding validation logic before the upsert operations to prevent potential issues from missing or invalid names.

@Marfuen
Marfuen merged commit 6379e1b into main Feb 18, 2025
This was referenced Feb 25, 2025

This branch had an error being deployed

1 failed and 2 active deployments
Preview – app — 1399996f Deployed Feb 18, 2025 by vercel[bot]
Preview – web — 1399996f Deployed Feb 18, 2025 by vercel[bot]
Preview – comp-portal — 1399996f Deployed Feb 18, 2025 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants