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

fix: Move deployments between systems #255

Merged
merged 2 commits into from
Dec 9, 2024

Conversation

adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Dec 6, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced the EditDeploymentSection with a new systemId selection field, allowing users to choose from available systems.
    • Improved data handling by integrating system-related information into deployment updates.
  • Bug Fixes

    • Streamlined the handling of workspace identifiers to ensure accurate data flow between components.
  • Documentation

    • Updated method signatures and types to reflect changes in deployment handling and event processing.
  • Chores

    • Added new exports for deployment updates, improving module accessibility.

Copy link
Contributor

coderabbitai bot commented Dec 6, 2024

Walkthrough

This pull request includes significant updates across multiple files, primarily focusing on the EditDeploymentSection component and related functionalities. Key changes involve modifying import styles, updating component props, and enhancing API interactions to include system-related data. Additionally, several functions have been renamed for clarity, and new methods have been introduced to manage deployment updates and system changes. The overall structure of the deployment schema and related database operations has been preserved while improving the handling of deployment events and their associated data.

Changes

File Change Summary
apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx Updated import style for deploymentSchema, restructured component props, modified onSubmit function, and added a new systemId form field.
apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/page.tsx Adjusted DeploymentPage to use workspaceId, updated EditDeploymentSection to accept systems prop, and ensured Variables component uses workspaceId.
packages/api/src/router/deployment.ts Renamed updateDeployment to updateDeploymentSchema, modified event handling for deleted deployments.
packages/db/src/schema/deployment.ts Added new type UpdateDeployment, which infers its structure from the existing schema.
packages/job-dispatch/src/deployment-update.ts Introduced handleDeploymentSystemChanged and updateDeployment functions for managing deployment updates.
packages/job-dispatch/src/events/handlers/resource-removed.ts Added early return for runhooks check and logging of removal events.
packages/job-dispatch/src/events/index.ts Simplified handleEvent function to directly call handleResourceRemoved.
packages/job-dispatch/src/events/triggers/deployment-removed.ts Renamed function to getEventsForDeploymentRemoved and added systemId parameter.
packages/job-dispatch/src/events/triggers/index.ts Replaced export of deployment-deleted.js with deployment-removed.js.
packages/job-dispatch/src/index.ts Added export for deployment-update.js to enhance module interface.

Possibly related PRs

  • fix: Edit deployment variable #164: The changes in this PR involve the addition of a new EditVariableDialog component, which is related to the modifications in the EditDeploymentSection component in the main PR, as both involve handling deployment-related data and user interactions.
  • fix: Deployment retry policy #248: This PR introduces a retryCount field in the CreateDeploymentDialog, EditDeploymentDialog, and EditDeploymentSection, which aligns with the changes in the main PR that also involve the EditDeploymentSection component and its handling of deployment properties.
  • Fix: Approval Usability #172: The modifications in this PR regarding the PolicyApprovalRow component may relate to the overall deployment process, which is also a focus of the main PR, particularly in how deployments are managed and approved.
  • fix: Reuse filter endpoints for deployment resource drawer #239: This PR's updates to the DeploymentResourceDrawer component may connect to the main PR's focus on deployment management, as both involve enhancements to how deployments and their resources are handled.
  • fix: Clean up system filters #240: The cleanup of system filters in this PR may relate to the overall structure and management of deployments, which is a key aspect of the main PR's changes.

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 generate docstrings to generate docstrings for this PR. (Experiment)
  • @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: 7

🧹 Outside diff range and nitpick comments (5)
packages/job-dispatch/src/deployment-update.ts (1)

81-87: Simplify fetching the system associated with the updated deployment

Since updatedDeployment.systemId is already known, consider using a relation or join to fetch the system in a single query.

You might modify the code as follows to streamline the retrieval:

- const sys = await db
-     .select()
-     .from(SCHEMA.system)
-     .where(eq(SCHEMA.system.id, updatedDeployment.systemId))
-     .then(takeFirst);
+ const updatedDeploymentWithSystem = await db.query.deployment.findFirst({
+   where: eq(SCHEMA.deployment.id, updatedDeployment.id),
+   with: { system: true },
+ });
+ return updatedDeploymentWithSystem;
packages/job-dispatch/src/events/handlers/resource-removed.ts (1)

23-25: Consider storing the event even if no runhooks are present

The early return when runhooks.length === 0 prevents the event from being inserted into the database. If it's important to log all resource removal events, consider inserting the event regardless of the presence of runhooks.

You might adjust the code as follows:

 if (runhooks.length === 0) {
+   await db.insert(SCHEMA.event).values(event);
    return;
 }
 await db.insert(SCHEMA.event).values(event);
apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx (2)

6-6: Consider more specific imports

Instead of using wildcard import * as schema, consider importing specific entities to improve code maintainability and bundle size optimization.

-import * as schema from "@ctrlplane/db/schema";
+import { Deployment, System, deploymentSchema } from "@ctrlplane/db/schema";

Also applies to: 19-25


131-154: Add loading state for system changes

The system selection UI should reflect when a system change is in progress. Consider adding a loading state to prevent multiple submissions and improve user experience.

           <FormField
             control={form.control}
             name="systemId"
             render={({ field: { value, onChange } }) => (
               <FormItem>
                 <FormLabel>System</FormLabel>
                 <FormControl>
-                  <Select value={value} onValueChange={onChange}>
+                  <Select 
+                    value={value} 
+                    onValueChange={onChange}
+                    disabled={form.formState.isSubmitting}
+                  >
                     <SelectTrigger>
                       <SelectValue placeholder="Select a system" />
                     </SelectTrigger>
                     <SelectContent>
                       {systems.map((system) => (
                         <SelectItem key={system.id} value={system.id}>
                           {system.name}
                         </SelectItem>
                       ))}
                     </SelectContent>
                   </Select>
                 </FormControl>
                 <FormMessage />
               </FormItem>
             )}
           />
packages/api/src/router/deployment.ts (1)

Line range hint 474-479: Verify event handling failures during deployment deletion.

The code uses Promise.allSettled which is good for handling multiple events, but we should verify the behavior when events fail. Consider logging failed events and potentially notifying administrators.

 const events = await getEventsForDeploymentRemoved(dep, dep.systemId);
-await Promise.allSettled(events.map(handleEvent));
+const results = await Promise.allSettled(events.map(handleEvent));
+
+// Log any failed events
+const failedEvents = results
+  .filter((r): r is PromiseRejectedResult => r.status === 'rejected')
+  .map(r => r.reason);
+if (failedEvents.length > 0) {
+  console.error('Failed to handle some deployment removal events:', failedEvents);
+  // Consider notifying administrators or adding metrics
+}
+
 return ctx.db
   .delete(deployment)
   .where(eq(deployment.id, input))
   .returning()
   .then(takeFirst);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c111c90 and f9512cb.

📒 Files selected for processing (10)
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/page.tsx (3 hunks)
  • packages/api/src/router/deployment.ts (3 hunks)
  • packages/db/src/schema/deployment.ts (1 hunks)
  • packages/job-dispatch/src/deployment-update.ts (1 hunks)
  • packages/job-dispatch/src/events/handlers/resource-removed.ts (1 hunks)
  • packages/job-dispatch/src/events/index.ts (1 hunks)
  • packages/job-dispatch/src/events/triggers/deployment-removed.ts (1 hunks)
  • packages/job-dispatch/src/events/triggers/index.ts (1 hunks)
  • packages/job-dispatch/src/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/job-dispatch/src/events/triggers/index.ts
🔇 Additional comments (7)
packages/job-dispatch/src/events/index.ts (1)

8-9: Verify the impact of removing the event insertion into the database

The handleEvent function no longer inserts the event into the database. Ensure that this change doesn't affect any downstream processes that rely on events being stored.

