Skip to content

Com 20 cloud tests - #100

Closed
carhartlewis wants to merge 35 commits into
mainfrom
COM-20-cloud-tests
Closed

carhartlewis wants to merge 35 commits into
mainfrom
COM-20-cloud-tests

Conversation

@carhartlewis

@carhartlewis carhartlewis commented Mar 4, 2025 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive Cloud Tests module that lets users register, view, and filter cloud test results with enhanced tables, loading indicators, and dynamic pagination.
    • Integrated AWS Security Hub capabilities and automated task scheduling for streamlined evidence collection.
    • Expanded navigation with a new Cloud Tests menu option and updated integration options.
  • Documentation

    • Added localized content for Cloud Tests across multiple languages.
  • Chores

    • Performed backend enhancements and dependency updates to support these advances.

@vercel

vercel Bot commented Mar 4, 2025

Copy link
Copy Markdown

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

Name Status Preview Comments Updated (UTC)
app ❌ Failed (Inspect) Mar 4, 2025 9:01pm
2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
comp-portal ⬜️ Skipped (Inspect) Mar 4, 2025 9:01pm
web ⬜️ Skipped (Inspect) Mar 4, 2025 9:01pm

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Mar 4, 2025 •

Copy link
Copy Markdown

Walkthrough

The update introduces several new features and improvements across the codebase. A new test registration schema and associated server actions facilitate employee creation and test record retrieval. Multiple React components, hooks, and pages have been added for displaying test details, listing tests, and managing pagination and filtering. Additionally, new TypeScript types and localization sections for cloud tests have been introduced in various languages. On the integrations front, AWS Security Hub functionality is now incorporated with new configuration files, a logo component, and a script for findings retrieval. Database migrations and Prisma schema updates support integration result storage.

Changes

File(s) Change Summary
apps/app/src/actions/schema.ts
apps/app/src/actions/tests/register-test-action.ts
Added new schema registerTestSchema and server action createTestAction for employee test registration.
apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/actions/get-test.ts
apps/app/src/app/[locale]/(app)/(dashboard)/tests/actions/get-tests.ts
Introduced endpoints getTest and getTests for retrieving single and multiple test records with input validation and error handling.
apps/app/src/app/[locale]/(app)/(dashboard)/tests/actions/types.ts
apps/app/src/app/[locale]/(app)/(dashboard)/tests/types/index.ts
.../tests/types/search-params.ts
Added new TypeScript types, error constants, and schemas for cloud test details and structured search parameter handling.
apps/app/src/app/[locale]/(app)/(dashboard)/tests/components/Test.tsx
apps/app/src/app/[locale]/(app)/(dashboard)/tests/components/TestsList*.tsx
apps/app/src/app/[locale]/(app)/(dashboard)/tests/hooks/useTest*.ts
.../tests/page.tsx
.../tests/layout.tsx
apps/app/src/components/main-menu.tsx
Introduced new React components, hooks, pages, and layouts for displaying and managing test details including skeleton loaders, pagination, and filter toolbar, and updated the main menu to include a tests option.
apps/app/src/components/tables/tests/* Added new table components and interfaces for listing test data (headers, pagination, empty states, loading states) using the data-table library.
apps/app/src/jobs/tasks/integration/* Introduced new job tasks for processing integration results, scheduling integration runs, and sending task email notifications.
apps/app/src/locales/{en,es,fr,no,pt}.ts Added new localization sections for “tests” with translations and UI strings in multiple languages.
package.json
packages/db/prisma/migrations/*
packages/db/prisma/schema.prisma
Added AWS SecurityHub dependency, a new SQL migration for the Organization_integration_results table, and corresponding Prisma model updates with new relationships and indexes.
packages/integrations/src/aws/*
packages/integrations/src/github/config.ts
packages/integrations/src/gusto/config.ts
packages/integrations/src/index.ts
Introduced AWS integration with new configuration, logo component, and fetchSecurityFindings function; updated GitHub and Gusto config descriptions and exported AWS integration in the main module.
packages/ui/src/components/icons.tsx Added a new CloudSync icon (MdCloudSync) to the UI icon set.
run-security-hub.ts Added a new script to run AWS Security Hub findings retrieval and reporting, with grouping and detailed log output.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ActionAPI as RegisterTestAction
    participant SchemaValidator as Zod Validator
    participant AuthService
    participant Database

    Client->>ActionAPI: Send registration request with employee data
    ActionAPI->>SchemaValidator: Validate input using registerTestSchema
    SchemaValidator-->>ActionAPI: Return validated data / error
    ActionAPI->>AuthService: Verify user's organization membership
    alt Not Authorized
        AuthService-->>ActionAPI: Return authorization error
        ActionAPI-->>Client: Send error response
    else Authorized
        ActionAPI->>Database: Create new employee record
        Database-->>ActionAPI: Confirm creation
        ActionAPI->>Database: Upsert default tasks and assign to employee
        Database-->>ActionAPI: Confirm task assignments
        ActionAPI-->>Client: Send success response
    end
Loading
sequenceDiagram
    participant Script
    participant AWS as AWS Security Hub
    participant Logger

    Script->>Logger: Log start of findings retrieval
    Script->>AWS: Call fetchSecurityFindings with AWS credentials
    AWS-->>Script: Return fetched findings (supporting pagination)
    Script->>Script: Group findings by severity
    Script->>Logger: Log counts and details for CRITICAL/HIGH findings
    Script->>Logger: Log completion message and exit
Loading

Poem

I'm a happy bunny, hopping in delight,
New tests and actions make my code shine bright.
Schemas, hooks, and pages all spring to life,
Integrations and migrations cut through strife!
With a hop and a skip, the updates unroll,
CodeRabbit cheers as our improvements take hold!

✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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.
  • @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.

@linear

linear Bot commented Mar 4, 2025

Copy link
Copy Markdown

COM-20 Cloud Tests

@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: 35

🧹 Nitpick comments (65)
apps/app/src/app/[locale]/(app)/(dashboard)/tests/types/search-params.ts (2)

7-12: Consider adding validation for date-related parameters.

The search parameters are well-structured with appropriate parsers, but consider adding validation or formatting requirements for the start and end parameters if they're expected to be dates or follow a specific format.

You could enhance the type safety by using a date parser with validation:

-  start: parseAsString,
-  end: parseAsString,
+  start: parseAsString.withConstraint((value) => {
+    // Add validation logic for date format
+    return /^\d{4}-\d{2}-\d{2}$/.test(value) || "Invalid date format (YYYY-MM-DD required)";
+  }),
+  end: parseAsString.withConstraint((value) => {
+    // Add validation logic for date format
+    return /^\d{4}-\d{2}-\d{2}$/.test(value) || "Invalid date format (YYYY-MM-DD required)";
+  }),

Or if you're using a different date format, adjust the regex pattern accordingly.


1-12: Add JSDoc comments to explain purpose of parameters.

Consider adding JSDoc comments to explain what each parameter is used for, especially for future developers who might need to maintain this code.

+/**
+ * Search parameters cache for tests listing
+ * @property {string} q - Search query for filtering tests
+ * @property {number} page - Current page number (zero-based)
+ * @property {string} start - Start date for filtering tests
+ * @property {string} end - End date for filtering tests
+ */
export const searchParamsCache = createSearchParamsCache({
  q: parseAsString,
  page: parseAsInteger.withDefault(0),
  start: parseAsString,
  end: parseAsString,
});
apps/app/src/app/[locale]/(app)/(dashboard)/tests/components/TestsListSkeleton.tsx (2)

8-14: Consider generating skeleton rows programmatically to reduce repetition.

The multiple skeleton rows follow an identical pattern with only height differences. This repetition could be simplified.

- <div className="flex flex-col gap-0.5">
-   <div className="flex items-center gap-0.5 h-[53px]">
-     <Skeleton className="h-full w-2/5" />
-     <Skeleton className="h-full w-1/5" />
-     <Skeleton className="h-full w-1/5" />
-     <Skeleton className="h-full w-1/5" />
-   </div>
+ <div className="flex flex-col gap-0.5">
+   {[
+     { height: "h-[53px]" },
+     { height: "h-[37px]" },
+     { height: "h-[37px]" },
+     { height: "h-[37px]" }
+   ].map((row, index) => (
+     <div key={index} className={`flex items-center gap-0.5 ${row.height}`}>
+       <Skeleton className="h-full w-2/5" />
+       <Skeleton className="h-full w-1/5" />
+       <Skeleton className="h-full w-1/5" />
+       <Skeleton className="h-full w-1/5" />
+     </div>
+   ))}

15-36: Add accessibility attributes to skeletons for better screen reader support.

Skeleton components should have appropriate ARIA attributes to improve accessibility for screen reader users.

Add an appropriate aria-label to the container to indicate this is a loading state:

- <div className="relative overflow-hidden">
+ <div className="relative overflow-hidden" aria-label="Loading tests" role="status">
apps/app/src/components/tables/tests/columns.tsx (1)

3-11: Consider enhancing the TestType interface with JSDoc comments and more specific types.

The interface is well-structured but could benefit from additional documentation and more specific types.

/**
 * Represents a test record in the application
 */
export interface TestType {
  /** Unique identifier for the test */
  id: string;
-  severity: string | null;
+  /** Indicates the severity level of the test result - e.g., 'high', 'medium', 'low' */
+  severity: 'high' | 'medium' | 'low' | null;
  /** Result status of the test */
-  result: string;
+  result: 'pass' | 'fail' | 'warning' | 'info' | string;
  /** Title or name of the test */
  title: string;
  /** Provider of the test, e.g., 'aws', 'gcp' */
  provider: string;
  /** When the test was created */
  createdAt: Date;
-  assignedUser: null;
+  /** User assigned to the test, null if unassigned */
+  assignedUser: null | { id: string; name: string };
}
packages/integrations/src/aws/assets/logo.tsx (1)

1-51: Make the logo component more flexible by accepting size props.

The component has hardcoded dimensions, which limits its reusability.

