Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

init #202

Closed
wants to merge 4 commits into from
Closed

init #202

wants to merge 4 commits into from

Conversation

adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Nov 6, 2024

Summary by CodeRabbit

  • New Features

    • Introduced new event handling for environment deletions.
    • Added validation schemas for environment-related events.
    • Established new database schemas for runhooks and associated events.
    • Implemented a new package for event management.
  • Bug Fixes

    • Streamlined the expired environment checking process.
  • Documentation

    • Expanded module exports to include new event handling functionalities.
  • Chores

    • Added ESLint and TypeScript configuration for the new events package.
    • Updated Dockerfile to include the new events package.

Copy link
Contributor

coderabbitai bot commented Nov 6, 2024

Walkthrough

This pull request introduces several changes across multiple files, primarily focusing on the integration of a new dependency, @ctrlplane/events, into the package.json of the @ctrlplane/jobs project. It modifies the run method in the expired-env-checker module to streamline event processing for expired environments. Additionally, new database schemas for handling runhooks and events are defined, and new TypeScript files are created to manage environment-related events, including validation and event handling logic.

Changes

File Path Change Summary
apps/jobs/package.json Added dependency: "@ctrlplane/events": "workspace:*" in dependencies.
apps/jobs/src/expired-env-checker/index.ts Modified run method to remove lodash usage and streamline event object creation for expired environments.
packages/db/src/schema/hook.ts Added new table schemas: runhook and runhook_event for handling runhooks and their events.
packages/db/src/schema/index.ts Added export statement: export * from "./hook.js"; to include new schemas in the public API.
packages/events/eslint.config.js Introduced new ESLint configuration file for the @ctrlplane/events package.
packages/events/package.json Created new package.json for @ctrlplane/events with metadata, scripts, and dependencies.
packages/events/src/environments/environment-delete.ts Added handleEnvironmentDeleted function to manage environment deletion events.
packages/events/src/index.ts Added handleHookEvent function to process hook events, specifically for environment deletion.
packages/events/tsconfig.json Introduced TypeScript configuration file for the @ctrlplane/events package.
packages/validators/src/events/hook-events/environment.ts Added validation schemas and types for environment events using Zod.
packages/validators/src/events/hook-events/index.ts Created union type HookEvent and type guard functions for environment events.
packages/validators/src/events/index.ts Added export statement to re-export entities from ./hook-events/index.js.
apps/jobs/Dockerfile Added line to copy packages/events/package.json into the Docker image.

Possibly related PRs

  • refactor job policy checker app #151: The changes in the main PR involve adding a new dependency (@ctrlplane/events) in the package.json file, which is relevant to the refactor of the job policy checker app that also involves modifications to the apps/jobs/package.json.
  • fix: Target variables #170: The addition of the @ctrlplane/secrets dependency in the apps/webservice/package.json is similar to the main PR's addition of @ctrlplane/events, indicating a pattern of expanding dependencies that may relate to event handling or job management.
  • fix: Create jobs for latest valid releases on env create #197: The introduction of the createJobsForNewEnvironment function in the context of creating jobs for environments aligns with the event handling focus of the main PR, which adds a dependency that may facilitate event-driven job processing.

Suggested reviewers

  • jsbroks

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 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.

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (11)
packages/events/src/index.ts (1)

7-10: Consider adding observability and extensibility.

As this appears to be an initial implementation, consider these architectural improvements:

  1. Add structured logging for event handling (success/failure)
  2. Consider implementing a more scalable event handling pattern (e.g., strategy pattern) for future event types
  3. Add metrics/monitoring for event processing

Would you like me to provide example implementations for any of these suggestions?

packages/validators/src/events/index.ts (1)

Line range hint 1-24: Consider adding JSDoc comments for event types.

The file structure is well-organized, but adding documentation for the event types and channels would improve maintainability. Consider documenting:

  • Purpose and usage of each event type
  • Expected payload structure and constraints
  • Channel enum values and their use cases

Example documentation:

/**
 * Channels used for different types of events in the system.
 */
export enum Channel {
  /** Events related to job synchronization */
  JobSync = "job-sync",
  // ... (document other channels)
}

/**
 * Event schema for target scanning operations.
 * @property targetProviderId - Unique identifier for the target provider
 */
export const targetScanEvent = z.object({ targetProviderId: z.string() });
packages/db/src/schema/index.ts (1)

Line range hint 9-9: Remove duplicate export of runbook.js

The file contains two identical export statements for runbook.js. This redundancy should be eliminated.

Apply this diff to remove the duplicate:

 export * from "./runbook.js";
 export * from "./team.js";
 export * from "./job.js";
 export * from "./workspace.js";
 export * from "./github.js";
 export * from "./deployment-variables.js";
 export * from "./dashboard.js";
 export * from "./variable-sets.js";
 export * from "./invite.js";
 export * from "./target-group.js";
 export * from "./runbook-variables.js";
 export * from "./rbac.js";
-export * from "./runbook.js";
 export * from "./job-agent.js";

Also applies to: 20-20

packages/db/src/schema/hook.ts (2)

5-12: Consider adding indexes for performance optimization.

The runhook table might benefit from additional indexes:

  1. A composite index on (scope_type, scope_id) for efficient lookups
  2. An index on runbook_id for efficient joins

Here's how you could add them:

 export const runhook = pgTable("runhook", {
   id: uuid("id").primaryKey().defaultRandom(),
   scopeType: text("scope_type").notNull(),
   scopeId: uuid("scope_id").notNull(),
   runbookId: uuid("runbook_id")
     .references(() => runbook.id, { onDelete: "cascade" })
     .notNull(),
-});
+}, (table) => ({
+  scopeIdx: index("runhook_scope_idx").on(table.scopeType, table.scopeId),
+  runbookIdx: index("runhook_runbook_idx").on(table.runbookId),
+}));

5-12: Document the valid values for scope_type.

Please add documentation comments explaining:

  1. What scope_type represents
  2. The valid values it can contain
  3. Any business rules associated with it

Add documentation like this:

+/**
+ * Represents a runhook configuration that links a scope (identified by type and ID) 
+ * to a runbook.
+ * 
+ * @property scopeType - The type of scope this hook applies to.
+ *                     Valid values: [list valid values here]
+ * @property scopeId - The unique identifier of the scope
+ * @property runbookId - Reference to the runbook to be executed
+ */
 export const runhook = pgTable("runhook", {
packages/validators/src/events/hook-events/environment.ts (2)

3-8: Consider enhancing the base schema with stricter validation.

While the current schema is functional, consider adding:

  • ISO datetime format validation for createdAt
  • Additional common fields that might be useful across all environment events (e.g., version, source)
 const environmentBaseEvent = z.object({
-  createdAt: z.string().datetime(),
+  createdAt: z.string().datetime().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/),
+  version: z.string(),
+  source: z.string(),
   payload: z.object({
     environmentId: z.string().uuid(),
   }),
 });

10-13: Add JSDoc documentation for the event enum.

Consider adding documentation to improve code maintainability and clarity.

+/**
+ * Represents the types of environment-related events that can occur in the system.
+ * @enum {string}
+ */
 export enum EnvironmentEvent {
+  /** Emitted when an environment is deleted from the system */
   Deleted = "environment.deleted",
+  /** Emitted when a new environment is created in the system */
   Created = "environment.created",
 }
packages/validators/src/events/hook-events/index.ts (2)

15-19: Consider enhancing error handling in the Zod union schema.

While the implementation is correct, adding a custom error message would improve debugging and error reporting.

 export const HookEvent = z.union([
   environmentDeletedEvent,
   environmentCreatedEvent,
-]);
+], {
+  errorMap: (issue, ctx) => ({
+    message: `Invalid hook event type. Expected 'environment.deleted' or 'environment.created', got '${ctx.data?.type}'`,
+  }),
+});

21-27: Add JSDoc comments to improve documentation.