Run the following script to check for dependencies on event storage:

packages/job-dispatch/src/index.ts (1)

3-3: New module deployment-update.js successfully exported

The addition of export * from "./deployment-update.js"; correctly exposes the new deployment update functionalities.

packages/job-dispatch/src/events/triggers/deployment-removed.ts (1)

11-18: Passing systemId as a parameter enhances flexibility

By accepting systemId as a parameter, the function is more flexible and can handle cases where the system ID differs from the deployment's associated system. This is beneficial for scenarios where the deployment is moved between systems.

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

73-73: LGTM: Type export enhances type safety

The new UpdateDeployment type correctly infers its structure from the updateDeployment schema, providing proper type safety for deployment updates.

apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/page.tsx (2)

150-151: LGTM: Improved workspace ID handling

Good refactoring to extract workspaceId early and use it consistently throughout the component. The systems list fetch is appropriately placed after workspace validation.

Also applies to: 154-154


167-167: Verify system access permissions

Ensure that the user has permissions to view and select from all systems in the workspace. Consider filtering the systems list based on user permissions.

Also applies to: 177-177

apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx (1)

30-35: Consider adding validation for system selection

Add validation to ensure the selected system is valid and the user has appropriate permissions.

Comment on lines +74 to +79
if (prevDeployment.systemId !== updatedDeployment.systemId)
await handleDeploymentSystemChanged(
updatedDeployment,
prevDeployment.systemId,
userId,
);
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

Add braces to multi-line if statement for clarity

The if statement spans multiple lines and calls an asynchronous function. To improve readability and prevent potential errors, it's recommended to wrap the block in braces.

Apply this diff:

 if (prevDeployment.systemId !== updatedDeployment.systemId)
-   await handleDeploymentSystemChanged(
-     updatedDeployment,
-     prevDeployment.systemId,
-     userId,
-   );
+ {
+   await handleDeploymentSystemChanged(
+     updatedDeployment,
+     prevDeployment.systemId,
+     userId,
+   );
+ }
📝 Committable suggestion

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

Suggested change
if (prevDeployment.systemId !== updatedDeployment.systemId)
await handleDeploymentSystemChanged(
updatedDeployment,
prevDeployment.systemId,
userId,
);
if (prevDeployment.systemId !== updatedDeployment.systemId) {
await handleDeploymentSystemChanged(
updatedDeployment,
prevDeployment.systemId,
userId,
);
}

Comment on lines +67 to +73
const updatedDeployment = await db
.update(SCHEMA.deployment)
.set(data)
.where(eq(SCHEMA.deployment.id, deploymentId))
.returning()
.then(takeFirst);

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

Check if updatedDeployment is undefined after the update

After updating the deployment, updatedDeployment might be undefined if the update did not affect any rows. Add error handling to manage this scenario.

Apply this diff to ensure updatedDeployment is valid:

 const updatedDeployment = await db
     .update(SCHEMA.deployment)
     .set(data)
     .where(eq(SCHEMA.deployment.id, deploymentId))
     .returning()
     .then(takeFirst);
+ if (!updatedDeployment) {
+   throw new Error(`Failed to update deployment with id ${deploymentId}`);
+ }
📝 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 updatedDeployment = await db
.update(SCHEMA.deployment)
.set(data)
.where(eq(SCHEMA.deployment.id, deploymentId))
.returning()
.then(takeFirst);
const updatedDeployment = await db
.update(SCHEMA.deployment)
.set(data)
.where(eq(SCHEMA.deployment.id, deploymentId))
.returning()
.then(takeFirst);
if (!updatedDeployment) {
throw new Error(`Failed to update deployment with id ${deploymentId}`);
}

Comment on lines +61 to +66
const prevDeployment = await db
.select()
.from(SCHEMA.deployment)
.where(eq(SCHEMA.deployment.id, deploymentId))
.then(takeFirst);

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

Ensure prevDeployment is valid and handle potential errors