- export const Logo = () => {
+ export const Logo = ({ width = 131.417, height = 50 }) => {
	return (
		<svg
			xmlns="http://www.w3.org/2000/svg"
			version="1.1"
			id="Layer_1"
			xmlnsXlink="http://www.w3.org/1999/xlink"
			x="0px"
			y="0px"
-			width={131.417}
-			height={50}
+			width={width}
+			height={height}
			viewBox="0 0 500 182"
			style={{ enableBackground: "new 0 0 304 182" }}
			xmlSpace="preserve">
apps/app/src/components/tables/tests/server-columns.tsx (1)

6-14: Add TypeScript interface for return type to improve type safety.

Adding a return type would enhance code maintainability and provide better type checking.

+ interface ColumnHeaders {
+   severity: string;
+   result: string;
+   title: string;
+   provider: string;
+   status: string;
+   createdAt: string;
+   assignedUser: string;
+ }

- export async function getServerColumnHeaders() {
+ export async function getServerColumnHeaders(): Promise<ColumnHeaders> {
  const t = await getI18n();

  return {
    severity: t("tests.table.severity"),
    result: t("tests.table.result"),
    title: t("tests.table.title"),
    provider: t("tests.table.provider"),
    status: t("tests.table.status"),
    createdAt: t("tests.table.createdAt"),
    assignedUser: t("tests.table.assignedUser"),
  };
}
apps/app/src/app/[locale]/(app)/(dashboard)/tests/layout.tsx (1)

1-18: Well-structured layout component for the tests section.

The layout component is properly implemented with:

  • Correct async function definition
  • Proper use of getI18n for localization
  • Appropriate secondary menu setup
  • Consistent styling with other layouts

The implementation follows the project's patterns for route-specific layouts.

Consider future expandability of the secondary menu.

Currently, there's only one item in the SecondaryMenu. If there will be more test-related sections in the future, this layout is ready to accommodate them.

apps/app/src/jobs/tasks/integration/utils/task-email-notification.tsx (2)

5-16: Consider using Date type for task.dueDate instead of string.

For better type safety and consistency, consider using the Date type for the dueDate field rather than string. This would make it clearer what format is expected and ensure proper date handling.

interface Props {
  owner: {
    id: string;
    fullName?: string;
    email: string;
    organizationId: string;
  };
  task: {
    recordId: string;
-    dueDate: string;
+    dueDate: Date;
  };
}

18-49: Well-implemented email notification with proper error handling.

The function is well-structured with:

  • Clear separation of email rendering and notification triggering
  • Proper error handling with logging and re-throwing
  • Good use of the TaskReminderEmail component

The overall implementation follows best practices for notification handling.

Enhance fallback name for better user experience.

The current fallback of "there" when a name isn't provided works, but consider a more personalized approach:

<TaskReminderEmail
  email={owner.email}
-  name={owner.fullName ?? "there"}
+  name={owner.fullName ?? owner.email.split('@')[0] ?? "User"}
  dueDate={task.dueDate}
  recordId={task.recordId}
/>

This extracts the username part of the email as a more personalized fallback before using a generic term.

packages/integrations/src/aws/config.ts (2)

31-38: Session token shouldn't be required for standard AWS authentication.

The session token is marked as required, but it's only needed for temporary credentials (as correctly noted in the comment on line 22). For standard API keys, it should be optional.

  {
    id: "session_token",
    label: "AWS session token",
    description: "The API session token AWS account",
    type: "text",
-   required: true,
+   required: false,
    value: "",
  },

18-30: Consider adding password type for the secret access key field.

The secret access key should use a password field type to mask the sensitive information during input.

  {
    id: "secret_access_key",
    label: "AWS secret access key",
    description: "The API secret access key for your AWS account",
-   type: "text",
+   type: "password",
    required: true,
    value: "",
  },
packages/integrations/src/aws/src/index.ts (4)

1-7: Use import type for type-only imports.

Several imports are only used as types, which should be imported with the import type syntax.

import {
  SecurityHubClient,
-  GetFindingsCommand,
-  SecurityHubClientConfig,
-  GetFindingsCommandInput,
-  GetFindingsCommandOutput
+  GetFindingsCommand
} from "@aws-sdk/client-securityhub";
+import type {
+  SecurityHubClientConfig,
+  GetFindingsCommandInput,
+  GetFindingsCommandOutput
+} from "@aws-sdk/client-securityhub";
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-7: Some named imports are only used as types.

This import is only used as a type.

This import is only used as a type.

This import is only used as a type.

Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
Safe fix: Add inline type keywords.

(lint/style/useImportType)


38-41: Use const instead of let for variables that aren't reassigned.

command and allFindings are never reassigned and should use const instead of let.

-    let command = new GetFindingsCommand(params);
-    let response: GetFindingsCommandOutput = await securityHubClient.send(command);
+    const command = new GetFindingsCommand(params);
+    const response: GetFindingsCommandOutput = await securityHubClient.send(command);

-    let allFindings: any[] = response.Findings || [];
+    const allFindings: SecurityFinding[] = response.Findings || [];
🧰 Tools
🪛 Biome (1.9.4)

[error] 38-38: This let declares a variable that is only assigned once.

'command' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)


[error] 41-41: This let declares a variable that is only assigned once.

'allFindings' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)


42-54: Add a timeout or maximum iterations to the pagination loop.

The pagination loop may run indefinitely if there are too many findings. Consider adding a limit to the number of pages retrieved or a timeout mechanism.

    let nextToken = response.NextToken;
+    let pageCount = 1;
+    const MAX_PAGES = 50; // Adjust based on your requirements

    // 3. Loop to paginate through all results if there are more than 100 findings
-    while (nextToken) {
+    while (nextToken && pageCount < MAX_PAGES) {
      const nextPageParams: GetFindingsCommandInput = { ...params, NextToken: nextToken };
      response = await securityHubClient.send(new GetFindingsCommand(nextPageParams));
      
      if (response.Findings) {
        allFindings.push(...response.Findings);
      }
      
      nextToken = response.NextToken;
+      pageCount++;
    }
+    
+    if (nextToken && pageCount >= MAX_PAGES) {
+      console.warn(`Retrieved maximum of ${MAX_PAGES} pages. There might be more findings available.`);
+    }

56-60: Improve error handling with more specific error information.

The current error handling logs a generic message. Add more context and consider different types of AWS errors.

-    console.log(`Retrieved ${allFindings.length} findings`);
+    console.info(`Retrieved ${allFindings.length} findings from AWS Security Hub`);
     return allFindings;
   } catch (error) {
-    console.error("Error fetching Security Hub findings:", error);
+    if (error.name === 'AccessDeniedException') {
+      console.error("Access denied when fetching Security Hub findings. Check IAM permissions:", error);
+    } else if (error.name === 'ThrottlingException') {
+      console.error("AWS API rate limit exceeded when fetching Security Hub findings:", error);
+    } else {
+      console.error("Error fetching Security Hub findings:", error);
+    }
     throw error;
   }
apps/app/src/locales/no.ts (1)

718-796: Test translation keys look good, with one small improvement needed.

The Norwegian translations for the tests section are well structured and comprehensive, covering all necessary UI elements.

One minor improvement: the description for the session token in line 34 has an incomplete sentence, which should be reflected in this translation section as well.

For consistency with your updated AWS integration description, consider updating the related auth_config description in the tests section:

 auth_config: {
   label: "Autentiseringskonfigurasjon",
-   placeholder: "Skriv inn JSON-autentiseringskonfigurasjon"
+   placeholder: "Skriv inn JSON-autentiseringskonfigurasjon (inkludert midlertidige legitimasjon hvis nødvendig)"
 }
apps/app/src/components/tables/tests/loading.tsx (1)

9-9: Move static array outside the component to avoid recreation on each render.

The static array is created every time the component renders. Move it outside the component to improve performance.

import { Suspense } from "react";
import { DataTableHeader } from "./data-table-header";

- const data = [...Array(10)].map((_, i) => ({ id: i.toString() }));
+ // Create static array only once
+ const SKELETON_ROWS = [...Array(10)].map((_, i) => ({ id: i.toString() }));

export function Loading({
apps/app/src/app/[locale]/(app)/(dashboard)/tests/hooks/useTest.ts (4)

10-14: Simplify conditional logic by removing unnecessary else clause.

The else clause can be omitted since the previous branch already returns a value in the success case.

if (response.success) {
  return response.data;
- } else {
-   throw response.error;
}
+ throw response.error;
🧰 Tools
🪛 Biome (1.9.4)

[error] 12-14: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)


16-19: Ensure error type safety with more precise type checking.

The current error handling checks if the error object has a 'message' property, but this doesn't guarantee it conforms to the full AppError type specification. Consider implementing more robust type checking.

if (error && typeof error === 'object' && 'message' in error) {
-  throw error as AppError;
+  // Ensure the error conforms to AppError interface
+  const appError: AppError = {
+    code: 'UNEXPECTED_ERROR',
+    message: (error as {message: string}).message
+  };
+  throw appError;
}

23-31: Consider adding a means to refresh data on demand.

The current SWR configuration disables automatic revalidation on focus and reconnect. While this can reduce unnecessary network requests, it may also lead to stale data. Consider exposing the mutate function more explicitly in your API to allow consumers to trigger refreshes when needed.


25-25: Add retry mechanism for network failures.

The current implementation doesn't specify retry behavior for failed requests. Adding retry options would improve resilience against temporary network issues.

useSWR<Test, AppError>(
  testId ? ["cloud-test-details", testId] : null,
  () => fetchTest(testId),
  {
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
+   onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
+     // Only retry on network errors, up to 3 times
+     if (error.code === 'NETWORK_ERROR' && retryCount < 3) {
+       // Retry after 5 seconds
+       setTimeout(() => revalidate({ retryCount }), 5000);
+     }
+   },
  }
)
apps/app/src/app/[locale]/(app)/(dashboard)/tests/hooks/useTests.ts (2)

13-35: Refactor repetitive error handling to reduce duplication.

The error handling logic is repeated three times with similar structure. Consider creating a helper function to reduce code duplication.

+ /** Helper function to create and throw an AppError */
+ function throwAppError(code: string, message: string): never {
+   const error: AppError = {
+     code,
+     message,
+   };
+   throw error;
+ }

async function fetchTests(input: TestsInput): Promise<TestsResponse> {
  const result = await getTests(input);

  if (!result) {
-   const error: AppError = {
-     code: "UNEXPECTED_ERROR",
-     message: "An unexpected error occurred",
-   };
-   throw error;
+   throwAppError("UNEXPECTED_ERROR", "An unexpected error occurred");
  }

  if (result.serverError) {
-   const error: AppError = {
-     code: "UNEXPECTED_ERROR",
-     message: result.serverError || "An unexpected error occurred",
-   };
-   throw error;
+   throwAppError("UNEXPECTED_ERROR", result.serverError || "An unexpected error occurred");
  }

  if (!result.data) {
-   const error: AppError = {
-     code: "UNEXPECTED_ERROR",
-     message: "No data returned from server",
-   };
-   throw error;
+   throwAppError("UNEXPECTED_ERROR", "No data returned from server");
  }

  return result.data.data as TestsResponse;
}

44-46: Enhance provider validation with a type constant.

The provider validation could be improved by using a constant for valid providers, which would make the code more maintainable.

+ // Define valid cloud providers as a constant
+ const VALID_PROVIDERS = ["AWS", "AZURE", "GCP"] as const;
+ type CloudProvider = typeof VALID_PROVIDERS[number];

- const provider = providerParam && ["AWS", "AZURE", "GCP"].includes(providerParam) 
-   ? providerParam as "AWS" | "AZURE" | "GCP" 
-   : undefined;
+ const provider = providerParam && VALID_PROVIDERS.includes(providerParam as CloudProvider)
+   ? providerParam as CloudProvider
+   : undefined;
apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/actions/types.ts (2)

11-14: Enhance testId validation with additional constraints.

The current schema only validates that testId is a string without additional constraints. Consider adding further validation if needed.

// Define the input schema
export const cloudTestDetailsInputSchema = z.object({
-  testId: z.string()
+  testId: z.string().min(1, "TestId is required").trim()
});

16-19: Add JSDoc comments to improve type documentation.

Adding detailed JSDoc comments would help other developers understand how to use the ActionResponse type correctly.

+/**
+ * Represents a type-safe response from a server action.
+ * @template T The type of data returned on success
+ * @returns Either a success response with data or a failure response with an error
+ */
export type ActionResponse<T> = Promise<
  { success: true; data: T } | { success: false; error: AppError }
>; 
apps/app/src/components/tables/tests/empty-states.tsx (1)

48-49: Improve positioning approach for NoTests component.

Using absolute positioning with top-0 left-0 may cause layout issues if parent elements don't have position: relative. Consider a more robust layout approach.

-<div className="mt-24 absolute w-full top-0 left-0 flex items-center justify-center z-20">
+<div className="mt-24 flex items-center justify-center w-full z-20">
apps/app/src/app/[locale]/(app)/(dashboard)/tests/actions/get-tests.ts (3)

3-3: Update the import to match project branding.

The import is referencing "@bubba/db" which appears to be from an old brand name. This should be updated to maintain consistent branding throughout the codebase.

-import { db } from "@bubba/db";
+import { db } from "@compai/db";

105-105: Avoid using any type in transformations.

Using any type bypasses TypeScript's type checking benefits and can lead to runtime errors. Define proper types for the integration results to maintain type safety.

-  const transformedTests = integrationResults.map((result: any) => {
+  const transformedTests = integrationResults.map((result) => {

For a more complete solution, you might want to define an interface for the result type:

interface IntegrationResult {
  id: string;
  label?: string;
  status: string;
  title?: string;
  completedAt?: Date;
  organizationIntegration: {
    id: string;
    name: string;
    integration_id: string;
  };
}

123-123: Enhance error logging to include error details.

The current error logging only includes a generic message. Including error details would help with debugging issues.

-  console.error("Error fetching integration results:", error);
+  console.error("Error fetching integration results:", error instanceof Error ? error.message : error);
+  if (error instanceof Error && error.stack) {
+    console.debug(error.stack);
+  }
apps/app/src/components/tables/tests/data-table.tsx (2)

172-179: Simplify conditional class assignment for responsive table cells.

The current implementation checks every column ID in a single conditional, which is redundant and harder to maintain. This can be simplified by checking if the current column ID is in a list of responsive columns.

-className={cn({
-  "hidden md:table-cell":
-    cell.column.id === "severity" ||
-    cell.column.id === "result" ||
-    cell.column.id === "title" ||
-    cell.column.id === "provider" ||
-    cell.column.id === "createdAt" ||
-    cell.column.id === "assignedUser",
-})}
+className={cn({
+  "hidden md:table-cell": ["severity", "result", "title", "provider", "createdAt", "assignedUser"].includes(cell.column.id)
+})}

Alternatively, if some columns should remain visible on mobile, you could maintain a list of mobile-only columns:

+const mobileHiddenColumns = ["severity", "result", "provider", "createdAt", "assignedUser"];
+
+className={cn({
+  "hidden md:table-cell": mobileHiddenColumns.includes(cell.column.id)
+})}

164-167: Add error handling for navigation to test details.

The component navigates to a test detail page when a row is clicked, but it doesn't handle the case where the test ID might be missing or invalid.

  onClick={() => {
    const test = row.original;
-   router.push(`/tests/${test.id}`);
+   if (test?.id) {
+     router.push(`/tests/${test.id}`);
+   } else {
+     console.warn('Cannot navigate: Test ID is missing');
+   }
  }}
apps/app/src/app/[locale]/(app)/(dashboard)/tests/components/TestsList.tsx (1)

67-67: Remove unnecessary type cast to TestType[].

The type assertion tests as TestType[] indicates a potential type mismatch. It would be better to ensure the types align properly between what useTests returns and what DataTable expects.

  <DataTable
    columnHeaders={columnHeaders}
-   data={tests as TestType[]}
+   data={tests}
    pageCount={Math.ceil(total / per_page)}
    currentPage={page}
  />

To fix this properly, ensure that the useTests hook returns the correct type that matches the TestType interface.

apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/components/Test.tsx (3)

60-66: Consider using an enum or constants for provider values.

The provider mapping logic could be more maintainable with a lookup object rather than nested ternary operators.

-  // Format the test provider for display
-  const providerLabel = cloudTest.provider === "aws" 
-    ? "Amazon Web Services" 
-    : cloudTest.provider === "AZURE" 
-      ? "Microsoft Azure" 
-      : "Google Cloud Platform";
+  // Format the test provider for display
+  const providerLabels = {
+    aws: "Amazon Web Services",
+    AZURE: "Microsoft Azure",
+    GCP: "Google Cloud Platform"
+  };
+  const providerLabel = providerLabels[cloudTest.provider as keyof typeof providerLabels] || cloudTest.provider;

82-99: Simplify the getRunStatusIcon function by removing unnecessary else clauses.

The function contains several unnecessary else clauses that can be removed since previous branches already return.

  // Helper function to get the appropriate icon for test run status
  const getRunStatusIcon = (status: string, result: string | null) => {
    if (status === "COMPLETED") {
      if (result === "PASS") {
        return <CheckCircle2 className="h-4 w-4 text-green-500" />;
      } else if (result === "FAIL") {
        return <XCircle className="h-4 w-4 text-red-500" />;
-      } else {
-        return <Info className="h-4 w-4 text-blue-500" />;
      }
+      return <Info className="h-4 w-4 text-blue-500" />;
    } else if (status === "IN_PROGRESS") {
      return <Clock className="h-4 w-4 text-blue-500 animate-pulse" />;
    } else if (status === "PENDING") {
      return <Clock className="h-4 w-4 text-yellow-500" />;
-    } else {
-      return <Info className="h-4 w-4" />;
    }
+    return <Info className="h-4 w-4" />;
  };
🧰 Tools
🪛 Biome (1.9.4)

[error] 87-91: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)


[error] 89-91: This else clause can be omitted because previous branches break early.

(lint/style/noUselessElse)


[error] 92-98: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)


[error] 94-98: This else clause can be omitted because previous branches break early.

(lint/style/noUselessElse)


[error] 96-98: This else clause can be omitted because previous branches break early.

(lint/style/noUselessElse)


197-199: Consider adding a fallback message when no result details are available.

If cloudTest.resultDetails is undefined, the pre block will only show "undefined". Adding a fallback message would be more user-friendly.

  <pre className="bg-muted p-4 rounded-md overflow-auto text-sm">
-   {JSON.stringify(cloudTest.resultDetails, null, 2)}
+   {cloudTest.resultDetails 
+     ? JSON.stringify(cloudTest.resultDetails, null, 2) 
+     : "No detailed results available"}
  </pre>
run-security-hub.ts (2)

26-28: Optimize performance by using for...of instead of forEach.

For large arrays, replacing forEach with for...of can lead to better performance.

-    Object.entries(groupedBySeverity).forEach(([severity, count]) => {
-      console.log(`  ${severity}: ${count} findings`);
-    });
+    for (const [severity, count] of Object.entries(groupedBySeverity)) {
+      console.log(`  ${severity}: ${count} findings`);
+    }
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-28: Prefer for...of instead of forEach.

forEach may lead to performance issues when working with large arrays. When combined with functions like filter or map, this causes multiple iterations over the same type.

(lint/complexity/noForEach)


37-44: Consider adding command line options for filtering findings.

The script could be more versatile by accepting command line arguments to filter findings by severity, age, or other criteria.

+#!/usr/bin/env bun
+
+import { fetchSecurityFindings } from './packages/integrations/src/aws/src/index';
+import { parseArgs } from 'node:util';
+
+// Parse command line arguments
+const { values } = parseArgs({
+  options: {
+    severity: {
+      type: 'string',
+      short: 's',
+      default: 'all'
+    },
+    limit: {
+      type: 'string',
+      short: 'l',
+      default: '10'
+    }
+  }
+});
+
+const severityFilter = values.severity;
+const limit = parseInt(values.limit as string);

// Then in your highSeverityFindings filter:
const highSeverityFindings = findings
  .filter(f => {
    if (severityFilter === 'all') {
      return f.Severity?.Label === 'CRITICAL' || f.Severity?.Label === 'HIGH';
    }
    return f.Severity?.Label === severityFilter.toUpperCase();
  })
  .slice(0, limit);

Would you like me to create a pull request that enhances this script with command-line options for improved functionality?

apps/app/src/actions/tests/register-test-action.ts (2)

9-26: Consider extracting DEFAULT_TASKS to a shared constants file.

These default tasks might be needed in other parts of the application. Consider moving them to a shared constants file for reusability.

-const DEFAULT_TASKS = [
-  {
-    code: "POLICY-ACCEPT",
-    name: "Policy Acceptance",
-    description: "Review and accept company policies",
-  },
-  {
-    code: "INSTALL-AGENT",
-    name: "Install Monitoring Agent",
-    description:
-      "Install and configure the security monitoring agent on your device",
-  },
-  {
-    code: "DEVICE-SECURITY",
-    name: "Device Security",
-    description: "Complete device security checklist and configuration",
-  },
-] as const;
+import { DEFAULT_TASKS } from "@/constants/employeeTasks";

62-74: Consider using Prisma's createMany for better performance.

Using Promise.all with multiple database operations can be less efficient than using Prisma's bulk operations.

-  // Create or get the required task definitions first and store their IDs
-  const requiredTasks = await Promise.all(
-    DEFAULT_TASKS.map(async (task) => {
-      return db.employeeRequiredTask.upsert({
-        where: { code: task.code },
-        create: {
-          code: task.code,
-          name: task.name,
-          description: task.description,
-        },
-        update: {},
-      });
-    })
-  );
+  // Create or get the required task definitions first and store their IDs
+  // Use transaction to ensure atomicity
+  const requiredTasks = await db.$transaction(
+    DEFAULT_TASKS.map(task => 
+      db.employeeRequiredTask.upsert({
+        where: { code: task.code },
+        create: {
+          code: task.code,
+          name: task.name,
+          description: task.description,
+        },
+        update: {},
+      })
+    )
+  );
apps/app/src/jobs/tasks/integration/integration-schedule.ts (1)

46-62: Consider partial-failure handling and retry strategies.

While you log errors and return a failure status if any trigger fails, partial successes remain untracked if only some triggers fail. If partial success is acceptable, think about individually retrying only the failed triggers instead of failing the entire batch. For large sets of integrations, a more granular approach can prevent losing valid integration runs.

apps/app/src/components/tables/tests/filter-toolbar.tsx (2)

70-76: Unify refresh actions for responsive layouts.

You have separate refresh button blocks for mobile (md:hidden) and desktop (md:flex). While they serve responsive layouts, consider extracting them into a shared UI component or function to reduce future maintenance overhead.


79-99: Avoid duplicating the refresh button.

Lines 72-75 and 96-99 both include essentially the same “Refresh” button. Extract a dedicated <RefreshButton> to keep layout logic simpler and consistent.

-<Button asChild variant="action">
-  <Link href="/integrations">
-    {t("tests.actions.refresh")}
-  </Link>
-</Button>

+function RefreshButton() {
+  return (
+    <Button asChild variant="action">
+      <Link href="/integrations">
+        {t("tests.actions.refresh")}
+      </Link>
+    </Button>
+  );
+}
apps/app/src/jobs/tasks/integration/integration-results.ts (4)

7-12: Use import type for type-only imports.

The static analysis indicates some named imports appear to be used only as types (e.g. GetFindingsCommandOutput). To reduce bundle size and improve clarity, import them with import type rather than a runtime import.

-import {
+import type {
   SecurityHubClientConfig,
   GetFindingsCommandInput,
   GetFindingsCommandOutput
 } from "@aws-sdk/client-securityhub";
🧰 Tools
🪛 Biome (1.9.4)

[error] 6-12: Some named imports are only used as types.

This import is only used as a type.

This import is only used as a type.

This import is only used as a type.

Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
Safe fix: Add inline type keywords.

(lint/style/useImportType)


43-43: Use const for variables assigned only once.

command is never reassigned after its creation. This is a minor style improvement to convey immutability and clarity.

-let command = new GetFindingsCommand(params);
+const command = new GetFindingsCommand(params);
🧰 Tools
🪛 Biome (1.9.4)

[error] 43-43: This let declares a variable that is only assigned once.

'command' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)


46-46: Use const for allFindings whenever possible.

Similarly, allFindings remains unchanged after initialization, so using const is more appropriate.

-let allFindings: any[] = response.Findings || [];
+const allFindings: any[] = response.Findings || [];
🧰 Tools
🪛 Biome (1.9.4)

[error] 46-46: This let declares a variable that is only assigned once.

'allFindings' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)


135-137: Omit the else clause when the preceding block returns.

Since you return at line 134, the else block at lines 135-137 is unreachable. You can remove it to simplify the flow.

} else {
  logger.warn(`Integration ${integrationId} does not have a fetchSecurityFindings method`);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 135-137: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)

apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/actions/get-test.ts (4)

3-10: Consider using import type for the Test import

The Test import is only used as a type and not as a value. Importing it with import type ensures that it will be removed by the compiler and avoids loading unnecessary modules.

import { db } from "@bubba/db";
import { auth } from "@/auth";
import { 
  appErrors, 
- type ActionResponse 
+ type ActionResponse
} from "./types";

-import { Test } from "../../types";
+import type { Test } from "../../types";
🧰 Tools
🪛 Biome (1.9.4)

[error] 10-10: All these imports are only used as types.

Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
Safe fix: Use import type.

(lint/style/useImportType)


23-31: Consider adding parameterized queries for better security

While Prisma's $queryRaw does provide some SQL injection protection, using template literals directly with user input is generally not the best practice. Consider using parameterized queries with Prisma's tagged template literal approach.

// Using raw SQL query to get the result since the Prisma client property name is causing issues
const results = await db.$queryRaw<any[]>`
  SELECT r.*, i.id as "integrationId", i.name as "integrationName", 
         i.integration_id, i.settings, i.user_settings
  FROM "Organization_integration_results" r
  JOIN "OrganizationIntegrations" i ON r."organizationIntegrationId" = i.id
- WHERE r.id = ${testId} AND r."organizationId" = ${organizationId}
+ WHERE r.id = ${db.raw(testId)} AND r."organizationId" = ${db.raw(organizationId)}
  LIMIT 1
`;

42-47: Document the reason for placeholder user object

The comment mentions "Create a placeholder user object since the schema doesn't have user info anymore" but doesn't explain why this approach was chosen or if this is a temporary solution. Consider adding more detailed documentation about this design decision.

// Create a placeholder user object since the schema doesn't have user info anymore
+ // TODO: This is a temporary solution until we implement proper user assignment for tests
+ // The schema no longer contains user information directly, so we're using session data
const placeholderUser = {
  id: session.user.id,
  name: session.user.name || null,
  email: session.user.email || null,
};

50-62: Validate all required fields before mapping to the Test object

The current implementation assumes that all required fields exist in the integration result. Consider adding validation to ensure all required fields are present before mapping to prevent runtime errors.

// Format the result to match the expected CloudTestResult structure
+ // Ensure all required fields exist
+ if (!integrationResult.id || !integrationResult.integration_id) {
+   throw new Error("Missing required fields in integration result");
+ }
+
const result: Test = {
  id: integrationResult.id,
  title: integrationResult.title || integrationResult.integrationName,
  description: typeof integrationResult.resultDetails === 'object' && integrationResult.resultDetails 
    ? (integrationResult.resultDetails as any).description || "" 
    : "",
  provider: integrationResult.integration_id,
  status: integrationResult.status,
  resultDetails: integrationResult.resultDetails,
  label: integrationResult.label,
  assignedUserId: placeholderUser,
  completedAt: integrationResult.completedAt,
};
apps/app/src/components/tables/tests/data-table-pagination.tsx (2)

51-64: Consider using the per_page parameter from the URL with a default value fallback

The component correctly uses the per_page parameter from the URL, but it would be better to also provide a default value if the parameter is missing or invalid.

<Select
- value={searchParams.get("per_page") || "10"}
+ value={searchParams.get("per_page") || "10"}
+ defaultValue="10"
  onValueChange={createPerPageQuery}
>

47-87: Consider handling edge case when pageCount is 0

If there are no items or pages (pageCount = 0), the component might display "1 of 0" which is confusing. Consider handling this edge case.

<div className="flex items-center justify-between mt-4">
  <div className="flex items-center space-x-2">
    <Select
      value={searchParams.get("per_page") || "10"}
      onValueChange={createPerPageQuery}
    >
      <SelectTrigger className="h-8 w-[70px]">
        <SelectValue placeholder="10" />
      </SelectTrigger>
      <SelectContent side="top">
        {[10, 20, 30, 40, 50].map((pageSize) => (
          <SelectItem key={pageSize} value={pageSize.toString()}>
            {pageSize}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  </div>
+ {pageCount > 0 ? (
  <div className="flex items-center space-x-2">
    <Button
      variant="outline"
      className="h-8 w-8 p-0"
      onClick={() => createPageQuery(currentPage - 1)}
      disabled={currentPage <= 1}
    >
      <ChevronLeft className="h-4 w-4" />
    </Button>
    <div className="flex w-[60px] items-center justify-center text-sm font-medium">
      {currentPage} of {pageCount}
    </div>
    <Button
      variant="outline"
      className="h-8 w-8 p-0"
      onClick={() => createPageQuery(currentPage + 1)}
      disabled={currentPage >= pageCount}
    >
      <ChevronRight className="h-4 w-4" />
    </Button>
  </div>
+ ) : (
+   <div className="flex w-[60px] items-center justify-center text-sm font-medium">
+     No pages
+   </div>
+ )}
</div>
apps/app/src/components/tables/tests/data-table-header.tsx (2)

52-57: Consider refactoring isVisible to be more robust

The current implementation of isVisible has an optional chaining that might not work as expected if table is undefined. A more robust implementation would check if table exists before continuing.

const isVisible = (id: string) =>
  loading ||
- table
-   ?.getAllLeafColumns()
-   .find((col) => col.id === id)
-   ?.getIsVisible();
+ (table && table.getAllLeafColumns().find((col) => col.id === id)?.getIsVisible());

25-25: Unused isEmpty prop

The component accepts an isEmpty prop in its type definition, but this prop is not used in the component implementation.

- export function DataTableHeader({ table, loading }: Props) {
+ export function DataTableHeader({ table, loading, isEmpty }: Props) {

Or remove it from the Props type if it's truly not needed:

type Props = {
  table?: {
    getIsAllPageRowsSelected: () => boolean;
    getIsSomePageRowsSelected: () => boolean;
    getAllLeafColumns: () => {
      id: string;
      getIsVisible: () => boolean;
    }[];
    toggleAllPageRowsSelected: (value: boolean) => void;
  };
  loading?: boolean;
- isEmpty?: boolean;
};
apps/app/src/app/[locale]/(app)/(dashboard)/tests/types/index.ts (2)

19-25: Add validation rules to input schema

The current testsInputSchema lacks validation rules for its fields. Consider adding more specific validation for better type safety and user input handling.

export const testsInputSchema = z.object({
- search: z.string().optional(),
+ search: z.string().trim().optional(),
  provider: z.enum(["AWS", "AZURE", "GCP"]).optional(),
- status: z.string().optional(),
+ status: z.string().min(1).max(50).optional(),
- page: z.number().optional(),
+ page: z.number().int().positive().optional(),
- per_page: z.number().optional(),
+ per_page: z.number().int().min(1).max(100).optional(),
});

48-57: Add more specific error codes

The current error codes are quite generic. Consider adding more specific error codes for better error handling and user feedback.

export const appErrors = {
  UNAUTHORIZED: {
    code: "UNAUTHORIZED" as const,
    message: "You are not authorized to view employees",
  },
  UNEXPECTED_ERROR: {
    code: "UNEXPECTED_ERROR" as const,
    message: "An unexpected error occurred",
  },
+ NOT_FOUND: {
+   code: "NOT_FOUND" as const,
+   message: "The requested test was not found",
+ },
+ VALIDATION_ERROR: {
+   code: "VALIDATION_ERROR" as const,
+   message: "Invalid input parameters",
+ },
} as const;
packages/db/prisma/schema.prisma (4)

162-163: Consider removing unnecessary empty line.

There's an empty line at line 162 that doesn't serve any purpose and is inconsistent with the spacing pattern used throughout the rest of the schema file.

  organizationId String
  organization   Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
-  
  results OrganizationIntegrationResults[]

1076-1095: Consider using an enum for the status field.

The status field is defined as a required string, but it would benefit from being constrained to a predefined set of values using an enum. This would ensure consistency and prevent invalid status values from being stored.

+enum IntegrationResultStatus {
+  passed
+  failed
+  error
+  in_progress
+  pending
+}

model OrganizationIntegrationResults {
  id                        String        @id @default(cuid())
  title                     String?
-  status                    String
+  status                    IntegrationResultStatus
  label                     String?       // PASS, FAIL, ERROR
  resultDetails             Json?         // Stores detailed test results
  completedAt               DateTime?     @default(now())
  organizationIntegrationId String
  organizationId            String
  assignedUserId            String?

  assignedUser             User?          @relation(fields: [assignedUserId], references: [id], onDelete: Cascade)
  organizationIntegration  OrganizationIntegrations @relation(fields: [organizationIntegrationId], references: [id], onDelete: Cascade)
  organization             Organization  @relation(fields: [organizationId], references: [id], onDelete: Cascade)

  @@index([assignedUserId])
  @@index([organizationIntegrationId])
  @@index([organizationId])
  @@map("Organization_integration_results")
}

1081-1082: Consider making label more type-safe.

The label field has a comment indicating it can be "PASS", "FAIL", or "ERROR", but it's defined as an optional string. Consider using an enum to enforce these values for better type safety and data integrity.

+enum IntegrationResultLabel {
+  pass
+  fail
+  error
+}

model OrganizationIntegrationResults {
  id                        String        @id @default(cuid())
  title                     String?
  status                    String
-  label                     String?       // PASS, FAIL, ERROR
+  label                     IntegrationResultLabel?
  resultDetails             Json?         // Stores detailed test results
  // rest of model...
}

1087-1087: Verify onDelete cascade behavior implications.

Using onDelete: Cascade for the User relationship means that if a user is deleted, all their assigned test results will also be deleted. Ensure this is the intended behavior, or consider using SetNull instead to preserve test history while removing the assignee reference.

You may want to consider whether preserving test history is more important than maintaining referential integrity when a user is deleted. Using onDelete: SetNull would keep the test results but set the assignedUserId to null, which might be preferable for audit purposes.

packages/db/prisma/migrations/20250227185651_add_organization_integration_results/migration.sql (1)

7-7: Consider adding constraint for JSON validation.

For the resultDetails JSONB field, consider adding a check constraint to validate the JSON structure if you have a specific expected format.

While PostgreSQL doesn't provide built-in JSON schema validation, you could add a basic check constraint to ensure certain required fields are present:

CREATE TABLE "Organization_integration_results" (
    // other fields...
    "resultDetails" JSONB,
+    CONSTRAINT "resultDetails_has_required_fields" CHECK ("resultDetails" ? 'testId' AND "resultDetails" ? 'findings'),
    // remaining fields...
);

Note: This is just an example. Adjust the fields based on your actual required structure.

apps/app/src/locales/en.ts (2)

614-615: Remove unnecessary blank line.

There's an empty line after the invalid_json property that doesn't serve any purpose and is inconsistent with the spacing pattern in the rest of the file.

      invalid_json: "Invalid JSON configuration provided",
-      
      title_field: {

633-634: Consider adding validation instructions for JSON fields.

The translation for authentication configuration mentions JSON format but doesn't provide guidance on required structure or format. Consider adding a tooltip or help text to assist users in entering valid JSON.

      auth_config: {
        label: "Authentication Configuration", 
-        placeholder: "Enter JSON authentication configuration"
+        placeholder: "Enter JSON authentication configuration",
+        tooltip: "Enter valid JSON with required authentication details like API keys or credentials. Example: { \"apiKey\": \"your-key\", \"region\": \"us-east-1\" }"
      }
apps/app/src/locales/fr.ts (1)

182-183: Ensure consistent formatting in sidebar entries.

There's an unnecessary comma after the people entry. While this doesn't affect functionality, it's inconsistent with the formatting in the rest of the file.

-    people: "Personnes",
+    people: "Personnes"
     tests: "Tests en nuage"
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0287f96 and b1761b0.

⛔ Files ignored due to path filters (4)
  • apps/app/languine.lock is excluded by !**/*.lock
  • bun.lock is excluded by !**/*.lock
  • packages/integrations/src/aws/assets/image.png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (43)
  • apps/app/src/actions/schema.ts (1 hunks)
  • apps/app/src/actions/tests/register-test-action.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/actions/get-test.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/actions/types.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/components/Test.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/actions/get-tests.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/components/TestsList.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/components/TestsListSkeleton.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/hooks/useTest.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/hooks/useTests.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/layout.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/page.tsx (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/types/index.ts (1 hunks)
  • apps/app/src/app/[locale]/(app)/(dashboard)/tests/types/search-params.ts (1 hunks)
  • apps/app/src/components/main-menu.tsx (2 hunks)
  • apps/app/src/components/tables/tests/columns.tsx (1 hunks)
  • apps/app/src/components/tables/tests/data-table-header.tsx (1 hunks)
  • apps/app/src/components/tables/tests/data-table-pagination.tsx (1 hunks)
  • apps/app/src/components/tables/tests/data-table.tsx (1 hunks)
  • apps/app/src/components/tables/tests/empty-states.tsx (1 hunks)
  • apps/app/src/components/tables/tests/filter-toolbar.tsx (1 hunks)
  • apps/app/src/components/tables/tests/loading.tsx (1 hunks)
  • apps/app/src/components/tables/tests/server-columns.tsx (1 hunks)
  • apps/app/src/jobs/tasks/integration/integration-results.ts (1 hunks)
  • apps/app/src/jobs/tasks/integration/integration-schedule.ts (1 hunks)
  • apps/app/src/jobs/tasks/integration/utils/task-email-notification.tsx (1 hunks)
  • apps/app/src/locales/en.ts (2 hunks)
  • apps/app/src/locales/es.ts (2 hunks)
  • apps/app/src/locales/fr.ts (2 hunks)
  • apps/app/src/locales/no.ts (2 hunks)
  • apps/app/src/locales/pt.ts (2 hunks)
  • package.json (1 hunks)
  • packages/db/prisma/migrations/20250227185651_add_organization_integration_results/migration.sql (1 hunks)
  • packages/db/prisma/schema.prisma (4 hunks)
  • packages/integrations/src/aws/assets/logo.tsx (1 hunks)
  • packages/integrations/src/aws/config.ts (1 hunks)
  • packages/integrations/src/aws/src/index.ts (1 hunks)
  • packages/integrations/src/github/config.ts (1 hunks)
  • packages/integrations/src/gusto/config.ts (1 hunks)
  • packages/integrations/src/index.ts (1 hunks)
  • packages/ui/src/components/icons.tsx (2 hunks)
  • run-security-hub.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/integrations/src/github/config.ts
  • packages/integrations/src/gusto/config.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/integrations/src/aws/src/index.ts

[error] 1-7: Some named imports are only used as types.

This import is only used as a type.

This import is only used as a type.

This import is only used as a type.

Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
Safe fix: Add inline type keywords.

(lint/style/useImportType)


[error] 38-38: This let declares a variable that is only assigned once.

'command' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)


[error] 41-41: This let declares a variable that is only assigned once.

'allFindings' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)

run-security-hub.ts

[error] 26-28: Prefer for...of instead of forEach.

forEach may lead to performance issues when working with large arrays. When combined with functions like filter or map, this causes multiple iterations over the same type.

(lint/complexity/noForEach)

apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/actions/get-test.ts

[error] 10-10: All these imports are only used as types.

Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
Safe fix: Use import type.

(lint/style/useImportType)

apps/app/src/jobs/tasks/integration/integration-results.ts

[error] 135-137: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)


[error] 6-12: Some named imports are only used as types.

This import is only used as a type.

This import is only used as a type.

This import is only used as a type.

Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
Safe fix: Add inline type keywords.

(lint/style/useImportType)


[error] 43-43: This let declares a variable that is only assigned once.

'command' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)


[error] 46-46: This let declares a variable that is only assigned once.

'allFindings' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)


[error] 97-97: Unexpected constant condition.

(lint/correctness/noConstantCondition)


[error] 100-100: Unexpected constant condition.

(lint/correctness/noConstantCondition)

apps/app/src/app/[locale]/(app)/(dashboard)/tests/hooks/useTest.ts

[error] 12-14: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)

apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/components/Test.tsx

[error] 87-91: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)


[error] 89-91: This else clause can be omitted because previous branches break early.

(lint/style/noUselessElse)


[error] 92-98: This else clause can be omitted because previous branches break early.

Unsafe fix: Omit the else clause.

(lint/style/noUselessElse)


[error] 94-98: This else clause can be omitted because previous branches break early.

(lint/style/noUselessElse)


[error] 96-98: This else clause can be omitted because previous branches break early.

(lint/style/noUselessElse)

🔇 Additional comments (55)
packages/ui/src/components/icons.tsx (2)

19-19: Clean icon import added correctly.

The MdCloudSync icon is correctly imported from the react-icons/md package, following the established alphabetical ordering pattern of the existing imports.


735-735: Icon properly added to the exports.

The CloudSync icon is correctly added to the Icons export object, maintaining consistency with the existing pattern in the file.

package.json (1)

28-28:

Details

✅ Verification successful

Verify version compatibility of AWS Security Hub client.

The addition of this AWS SDK client is appropriate for integrating with AWS Security Hub functionality. Consider whether this dependency should be scoped to the integrations package rather than at the root level if it's only used there.


🏁 Script executed:

#!/bin/bash
# Check where the AWS Security Hub client is being used

echo "Searching for imports of @aws-sdk/client-securityhub..."
rg --type ts "@aws-sdk/client-securityhub" -A 3 -B 3

echo "Checking if any workspace-specific package.json already includes this dependency..."
fd "package.json" packages apps | xargs grep -l "@aws-sdk/client-securityhub" || echo "Not found in workspace package.json files"

Length of output: 1586


Dependency Usage Confirmed: Continue with Current Setup

We've verified that the AWS Security Hub client (@aws-sdk/client-securityhub at version ^3.751.0) is imported in both:

  • apps/app/src/jobs/tasks/integration/integration-results.ts
  • packages/integrations/src/aws/src/index.ts

Since the dependency isn’t isolated solely to the integrations package, keeping it in the root appears justified. Please ensure that the SDK version remains compatible with your AWS integration workflows through your standard testing.

packages/integrations/src/index.ts (2)

3-3: AWS integration import added correctly.

The Aws import is properly added from the correct path.


5-5: AWS integration properly added to exported integrations array.

The AWS integration is correctly added to the integrations export array alongside the existing GitHub and Gusto integrations.

apps/app/src/app/[locale]/(app)/(dashboard)/tests/types/search-params.ts (1)

1-5: Appropriate imports from nuqs/server.

The imports are correctly specified for the search parameters caching functionality.

apps/app/src/app/[locale]/(app)/(dashboard)/tests/components/TestsListSkeleton.tsx (2)

1-2: Looks good - imports are minimal and properly organized.

The component imports only what it needs - the FilterToolbar for layout consistency and the Skeleton component for loading state visualization.


4-7: Looks good - component structure follows best practices.

The component is properly exported and has a clear, focused responsibility: displaying a loading state for the tests list.

apps/app/src/components/tables/tests/columns.tsx (2)

1-2: The "use client" directive suggests this component might be used in both client and server contexts.

The "use client" directive indicates this component is intended for client-side usage, but the interface itself doesn't have any client-specific functionality.


12-18: Good explanatory comments about the file's purpose.

The comments clearly explain that column definitions have been moved to another file and this file now serves a more focused purpose.

packages/integrations/src/aws/assets/logo.tsx (1)

10-14: Resolve mismatch between viewBox and enableBackground dimensions.

There's a mismatch between the viewBox dimensions (500x182) and the enableBackground dimensions (304x182), which might cause scaling issues.

			width={131.417}
			height={50}
			viewBox="0 0 500 182"
-			style={{ enableBackground: "new 0 0 304 182" }}
+			style={{ enableBackground: "new 0 0 500 182" }}
			xmlSpace="preserve">
apps/app/src/components/tables/tests/server-columns.tsx (1)

1-5: Function follows the server component pattern correctly.

The import and async function setup demonstrate proper usage of React Server Components with i18n.

apps/app/src/components/main-menu.tsx (2)

31-31: Good addition of the cloud sync icon for tests path.

The new icon choice for the tests section is appropriate and consistent with the application's icon naming conventions.


187-191: Successfully added new menu item for tests section.

The addition of the tests menu item follows the established pattern in the codebase, correctly using the translation function and setting appropriate defaults.

packages/integrations/src/aws/config.ts (1)

8-12:

Details

❓ Verification inconclusive

Logo component should follow AWS brand guidelines.

Ensure the Logo component adheres to AWS's brand guidelines for partners and integrations. AWS typically has specific requirements for how their logo can be displayed in third-party applications.


🌐 Web query:

What are the AWS brand guidelines for logos in partner integrations?

💡 Result:

AWS brand guidelines for logos in partner integrations are designed to maintain consistency, clarify partnerships, and ensure proper representation of AWS's role. Key requirements include:


Logo Usage Tiers & Permissions

  • Select Tier+ Access: AWS Logo usage is restricted to Select tier partners and above for indicating solution support, cloud provider relationships, or technical integration[2][7].
  • Badge Manager: Partners create custom badges (designation, certification, qualified devices/software) via AWS Partner Central to showcase achievements[1][7].

Messaging & Placement Guidelines

  • Phrasing: Use "runs on AWS" or "integrates with AWS" instead of "in AWS"[2][3].
  • Logo Placement:
    • Partner-Led Scenarios: Partner logo primary (top-left), AWS branding secondary (e.g., badges in bottom-right)[3][7].
    • AWS-Led Scenarios: AWS logo primary; partner logos appear under "In collaboration with" in secondary positions[3][9].
  • Co-Branding: Requires written AWS approval and shared campaign control[3][9].

Design & Technical Specifications

  • Clear Space: Maintain clear space around logos equal to the height of the logo's first character[2][3].
  • Color Variations: Use Squid Ink/white logos on light/dark backgrounds respectively[3][8].
  • Prohibited Uses:
    • Ads, social media, or promotional items[2].
    • Combining AWS logos with partner logos using connectors (e.g., "+", "&")[2].
    • Standalone AWS logos in shapes/illustrations without clear space[2][3].

Regional Restrictions

  • Mainland China: Use Amazon Cloud Technologies logos instead of AWS logos[3][8].
  • Global Use: AWS logos are permitted worldwide except China[3][8].

Approved Brand Treatments

  • Powered by AWS Logo: For solutions built on AWS infrastructure[3][7].
  • AWS Marketplace Logo: For solutions listed on AWS Marketplace[1][7].
  • Certification Badges: Display active AWS Certifications (e.g., "100 AWS Certifications")[3][7].

Partners can access the full AWS Partner Creative and Messaging Guide, logos, and templates via the Marketing Toolkit in AWS Partner Central[1][3][7].

Citations:


Action Required: Update AWS Logo Compliance

Please ensure the Logo component strictly adheres to AWS brand guidelines for partner integrations. In particular, verify that:

  • The logo usage complies with AWS’ permitted tiers and approved brand treatments.
  • Proper clear space is maintained (equal to the height of the logo’s first character).
  • Appropriate color variations and placement guidelines are followed (e.g., correct messaging such as "runs on AWS" or "integrates with AWS" and logo positioning based on partnership context).

Refer to the AWS Partner Creative and Messaging Guide (as detailed in the provided web query results) for all specific requirements.

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

182-183: Ensure consistent comma formatting in the sidebar section.

The comma after "Personer" on line 182 appears to be a formatting change. Make sure this follows the pattern used in other localization files for consistency.

apps/app/src/components/tables/tests/loading.tsx (1)

57-57: Empty table cell might need a skeleton or accessibility label.

The last table cell is empty, which might be intentional for an action column, but consider adding a visual indicator or accessibility context.

Is this empty cell intended for an actions column? If so, consider adding a minimal skeleton representation for consistency:

-  <TableCell className="w-[50px]" />
+  <TableCell className="w-[50px]">
+    {!isEmpty && (
+      <Skeleton 
+        className="h-[20px] w-[20px] rounded-full opacity-40" 
+        aria-hidden="true"
+      />
+    )}
+  </TableCell>
apps/app/src/app/[locale]/(app)/(dashboard)/tests/hooks/useTests.ts (1)

37-37:

Details

❓ Verification inconclusive

Verify the nested data structure and avoid double type assertion.

The line return result.data.data as TestsResponse suggests a confusing API design with nested data properties or unnecessary type assertions. Consider restructuring or adding comments to explain the data structure.


🏁 Script executed:

#!/bin/bash
# Check the actual structure of the response from getTests
ast-grep --pattern 'function getTests($_, $_) {
  $$$
  return $_
}'

Length of output: 70


Action Required: Verify and Clarify Nested Data Structure

The current implementation of

return result.data.data as TestsResponse;

relies on a nested property (data.data), which can be confusing and may indicate a redundant or unintended API response structure. Since our initial AST search for the getTests function’s return structure provided no clear output, please verify manually that:

  • The API response is intentionally structured with nested data properties.
  • A single type assertion wouldn’t suffice, or if additional documentation/comments could clarify why a double access (i.e., result.data.data) is necessary.
  • There isn’t an opportunity to restructure the response (or the type definitions) to avoid potential double assertions.

Once you confirm that the current design is intentional, consider adding an inline comment or refactoring the data structure to improve readability and maintainability.

apps/app/src/app/[locale]/(app)/(dashboard)/tests/actions/get-tests.ts (1)

110-114:

Details

❓ Verification inconclusive

Handle potential null/undefined values in result transformation.

The code assumes that result.organizationIntegration always exists and has the expected properties, which might cause runtime errors if the data structure changes.

Consider adding safeguards:

  return {
    id: result.id,
    severity: result.label,
    result: result.status,
-    title: result.title || result.organizationIntegration.name,
-    provider: result.organizationIntegration.integration_id,
+    title: result.title || (result.organizationIntegration?.name ?? 'Unnamed Test'),
+    provider: result.organizationIntegration?.integration_id ?? 'unknown',
    createdAt: result.completedAt || new Date(),
    // The executedBy information is no longer available in the new schema
    assignedUser: null,
  };

🏁 Script executed:

#!/bin/bash
# Check if there are any integration results without valid organizationIntegration data
# This will help us understand if this is a realistic concern

cat << 'EOF' | node
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();

async function checkIntegrationResults() {
  try {
    const results = await prisma.organizationIntegrationResults.findMany({
      where: {
        OR: [
          { organizationIntegration: null },
          { organizationIntegration: { name: null } },
          { organizationIntegration: { integration_id: null } }
        ]
      },
      select: {
        id: true
      }
    });
    
    console.log(`Found ${results.length} integration results with missing organizationIntegration data`);
    if (results.length > 0) {
      console.log('Sample IDs:', results.slice(0, 5).map(r => r.id));
    }
  } catch (error) {
    console.error('Error checking integration results:', error);
  } finally {
    await prisma.$disconnect();
  }
}

checkIntegrationResults();
EOF

Length of output: 789


Action Required: Verify Integration Data Handling in Production Environment

It appears that the verification script did not produce useful output due to a missing dependency (@prisma/client). Because the script failed in this sandbox environment, it's important that you manually verify that missing or incomplete organizationIntegration data is handled correctly in your production or development environment.

A few points to note:

  • Check Environment Dependencies: Ensure that the environment used for manual testing has the @prisma/client module installed.
  • Code Safety: Update the transformation logic to safely handle cases where result.organizationIntegration might be null or missing expected properties.
  • Manual Verification: Re-run the integration data checks with a correctly configured environment to confirm that the database schema and corresponding data meet the new assumptions.

Below is the recommended diff snippet that applies safeguards by using optional chaining and default values:

  return {
    id: result.id,
    severity: result.label,
    result: result.status,
-    title: result.title || result.organizationIntegration.name,
-    provider: result.organizationIntegration.integration_id,
+    title: result.title || (result.organizationIntegration?.name ?? 'Unnamed Test'),
+    provider: result.organizationIntegration?.integration_id ?? 'unknown',
    createdAt: result.completedAt || new Date(),
    // The executedBy information is no longer available in the new schema
    assignedUser: null,
  };

Please verify these changes in an environment where the integration data can be properly checked.

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

138-142:

Details

❓ Verification inconclusive

Add validation for column headers to prevent runtime errors.

The code assumes that all column IDs exist in the provided column headers object, which could lead to runtime errors if there's a mismatch between the expected headers and what's provided.

Consider adding validation or default values:

  const columns = clientColumns.map((col) => ({
    ...col,
-   header: columnHeaders[col.id as keyof typeof columnHeaders],
+   header: columnHeaders[col.id as keyof typeof columnHeaders] || col.id,
    accessorFn: (row: TestType) => row[col.id as keyof TestType],
  }));

🏁 Script executed:

#!/bin/bash
# Check if all required column headers are consistently provided across the codebase

# Find all instances where DataTable is imported
echo "Looking for DataTable imports:"
rg -A 3 "import.*DataTable" --type ts --type tsx

# Find where columnHeaders are being passed to the DataTable
echo "\nLooking for columnHeaders prop passed to DataTable:"
rg "columnHeaders=\{.*\}" --type tsx -A 3

Length of output: 338


Below is an updated request for manual verification with an improved shell script to search for DataTable imports and the use of the columnHeaders prop in both TypeScript (.ts) and TSX (.tsx) files.

Please verify the consistency of the columnHeaders usage across the codebase
The original verification command produced errors due to unrecognized file types for TSX files. To ensure that our validation for column headers is comprehensive and that no runtime errors will occur, please run the following updated shell script:

#!/bin/bash
echo "Searching for DataTable imports in .ts and .tsx files:"
rg "import.*DataTable" -g "*.ts" -g "*.tsx" -A 3

echo -e "\nSearching for columnHeaders prop usage in .ts and .tsx files:"
rg "columnHeaders=\{.*\}" -g "*.ts" -g "*.tsx" -A 3

If the output indicates that there are cases where a column header might be missing, consider updating the mapping as suggested:

  const columns = clientColumns.map((col) => ({
    ...col,
-   header: columnHeaders[col.id as keyof typeof columnHeaders],
+   header: columnHeaders[col.id as keyof typeof columnHeaders] || col.id,
    accessorFn: (row: TestType) => row[col.id as keyof TestType],
  }));

This fallback ensures that if a column header isn’t provided, the column ID is used instead, preventing potential runtime errors.

apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/components/Test.tsx (2)

22-34: Good error handling implementation.

The error state is properly handled with an informative alert that displays the specific error message or falls back to a generic one.


36-58: Loading state implementation is well-designed.

Good use of skeleton loaders to indicate content is being fetched, providing a better user experience while waiting for data.

run-security-hub.ts (3)

18-22: Well-implemented grouping of findings by severity.

Good use of reduce to efficiently group the findings by severity, with proper type annotation for the accumulator.


54-62: Good use of Promise handling for the main function execution.

The promise chain for executing the main function has proper success and error handling with appropriate exit codes.


3-3:

Details

✅ Verification successful

Use absolute imports for better maintainability.

The relative import path is brittle and may break if the script location changes. Consider using absolute imports configured via tsconfig paths.

-import { fetchSecurityFindings } from './packages/integrations/src/aws/src/index';
+import { fetchSecurityFindings } from '@bubba/integrations/aws';

🏁 Script executed:

#!/bin/bash
# Check if the project uses tsconfig paths for imports

# Look for tsconfig.json files that might configure paths
fd -t f "tsconfig.*json" -X grep -l "paths" {}

Length of output: 154


Action: Update the import in run-security-hub.ts from a brittle relative import to an absolute import using your configured tsconfig paths.

  • The output from your search confirms that several tsconfig files (e.g., in ./apps/portal/, ./apps/web/, etc.) contain path configurations. This supports the use of absolute imports.
  • Refactor the current import:
    -import { fetchSecurityFindings } from './packages/integrations/src/aws/src/index';
    +import { fetchSecurityFindings } from '@bubba/integrations/aws';
  • This update will improve maintainability by reducing risk if file locations change.
apps/app/src/actions/tests/register-test-action.ts (1)

50-59: Well-structured employee creation with proper data handling.

The employee creation code properly structures the data and includes all necessary fields from the input and context.

apps/app/src/app/[locale]/(app)/(dashboard)/tests/page.tsx (3)

17-22: Good authorization check with appropriate redirect.

The component correctly checks for the presence of an organization ID and redirects if not available, ensuring proper access control.


29-42: Well-implemented metadata generation.

The generateMetadata function correctly handles localization and sets the page title based on the translated string.


9-13:

Details

❓ Verification inconclusive

Unusual Promise type for Next.js params.

The params parameter is typed as a Promise, which is unusual for Next.js page components. Typically, Next.js passes these parameters directly as objects.


🏁 Script executed:

#!/bin/bash
# Check for other instances of params as Promise in the codebase

echo "Looking for other instances of 'params: Promise<{' in page components..."
rg "params: Promise<\{" --type tsx

echo "Checking Next.js documentation for typical params typing..."
curl -s https://nextjs.org/docs/app/api-reference/file-conventions/page | grep -A 10 "export default function Page"

Length of output: 472


Potential Issue with Asynchronous Typing for Next.js Page Params

The page component in apps/app/src/app/[locale]/(app)/(dashboard)/tests/page.tsx declares its params as a Promise (params: Promise<{ locale: string }>), which is unconventional since Next.js typically passes these parameters synchronously as plain objects.

  • Action Required: Please verify whether this asynchronous typing was intentional. If it's by design (perhaps to accommodate an async data-fetching scenario), consider adding a comment to explain the rationale. Otherwise, updating params to the expected type (e.g., { locale: string }) would better align with standard Next.js practices.
apps/app/src/jobs/tasks/integration/integration-schedule.ts (1)

10-11: Consider utilizing upcomingThreshold in the DB query.

Currently, upcomingThreshold is computed but never used to filter or process integrations before triggering. If your intent is to only send integration runs for the upcoming 7 days, ensure the query or subsequent logic references this value.

apps/app/src/components/tables/tests/filter-toolbar.tsx (1)

17-55: Validate the debounced routing logic.

Your approach with useTransition and useDebounce is a common pattern for improving performance. Confirm the transition remains responsive for users with slower connections or large data sets. You may also consider error handling or fallback UI for potential routing issues.

apps/app/src/app/[locale]/(app)/(dashboard)/tests/[testId]/actions/get-test.ts (1)

15-20: LGTM: Authentication and organization ID validation is correctly implemented

The code properly authenticates the user and validates that an organization ID is available before proceeding.

apps/app/src/components/tables/tests/data-table-pagination.tsx (3)

15-18: LGTM: The component interface is well-defined

The interface clearly describes the required props for the DataTablePagination component.


28-35: LGTM: The page query creation is well-implemented

The createPageQuery callback correctly updates the URL parameters and preserves other query parameters.


37-45: LGTM: The per-page query creation resets to page 1 appropriately

The createPerPageQuery callback correctly resets to the first page when changing items per page, which is a good UX practice.

apps/app/src/components/tables/tests/data-table-header.tsx (2)

11-23: LGTM: Good type definition with clear optional properties

The Props type is well-defined with appropriate optional properties and clear structure.


34-50: LGTM: Sort query creation logic is well-implemented

The sort query creation logic correctly toggles between ascending, descending, and no sorting states.

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

105-105: Well-structured relationship field for integration results.

The addition of the integrationResults field to the User model establishes a clear one-to-many relationship with the new OrganizationIntegrationResults model, allowing users to be assigned to multiple integration test results.


148-148: Appropriate relationship field for integration results.

The OrganizationIntegrationResults field in the Organization model properly establishes the relationship between organizations and their integration test results. This follows the existing pattern in the schema for other organization-related models.


1082-1082: Add JSON validation for resultDetails.

The resultDetails field is using a JSON type to store flexible test result data. Consider adding validation at the application level to ensure the JSON structure meets your expected format for test results, especially if this data will be used for reporting or analytics.

It appears that JSON validation would need to be handled in your application code since Prisma doesn't provide schema-level JSON validation. You may want to implement a validation layer in your API endpoints or services that handle test result creation and updates.

packages/db/prisma/migrations/20250227185651_add_organization_integration_results/migration.sql (3)

1-14: Well-structured table creation with appropriate fields.

The table creation statement is well-structured and aligns properly with the Prisma schema. All necessary fields are defined with appropriate data types.


16-23: Appropriate indexes for query optimization.

The creation of indexes on foreign key columns (organizationIntegrationId, organizationId, and assignedUserId) is a good practice for optimizing query performance, especially for relationship lookups.


25-32: Cascade delete constraints properly defined.

The foreign key constraints with ON DELETE CASCADE ON UPDATE CASCADE are properly defined to maintain referential integrity. This ensures that when a related record is deleted or updated, the corresponding integration results are also appropriately handled.

Ensure that the cascade delete behavior is the intended design, especially for the assignedUserId foreign key. If you want to preserve test results history when a user is deleted, you might want to consider using ON DELETE SET NULL instead for this specific constraint.

apps/app/src/locales/en.ts (2)

185-185: Sidebar entry added for new feature.

The addition of "Cloud Tests" to the sidebar navigation is concise and follows the naming pattern of other entries.


586-647: Comprehensive localization for Cloud Tests feature.

The localization section for the Cloud Tests feature is well-structured and complete. It includes all necessary strings for actions, empty states, filters, registration form, and table display.

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

182-183: LGTM: Sidebar menu entry for cloud tests added correctly.

The addition of "Pruebas en la Nube" to the sidebar menu maintains consistency with the application's navigation structure.


718-736: LGTM: Cloud tests section properly structured with appropriate Spanish translations.

The new section for cloud tests includes correctly translated strings for the main title, actions (create, clear, refresh), and empty states. This follows the same pattern as other feature sections in the localization file.


737-740: LGTM: Appropriate filter translations for cloud tests.

The search and provider filter translations are clear and consistent with other filter translations in the application.


741-784: LGTM: Comprehensive form field translations for test registration.

The registration section includes all necessary form fields with appropriate labels and placeholders. The section covers email, role, name, department fields as well as specialized cloud test fields like title, description, provider, and configuration options.


785-796: LGTM: Table column headers correctly translated.

All necessary table columns for the cloud tests listing are appropriately translated, including title, provider, status, execution time, severity, results, creation date, and user assignment information.

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

182-183: LGTM: Sidebar menu entry for cloud tests added correctly.

The addition of "Testes na Nuvem" to the sidebar menu maintains consistency with the application's Portuguese navigation structure.


718-736: LGTM: Cloud tests section properly structured with appropriate Portuguese translations.

The new section for cloud tests includes well-translated strings for the main title, actions (create, clear, refresh), and empty states, following the same pattern as other feature sections in the localization file.


737-740: LGTM: Appropriate filter translations for cloud tests.

The search and provider filter translations are clear and consistent with other Portuguese filter translations in the application.


741-784: LGTM: Comprehensive form field translations for test registration.

The registration section includes all necessary form fields with appropriate labels and placeholders translated to Portuguese. The section covers email, role, name, department fields as well as specialized cloud test fields like title, description, provider, and configuration options.


785-796: LGTM: Table column headers correctly translated to Portuguese.

All necessary table columns for the cloud tests listing are appropriately translated, including title, provider, status, execution time, severity, results, creation date, and user assignment information.

Comment on lines +1 to +14
export const Logo = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
width={131.417}
height={50}
viewBox="0 0 500 182"
style={{ enableBackground: "new 0 0 304 182" }}
xmlSpace="preserve">

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 accessibility attributes to the SVG logo.

The SVG lacks accessibility attributes which are important for screen readers.

export const Logo = () => {
	return (
		<svg
			xmlns="http://www.w3.org/2000/svg"
			version="1.1"
			id="Layer_1"
			xmlnsXlink="http://www.w3.org/1999/xlink"
			x="0px"
			y="0px"
			width={131.417}
			height={50}
			viewBox="0 0 500 182"
			style={{ enableBackground: "new 0 0 304 182" }}
+			aria-label="AWS Logo"
+			role="img"
			xmlSpace="preserve">
📝 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
export const Logo = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
width={131.417}
height={50}
viewBox="0 0 500 182"
style={{ enableBackground: "new 0 0 304 182" }}
xmlSpace="preserve">
export const Logo = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
width={131.417}
height={50}
viewBox="0 0 500 182"
style={{ enableBackground: "new 0 0 304 182" }}
aria-label="AWS Logo"
role="img"
xmlSpace="preserve">
{/* ...rest of the SVG content... */}
</svg>
);
};

Comment on lines +3 to +15
export async function getServerColumnHeaders() {
const t = await getI18n();

return {
severity: t("tests.table.severity"),
result: t("tests.table.result"),
title: t("tests.table.title"),
provider: t("tests.table.provider"),
status: t("tests.table.status"),
createdAt: t("tests.table.createdAt"),
assignedUser: t("tests.table.assignedUser"),
};
}

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 error handling for i18n function failures.

The function doesn't handle potential errors from the i18n system, which could lead to runtime failures.

export async function getServerColumnHeaders() {
-  const t = await getI18n();
+  try {
+    const t = await getI18n();
+    
+    return {
+      severity: t("tests.table.severity"),
+      result: t("tests.table.result"),
+      title: t("tests.table.title"),
+      provider: t("tests.table.provider"),
+      status: t("tests.table.status"),
+      createdAt: t("tests.table.createdAt"),
+      assignedUser: t("tests.table.assignedUser"),
+    };
+  } catch (error) {
+    console.error("Failed to load column headers:", error);
+    // Fallback to English or default strings
+    return {
+      severity: "Severity",
+      result: "Result",
+      title: "Title",
+      provider: "Provider",
+      status: "Status",
+      createdAt: "Created At",
+      assignedUser: "Assigned User",
+    };
+  }
-
-  return {
-    severity: t("tests.table.severity"),
-    result: t("tests.table.result"),
-    title: t("tests.table.title"),
-    provider: t("tests.table.provider"),
-    status: t("tests.table.status"),
-    createdAt: t("tests.table.createdAt"),
-    assignedUser: t("tests.table.assignedUser"),
-  };
}
📝 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
export async function getServerColumnHeaders() {
const t = await getI18n();
return {
severity: t("tests.table.severity"),
result: t("tests.table.result"),
title: t("tests.table.title"),
provider: t("tests.table.provider"),
status: t("tests.table.status"),
createdAt: t("tests.table.createdAt"),
assignedUser: t("tests.table.assignedUser"),
};
}
export async function getServerColumnHeaders() {
try {
const t = await getI18n();
return {
severity: t("tests.table.severity"),
result: t("tests.table.result"),
title: t("tests.table.title"),
provider: t("tests.table.provider"),
status: t("tests.table.status"),
createdAt: t("tests.table.createdAt"),
assignedUser: t("tests.table.assignedUser"),
};
} catch (error) {
console.error("Failed to load column headers:", error);
// Fallback to English or default strings
return {
severity: "Severity",
result: "Result",
title: "Title",
provider: "Provider",
status: "Status",
createdAt: "Created At",
assignedUser: "Assigned User",
};
}
}

Comment on lines +286 to +294
export const registerTestSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
department: z.nativeEnum(Departments, {
required_error: "Department is required",
}),
externalEmployeeId: z.string().optional(),
isActive: z.boolean().default(true),
});

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