The type guards are well-implemented, but adding JSDoc comments would improve code documentation and IDE support.

-// typeguards
+/**
+ * Type guard to check if an event is an EnvironmentDeletedEvent
+ * @param event - The hook event to check
+ * @returns True if the event is an EnvironmentDeletedEvent
+ */
 export const isEnvironmentDeletedEvent = (
   event: HookEvent,
 ): event is EnvironmentDeletedEvent => event.type === EnvironmentEvent.Deleted;
+
+/**
+ * Type guard to check if an event is an EnvironmentCreatedEvent
+ * @param event - The hook event to check
+ * @returns True if the event is an EnvironmentCreatedEvent
+ */
 export const isEnvironmentCreatedEvent = (
   event: HookEvent,
 ): event is EnvironmentCreatedEvent => event.type === EnvironmentEvent.Created;
apps/jobs/src/expired-env-checker/index.ts (1)

Line range hint 21-24: Add logging for deletion results

Consider adding logging to track the number of environments deleted and capture any deletion failures. This will help with monitoring and debugging.

Here's a suggested improvement:

-  await db
-    .delete(SCHEMA.environment)
-    .where(inArray(SCHEMA.environment.id, envIds));
+  try {
+    const result = await db
+      .delete(SCHEMA.environment)
+      .where(inArray(SCHEMA.environment.id, envIds));
+    console.log(`Successfully deleted ${envIds.length} expired environments`);
+  } catch (error) {
+    console.error('Failed to delete expired environments:', error);
+    throw error;
+  }
packages/events/src/environments/environment-delete.ts (1)

94-107: Add Error Handling for Promise Operations

Currently, the promises returned by dispatchRunbook are executed with Promise.all, but any rejection in these promises could lead to unhandled promise rejections.

Consider adding error handling to manage any potential rejections from dispatchRunbook. This will improve the robustness of your function.

Apply this diff to handle potential errors:

await Promise.all(handleLifecycleHooksForTargets);
+ // Optionally handle errors
+ await Promise.all(handleLifecycleHooksForTargets).catch((error) => {
+   // Handle the error appropriately
+   console.error("Error dispatching runbooks:", error);
+   // You might want to perform additional error handling or logging here
+ });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4cba9 and 22d1b60.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • apps/jobs/package.json (1 hunks)
  • apps/jobs/src/expired-env-checker/index.ts (1 hunks)
  • packages/db/src/schema/hook.ts (1 hunks)
  • packages/db/src/schema/index.ts (1 hunks)
  • packages/events/eslint.config.js (1 hunks)
  • packages/events/package.json (1 hunks)
  • packages/events/src/environments/environment-delete.ts (1 hunks)
  • packages/events/src/index.ts (1 hunks)
  • packages/events/tsconfig.json (1 hunks)
  • packages/validators/src/events/hook-events/environment.ts (1 hunks)
  • packages/validators/src/events/hook-events/index.ts (1 hunks)
  • packages/validators/src/events/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/events/eslint.config.js
  • packages/events/package.json
  • packages/events/tsconfig.json
🔇 Additional comments (17)
packages/events/src/index.ts (1)

1-5: LGTM! Clean import organization.

The imports are well-structured with clear separation between external and local imports.

packages/validators/src/events/index.ts (1)

3-3: Verify the hook-events module structure.

The export statement follows the barrel pattern correctly. Let's verify the referenced module structure.

✅ Verification successful

Let me generate another script to verify the specific file structure since the previous commands had issues:


Export statement is correctly implemented and follows the project's conventions

The verification confirms that:

  • The hook-events module exists and has proper structure
  • The index.ts file correctly re-exports from environment.js
  • No circular dependencies are present
  • The .js extension in imports follows the project's ESM conventions
  • The module provides proper type definitions and type guards
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the hook-events module structure and potential issues

# Test 1: Check if the referenced file exists
echo "Checking for hook-events/index.ts or hook-events/index.js..."
fd --type f "index\.(ts|js)$" --parent-dir "hook-events"