The variable prevDeployment may be undefined if no deployment matches the deploymentId. Consider adding error handling to manage this scenario, possibly by throwing an error if prevDeployment is not found.

Apply this diff to handle the potential undefined value:

 const prevDeployment = await db
     .select()
     .from(SCHEMA.deployment)
     .where(eq(SCHEMA.deployment.id, deploymentId))
     .then(takeFirst);
+ if (!prevDeployment) {
+   throw new Error(`Deployment with id ${deploymentId} not found`);
+ }
📝 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 prevDeployment = await db
.select()
.from(SCHEMA.deployment)
.where(eq(SCHEMA.deployment.id, deploymentId))
.then(takeFirst);
const prevDeployment = await db
.select()
.from(SCHEMA.deployment)
.where(eq(SCHEMA.deployment.id, deploymentId))
.then(takeFirst);
if (!prevDeployment) {
throw new Error(`Deployment with id ${deploymentId} not found`);
}

Comment on lines 17 to 18
const events = await getEventsForDeploymentRemoved(deployment, prevSystemId);
await Promise.allSettled(events.map(handleEvent));
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

Handle rejections from Promise.allSettled when processing events

Currently, the results of Promise.allSettled are not being checked. If any of the handleEvent promises are rejected, the errors will be silently ignored. Consider handling the rejections to ensure that any errors are properly logged or addressed.

Apply this diff to handle rejections:

 const events = await getEventsForDeploymentRemoved(deployment, prevSystemId);
- await Promise.allSettled(events.map(handleEvent));
+ const results = await Promise.allSettled(events.map(handleEvent));
+ results.forEach((result) => {
+   if (result.status === 'rejected') {
+     // Handle or log the error
+     console.error('Error handling event:', result.reason);
+   }
+ });

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