Avoid schema duplication with createEmployeeSchema.

The registerTestSchema is identical to the existing createEmployeeSchema (lines 276-284). This duplication could lead to maintenance issues if one schema is updated while the other is forgotten.

Consider one of these approaches:

  1. Reuse the existing schema if the validation requirements are the same:
-export const registerTestSchema = z.object({
-  name: z.string().min(1, "Name is required"),
-  email: z.string().email("Invalid email address"),
-  department: z.nativeEnum(Departments, {
-    required_error: "Department is required",
-  }),
-  externalEmployeeId: z.string().optional(),
-  isActive: z.boolean().default(true),
-});
+export const registerTestSchema = createEmployeeSchema;
  1. Create a base schema for both to share:
+const personSchema = z.object({
+  name: z.string().min(1, "Name is required"),
+  email: z.string().email("Invalid email address"),
+  department: z.nativeEnum(Departments, {
+    required_error: "Department is required",
+  }),
+  externalEmployeeId: z.string().optional(),
+  isActive: z.boolean().default(true),
+});
+
-export const createEmployeeSchema = z.object({
-  name: z.string().min(1, "Name is required"),
-  email: z.string().email("Invalid email address"),
-  department: z.nativeEnum(Departments, {
-    required_error: "Department is required",
-  }),
-  externalEmployeeId: z.string().optional(),
-  isActive: z.boolean().default(true),
-});
+export const createEmployeeSchema = personSchema;
+
-export const registerTestSchema = z.object({
-  name: z.string().min(1, "Name is required"),
-  email: z.string().email("Invalid email address"),
-  department: z.nativeEnum(Departments, {
-    required_error: "Department is required",
-  }),
-  externalEmployeeId: z.string().optional(),
-  isActive: z.boolean().default(true),
-});
+export const registerTestSchema = personSchema;
📝 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
export const registerTestSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email address"),
department: z.nativeEnum(Departments, {
required_error: "Department is required",
}),
externalEmployeeId: z.string().optional(),
isActive: z.boolean().default(true),
});
export const registerTestSchema = createEmployeeSchema;