# Test 2: Check for potential circular dependencies
echo "Checking for potential circular dependencies..."
rg -l "from.*events" "hook-events/"

# Test 3: Verify consistent import path conventions across the codebase
echo "Verifying import path conventions..."
rg "from.*\.js[\"']" --type ts

Length of output: 27709


Script:

#!/bin/bash
# Description: Verify the hook-events module structure and potential issues

# Test 1: Check if the referenced file exists
echo "Checking for hook-events module files..."
fd "hook-events" packages/validators/src/events/

# Test 2: Check for potential circular dependencies
echo "Checking for potential circular dependencies..."
rg -l "from.*events" packages/validators/src/events/hook-events/

# Test 3: Check the content of hook-events/index.ts
echo "Checking hook-events/index.ts content..."
cat packages/validators/src/events/hook-events/index.ts

Length of output: 1216

packages/db/src/schema/index.ts (1)

22-22: LGTM!

The new export follows the established pattern and integrates well with the existing schema exports.

packages/db/src/schema/hook.ts (1)

1-4: LGTM! Imports are well-structured.

The imports are specific and properly scoped, following ESM best practices.

packages/validators/src/events/hook-events/environment.ts (1)

1-23: Verify event schema usage across the codebase.

Let's ensure these schemas are properly utilized in event handlers and consumers.

✅ Verification successful

Event schemas are properly integrated across the codebase

The verification shows proper integration of the environment event schemas:

  • Type guards (isEnvironmentDeletedEvent, isEnvironmentCreatedEvent) are correctly implemented and used
  • Event handler for deleted environments exists and uses proper typing
  • Event emission is properly typed in the expired environment checker
  • All imports and type references are consistent
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of environment events across the codebase

# Check for event handler implementations
echo "Checking event handler implementations..."
rg -t typescript "handleEnvironment(Created|Deleted)" -A 5

# Check for event emission points
echo "Checking event emission points..."
rg -t typescript "emit.*environment\.(created|deleted)" -A 5

# Check for proper type imports
echo "Checking type imports..."
rg -t typescript "import.*Environment(Created|Deleted)Event" -A 2

Length of output: 525


Script:

#!/bin/bash
# Description: Check usage of environment events across the codebase without typescript flag

# Check for event handler implementations
echo "Checking event handler implementations..."
rg "handleEnvironment(Created|Deleted)" -A 5

# Check for event emission points
echo "Checking event emission points..."
rg "emit.*environment\.(created|deleted)" -A 5

# Check for imports and usages
echo "Checking imports and usages..."
rg "Environment(Created|Deleted)Event" -A 2

# Check for enum usage
echo "Checking enum usage..."
rg "EnvironmentEvent\.(Created|Deleted)" -A 2

Length of output: 5129

packages/validators/src/events/hook-events/index.ts (2)

1-13: LGTM! Well-organized imports and exports.

The imports are properly structured, separating type imports from value imports, and the re-export pattern promotes good module organization.


3-11: Verify the environment.js imports and type usage.

Let's ensure the imported types and constants are properly defined and used consistently.

✅ Verification successful

All environment event types and imports are correctly defined and used

The verification shows that:

  • The environment.ts file exists at packages/validators/src/events/hook-events/environment.ts and properly exports all the referenced types and constants
  • The types are consistently used across the codebase:
    • EnvironmentEvent enum is used in event handling and job processing
    • EnvironmentCreatedEvent and EnvironmentDeletedEvent types are properly imported and used in type checks
    • HookEvent is correctly used as the main event type in the event handling system
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify environment.js exists and check its exports
fd -e js -e ts "environment\.js$" --exec cat {} \;

# Check usage of these types across the codebase
rg -t typescript "EnvironmentEvent\.(Created|Deleted)" 

# Verify consistent usage of HookEvent type
rg -t typescript "HookEvent"

Length of output: 211


Script:

#!/bin/bash
# Search for environment.js/ts file
fd "environment\.(js|ts)$"