Comment on lines +54 to 61
.then((updatedDeployment) => {
if (
data.slug !== deployment.slug ||
updatedDeployment.systemId !== deployment.systemId
)
router.replace(
`/${workspaceSlug}/systems/${systemSlug}/deployments/${data.slug}`,
`/${workspaceSlug}/systems/${updatedDeployment.system.slug}/deployments/${data.slug}`,
);
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

Add confirmation dialog for system changes

Moving a deployment between systems is a significant operation that might have downstream effects. Consider adding a confirmation dialog when the system is changed.

   const onSubmit = handleSubmit((data) => {
     updateDeployment
       .mutateAsync({ id: deployment.id, data })
       .then((updatedDeployment) => {
-        if (
-          data.slug !== deployment.slug ||
-          updatedDeployment.systemId !== deployment.systemId
-        )
-          router.replace(
-            `/${workspaceSlug}/systems/${updatedDeployment.system.slug}/deployments/${data.slug}`,
-          );
+        const isSystemChanged = updatedDeployment.systemId !== deployment.systemId;
+        const isSlugChanged = data.slug !== deployment.slug;
+        
+        if (isSystemChanged) {
+          // Add confirmation dialog here
+          const confirmed = window.confirm(
+            "Moving a deployment to a different system may affect its behavior. Are you sure?"
+          );
+          if (!confirmed) return;
+        }
+        
+        if (isSystemChanged || isSlugChanged) {
+          router.replace(
+            `/${workspaceSlug}/systems/${updatedDeployment.system.slug}/deployments/${data.slug}`,
+          );
+        }
         router.refresh();
       })

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

Comment on lines +455 to +457
.input(z.object({ id: z.string().uuid(), data: updateDeploymentSchema }))
.mutation(({ ctx, input }) =>
ctx.db
.update(deployment)
.set(input.data)
.where(eq(deployment.id, input.id))
.returning(),
updateDeployment(input.id, input.data, ctx.session.user.id),
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

Add error handling for the external updateDeployment call.

The update mutation now delegates to updateDeployment from job-dispatch, which is good for centralization. However, we should ensure proper error handling for this external call.

 .input(z.object({ id: z.string().uuid(), data: updateDeploymentSchema }))
-.mutation(({ ctx, input }) =>
-  updateDeployment(input.id, input.data, ctx.session.user.id),
+.mutation(async ({ ctx, input }) => {
+  try {
+    return await updateDeployment(input.id, input.data, ctx.session.user.id);
+  } catch (error) {
+    // Log the error for debugging
+    console.error('Failed to update deployment:', error);
+    throw new Error('Failed to update deployment. Please try again.');
+  }
+}),
📝 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
.input(z.object({ id: z.string().uuid(), data: updateDeploymentSchema }))
.mutation(({ ctx, input }) =>
ctx.db
.update(deployment)
.set(input.data)
.where(eq(deployment.id, input.id))
.returning(),
updateDeployment(input.id, input.data, ctx.session.user.id),
.input(z.object({ id: z.string().uuid(), data: updateDeploymentSchema }))
.mutation(async ({ ctx, input }) => {
try {
return await updateDeployment(input.id, input.data, ctx.session.user.id);
} catch (error) {
// Log the error for debugging
console.error('Failed to update deployment:', error);
throw new Error('Failed to update deployment. Please try again.');
}
}),

@@ -33,14 +33,15 @@
runbookVariable,
runhook,
system,
updateDeployment,
updateDeployment as updateDeploymentSchema,
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Migration to new function names needs attention

The verification revealed several concerns:

  • updateDeployment is still being used across multiple files in the codebase, suggesting incomplete migration
  • While getEventsForDeploymentDeleted has been fully removed, both new names (updateDeploymentSchema and getEventsForDeploymentRemoved) appear in the expected locations

Files needing attention for updateDeployment usage:

  • packages/api/src/router/deployment-variable.ts
  • packages/db/src/schema/deployment.ts
  • packages/db/src/schema/deployment-variables.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/EditDeploymentSection.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/EditDeploymentDialog.tsx
🔗 Analysis chain

Verify consistent usage of renamed functions across the codebase.

The imports have been renamed to better reflect their purposes:

  • updateDeploymentupdateDeploymentSchema
  • getEventsForDeploymentDeletedgetEventsForDeploymentRemoved

Also applies to: 42-42, 44-44


Let me modify the search patterns to get more accurate results.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that old function names are not used elsewhere in the codebase

# Test 1: Check for any remaining usage of old function names
echo "Checking for old function names..."
rg -l "updateDeployment(?!Schema)" || echo "No occurrences found"
rg -l "getEventsForDeploymentDeleted" || echo "No occurrences found"

# Test 2: Verify the new function signatures
echo -e "\nVerifying new function signatures..."
ast-grep --pattern 'function updateDeploymentSchema($_) { $$$ }'
ast-grep --pattern 'function getEventsForDeploymentRemoved($_) { $$$ }'

Length of output: 731


Script:

#!/bin/bash
# Description: Verify function names with improved search patterns

# Check for updateDeployment usage (excluding Schema)
echo "Checking updateDeployment usage..."
rg -l --pcre2 'updateDeployment(?!Schema)' || echo "No occurrences found"

# Check for getEventsForDeploymentDeleted
echo -e "\nChecking getEventsForDeploymentDeleted usage..."
rg -l 'getEventsForDeploymentDeleted' || echo "No occurrences found"

# Check for new function names
echo -e "\nChecking new function names..."
rg -l 'updateDeploymentSchema' || echo "No updateDeploymentSchema found"
rg -l 'getEventsForDeploymentRemoved' || echo "No getEventsForDeploymentRemoved found"

# Check the actual implementations
echo -e "\nChecking implementations..."
rg -A 2 'function updateDeploymentSchema' || echo "No updateDeploymentSchema implementation found"
rg -A 2 'function getEventsForDeploymentRemoved' || echo "No getEventsForDeploymentRemoved implementation found"

Length of output: 1463

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: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f9512cb and 08b82bd.

📒 Files selected for processing (1)
  • packages/job-dispatch/src/deployment-update.ts (1 hunks)
🔇 Additional comments (3)
packages/job-dispatch/src/deployment-update.ts (3)

125-130: Ensure prevDeployment is valid and handle potential errors

The previous review comment about handling undefined prevDeployment is still valid.


131-137: Check if updatedDeployment is undefined after the update

The previous review comment about handling undefined updatedDeployment is still valid.


138-143: Add braces to multi-line if statement for clarity

The previous review comment about adding braces to the if statement is still valid.

Comment on lines +145 to +152
const sys = await db
.select()
.from(SCHEMA.system)
.where(eq(SCHEMA.system.id, updatedDeployment.systemId))
.then(takeFirst);

return { ...updatedDeployment, system: sys };
};
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 error handling for system query and validate return object

The final system query could fail silently, and the return object structure should be validated before returning.

Apply this diff:

-  const sys = await db
+  const system = await db
     .select()
     .from(SCHEMA.system)
     .where(eq(SCHEMA.system.id, updatedDeployment.systemId))
     .then(takeFirst);
+  
+  if (!system) {
+    throw new Error(
+      `System not found for updated deployment: ${updatedDeployment.systemId}`
+    );
+  }
 
-  return { ...updatedDeployment, system: sys };
+  return { ...updatedDeployment, system };
📝 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 sys = await db
.select()
.from(SCHEMA.system)
.where(eq(SCHEMA.system.id, updatedDeployment.systemId))
.then(takeFirst);
return { ...updatedDeployment, system: sys };
};
const system = await db
.select()
.from(SCHEMA.system)
.where(eq(SCHEMA.system.id, updatedDeployment.systemId))
.then(takeFirst);
if (!system) {
throw new Error(
`System not found for updated deployment: ${updatedDeployment.systemId}`
);
}
return { ...updatedDeployment, system };
};

Comment on lines +19 to +65
const getResourcesOnlyInNewSystem = async (
newSystemId: string,
oldSystemId: string,
) => {
const hasFilter = isNotNull(SCHEMA.environment.resourceFilter);
const newSystem = await db.query.system.findFirst({
where: eq(SCHEMA.system.id, newSystemId),
with: { environments: { where: hasFilter } },
});

const oldSystem = await db.query.system.findFirst({
where: eq(SCHEMA.system.id, oldSystemId),
with: { environments: { where: hasFilter } },
});

if (newSystem == null || oldSystem == null) return [];

const newSystemFilter: ResourceCondition = {
type: FilterType.Comparison,
operator: ComparisonOperator.Or,
conditions: newSystem.environments
.flatMap((env) => env.resourceFilter)
.filter(isPresent),
};

const notInOldSystemFilter: ResourceCondition = {
type: FilterType.Comparison,
operator: ComparisonOperator.Or,
not: true,
conditions: oldSystem.environments
.flatMap((env) => env.resourceFilter)
.filter(isPresent),
};

const filter: ResourceCondition = {
type: FilterType.Comparison,
operator: ComparisonOperator.And,
conditions: [newSystemFilter, notInOldSystemFilter],
};

return db.query.resource.findMany({
where: and(
SCHEMA.resourceMatchesMetadata(db, filter),
isNull(SCHEMA.resource.deletedAt),
),
});
};
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

Add error handling for database queries

The function should handle potential database errors that might occur during system queries. Currently, errors would propagate up the call stack without any context about the operation that failed.

Apply this diff:

 const getResourcesOnlyInNewSystem = async (
   newSystemId: string,
   oldSystemId: string,
 ) => {
   const hasFilter = isNotNull(SCHEMA.environment.resourceFilter);
-  const newSystem = await db.query.system.findFirst({
-    where: eq(SCHEMA.system.id, newSystemId),
-    with: { environments: { where: hasFilter } },
-  });
+  try {
+    const newSystem = await db.query.system.findFirst({
+      where: eq(SCHEMA.system.id, newSystemId),
+      with: { environments: { where: hasFilter } },
+    });
 
-  const oldSystem = await db.query.system.findFirst({
-    where: eq(SCHEMA.system.id, oldSystemId),
-    with: { environments: { where: hasFilter } },
-  });
+    const oldSystem = await db.query.system.findFirst({
+      where: eq(SCHEMA.system.id, oldSystemId),
+      with: { environments: { where: hasFilter } },
+    });
 
-  if (newSystem == null || oldSystem == null) return [];
+    if (newSystem == null || oldSystem == null) return [];
+    // ... rest of the function
+  } catch (error) {
+    throw new Error(
+      `Failed to fetch systems for comparison: ${error.message}`,
+      { cause: error }
+    );
+  }

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

Comment on lines +67 to +118
export const handleDeploymentSystemChanged = async (
deployment: SCHEMA.Deployment,
prevSystemId: string,
userId?: string,
) => {
const resourcesOnlyInNewSystem = await getResourcesOnlyInNewSystem(
deployment.systemId,
prevSystemId,
);

const events = resourcesOnlyInNewSystem.map((resource) => ({
action: "deployment.resource.removed" as const,
payload: { deployment, resource },
}));
await Promise.allSettled(events.map(handleEvent));

const isDeploymentHook = and(
eq(SCHEMA.hook.scopeType, "deployment"),
eq(SCHEMA.hook.scopeId, deployment.id),
);
await db.query.hook
.findMany({
where: isDeploymentHook,
with: { runhooks: { with: { runbook: true } } },
})
.then((hooks) => {
const runbookIds = hooks.flatMap((h) =>
h.runhooks.map((rh) => rh.runbook.id),
);
return db
.update(SCHEMA.runbook)
.set({ systemId: deployment.systemId })
.where(inArray(SCHEMA.runbook.id, runbookIds));
});

const createTriggers =
userId != null
? createReleaseJobTriggers(db, "new_release").causedById(userId)
: createReleaseJobTriggers(db, "new_release");
await createTriggers
.deployments([deployment.id])
.resources(resourcesOnlyInNewSystem.map((r) => r.id))
.filter(isPassingReleaseStringCheckPolicy)
.then(createJobApprovals)
.insert()
.then((triggers) =>
dispatchReleaseJobTriggers(db)
.releaseTriggers(triggers)
.filter(isPassingAllPolicies)
.dispatch(),
);
};
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

Wrap related database operations in a transaction

Multiple database operations (runbook updates and trigger creation) should be wrapped in a transaction to ensure data consistency in case of failures.

Apply this diff:

 export const handleDeploymentSystemChanged = async (
   deployment: SCHEMA.Deployment,
   prevSystemId: string,
   userId?: string,
 ) => {
+  return db.transaction(async (tx) => {
   const resourcesOnlyInNewSystem = await getResourcesOnlyInNewSystem(
     deployment.systemId,
     prevSystemId,
   );

   const events = resourcesOnlyInNewSystem.map((resource) => ({
     action: "deployment.resource.removed" as const,
     payload: { deployment, resource },
   }));
   await Promise.allSettled(events.map(handleEvent));

   const isDeploymentHook = and(
     eq(SCHEMA.hook.scopeType, "deployment"),
     eq(SCHEMA.hook.scopeId, deployment.id),
   );
-  await db.query.hook
+  await tx.query.hook
     .findMany({
       where: isDeploymentHook,
       with: { runhooks: { with: { runbook: true } } },
     })
     .then((hooks) => {
       const runbookIds = hooks.flatMap((h) =>
         h.runhooks.map((rh) => rh.runbook.id),
       );
-      return db
+      return tx
         .update(SCHEMA.runbook)
         .set({ systemId: deployment.systemId })
         .where(inArray(SCHEMA.runbook.id, runbookIds));
     });

   const createTriggers =
     userId != null
-      ? createReleaseJobTriggers(db, "new_release").causedById(userId)
-      : createReleaseJobTriggers(db, "new_release");
+      ? createReleaseJobTriggers(tx, "new_release").causedById(userId)
+      : createReleaseJobTriggers(tx, "new_release");
   await createTriggers
     .deployments([deployment.id])
     .resources(resourcesOnlyInNewSystem.map((r) => r.id))
     .filter(isPassingReleaseStringCheckPolicy)
     .then(createJobApprovals)
     .insert()
     .then((triggers) =>
-      dispatchReleaseJobTriggers(db)
+      dispatchReleaseJobTriggers(tx)
         .releaseTriggers(triggers)
         .filter(isPassingAllPolicies)
         .dispatch(),
     );
+  });
 };
📝 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 handleDeploymentSystemChanged = async (
deployment: SCHEMA.Deployment,
prevSystemId: string,
userId?: string,
) => {
const resourcesOnlyInNewSystem = await getResourcesOnlyInNewSystem(
deployment.systemId,
prevSystemId,
);
const events = resourcesOnlyInNewSystem.map((resource) => ({
action: "deployment.resource.removed" as const,
payload: { deployment, resource },
}));
await Promise.allSettled(events.map(handleEvent));
const isDeploymentHook = and(
eq(SCHEMA.hook.scopeType, "deployment"),
eq(SCHEMA.hook.scopeId, deployment.id),
);
await db.query.hook
.findMany({
where: isDeploymentHook,
with: { runhooks: { with: { runbook: true } } },
})
.then((hooks) => {
const runbookIds = hooks.flatMap((h) =>
h.runhooks.map((rh) => rh.runbook.id),
);
return db
.update(SCHEMA.runbook)
.set({ systemId: deployment.systemId })
.where(inArray(SCHEMA.runbook.id, runbookIds));
});
const createTriggers =
userId != null
? createReleaseJobTriggers(db, "new_release").causedById(userId)
: createReleaseJobTriggers(db, "new_release");
await createTriggers
.deployments([deployment.id])
.resources(resourcesOnlyInNewSystem.map((r) => r.id))
.filter(isPassingReleaseStringCheckPolicy)
.then(createJobApprovals)
.insert()
.then((triggers) =>
dispatchReleaseJobTriggers(db)
.releaseTriggers(triggers)
.filter(isPassingAllPolicies)
.dispatch(),
);
};
export const handleDeploymentSystemChanged = async (
deployment: SCHEMA.Deployment,
prevSystemId: string,
userId?: string,
) => {
return db.transaction(async (tx) => {
const resourcesOnlyInNewSystem = await getResourcesOnlyInNewSystem(
deployment.systemId,
prevSystemId,
);
const events = resourcesOnlyInNewSystem.map((resource) => ({
action: "deployment.resource.removed" as const,
payload: { deployment, resource },
}));
await Promise.allSettled(events.map(handleEvent));
const isDeploymentHook = and(
eq(SCHEMA.hook.scopeType, "deployment"),
eq(SCHEMA.hook.scopeId, deployment.id),
);
await tx.query.hook
.findMany({
where: isDeploymentHook,
with: { runhooks: { with: { runbook: true } } },
})
.then((hooks) => {
const runbookIds = hooks.flatMap((h) =>
h.runhooks.map((rh) => rh.runbook.id),
);
return tx
.update(SCHEMA.runbook)
.set({ systemId: deployment.systemId })
.where(inArray(SCHEMA.runbook.id, runbookIds));
});
const createTriggers =
userId != null
? createReleaseJobTriggers(tx, "new_release").causedById(userId)
: createReleaseJobTriggers(tx, "new_release");
await createTriggers
.deployments([deployment.id])
.resources(resourcesOnlyInNewSystem.map((r) => r.id))
.filter(isPassingReleaseStringCheckPolicy)
.then(createJobApprovals)
.insert()
.then((triggers) =>
dispatchReleaseJobTriggers(tx)
.releaseTriggers(triggers)
.filter(isPassingAllPolicies)
.dispatch(),
);
});
};

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