Comment on lines +32 to +35
id: "session_token",
label: "AWS session token",
description: "The API session token AWS account",
type: "text",

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

Improve session token description.

The current description for the session token is incomplete and unclear.

  {
    id: "session_token",
    label: "AWS session token",
-   description: "The API session token AWS account",
+   description: "The temporary API session token for your AWS account (only required when using temporary credentials)",
    type: "text",
    required: true,
    value: "",
  },
📝 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
id: "session_token",
label: "AWS session token",
description: "The API session token AWS account",
type: "text",
{
id: "session_token",
label: "AWS session token",
description: "The temporary API session token for your AWS account (only required when using temporary credentials)",
type: "text",
required: true,
value: "",
},

* Fetches security findings from AWS Security Hub
* @returns Promise containing an array of findings
*/
async function fetchSecurityFindings(AWS_REGION: string, AWS_ACCESS_KEY_ID: string, AWS_SECRET_ACCESS_KEY: string, AWS_SESSION_TOKEN: string): Promise<any[]> {

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

Define a specific return type instead of any[].

Using any[] as a return type loses type safety. It's better to define an interface for the findings structure.

-async function fetchSecurityFindings(AWS_REGION: string, AWS_ACCESS_KEY_ID: string, AWS_SECRET_ACCESS_KEY: string, AWS_SESSION_TOKEN: string): Promise<any[]> {
+interface SecurityFinding {
+  // Define the structure based on AWS Security Hub finding fields
+  Id: string;
+  ProductArn: string;
+  Title: string;
+  Description: string;
+  Severity: {
+    Label: string;
+    // other severity fields
+  };
+  // other relevant fields
+}
+
+async function fetchSecurityFindings(AWS_REGION: string, AWS_ACCESS_KEY_ID: string, AWS_SECRET_ACCESS_KEY: string, AWS_SESSION_TOKEN: string): Promise<SecurityFinding[]> {
📝 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
async function fetchSecurityFindings(AWS_REGION: string, AWS_ACCESS_KEY_ID: string, AWS_SECRET_ACCESS_KEY: string, AWS_SESSION_TOKEN: string): Promise<any[]> {
interface SecurityFinding {
// Define the structure based on AWS Security Hub finding fields
Id: string;
ProductArn: string;
Title: string;
Description: string;
Severity: {
Label: string;
// other severity fields
};
// other relevant fields
}
async function fetchSecurityFindings(AWS_REGION: string, AWS_ACCESS_KEY_ID: string, AWS_SECRET_ACCESS_KEY: string, AWS_SESSION_TOKEN: string): Promise<SecurityFinding[]> {
// function implementation remains here
}

Comment on lines +60 to +157
<TableHeader>
<TableRow className="h-[45px] hover:bg-transparent">
{isVisible("severity") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("status")}
>
<span>{t("tests.table.severity")}</span>
{"severity" === column && value === "asc" && <ArrowDown size={16} />}
{"severity" === column && value === "desc" && <ArrowUp size={16} />}
</Button>
</TableHead>
)}
{isVisible("result") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("result")}
>
<span>{t("tests.table.result")}</span>
{"result" === column && value === "asc" && <ArrowDown size={16} />}
{"result" === column && value === "desc" && <ArrowUp size={16} />}
</Button>
</TableHead>
)}