# If file exists, check its contents
fd "environment\.(js|ts)$" --exec cat {} \;

# Search for type usage without file type flag
rg "EnvironmentCreatedEvent|EnvironmentDeletedEvent|EnvironmentEvent"

# Search for hook events
rg "HookEvent"

Length of output: 23525

apps/jobs/src/expired-env-checker/index.ts (2)

1-5: LGTM! Clean import organization

The new imports for event handling capabilities are properly organized and align well with the module's new event-driven approach.


8-11: Verify handling of associated deployment records

The query has been simplified by removing the deployment schema join. While this makes the code cleaner, we should verify that this doesn't leave orphaned deployment records.

Let's check if there's any cleanup logic for associated deployments:

✅ Verification successful

No issues with removing the deployment join

The simplified query in the expired environment checker is correct. When an environment is deleted:

  1. The system emits an environment.deleted event
  2. The event handler (handleEnvironmentDeleted) properly manages any associated deployments and targets
  3. The deletion of the environment record triggers cleanup through event handlers rather than through the query itself

The original review concern about orphaned deployment records is not an issue as the system handles this through its event-driven architecture.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for deployment cleanup logic related to environment deletion
rg -l 'deployment.*delete.*environment' --type ts

# Check for foreign key constraints that might handle this automatically
rg -l 'foreignKey.*environment.*deployment' --type ts

Length of output: 107


Script:

#!/bin/bash
# Let's check the schema definition to understand the relationship between environment and deployment
ast-grep --pattern 'export const SCHEMA = {
  $$$
}'

# Also search for any cascade delete operations or cleanup logic
rg -B 5 -A 5 'environment.*delete|delete.*environment' --type ts

# Check for any database migration files that might define the relationships
fd -e sql -e ts migration

Length of output: 17053


Script:

#!/bin/bash
# Let's check for any foreign key constraints or cascade delete operations in the schema
ast-grep --pattern 'createTable($_, $_)' 

# Let's also check for any environment deletion event handlers
ast-grep --pattern 'handleEnvironmentDeleted($$$)'

# And check for any deployment cleanup in the expired env checker
rg -B 5 -A 5 'expiredEnvironments' --type ts

Length of output: 2009


Script:

#!/bin/bash
# Let's examine the environment deletion event handler implementation
rg -B 10 -A 10 'handleEnvironmentDeleted' --type ts

# And check the environment event handler in the events package
cat packages/events/src/environments/environment-delete.ts

Length of output: 4939

apps/jobs/package.json (2)

17-17: LGTM! Dependency addition looks correct.

The new @ctrlplane/events workspace dependency is properly added and follows the project's dependency management conventions.


17-17: Verify the workspace package configuration.

Let's ensure the new @ctrlplane/events package is properly configured in the workspace.

✅ Verification successful

Workspace package configuration is properly set up

The verification confirms that:

  • Package directory exists at packages/events
  • Package.json is correctly configured with:
    • Proper name @ctrlplane/events
    • Required workspace dependencies
    • Valid exports configuration
    • Essential build scripts
  • Required source files are present:
    • src/index.ts
    • src/environments/environment-delete.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and basic configuration of @ctrlplane/events package

# Test 1: Check if the package directory exists
echo "Checking package directory..."
fd -t d "events$" packages/

# Test 2: Verify package.json exists and has correct configuration
echo "Checking package.json configuration..."
fd -t f "package.json" packages/events -x cat {} \; 2>/dev/null

# Test 3: Check for essential files mentioned in the summary
echo "Checking for essential files..."
fd -t f "environment-delete.ts|index.ts" packages/events/src 2>/dev/null

Length of output: 1565

packages/events/src/environments/environment-delete.ts (6)

35-38: Confirm Logical Conditions in 'checks' are Accurate

The construction of the checks condition using and is logically sound. It correctly ensures that:

  • The targetFilter is not null.
  • The environment ID is not equal to the deleted environment's ID.

39-44: Verify Handling of Empty 'system.environments' Array

After fetching the system, you access system.environments. If system.environments is an empty array, subsequent operations like mapping over envFilters might not behave as expected.

Please verify that system.environments will always contain at least one environment or adjust the code to handle the case where system.environments is empty to prevent potential errors downstream.


45-47: Ensure 'envFilters' Correctly Reflects Active Environments

The mapping and filtering of system.environments to create envFilters is appropriate. It collects all present targetFilter values from the environments.


56-71: Confirm Logic for 'removedFromSystemTargets' Selection

Using a conditional to determine removedFromSystemTargets based on envFilters.length is logical. If there are other environment filters, you compute the targets removed from the system; otherwise, you assume all targets are removed.


110-111: Export Statement Provides Clear Public Interface

Exporting handleEnvironmentDeleted as the public interface to handleTargets is appropriate. It keeps the internal implementation encapsulated and provides a clean API for handling the EnvironmentDeletedEvent.


73-92: ⚠️ Potential issue

Handle Empty 'system.deployments' When Building 'or' Condition

If system.deployments is an empty array, the spread operator in the or condition will result in no arguments being passed to or(). Depending on the implementation of the or() function from @ctrlplane/db, this might cause an error or unintended behavior.

Consider adding a check for system.deployments.length before constructing the or condition to handle cases where there are no deployments.

Apply this diff to handle the empty case:

+ if (system.deployments.length === 0) return;

const runhooks = await db
  .select()
  .from(SCHEMA.runhook)
  .innerJoin(
    SCHEMA.runhookEvent,
    eq(SCHEMA.runhookEvent.runhookId, SCHEMA.runhook.id),
  )
  .where(
    or(
      ...system.deployments.map((deployment) =>
        and(
          eq(SCHEMA.runhook.scopeType, "deployment"),
          eq(SCHEMA.runhook.scopeId, deployment.id),
          eq(SCHEMA.runhookEvent.eventType, "environment.deleted"),
        ),
      ),
    ),
  )
  .then((r) => r.map((rh) => rh.runhook));

Alternatively, ensure that the or() function gracefully handles an empty array of conditions without throwing an error.