{isVisible("title") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("title")}
>
<span>{t("tests.table.title")}</span>
{"title" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"title" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
{isVisible("provider") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("provider")}
>
<span>{t("tests.table.provider")}</span>
{"provider" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"provider" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
{isVisible("createdAt") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("createdAt")}
>
<span>{t("tests.table.createdAt")}</span>
{"createdAt" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"createdAt" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
{isVisible("assignedUser") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("assignedUser")}
>
<span>{t("tests.table.assignedUser")}</span>
{"assignedUser" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"assignedUser" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
</TableRow>

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

Consider refactoring to reduce code duplication

There's significant repetition in the table header rendering code. Consider creating a helper function to generate the column headers to reduce duplication.

Here's how you could refactor this:

+ const renderColumnHeader = (id: string, label: string) => {
+   if (!isVisible(id)) return null;
+   
+   return (
+     <TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
+       <Button
+         className="p-0 hover:bg-transparent space-x-2"
+         variant="ghost"
+         onClick={() => createSortQuery(id)}
+       >
+         <span>{label}</span>
+         {id === column && value === "asc" && <ArrowDown size={16} />}
+         {id === column && value === "desc" && <ArrowUp size={16} />}
+       </Button>
+     </TableHead>
+   );
+ };

return (
  <TableHeader>
    <TableRow className="h-[45px] hover:bg-transparent">
-     {isVisible("severity") && (
-       <TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
-         <Button
-           className="p-0 hover:bg-transparent space-x-2"
-           variant="ghost"
-           onClick={() => createSortQuery("status")}
-         >
-           <span>{t("tests.table.severity")}</span>
-           {"severity" === column && value === "asc" && <ArrowDown size={16} />}
-           {"severity" === column && value === "desc" && <ArrowUp size={16} />}
-         </Button>
-       </TableHead>
-     )}
+     {renderColumnHeader("severity", t("tests.table.severity"))}
+     {renderColumnHeader("result", t("tests.table.result"))}
+     {renderColumnHeader("title", t("tests.table.title"))}
+     {renderColumnHeader("provider", t("tests.table.provider"))}
+     {renderColumnHeader("createdAt", t("tests.table.createdAt"))}
+     {renderColumnHeader("assignedUser", t("tests.table.assignedUser"))}
-     ... // Remove all the other repeated code blocks
    </TableRow>
  </TableHeader>
);
📝 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
<TableHeader>
<TableRow className="h-[45px] hover:bg-transparent">
{isVisible("severity") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("status")}
>
<span>{t("tests.table.severity")}</span>
{"severity" === column && value === "asc" && <ArrowDown size={16} />}
{"severity" === column && value === "desc" && <ArrowUp size={16} />}
</Button>
</TableHead>
)}
{isVisible("result") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("result")}
>
<span>{t("tests.table.result")}</span>
{"result" === column && value === "asc" && <ArrowDown size={16} />}
{"result" === column && value === "desc" && <ArrowUp size={16} />}
</Button>
</TableHead>
)}
{isVisible("title") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("title")}
>
<span>{t("tests.table.title")}</span>
{"title" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"title" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
{isVisible("provider") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("provider")}
>
<span>{t("tests.table.provider")}</span>
{"provider" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"provider" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
{isVisible("createdAt") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("createdAt")}
>
<span>{t("tests.table.createdAt")}</span>
{"createdAt" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"createdAt" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
{isVisible("assignedUser") && (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery("assignedUser")}
>
<span>{t("tests.table.assignedUser")}</span>
{"assignedUser" === column && value === "asc" && (
<ArrowDown size={16} />
)}
{"assignedUser" === column && value === "desc" && (
<ArrowUp size={16} />
)}
</Button>
</TableHead>
)}
</TableRow>
// Ensure that this helper function is defined in the appropriate scope.
const renderColumnHeader = (id: string, label: string) => {
if (!isVisible(id)) return null;
return (
<TableHead className="min-w-[120px] px-3 md:px-4 py-2 hidden md:table-cell">
<Button
className="p-0 hover:bg-transparent space-x-2"
variant="ghost"
onClick={() => createSortQuery(id)}
>
<span>{label}</span>
{id === column && value === "asc" && <ArrowDown size={16} />}
{id === column && value === "desc" && <ArrowUp size={16} />}
</Button>
</TableHead>
);
};
return (
<TableHeader>
<TableRow className="h-[45px] hover:bg-transparent">
{renderColumnHeader("severity", t("tests.table.severity"))}
{renderColumnHeader("result", t("tests.table.result"))}
{renderColumnHeader("title", t("tests.table.title"))}
{renderColumnHeader("provider", t("tests.table.provider"))}
{renderColumnHeader("createdAt", t("tests.table.createdAt"))}
{renderColumnHeader("assignedUser", t("tests.table.assignedUser"))}
</TableRow>
</TableHeader>
);

Comment on lines +48 to +57
export const appErrors = {
UNAUTHORIZED: {
code: "UNAUTHORIZED" as const,
message: "You are not authorized to view employees",
},
UNEXPECTED_ERROR: {
code: "UNEXPECTED_ERROR" as const,
message: "An unexpected error occurred",
},
} as const; No newline at end of file

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 in UNAUTHORIZED error

The error message for UNAUTHORIZED refers to "employees" but this file is about tests. Update the error message to reflect the correct context.

export const appErrors = {
  UNAUTHORIZED: {
    code: "UNAUTHORIZED" as const,
-   message: "You are not authorized to view employees",
+   message: "You are not authorized to view tests",
  },
  UNEXPECTED_ERROR: {
    code: "UNEXPECTED_ERROR" as const,
    message: "An unexpected error occurred",
  },
} as const;
📝 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
export const appErrors = {
UNAUTHORIZED: {
code: "UNAUTHORIZED" as const,
message: "You are not authorized to view employees",
},
UNEXPECTED_ERROR: {
code: "UNEXPECTED_ERROR" as const,
message: "An unexpected error occurred",
},
} as const;
export const appErrors = {
UNAUTHORIZED: {
code: "UNAUTHORIZED" as const,
message: "You are not authorized to view tests",
},
UNEXPECTED_ERROR: {
code: "UNEXPECTED_ERROR" as const,
message: "An unexpected error occurred",
},
} as const;

Comment on lines +3 to +17
export const testSchema = z.object({
id: z.string(),
title: z.string(),
description: z.string().nullable(),
provider: z.string(),
status: z.string(),
resultDetails: z.any(),
label: z.string().nullable(),
completedAt: z.date(),
assignedUserId: z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string().nullable(),
})
});

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