Comment on lines 7 to 10
export const handleHookEvent = (event: HookEvent): Promise<void> => {
if (isEnvironmentDeletedEvent(event)) handleEnvironmentDeleted(event);
throw new Error(`Unknown event type: ${event.type}`);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix critical issues in event handling logic.

The current implementation has several issues:

  1. The throw statement will execute regardless of the event type
  2. The Promise from handleEnvironmentDeleted is not awaited
  3. No return statement after handling the event
  4. Missing error handling for handleEnvironmentDeleted

Apply this fix:

-export const handleHookEvent = (event: HookEvent): Promise<void> => {
-  if (isEnvironmentDeletedEvent(event)) handleEnvironmentDeleted(event);
-  throw new Error(`Unknown event type: ${event.type}`);
-};
+export const handleHookEvent = async (event: HookEvent): Promise<void> => {
+  try {
+    if (isEnvironmentDeletedEvent(event)) {
+      await handleEnvironmentDeleted(event);
+      return;
+    }
+    throw new Error(`Unknown event type: ${event.type}`);
+  } catch (error) {
+    throw new Error(`Failed to handle event: ${error.message}`);
+  }
+};
📝 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 handleHookEvent = (event: HookEvent): Promise<void> => {
if (isEnvironmentDeletedEvent(event)) handleEnvironmentDeleted(event);
throw new Error(`Unknown event type: ${event.type}`);
};
export const handleHookEvent = async (event: HookEvent): Promise<void> => {
try {
if (isEnvironmentDeletedEvent(event)) {
await handleEnvironmentDeleted(event);
return;
}
throw new Error(`Unknown event type: ${event.type}`);
} catch (error) {
throw new Error(`Failed to handle event: ${error.message}`);
}
};

Comment on lines +14 to +20
export const runhookEvent = pgTable("runhook_event", {
id: uuid("id").primaryKey().defaultRandom(),
runhookId: uuid("runhook_id")
.references(() => runhook.id, { onDelete: "cascade" })
.notNull(),
eventType: text("event_type").notNull(),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding essential fields for event tracking.

The runhook_event table appears to be missing some common fields typically needed for event tracking:

  1. Timestamp fields (created_at, processed_at)
  2. Event payload or metadata field
  3. Event status field
  4. Index on runhookId for efficient lookups

Consider enhancing the schema like this:

 export const runhookEvent = pgTable("runhook_event", {
   id: uuid("id").primaryKey().defaultRandom(),
   runhookId: uuid("runhook_id")
     .references(() => runhook.id, { onDelete: "cascade" })
     .notNull(),
   eventType: text("event_type").notNull(),
+  payload: jsonb("payload"),
+  status: text("status").notNull().default("pending"),
+  createdAt: timestamp("created_at").notNull().defaultNow(),
+  processedAt: timestamp("processed_at"),
+  error: text("error"),
-});
+}, (table) => ({
+  runhookIdx: index("runhook_event_runhook_idx").on(table.runhookId),
+  createdAtIdx: index("runhook_event_created_at_idx").on(table.createdAt),
+}));

Also, consider adding documentation for valid event types:

+/**
+ * Represents events associated with runhooks.
+ * 
+ * @property eventType - The type of event.
+ *                     Valid values: [list valid values here]
+ * @property payload - Additional event data in JSON format
+ * @property status - Event processing status (pending, processing, completed, failed)
+ */
 export const runhookEvent = pgTable("runhook_event", {
📝 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 runhookEvent = pgTable("runhook_event", {
id: uuid("id").primaryKey().defaultRandom(),
runhookId: uuid("runhook_id")
.references(() => runhook.id, { onDelete: "cascade" })
.notNull(),
eventType: text("event_type").notNull(),
});
/**
* Represents events associated with runhooks.
*
* @property eventType - The type of event.
* Valid values: [list valid values here]
* @property payload - Additional event data in JSON format
* @property status - Event processing status (pending, processing, completed, failed)
*/
export const runhookEvent = pgTable("runhook_event", {
id: uuid("id").primaryKey().defaultRandom(),
runhookId: uuid("runhook_id")
.references(() => runhook.id, { onDelete: "cascade" })
.notNull(),
eventType: text("event_type").notNull(),
payload: jsonb("payload"),
status: text("status").notNull().default("pending"),
createdAt: timestamp("created_at").notNull().defaultNow(),
processedAt: timestamp("processed_at"),
error: text("error"),
}, (table) => ({
runhookIdx: index("runhook_event_runhook_idx").on(table.runhookId),
createdAtIdx: index("runhook_event_created_at_idx").on(table.createdAt),
}));

Comment on lines +15 to +23
export const environmentDeletedEvent = environmentBaseEvent.extend({
type: z.literal(EnvironmentEvent.Deleted),
});
export type EnvironmentDeletedEvent = z.infer<typeof environmentDeletedEvent>;

export const environmentCreatedEvent = environmentBaseEvent.extend({
type: z.literal(EnvironmentEvent.Created),
});
export type EnvironmentCreatedEvent = z.infer<typeof environmentCreatedEvent>;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider extending event payloads with event-specific data.

The current implementation uses the same payload structure for both events. Consider adding event-specific fields to capture relevant data:

 export const environmentDeletedEvent = environmentBaseEvent.extend({
   type: z.literal(EnvironmentEvent.Deleted),
+  payload: z.object({
+    environmentId: z.string().uuid(),
+    deletedBy: z.string().uuid(),
+    reason: z.string().optional(),
+  }),
 });

 export const environmentCreatedEvent = environmentBaseEvent.extend({
   type: z.literal(EnvironmentEvent.Created),
+  payload: z.object({
+    environmentId: z.string().uuid(),
+    createdBy: z.string().uuid(),
+    configuration: z.record(z.unknown()),
+  }),
 });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +14 to +19
const events = expiredEnvironments.map((env) => ({
type: EnvironmentEvent.Deleted,
createdAt: new Date().toISOString(),
payload: { environmentId: env.id },
}));
await Promise.all(events.map(handleHookEvent));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error handling for event processing

While concurrent event processing is efficient, a failure in handleHookEvent for one environment could cause others to fail silently. Consider adding error handling and possibly a transaction to ensure atomicity.

Here's a suggested improvement:

-  await Promise.all(events.map(handleHookEvent));
+  try {
+    await Promise.all(
+      events.map(async (event) => {
+        try {
+          await handleHookEvent(event);
+        } catch (error) {
+          console.error(`Failed to process event for environment ${event.payload.environmentId}:`, error);
+          throw error; // Re-throw to trigger the outer catch
+        }
+      })
+    );
+  } catch (error) {
+    console.error('Failed to process some environment deletion events:', error);
+    throw error; // Prevent environment deletion if event processing fails
+  }
📝 Committable suggestion

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

Suggested change
const events = expiredEnvironments.map((env) => ({
type: EnvironmentEvent.Deleted,
createdAt: new Date().toISOString(),
payload: { environmentId: env.id },
}));
await Promise.all(events.map(handleHookEvent));
const events = expiredEnvironments.map((env) => ({
type: EnvironmentEvent.Deleted,
createdAt: new Date().toISOString(),
payload: { environmentId: env.id },
}));
try {
await Promise.all(
events.map(async (event) => {
try {
await handleHookEvent(event);
} catch (error) {
console.error(`Failed to process event for environment ${event.payload.environmentId}:`, error);
throw error; // Re-throw to trigger the outer catch
}
})
);
} catch (error) {
console.error('Failed to process some environment deletion events:', error);
throw error; // Prevent environment deletion if event processing fails
}

Comment on lines +20 to +26
const handleTargets = async (event: EnvironmentDeletedEvent) => {
const environment = await db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, event.payload.environmentId))
.then(takeFirstOrNull);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add Null Check for 'environment' Before Accessing Its Properties

While you check if environment?.targetFilter == null, there is a possibility that environment itself could be null if the database query does not find a matching record. Accessing environment.id later without confirming that environment is not null may lead to runtime errors.

Consider adding a null check for environment after retrieving it from the database to ensure it exists before accessing its properties.

Apply this diff to add the null check:

const environment = await db
  .select()
  .from(SCHEMA.environment)
  .where(eq(SCHEMA.environment.id, event.payload.environmentId))
  .then(takeFirstOrNull);

+ if (environment == null) return;

if (environment.targetFilter == null) return;
📝 Committable suggestion

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

Suggested change
const handleTargets = async (event: EnvironmentDeletedEvent) => {
const environment = await db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, event.payload.environmentId))
.then(takeFirstOrNull);
const handleTargets = async (event: EnvironmentDeletedEvent) => {
const environment = await db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, event.payload.environmentId))
.then(takeFirstOrNull);
if (environment == null) return;

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
apps/jobs/Dockerfile (1)

Node.js version 22 conflicts with package.json requirements

The package.json explicitly requires Node.js version >=20.12.0, but the Dockerfile is using Node 22 which is not yet an LTS release. This could lead to compatibility issues. Using Node.js 20 LTS would better align with the project's requirements while ensuring stability.

🔗 Analysis chain

Line range hint 1-1: Consider using a more stable Node.js version

The Dockerfile uses Node 22 which is very recent and might introduce stability issues in production. Consider using LTS version 20 instead.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if package.json specifies a different Node.js version requirement
rg -A 5 '"node":' package.json

Length of output: 213

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7a828 and fe8e9ef.

📒 Files selected for processing (2)
  • apps/jobs/Dockerfile (1 hunks)
  • packages/events/tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/events/tsconfig.json
🔇 Additional comments (1)
apps/jobs/Dockerfile (1)

30-30: LGTM: Package addition follows established pattern

The addition of packages/events/package.json is correctly placed with other package.json copies and follows the established pattern for dependency management in the multi-package setup.

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.

1 participant