Improve type safety by avoiding z.any()

Using z.any() for resultDetails eliminates type safety benefits. Consider defining a more specific schema based on the expected structure of the result details.

export const testSchema = z.object({
  id: z.string(),
  title: z.string(),
  description: z.string().nullable(),
  provider: z.string(),
  status: z.string(),
- resultDetails: z.any(),
+ resultDetails: z.record(z.unknown()).optional(),
  label: z.string().nullable(),
  completedAt: z.date(),
  assignedUserId: z.object({
    id: z.string(),
    name: z.string().nullable(),
    email: z.string().nullable(),
  })
});
📝 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
export const testSchema = z.object({
id: z.string(),
title: z.string(),
description: z.string().nullable(),
provider: z.string(),
status: z.string(),
resultDetails: z.any(),
label: z.string().nullable(),
completedAt: z.date(),
assignedUserId: z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string().nullable(),
})
});
export const testSchema = z.object({
id: z.string(),
title: z.string(),
description: z.string().nullable(),
provider: z.string(),
status: z.string(),
resultDetails: z.record(z.unknown()).optional(),
label: z.string().nullable(),
completedAt: z.date(),
assignedUserId: z.object({
id: z.string(),
name: z.string().nullable(),
email: z.string().nullable(),
})
});

Comment on lines +30 to +41
export interface TestsResponse {
tests: {
id: string;
severity: string | null;
result: string;
title: string;
provider: string;
createdAt: Date;
assignedUser: null;
}[];
total: number;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Ensure TestsResponse aligns with Test type

The TestsResponse interface doesn't align with the Test type derived from testSchema. This inconsistency could lead to confusion and bugs when mapping between these types.

Consider aligning the types by either:

  1. Making TestsResponse.tests match the Test type
  2. Creating a separate, explicitly named type for the test list items
export interface TestsResponse {
  tests: {
-   id: string;
-   severity: string | null;
-   result: string;
-   title: string;
-   provider: string;
-   createdAt: Date;
-   assignedUser: null;
+   // Either align with Test type:
+   id: string;
+   title: string;
+   description: string | null;
+   provider: string;
+   status: string;
+   resultDetails: unknown;
+   label: string | null;
+   completedAt: Date;
+   assignedUserId: {
+     id: string;
+     name: string | null;
+     email: string | null;
+   };
+   // Or create an explicitly named separate type:
+   // create a new interface like TestListItem and use it here
  }[];
  total: number;
}

🏁 Script executed:

#!/bin/bash
# Look for usages of TestsResponse to understand how it's being used
rg -A 3 -B 3 "TestsResponse" --type ts
# Look for conversions between Test and TestsResponse formats
rg -A 5 -B 5 "tests:" --type ts | grep -A 10 -B 10 "TestsResponse"

Length of output: 4604


Action: Align the TestsResponse interface with the Test type defined by testSchema

It looks like the current definition of TestsResponse in
apps/app/src/app/[locale]/(app)/(dashboard)/tests/types/index.ts
(lines 30–41) does not match the structure of the Test type (i.e. the output of z.infer<typeof testSchema>). This discrepancy may lead to mapping issues in functions—such as in the fetch logic in hooks/useTests.ts—where the response is cast as TestsResponse.

Recommendations:

  • Update the properties in TestsResponse.tests so they mirror the fields defined in the Test type (for example, including properties like id, title, description, provider, status, resultDetails, label, completedAt, and a nested assignedUserId object).
  • Alternatively, if the current structure is intentional, consider creating a new type (e.g. TestListItem) to clearly differentiate between the raw response and the domain-specific Test type.
export interface TestsResponse {
  tests: {
-   id: string;
-   severity: string | null;
-   result: string;
-   title: string;
-   provider: string;
-   createdAt: Date;
-   assignedUser: null;
+   // Option 1: Align these fields with the Test type
+   id: string;
+   title: string;
+   description: string | null;
+   provider: string;
+   status: string;
+   resultDetails: unknown;
+   label: string | null;
+   completedAt: Date;
+   assignedUserId: {
+     id: string;
+     name: string | null;
+     email: string | null;
+   };
+   // Option 2: Create a separately named type (e.g. TestListItem) and use it here
  }[];
  total: number;
}
📝 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
export interface TestsResponse {
tests: {
id: string;
severity: string | null;
result: string;
title: string;
provider: string;
createdAt: Date;
assignedUser: null;
}[];
total: number;
}
export interface TestsResponse {
tests: {
// Option 1: Align these fields with the Test type
id: string;
title: string;
description: string | null;
provider: string;
status: string;
resultDetails: unknown;
label: string | null;
completedAt: Date;
assignedUserId: {
id: string;
name: string | null;
email: string | null;
};
// Option 2: Create a separately named type (e.g. TestListItem) and use it here
}[];
total: number;
}

Comment on lines +718 to +796
tests: {
name: "Tests en nuage",
title: "Tests en nuage",
actions: {
create: "Ajouter un test en nuage",
clear: "Effacer les filtres",
refresh: "Rafraîchir"
},
empty: {
no_tests: {
title: "Aucun test en nuage pour l'instant",
description: "Commencez par créer votre premier test en nuage."
},
no_results: {
title: "Aucun résultat trouvé",
description: "Aucun test ne correspond à votre recherche",
description_with_filters: "Essayez d'ajuster vos filtres"
}
},
filters: {
search: "Rechercher des tests...",
role: "Filtrer par fournisseur"
},
register: {
title: "Ajouter un test Cloud",
description: "Configurer un nouveau test de conformité cloud.",
email: {
label: "Adresse e-mail",
placeholder: "Entrez l'adresse e-mail"
},
role: {
label: "Rôle",
placeholder: "Sélectionnez un rôle"
},
name: {
label: "Nom",
placeholder: "Entrez le nom"
},
department: {
label: "Département",
placeholder: "Sélectionnez un département"
},
submit: "Créer un test",
success: "Test créé avec succès",
error: "Échec de l'ajout du test",
invalid_json: "Configuration JSON invalide fournie",
title_field: {
label: "Titre du test",
placeholder: "Entrez le titre du test"
},
description_field: {
label: "Description",
placeholder: "Entrez la description du test"
},
provider: {
label: "Fournisseur Cloud",
placeholder: "Sélectionnez le fournisseur cloud"
},
config: {
label: "Configuration du test",
placeholder: "Entrez la configuration JSON pour le test"
},
auth_config: {
label: "Configuration d'authentification",
placeholder: "Entrez la configuration d'authentification JSON"
}
},
table: {
title: "Titre",
provider: "Fournisseur",
status: "Statut",
lastRun: "Dernière exécution",
no_results: "Aucun résultat trouvé",
severity: "Sévérité",
result: "Résultat",
createdAt: "Créé le",
assignedUser: "Utilisateur assigné",
assignedUserEmpty: "Non assigné"
}

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

French translation section contains inconsistencies with English version.

The French translation for the tests section includes fields like email, role, name, and department (lines 744-759) that don't appear in the English version. Additionally, the table structure includes a lastRun field (line 789) that isn't in the English version. These inconsistencies could lead to interface issues when switching languages.

Synchronize the French translations with the English structure to ensure consistency:

  1. Remove the email, role, name, and department sections that don't appear in the English version
  2. Add the missing translations for severity, result, createdAt fields
  3. Remove the lastRun field that isn't in the English version or add it to the English version if needed

Here's a partial example of the necessary changes:

    register: {
      title: "Ajouter un test Cloud",
      description: "Configurer un nouveau test de conformité cloud.",
-      email: {
-        label: "Adresse e-mail",
-        placeholder: "Entrez l'adresse e-mail"
-      },
-      role: {
-        label: "Rôle",
-        placeholder: "Sélectionnez un rôle"
-      },
-      name: {
-        label: "Nom",
-        placeholder: "Entrez le nom"
-      },
-      department: {
-        label: "Département",
-        placeholder: "Sélectionnez un département"
-      },
      submit: "Créer un test",
      success: "Test créé avec succès",
      error: "Échec de l'ajout du test",
      // Keep the rest of the fields that match English...
    },
    table: {
      title: "Titre",
      provider: "Fournisseur",
      status: "Statut",
-      lastRun: "Dernière exécution",
      no_results: "Aucun résultat trouvé",
      // Keep the severity, result, etc. fields...
    }
📝 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
tests: {
name: "Tests en nuage",
title: "Tests en nuage",
actions: {
create: "Ajouter un test en nuage",
clear: "Effacer les filtres",
refresh: "Rafraîchir"
},
empty: {
no_tests: {
title: "Aucun test en nuage pour l'instant",
description: "Commencez par créer votre premier test en nuage."
},
no_results: {
title: "Aucun résultat trouvé",
description: "Aucun test ne correspond à votre recherche",
description_with_filters: "Essayez d'ajuster vos filtres"
}
},
filters: {
search: "Rechercher des tests...",
role: "Filtrer par fournisseur"
},
register: {
title: "Ajouter un test Cloud",
description: "Configurer un nouveau test de conformité cloud.",
email: {
label: "Adresse e-mail",
placeholder: "Entrez l'adresse e-mail"
},
role: {
label: "Rôle",
placeholder: "Sélectionnez un rôle"
},
name: {
label: "Nom",
placeholder: "Entrez le nom"
},
department: {
label: "Département",
placeholder: "Sélectionnez un département"
},
submit: "Créer un test",
success: "Test créé avec succès",
error: "Échec de l'ajout du test",
invalid_json: "Configuration JSON invalide fournie",
title_field: {
label: "Titre du test",
placeholder: "Entrez le titre du test"
},
description_field: {
label: "Description",
placeholder: "Entrez la description du test"
},
provider: {
label: "Fournisseur Cloud",
placeholder: "Sélectionnez le fournisseur cloud"
},
config: {
label: "Configuration du test",
placeholder: "Entrez la configuration JSON pour le test"
},
auth_config: {
label: "Configuration d'authentification",
placeholder: "Entrez la configuration d'authentification JSON"
}
},
table: {
title: "Titre",
provider: "Fournisseur",
status: "Statut",
lastRun: "Dernière exécution",
no_results: "Aucun résultat trouvé",
severity: "Sévérité",
result: "Résultat",
createdAt: "Créé le",
assignedUser: "Utilisateur assigné",
assignedUserEmpty: "Non assigné"
}
tests: {
name: "Tests en nuage",
title: "Tests en nuage",
actions: {
create: "Ajouter un test en nuage",
clear: "Effacer les filtres",
refresh: "Rafraîchir"
},
empty: {
no_tests: {
title: "Aucun test en nuage pour l'instant",
description: "Commencez par créer votre premier test en nuage."
},
no_results: {
title: "Aucun résultat trouvé",
description: "Aucun test ne correspond à votre recherche",
description_with_filters: "Essayez d'ajuster vos filtres"
}
},
filters: {
search: "Rechercher des tests...",
role: "Filtrer par fournisseur"
},
register: {
title: "Ajouter un test Cloud",
description: "Configurer un nouveau test de conformité cloud.",
submit: "Créer un test",
success: "Test créé avec succès",
error: "Échec de l'ajout du test",
invalid_json: "Configuration JSON invalide fournie",
title_field: {
label: "Titre du test",
placeholder: "Entrez le titre du test"
},
description_field: {
label: "Description",
placeholder: "Entrez la description du test"
},
provider: {
label: "Fournisseur Cloud",
placeholder: "Sélectionnez le fournisseur cloud"
},
config: {
label: "Configuration du test",
placeholder: "Entrez la configuration JSON pour le test"
},
auth_config: {
label: "Configuration d'authentification",
placeholder: "Entrez la configuration d'authentification JSON"
}
},
table: {
title: "Titre",
provider: "Fournisseur",
status: "Statut",
no_results: "Aucun résultat trouvé",
severity: "Sévérité",
result: "Résultat",
createdAt: "Créé le",
assignedUser: "Utilisateur assigné",
assignedUserEmpty: "Non assigné"
}
}

This branch had an error being deployed

1 failed and 2 active deployments
Preview – app — b1761b02 Deployed Mar 4, 2025 by vercel[bot]
Preview – comp-portal — b1761b02 Deployed Mar 4, 2025 by vercel[bot]
Preview – web — b1761b02 Deployed Mar 4, 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.

4 participants