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

feat/be/threshold notifications #1179

Merged
merged 14 commits into from
Nov 25, 2024
Merged

Conversation

ajhollid
Copy link
Collaborator

@ajhollid ajhollid commented Nov 22, 2024

This PR handles infrastructure monitor notifications. This is a special case that requires checking for two types of thresholds

  1. notification threshold - a notification n checks
  2. metric threshold - a notification should only be sent when metrics exceed thresholds
  • Add an email template for infrastructure monitor alerts
  • Add a case for infrastructure monitor notificaitons to the JobQueue
  • Add metric specific notification thresholds to the Notification model (default set to 5, this can be user specified later)
    • Whenever a metric threshold is breached, notification threshold for that metric will be decremented
    • When notification threshold reaches 0, a notification is sent
  • Convert thresholds to decimal on FE, all percentages submitted to BE should be in decimal representation

Copy link

coderabbitai bot commented Nov 22, 2024

Walkthrough

The pull request introduces several modifications across multiple files, primarily affecting the handling of notifications, data fetching, and validation in the application. Key changes include updating the default date range in the InfrastructureDetails component, adding new alert thresholds in the NotificationSchema, and enhancing the getDateRange function to support a broader range of date options. Additionally, a new email template for hardware incidents is introduced, while an old template for threshold violations is removed. These changes collectively improve the application's monitoring and notification capabilities.

Changes

File Path Change Summary
Client/src/Pages/Infrastructure/Details/index.jsx Updated dateRange state from "day" to "all"; modified parameters in fetchData function.
Server/db/models/Notification.js Added new fields: alertThreshold, cpuAlertThreshold, memoryAlertThreshold, diskAlertThreshold.
Server/db/mongo/modules/monitorModule.js Expanded getDateRange function to include 'all' as a valid date range option.
Server/service/emailService.js Replaced thresholdViolatedTemplate with hardwareIncidentTemplate in the templateLookup object.
Server/service/jobQueue.js Added handling for notifications in createJobHandler and improved error handling in several methods.
Server/service/notificationService.js Updated sendEmail method to accept alerts parameter; added handleHardwareNotifications method.
Server/service/statusService.js Modified return value of updateStatus method for unchanged status; enhanced error logging in insertCheck.
Server/templates/hardwareIncident.mjml Introduced a new MJML template for hardware incident notifications.
Server/templates/thresholdViolated.mjml Deleted the MJML template for threshold violation notifications.
Server/validation/joi.js Updated dateRange validation schema to include "all" as a valid option.

Possibly related PRs

Suggested reviewers

  • jennifer-gan
  • marcelluscaio

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:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • 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

@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 (8)
Server/db/models/Notification.js (1)

35-40: Vomit on his sweater already! Let's clean this up! 🧹

Same issues as CPU and memory thresholds, but let's talk bigger picture.

Consider restructuring the thresholds into a nested object for better organization:

-alertThreshold: { ... },
-cpuAlertThreshold: { ... },
-memoryAlertThreshold: { ... },
-diskAlertThreshold: { ... },
+thresholds: {
+  type: {
+    base: {
+      type: Number,
+      default: 5,
+      min: 0,
+      max: 100
+    },
+    cpu: {
+      type: Number,
+      min: 0,
+      max: 100
+    },
+    memory: {
+      type: Number,
+      min: 0,
+      max: 100
+    },
+    disk: {
+      type: Number,
+      min: 0,
+      max: 100
+    }
+  },
+  validate: {
+    validator: function(thresholds) {
+      return ['cpu', 'memory', 'disk'].every(
+        metric => thresholds[metric] <= thresholds.base
+      );
+    }
+  }
+}

This structure would:

  1. Group related fields
  2. Simplify validation
  3. Make it easier to add new metric thresholds
  4. Prevent race conditions with default values
Server/service/emailService.js (2)

Line range hint 41-50: Mom's spaghetti moment: Error handling needs strengthening! 🍝

The loadTemplate function swallows errors and returns undefined, which could cause mysterious failures when trying to use the template later.

Here's how we can make it better:

 this.loadTemplate = (templateName) => {
   try {
     const templatePath = this.path.join(
       __dirname,
       `../templates/${templateName}.mjml`
     );
     const templateContent = this.fs.readFileSync(templatePath, "utf8");
     return this.compile(templateContent);
   } catch (error) {
     this.logger.error({
       message: error.message,
       service: SERVICE_NAME,
       method: "loadTemplate",
       stack: error.stack,
     });
+    throw new Error(`Failed to load template ${templateName}: ${error.message}`);
   }
 };

Line range hint 63-69: Knees weak, arms are heavy: Let's optimize these template loads! 🍝

The TODO comment suggests loading less-used templates on demand, but we're still loading all templates during construction. Consider implementing lazy loading for templates that aren't used frequently.

Here's a suggested approach:

-this.templateLookup = {
-  welcomeEmailTemplate: this.loadTemplate("welcomeEmail"),
-  employeeActivationTemplate: this.loadTemplate("employeeActivation"),
-  noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"),
-  serverIsDownTemplate: this.loadTemplate("serverIsDown"),
-  serverIsUpTemplate: this.loadTemplate("serverIsUp"),
-  hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"),
-};
+this.loadedTemplates = new Map();
+
+this.getTemplate = (templateName) => {
+  if (!this.loadedTemplates.has(templateName)) {
+    this.loadedTemplates.set(templateName, this.loadTemplate(templateName));
+  }
+  return this.loadedTemplates.get(templateName);
+};

Then update buildHtml to use this.getTemplate instead of this.templateLookup.

Server/service/statusService.js (2)

Line range hint 108-114: Yo, we need some threshold validation in here! 💪

Since we're implementing threshold notifications, the hardware metrics in buildCheck should validate against thresholds before storing.

Here's a suggested enhancement:

 if (type === "hardware") {
   const { cpu, memory, disk, host } = payload?.data ?? {};
   const { errors } = payload;
+  // Validate thresholds and mark breaches
+  const validateThreshold = (value, threshold) => ({
+    value,
+    breached: value > threshold
+  });
+  
   check.cpu = cpu ?? {};
+  check.cpu.threshold = validateThreshold(cpu?.usage, cpu?.threshold);
   check.memory = memory ?? {};
+  check.memory.threshold = validateThreshold(memory?.usage, memory?.threshold);
   check.disk = disk ?? {};
+  check.disk.threshold = validateThreshold(disk?.usage, disk?.threshold);
   check.host = host ?? {};
   check.errors = errors ?? [];
 }

Line range hint 146-153: Yo, let's beef up that error handling! 🎯

For threshold notifications, we need more detailed error context to quickly diagnose issues.

Consider enhancing the error logging:

 this.logger.error({
   message: error.message,
   service: this.SERVICE_NAME,
   method: "insertCheck",
   details: `Error inserting check for monitor: ${networkResponse?.monitorId}`,
+  context: {
+    type: networkResponse?.type,
+    thresholds: networkResponse?.payload?.data?.thresholds,
+    metrics: {
+      cpu: networkResponse?.payload?.data?.cpu?.usage,
+      memory: networkResponse?.payload?.data?.memory?.usage,
+      disk: networkResponse?.payload?.data?.disk?.usage
+    }
+  },
   stack: error.stack,
 });
Client/src/Pages/Infrastructure/Details/index.jsx (1)

Line range hint 318-320: Knees weak! Let's clean up these TODO comments! 💪

The FE team's TODOs about layout need addressing:

  1. The flexbox layout could be improved
  2. The component lacks loading states during data fetching

Consider this layout improvement:

-						flexWrap="wrap" // //FE team HELP! Better way to do this?
-						sx={{
-							"& > *": {
-								flexBasis: `calc(50% - ${theme.spacing(8)})`,
-								maxWidth: `calc(50% - ${theme.spacing(8)})`,
-							},
-						}}
+						sx={{
+							display: 'grid',
+							gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))',
+							gap: theme.spacing(8),
+						}}

Also, consider adding a loading state:

const [isLoading, setIsLoading] = useState(true);

// In fetchData:
setIsLoading(true);
try {
  // ... fetch data
} finally {
  setIsLoading(false);
}

// In render:
if (isLoading) {
  return <LoadingSpinner />;
}
Server/service/jobQueue.js (1)

171-175: Mom's spaghetti! Check for potential race conditions

The code processes infrastructure notifications and regular notifications sequentially. We should verify that the notification service handles concurrent notifications properly.

Consider implementing:

  1. Notification deduplication
  2. Rate limiting
  3. Proper locking mechanisms in the notification service

Would you like me to help create a GitHub issue to track these improvements?

Server/service/notificationService.js (1)

112-113: Correct typo in comment: 'decmremented' should be 'decremented'

Just a small typo to fix: in line 112, 'decmremented' should be 'decremented'. Making this correction will improve code readability.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a84916f and b9cfbd7.

📒 Files selected for processing (10)
  • Client/src/Pages/Infrastructure/Details/index.jsx (2 hunks)
  • Server/db/models/Notification.js (1 hunks)
  • Server/db/mongo/modules/monitorModule.js (1 hunks)
  • Server/service/emailService.js (1 hunks)
  • Server/service/jobQueue.js (1 hunks)
  • Server/service/notificationService.js (2 hunks)
  • Server/service/statusService.js (1 hunks)
  • Server/templates/hardwareIncident.mjml (1 hunks)
  • Server/templates/thresholdViolated.mjml (0 hunks)
  • Server/validation/joi.js (1 hunks)
💤 Files with no reviewable changes (1)
  • Server/templates/thresholdViolated.mjml
✅ Files skipped from review due to trivial changes (1)
  • Server/templates/hardwareIncident.mjml
🔇 Additional comments (9)
Server/db/models/Notification.js (1)

29-34: Same spaghetti, different plate! 🍝

The memory threshold has the same potential issues as the CPU threshold.

Apply similar fixes as suggested for cpuAlertThreshold.

Server/service/emailService.js (1)

68-68: Yo dawg, let's verify that hardware incident template! 🍝

The addition of hardwareIncidentTemplate looks good, but we should make sure everything's properly connected.

Let's check if the template file exists and there aren't any leftover references to the old template:

✅ Verification successful

Yo, this template's ready to drop some hot infrastructure alerts! 🍝

The verification's looking clean as mom's spaghetti:

  • New hardwareIncident.mjml template exists and is properly formatted
  • No lingering references to the old thresholdViolated template
  • Template has all the right ingredients: monitor details, alerts loop, and that fresh infrastructure styling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the new template exists
fd "hardwareIncident.mjml" Server/templates

# Look for any lingering references to the old template
rg "thresholdViolated" --type js --type ts

Length of output: 125


Script:

#!/bin/bash
# Let's check if there are any references to the old template in the email service
rg -A 5 -B 5 "thresholdViolated" Server/service/emailService.js

# Also check for any usage of the old template name in the entire codebase
rg -l "thresholdViolated"

# Let's verify the content of the new template
cat Server/templates/hardwareIncident.mjml

Length of output: 1723

Server/service/statusService.js (1)

32-33: Yo dawg, this change is fire but let's verify the consumers! 🔥

The enhanced return object with prevStatus is crucial for threshold notifications, but we should ensure all consumers are ready for this heat.

Let's check who's consuming this method:

✅ Verification successful

Yo, we're good homie! All consumers are handling the new return object like pros! 🍝

The verification shows that all consumers are already expecting and correctly using the enhanced return object:

  • Server/tests/services/statusService.test.js has test cases properly asserting the statusChanged and monitor.status properties
  • Server/service/jobQueue.js passes the response directly to handleInfrastructureNotifications, which needs the full object for threshold checks

No spaghetti code here - everything's properly structured and ready for the new heat! 🔥

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find all files that might be consuming updateStatus
rg -l "updateStatus\(" | while read -r file; do
  echo "=== $file ==="
  rg -A 5 "updateStatus\(" "$file"
done

Length of output: 1496

Client/src/Pages/Infrastructure/Details/index.jsx (1)

172-172: Yo! Let's verify this change to fetch all data by default

Changing the default date range from "day" to "all" could impact initial load performance. While this might be necessary for threshold calculations, we should consider:

  1. The amount of data being fetched
  2. Whether we need pagination or lazy loading
  3. If we can optimize by fetching only what's needed for initial render
✅ Verification successful

Yo dawg, this date range change is actually legit!

Looking at the codebase, I'm not sweating this change anymore. Here's why:

  • The API already handles pagination with optional limit param
  • Other components like PageSpeed use specific limits (50) when needed
  • Infrastructure component specifically sets limit: null and sortOrder: "asc", suggesting it needs the complete dataset for proper threshold analysis
  • The change aligns with the PR's goal of implementing infrastructure monitoring notifications and thresholds
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the typical data size for 'all' vs 'day'
rg -A 5 "getStatsByMonitorId.*dateRange: (?:\"all\"|\"day\")" --multiline

Length of output: 71


Script:

#!/bin/bash
# Let's search for the API call pattern more broadly
rg -B 2 -A 5 "getStatsByMonitorId"

# Also check for date range usage in the component
rg -B 2 -A 5 "dateRange" "Client/src/Pages/Infrastructure/Details/index.jsx"

# Look for any pagination or data loading logic
ast-grep --pattern 'useEffect($$$) {
  $$$
  getStatsByMonitorId($$$)
  $$$
}'

Length of output: 3288

Server/validation/joi.js (1)

181-181: Yo! We need to keep our dateRange validations consistent across schemas, dawg! 🍝

I noticed that while you've added "all" as a valid value here, other schemas like getMonitorByIdQueryValidation, getChecksQueryValidation, and getTeamChecksQueryValidation still only accept "day", "week", and "month". This inconsistency might cause confusion.

Let's check if this change should be applied to other schemas as well:

Consider one of these approaches:

  1. Update all dateRange validations consistently if "all" should be universally available
  2. Document why this specific endpoint needs different validation rules
  3. Create a shared constant for dateRange values to maintain consistency
Server/db/mongo/modules/monitorModule.js (2)

201-201: LGTM! Documentation update is accurate.

The JSDoc parameter type has been correctly updated to include the new 'all' option.


209-209: Yo! Let's verify performance impact of fetching all historical data.

While using new Date(0) is technically correct for fetching all data since epoch, this could potentially impact performance when dealing with large datasets. Let's verify if there are any safeguards in place.

Consider implementing the following safeguards if not already in place:

  1. Pagination for large datasets
  2. Rate limiting for API endpoints
  3. Caching for frequently accessed data
✅ Verification successful

Yo! The epoch date is safe with built-in performance controls!

After checking the codebase with sweaty palms, I can confirm that fetching all historical data is properly managed:

  • Data retrieval is split into two parallel queries using Promise.all for better performance
  • Built-in data processing includes:
    • Filtering invalid dates
    • Grouping by time periods
    • Optional data normalization
    • Configurable display limits via numToDisplay parameter
  • The getMonitorChecks function efficiently handles both all-time and date-ranged queries

The implementation has enough safeguards to prevent performance issues when fetching since epoch. No need to lose yourself in worry about this one!

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for pagination and rate limiting implementations

# Check for pagination in monitor routes and controllers
echo "Checking for pagination implementations..."
rg -A 5 "limit|skip|page|perPage" 

# Check for rate limiting middleware
echo "Checking for rate limiting..."
rg -A 5 "rateLimit|throttle"

# Check if the getMonitorChecks function is used with date ranges
echo "Checking usage of getMonitorChecks with date ranges..."
ast-grep --pattern 'getMonitorChecks($_, $_, $_, $_)'

Length of output: 118680


Script:

#!/bin/bash
# Check for pagination and rate limiting in monitor module
echo "Checking monitor module for pagination and rate limiting..."
rg -A 10 "getMonitorChecks|startDates" Server/db/mongo/modules/monitorModule.js

# Check for any query limits or date range handling
echo "Checking query handling..."
rg -A 10 "find.*sort.*limit|dateRange" Server/db/mongo/modules/monitorModule.js

# Check for any performance safeguards in the codebase
echo "Checking for performance safeguards..."
rg -A 5 "cache|index|aggregate" Server/db/mongo/modules/monitorModule.js

Length of output: 5031

Server/service/notificationService.js (2)

27-35: Ensure 'hardwareIncidentTemplate' can handle 'alerts' in the context

Great job adding the hardware-specific email notification! Please verify that the hardwareIncidentTemplate email template exists and is set up to handle the alerts array in the context. This will ensure that alert messages are properly displayed in the emails sent to users.


114-119: Verify reset logic for alert thresholds

When an alert threshold for a specific type drops below zero, the code resets it using notification.alertThreshold. Please ensure that notification.alertThreshold holds the correct value for resetting all type-specific thresholds. If each alert type should have its own initial threshold, consider storing and resetting to those individual values to maintain accuracy.

Comment on lines +23 to +28
cpuAlertThreshold: {
type: Number,
default: function () {
return this.alertThreshold;
},
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mom's spaghetti moment! Watch out for that default function! 🍝

The default function callback could lead to unexpected behavior if alertThreshold changes after document creation. Also missing CPU-specific validation rules.

Consider:

  1. Using a pre-save hook instead of default function
  2. Adding CPU-specific validation
 cpuAlertThreshold: {
   type: Number,
-  default: function () {
-    return this.alertThreshold;
-  },
+  min: [0, "CPU alert threshold cannot be negative"],
+  max: [100, "CPU alert threshold cannot exceed 100%"],
+  validate: {
+    validator: function(v) {
+      return v <= this.alertThreshold;
+    },
+    message: "CPU threshold cannot exceed base alert threshold"
+  }
 },
📝 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
cpuAlertThreshold: {
type: Number,
default: function () {
return this.alertThreshold;
},
},
cpuAlertThreshold: {
type: Number,
min: [0, "CPU alert threshold cannot be negative"],
max: [100, "CPU alert threshold cannot exceed 100%"],
validate: {
validator: function(v) {
return v <= this.alertThreshold;
},
message: "CPU threshold cannot exceed base alert threshold"
}
},

Comment on lines +19 to +22
alertThreshold: {
type: Number,
default: 5,
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Yo! The alertThreshold needs some guardrails, dawg! 🚧

The base threshold field could use some validation constraints and documentation. Consider adding:

  • Minimum value to prevent negative thresholds
  • Maximum value to prevent unreasonable alerts
  • Field description explaining what this threshold represents
 alertThreshold: {
   type: Number,
   default: 5,
+  min: [0, "Alert threshold cannot be negative"],
+  max: [100, "Alert threshold cannot exceed 100"],
+  description: "Number of times a metric can exceed its threshold before sending an alert"
 },
📝 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
alertThreshold: {
type: Number,
default: 5,
},
alertThreshold: {
type: Number,
default: 5,
min: [0, "Alert threshold cannot be negative"],
max: [100, "Alert threshold cannot exceed 100"],
description: "Number of times a metric can exceed its threshold before sending an alert"
},

Comment on lines +192 to +196
sortOrder: "asc",
limit: null,
dateRange: dateRange,
numToDisplay: 50,
normalize: true,
numToDisplay: null,
normalize: false,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mom's spaghetti! These parameter changes need attention! 🍝

The changes to getStatsByMonitorId parameters have significant implications:

  1. Removing display limits (numToDisplay: null) could affect rendering performance with large datasets
  2. Setting normalize: false suggests we need raw values for threshold calculations
  3. Explicit sortOrder: "asc" might be crucial for chronological threshold evaluation

Consider implementing:

-					sortOrder: "asc",
-					limit: null,
-					dateRange: dateRange,
-					numToDisplay: null,
-					normalize: false,
+					sortOrder: "asc",
+					limit: calculateOptimalLimit(dateRange),
+					dateRange: dateRange,
+					numToDisplay: INITIAL_DISPLAY_COUNT,
+					normalize: false,

Add these constants at the top:

const INITIAL_DISPLAY_COUNT = 50;
const calculateOptimalLimit = (range) => {
  return range === 'all' ? 1000 : 100; // Adjust based on actual needs
};

Comment on lines 169 to 177
// If this is an infrastructure monitor, we need to check for thresholds
if (monitor.type === "hardware") {
await this.notificationService.handleInfrastructureNotifications({
...networkResponse,
monitor,
prevStatus,
});
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Yo! Consider the order of operations in notification handling

The infrastructure notifications are processed before checking if the status has changed. This means we'll process infrastructure notifications even when the status hasn't changed, which might be unnecessary load.

Consider this alternative flow:

- // If this is an infrastructure monitor, we need to check for thresholds
- if (monitor.type === "hardware") {
-   await this.notificationService.handleInfrastructureNotifications({
-     ...networkResponse,
-     monitor,
-     prevStatus,
-   });
- }

//If status hasn't changed, we're done
if (statusChanged === false) return;

// if prevStatus is undefined, monitor is resuming, we're done
if (prevStatus === undefined) return;

+ // If this is an infrastructure monitor, we need to check for thresholds
+ if (monitor.type === "hardware") {
+   await this.notificationService.handleInfrastructureNotifications({
+     ...networkResponse,
+     monitor,
+     prevStatus,
+   });
+ }

this.notificationService.handleNotifications({

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

Comment on lines 171 to 175
await this.notificationService.handleInfrastructureNotifications({
...networkResponse,
monitor,
prevStatus,
});
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Yo! This code needs some error handling, for real!

The infrastructure notification handler is raw-dogging it without any try-catch blocks! Both in the implementation and at the call site, there's no error handling to catch those exceptions when they drop. Here's what's missing:

  • No try-catch in jobQueue.js around the handleInfrastructureNotifications call
  • No error handling inside the handleInfrastructureNotifications implementation
  • No error reporting mechanism if something goes wrong with the notification process

You should wrap that notification call in a try-catch block and handle any potential errors that might come up. Don't let those exceptions slip through like mom's spaghetti! 🍝

🔗 Analysis chain

Yo! Verify error handling in the notification service

The infrastructure notification handler is called with await but we need to ensure the notification service properly handles and reports errors.

Let's check the error handling implementation:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for error handling in handleInfrastructureNotifications
rg -A 5 "handleInfrastructureNotifications.*{" 

# Look for try-catch blocks around the method call
rg -B 5 -A 5 "catch.*{.*handleInfrastructureNotifications"

Length of output: 379


Script:

#!/bin/bash
# Search for handleInfrastructureNotifications implementation
ast-grep --pattern 'async handleInfrastructureNotifications($_) {
  $$$
}'

# Look for try-catch around the call site
ast-grep --pattern 'try {
  $$$
  handleInfrastructureNotifications($$$)
  $$$
} catch($_) {
  $$$
}'

# Search for error handling patterns in notification service
rg "class.*NotificationService" -A 5

Length of output: 695


Script:

#!/bin/bash
# Let's check the implementation of handleInfrastructureNotifications
rg -A 20 "handleInfrastructureNotifications.*\(" Server/service/notificationService.js

# Check for try-catch in jobQueue.js around the notification call
rg -B 10 -A 10 "notificationService\.handleInfrastructureNotifications" Server/service/jobQueue.js

Length of output: 1468

Copy link

@llamapreview llamapreview bot left a comment

Choose a reason for hiding this comment

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

Auto Pull Request Review from LlamaPReview

1. Overview

1.1 PR Summary

  • Business value and requirements alignment: This PR introduces infrastructure monitor notifications with two types of thresholds: notification thresholds and metric thresholds. This enhances the monitoring capabilities of BlueWave Uptime by providing alerts based on infrastructure metrics.
  • Key components modified: The changes impact both client and server components, including the InfrastructureDetails component, Notification model, JobQueue service, NotificationService, and email templates.
  • Impact assessment: The changes affect how notifications are processed and displayed, improving the system's capability to monitor infrastructure metrics.
  • System dependencies and integration impacts: The modifications introduce new interactions between the JobQueue, NotificationService, and StatusService, impacting the notification system's design.

1.2 Architecture Changes

  • System design modifications: Adds new notification types and thresholds, impacting the notification system's design.
  • Component interactions: Introduces new interactions between the JobQueue, NotificationService, and StatusService.
  • Integration points: Changes in the Notification model and JobQueue service affect how notifications are processed and sent.
  • Dependency changes and implications: No new dependencies introduced, but existing components are modified to include new functionality.

2. Detailed Technical Analysis

2.1 Code Logic Deep-Dive

Core Logic Changes

Client/src/Pages/Infrastructure/Details/index.jsx

  • File Path: Client/src/Pages/Infrastructure/Details/index.jsx
  • Function/Class Name: fetchData
  • Submitted PR Code:
    const [dateRange, setDateRange] = useState("all");
    const fetchData = async () => {
      try {
        const response = await networkService.getStatsByMonitorId({
          authToken: authToken,
          monitorId: monitorId,
          sortOrder: "asc",
          limit: null,
          dateRange: dateRange,
          numToDisplay: null,
          normalize: false,
        });
        setMonitor(response.data.data);
      } catch (error) {
        console.error(error);
      }
    };
  • Analysis:
    • Current logic and potential issues: Changing dateRange to "all" and sortOrder to "asc" might impact performance if the dataset is large.
    • Edge cases and error handling: No error handling for empty or incomplete response data.
    • **Cross-component impact **: Affects how data is fetched and displayed on the client side.
    • **Business logic considerations **: Aligns with the business need to view all data, but performance implications need consideration.
  • LlamaPReview Suggested Improvements:
    const [dateRange, setDateRange] = useState("all");
    const fetchData = async () => {
      try {
        const response = await networkService.getStatsByMonitorId({
          authToken: authToken,
          monitorId: monitorId,
          sortOrder: "asc",
          limit: null,
          dateRange: dateRange,
          numToDisplay: null,
          normalize: false,
        });
        if (response.data.data) {
          setMonitor(response.data.data);
        } else {
          console.error("No data received from server");
        }
      } catch (error) {
        console.error(error);
      }
    };
  • **Improvement rationale **:
    • Technical benefits: Adds additional error handling for empty responses.
    • Business value: Ensures that the client handles incomplete or empty data gracefully.
    • Risk assessment: Low risk, as it adds error handling without changing business logic.

Server/service/notificationService.js

  • File Path: Server/service/notificationService.js
  • Function/Class Name: handleInfrastructureNotifications
  • Submitted PR Code:
    async handleInfrastructureNotifications(networkResponse) {
      const thresholds = networkResponse?.monitor?.thresholds;
      if (thresholds === undefined) return; // No thresholds set, we're done
    
      // Get thresholds from monitor
      const {
        usage_cpu: cpuThreshold = -1,
        usage_memory: memoryThreshold = -1,
        usage_disk: diskThreshold = -1,
      } = thresholds;
    
      // Get metrics from response
      const metrics = networkResponse?.payload?.data ?? {};
      const {
        cpu: { usage_percent: cpuUsage = -1 } = {},
        memory: { usage_percent: memoryUsage = -1 } = {},
        disk = [],
      } = metrics;
    
      const alerts = {
        cpu:
          cpuThreshold !== -1 && cpuUsage > cpuThreshold / 100
            ? "CPU usage is above threshold"
            : null,
        memory:
          memoryThreshold !== -1 && memoryUsage > memoryThreshold / 100
            ? "Memory usage is above threshold"
            : null,
        disk: disk.some(
          (d) => diskThreshold !== -1 && d.usage_percent > diskThreshold / 100
        )
          ? "Disk usage is above threshold"
          : null,
      };
    
      const notifications = await this.db.getNotificationsByMonitorId(
        networkResponse.monitorId
      );
      for (const notification of notifications) {
        const alertsToSend = [];
        const alertTypes = ["cpu", "memory", "disk"];
    
        for (const type of alertTypes) {
          // Iterate over each alert type to see if any need to be decremented
          if (alerts[type] !== null) {
            notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered
    
            if (notification[`${type}AlertThreshold`] <= 0) {
              // If threshold drops below 0, reset and send notification
              notification[`${type}AlertThreshold`] = notification.alertThreshold;
    
              const formatAlert = {
                cpu: () =>
                  `${alerts.cpu} ${(cpuUsage * 100).toFixed(0)}% (${cpuThreshold}%)`,
                memory: () =>
                  `${alerts.memory} ${(memoryUsage * 100).toFixed(0)}% (${memoryThreshold}%)`,
                disk: () =>
                  `${alerts.disk} ${disk
                    .map((d, idx) => `Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%`)
                    .join(", ")} (${diskThreshold}%)`,
              };
    
              alertsToSend.push(formatAlert[type]());
            }
          }
        }
    
        await notification.save();
    
        if (alertsToSend.length === 0) continue; // No alerts to send, we're done
    
        if (notification.type === "email") {
          this.sendEmail(networkResponse, notification.address, alertsToSend);
        }
      }
    }
  • Analysis:
    • Current logic and potential issues: Logic for handling thresholds and sending notifications is complex and might be difficult to maintain.
    • Edge cases and error handling: No error handling for failures in this.db.getNotificationsByMonitorId or notification.save().
    • **Cross-component impact **: Impacts how notifications are processed and sent, affecting the NotificationService and EmailService.
    • **Business logic considerations **: Aligns with the business need to send notifications based on thresholds but could be optimized for readability and maintainability.
  • LlamaPReview Suggested Improvements:
    async handleInfrastructureNotifications(networkResponse) {
      const thresholds = networkResponse?.monitor?.thresholds;
      if (thresholds === undefined) return; // No thresholds set, we're done
    
      // Get thresholds from monitor
      const {
        usage_cpu: cpuThreshold = -1,
        usage_memory: memoryThreshold = -1,
        usage_disk: diskThreshold = -1,
      } = thresholds;
    
      // Get metrics from response
      const metrics = networkResponse?.payload?.data ?? {};
      const {
        cpu: { usage_percent: cpuUsage = -1 } = {},
        memory: { usage_percent: memoryUsage = -1 } = {},
        disk = [],
      } = metrics;
    
      const alerts = {
        cpu:
          cpuThreshold !== -1 && cpuUsage > cpuThreshold / 100
            ? "CPU usage is above threshold"
            : null,
        memory:
          memoryThreshold !== -1 && memoryUsage > memoryThreshold / 100
            ? "Memory usage is above threshold"
            : null,
        disk: disk.some(
          (d) => diskThreshold !== -1 && d.usage_percent > diskThreshold / 100
        )
          ? "Disk usage is above threshold"
          : null,
      };
    
      try {
        const notifications = await this.db.getNotificationsByMonitorId(
          networkResponse.monitorId
        );
      } catch (error) {
        console.error("Failed to get notifications: ", error);
        return;
      }
    
      for (const notification of notifications) {
        const alertsToSend = [];
        const alertTypes = ["cpu", "memory", "disk"];
    
        for (const type of alertTypes) {
          // Iterate over each alert type to see if any need to be decremented
          if (alerts[type] !== null) {
            notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered
    
            if (notification[`${type}AlertThreshold`] <= 0) {
              // If threshold drops below 0, reset and send notification
              notification[`${type}AlertThreshold`] = notification.alertThreshold;
    
              const formatAlert = {
                cpu: () =>
                  `${alerts.cpu} ${(cpuUsage * 100).toFixed(0)}% (${cpuThreshold}%)`,
                memory: () =>
                  `${alerts.memory} ${(memoryUsage * 100).toFixed(0)}% (${memoryThreshold}%)`,
                disk: () =>
                  `${alerts.disk} ${disk
                    .map((d, idx) => `Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%`)
                    .join(", ")} (${diskThreshold}%)`,
              };
    
              alertsToSend.push(formatAlert[type]());
            }
          }
        }
    
        try {
          await notification.save();
        } catch (error) {
          console.error("Failed to save notification: ", error);
          continue;
        }
    
        if (alertsToSend.length === 0) continue; // No alerts to send, we're done
    
        if (notification.type === "email") {
          this.sendEmail(networkResponse, notification.address, alertsToSend);
        }
      }
    }
  • **Improvement rationale **:
    • Technical benefits: Adds error handling for database operations.
    • Business value: Ensures that the system handles database failures gracefully.
    • Risk assessment: Low risk, as it adds error handling without changing business logic.

2.2 Implementation Quality

  • Code organization and structure: The changes are well-organized and modular, with clear separation of concerns.
  • Design patterns usage: Follows established patterns for notification handling and threshold management.
  • Error handling approach: Basic error handling is present but could be improved with more specific error messages and logging.
  • Resource management: No explicit resource management issues identified.

3. Critical Findings

3.1 Potential Issues

Critical Issues

  • Issue: No error handling for empty or incomplete response data in fetchData.
    • Impact: Could lead to undefined behavior or crashes if the response data is incomplete.
    • Business Consequences: Users might not see the expected data on the client side.
    • User Experience Effects: Users might experience a broken or incomplete UI.
    • Recommendation: Add error handling for empty or incomplete response data.

Warnings

  • Issue: No error handling for database operations in handleInfrastructureNotifications.
    • Potential risks: Database failures could lead to missed notifications.
    • Maintenance overhead: Difficult to debug and maintain without proper error handling.
    • Future scalability: Could become a bottleneck as the system scales.
    • Suggested improvements: Add try-catch blocks for database operations.

3.2 Code Quality Concerns

  • Maintainability aspects: The code is readable and maintainable but could benefit from additional comments and documentation.
  • Readability issues: The notification logic is complex and might be difficult to understand without proper documentation.
  • Performance bottlenecks: Fetching all data without pagination might lead to performance issues.

4. Security Assessment

4.1 Security Considerations

  • Authentication/Authorization impacts: No explicit changes affecting authentication or authorization.
  • Data handling concerns: Ensure proper validation of input data, especially for thresholds and metrics.
  • Input validation: Review input validation to ensure data integrity.
  • Security best practices: Follow best practices for data handling and validation.

4.2 Vulnerability Analysis

  • Potential security risks: No explicit security risks identified, but input validation should be reviewed.
  • Mitigation strategies: Implement robust input validation and data handling practices.
  • Security testing requirements: Conduct security testing to identify and mitigate potential vulnerabilities.

5. Testing Strategy

5.1 Test Coverage

  • Unit test analysis: Test the new notification logic, including edge cases and error handling.
  • Integration test requirements: Test the integration between the client and server components, including the new notification types.
  • Edge case validation: Validate edge cases, such as empty or incomplete response data and database failures.

5.2 Test Recommendations

Suggested Test Cases

// Unit test for fetchData
test('fetchData handles empty response data', async () => {
  const mockNetworkService = {
    getStatsByMonitorId: jest.fn().mockResolvedValue({ data: {} })
  };
  const fetchData = async () => {
    try {
      const response = await mockNetworkService.getStatsByMonitorId({});
      if (response.data.data) {
        setMonitor(response.data.data);
      } else {
        console.error("No data received from server");
      }
    } catch (error) {
      console.error(error);
    }
  };
  await fetchData();
  expect(console.error).toHaveBeenCalledWith("No data received from server");
});

// Unit test for handleInfrastructureNotifications
test('handleInfrastructureNotifications handles database errors', async () => {
  const mockDb = {
    getNotificationsByMonitorId: jest.fn().mockRejectedValue(new Error('Database error'))
  };
  const handleInfrastructureNotifications = async (networkResponse) => {
    try {
      const notifications = await mockDb.getNotificationsByMonitorId(networkResponse.monitorId);
    } catch (error) {
      console.error("Failed to get notifications: ", error);
      return;
    }
  };
  await handleInfrastructureNotifications({ monitorId: '123' });
  expect(console.error).toHaveBeenCalledWith("Failed to get notifications: ", new Error('Database error'));
});
  • Coverage improvements: Ensure that all edge cases and error scenarios are covered in the tests.
  • Performance testing needs: Conduct performance testing to ensure the system scales well with the current changes.

6. Documentation & Maintenance

  • Documentation updates needed: Update the documentation to reflect the new notification types and thresholds.
  • Long-term maintenance considerations: Ensure that the notification logic is well-documented and maintainable.
  • Technical debt and monitoring requirements: Monitor the performance of the system and address any technical debt identified during the PR review.

7. Deployment & Operations

  • Deployment impact and strategy: The changes impact the notification system and should be thoroughly tested before deployment.
  • Key operational considerations: Monitor the system for performance issues and address any identified bottlenecks.

8. Summary & Recommendations

8.1 Key Action Items

  1. Critical changes required:

    • Add error handling for empty or incomplete response data in fetchData.
  2. Important improvements suggested:

    • Add error handling for database operations in handleInfrastructureNotifications.
  3. Best practices to implement:

    • Follow best practices for data handling and validation.
  4. Cross-cutting concerns to address:

    • Ensure that the notification logic is well-documented and maintainable.

8.2 Future Considerations

  • Technical evolution path: Continuously improve the notification system based on user feedback and performance data.
  • Business capability evolution: Enhance the system's capabilities to monitor additional infrastructure metrics and improve alerting mechanisms.
  • System integration impacts: Ensure that the notification system is well-integrated with other components of the BlueWave Uptime tool.

💡 LlamaPReview Community
Have feedback on this AI Code review tool? Join our GitHub Discussions to share your thoughts and help shape the future of LlamaPReview.

Copy link

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

🧹 Outside diff range and nitpick comments (1)
Server/service/notificationService.js (1)

112-123: These percentage calculations making my knees weak!

The percentage calculations might have floating-point precision issues. Consider using a helper function for consistent formatting.

+		const formatPercentage = (value) => (value * 100).toFixed(0);
+
 		const formatAlert = {
 			cpu: () =>
-				`Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
+				`Your current CPU usage (${formatPercentage(cpuUsage)}%) is above your threshold (${formatPercentage(cpuThreshold)}%)`,
 			memory: () =>
-				`Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`,
+				`Your current memory usage (${formatPercentage(memoryUsage)}%) is above your threshold (${formatPercentage(memoryThreshold)}%)`,
 			disk: () =>
 				`Your current disk usage: ${disk
-					.map((d, idx) => `(Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%)`)
+					.map((d, idx) => `(Disk${idx}: ${formatPercentage(d.usage_percent)}%)`)
 					.join(", "
-				)} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
+				)} is above your threshold (${formatPercentage(diskThreshold)}%)`,
 		};
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b9cfbd7 and 8076335.

📒 Files selected for processing (2)
  • Client/src/Pages/Infrastructure/CreateMonitor/index.jsx (2 hunks)
  • Server/service/notificationService.js (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
Server/service/notificationService.js

[error] 89-89: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 90-90: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 91-93: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🔇 Additional comments (2)
Server/service/notificationService.js (2)

27-35: Yo, the email handling looks solid, but let's verify that template!

The changes to handle hardware monitor notifications are well-structured. The default empty alerts array maintains backward compatibility.

Let's make sure that template exists:

✅ Verification successful

Yo dawg, the template's legit! Everything checks out perfectly! 🍝

The hardwareIncidentTemplate exists and properly handles all the context variables we're passing:

  • Template location: Server/templates/hardwareIncident.mjml
  • Correctly uses {{monitor}}, {{url}}, and iterates through {{#each alerts}}
  • Solid infrastructure alert styling with that red heading
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the hardware incident template exists
fd -t f -e mjml hardwareIncident

Length of output: 73


Script:

#!/bin/bash
# Let's check the content of the template to ensure it handles the alerts array
cat Server/templates/hardwareIncident.mjml

Length of output: 1633


73-78: Yo dawg, we need to handle those percentages with care!

The threshold comparisons might need normalization. Consider whether the thresholds are stored as decimals (0-1) or percentages (0-100).

Let's check the schema:

✅ Verification successful

The previous pattern didn't yield results, so I'm broadening the search to find any threshold-related definitions or usage patterns. This will help us understand how thresholds are stored and compared throughout the codebase.


Yo, the threshold comparison is actually on point! 🍝

Looking at the alert formatting code, the system is already handling the decimal-to-percentage conversion correctly:

`${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`

The thresholds are stored as decimals (0-1) and properly converted to percentages (0-100) when displayed. The direct comparison using > operator is perfectly fine since both values are in the same decimal format.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the schema definition for threshold formats
ast-grep --pattern 'usage_cpu: {
  $$$
}'

Length of output: 43


Script:

#!/bin/bash
# Let's search for schema or type definitions related to thresholds
rg -g '!*.{log,lock}' -i '(schema|type|interface).*threshold' -A 5

# Also search for any threshold-related configurations or constants
rg -g '!*.{log,lock}' '(cpu|memory|disk).*threshold' -B 2 -A 2

# Check model definitions that might contain threshold info
fd -e js -e ts -e json "model|schema" --exec grep -l "threshold" {} \; | xargs cat

Length of output: 2215

}
}

await notification.save();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Catch that save like mom's spaghetti!

The notification.save() call should be wrapped in try-catch to handle potential database errors.

-			await notification.save();
+			try {
+				await notification.save();
+			} catch (error) {
+				this.logger.error({
+					message: 'Failed to save notification',
+					service: this.SERVICE_NAME,
+					method: 'handleInfrastructureNotifications',
+					error
+				});
+			}
📝 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
await notification.save();
try {
await notification.save();
} catch (error) {
this.logger.error({
message: 'Failed to save notification',
service: this.SERVICE_NAME,
method: 'handleInfrastructureNotifications',
error
});
}

Comment on lines +88 to +94
const alerts = {
cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false,
memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false,
disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold)
? true
: false,
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Mom's spaghetti says simplify these boolean expressions!

The boolean expressions can be simplified for better readability:

-			cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false,
-			memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false,
-			disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold)
-				? true
-				: false,
+			cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold,
+			memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold,
+			disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold),
📝 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 alerts = {
cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false,
memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false,
disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold)
? true
: false,
};
const alerts = {
cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold,
memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold,
disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold),
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 89-89: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 90-90: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 91-93: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

Comment on lines +135 to 136
if (form[k]) thresholds[k] = form[k] / 100;
delete form[k];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Yo dawg, we need to validate these threshold values before they drop!

The threshold conversion from percentage to decimal needs input validation to ensure we're not passing bogus values to the backend. Consider adding validation to ensure the input is between 0-100 before the division.

Here's a suggested fix:

-				if (form[k]) thresholds[k] = form[k] / 100;
+				if (form[k]) {
+					const value = parseFloat(form[k]);
+					if (isNaN(value) || value < 0 || value > 100) {
+						throw new Error(`Invalid threshold value for ${k}: must be between 0-100`);
+					}
+					thresholds[k] = value / 100;
+				}
📝 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 (form[k]) thresholds[k] = form[k] / 100;
delete form[k];
if (form[k]) {
const value = parseFloat(form[k]);
if (isNaN(value) || value < 0 || value > 100) {
throw new Error(`Invalid threshold value for ${k}: must be between 0-100`);
}
thresholds[k] = value / 100;
}
delete form[k];

@@ -158,6 +158,7 @@ const CreateInfrastructureMonitor = () => {
: infrastructureMonitor.name,
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
};

Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Yo, we've got a legit validation gap here!

Looking at the validation schema in validation.js, there's no validation rule for notifications in the infrastructureMonitorValidation object. This confirms our concern about dropping notifications before validation.

  • The schema validates CPU, memory, disk thresholds, but completely misses notifications
  • Other parts of the codebase (like the Redux slices) show that notifications are a core part of the monitor structure
  • The current validation approach could let invalid notification configs slip through

Consider this safer approach:

const infrastructureMonitorValidation = joi.object({
  // ... existing validation rules ...
  notifications: joi.array().min(1).required().messages({
    'array.min': 'At least one notification method is required',
    'any.required': 'Notifications configuration is required'
  })
});
🔗 Analysis chain

Hold up! We're dropping notifications before validation!

Based on the PR objectives, notifications and thresholds are crucial features. Removing the notifications property before validation means we're not validating the notification configuration. This could lead to invalid notification settings slipping through.

Let's verify the validation schema includes notification checks:

Consider this alternative approach:

-		delete form.notifications;
+		const { notifications, ...formWithoutNotifications } = form;
+		if (hasValidationErrors(formWithoutNotifications, infrastructureMonitorValidation, setErrors)) {
+			return;
+		}
+		if (!notifications || notifications.length === 0) {
+			setErrors(prev => ({ ...prev, notifications: "At least one notification method is required" }));
+			return;
+		}
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the validation schema includes notification fields
rg -A 10 'notifications?' src/Validation/validation.js

Length of output: 125


Script:

#!/bin/bash
# Let's first find the validation schema file
fd -e js -e jsx -e ts -e tsx validation

# Then let's search for any notification-related validation patterns in all files
rg -i "notification.*validation|validation.*notification" 

# Let's also check the component itself for validation logic
ast-grep --pattern 'const $_ = $$$validation$$$'

Length of output: 340


Script:

#!/bin/bash
# Let's check the validation file content
cat Client/src/Validation/validation.js

# Let's also check the CreateMonitor component for any validation schema
ast-grep --pattern 'const $_ = {
  $$$
  notifications: $$$,
  $$$
}'

# And check for any validation functions being used
rg -A 5 "hasValidationErrors|validateForm" Client/src/Pages/Infrastructure/CreateMonitor/index.jsx

Length of output: 10167

Copy link

@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 (3)
Server/service/notificationService.js (2)

112-118: Yo! These boolean expressions are heavier than mom's spaghetti!

Let's simplify these boolean expressions for better readability.

 const alerts = {
-  cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false,
-  memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false,
-  disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold)
-    ? true
-    : false,
+  cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold,
+  memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold,
+  disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold)
 };
🧰 Tools
🪛 Biome (1.9.4)

[error] 113-113: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 114-114: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 115-117: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


Line range hint 1-1: Yo! This service is getting heavier than mom's spaghetti!

Consider splitting this into separate services:

  • StatusNotificationService
  • HardwareNotificationService
  • EmailTemplateService

This would improve maintainability and follow the Single Responsibility Principle.

Would you like me to provide an example of how to refactor this into separate services?

🧰 Tools
🪛 Biome (1.9.4)

[error] 113-113: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 114-114: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 115-117: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

Server/tests/services/notificationService.test.js (1)

232-239: Remove redundant 'describe' block for cleaner test structure

Stay on beat—the extra describe block is unnecessary and adds clutter to your tests. Simplify by removing it.

Apply this diff to streamline your tests:

-        describe("it should return false if no thresholds are set", () => {
             it("should return false if no thresholds are set", async () => {
                 networkResponse.monitor.thresholds = undefined;
                 const res = await notificationService.handleHardwareNotifications(networkResponse);
                 expect(res).to.be.false;
             });
         
-        });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8076335 and b97e149.

📒 Files selected for processing (4)
  • Server/service/jobQueue.js (1 hunks)
  • Server/service/notificationService.js (1 hunks)
  • Server/service/statusService.js (2 hunks)
  • Server/tests/services/notificationService.test.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Server/service/jobQueue.js
  • Server/service/statusService.js
🧰 Additional context used
🪛 Biome (1.9.4)
Server/service/notificationService.js

[error] 113-113: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 114-114: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 115-117: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

Comment on lines 67 to 69
if (notification.type === "email") {
this.sendEmail(networkResponse, notification.address);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Yo! Don't let that async email slip through your fingers like mom's spaghetti!

The sendEmail call is missing an await, which could lead to unhandled promises.

 if (notification.type === "email") {
-  this.sendEmail(networkResponse, notification.address);
+  await this.sendEmail(networkResponse, notification.address);
 }
📝 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 (notification.type === "email") {
this.sendEmail(networkResponse, notification.address);
}
if (notification.type === "email") {
await this.sendEmail(networkResponse, notification.address);
}

Comment on lines +157 to +159
if (notification.type === "email") {
this.sendHardwareEmail(networkResponse, notification.address, alertsToSend);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Yo! Don't let that async email slip away like mom's spaghetti!

The sendHardwareEmail call is missing an await, which could lead to unhandled promises.

 if (notification.type === "email") {
-  this.sendHardwareEmail(networkResponse, notification.address, alertsToSend);
+  await this.sendHardwareEmail(networkResponse, notification.address, alertsToSend);
 }
📝 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 (notification.type === "email") {
this.sendHardwareEmail(networkResponse, notification.address, alertsToSend);
}
if (notification.type === "email") {
await this.sendHardwareEmail(networkResponse, notification.address, alertsToSend);
}

Comment on lines +174 to +177
if (networkResponse.monitor.type === "hardware") {
this.handleHardwareNotifications(networkResponse);
}
this.handleStatusNotifications(networkResponse);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Yo! These promises are slipping through like mom's spaghetti!

Both handler calls are missing await and we're not checking their results.

-  if (networkResponse.monitor.type === "hardware") {
-    this.handleHardwareNotifications(networkResponse);
+  let result = false;
+  if (networkResponse.monitor.type === "hardware") {
+    result = await this.handleHardwareNotifications(networkResponse);
   }
-  this.handleStatusNotifications(networkResponse);
+  const statusResult = await this.handleStatusNotifications(networkResponse);
+  return result || statusResult;
-  return true;
📝 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 (networkResponse.monitor.type === "hardware") {
this.handleHardwareNotifications(networkResponse);
}
this.handleStatusNotifications(networkResponse);
let result = false;
if (networkResponse.monitor.type === "hardware") {
result = await this.handleHardwareNotifications(networkResponse);
}
const statusResult = await this.handleStatusNotifications(networkResponse);
return result || statusResult;

Comment on lines +17 to +35
* Sends an email notification for hardware infrastructure alerts
*
* @param {Object} networkResponse - The response from the network monitor.
* @param {Object} networkResponse.monitor - The monitor object containing details about the monitored service.
* @param {string} networkResponse.monitor.name - The name of the monitor.
* @param {string} networkResponse.monitor.url - The URL of the monitor.
* @param {boolean} networkResponse.status - The current status of the monitor (true for up, false for down).
* @param {boolean} networkResponse.prevStatus - The previous status of the monitor (true for up, false for down).
* @param {string} address - The email address to send the notification to.
* @async
* @function sendHardwareEmail
* @param {Object} networkResponse - Response object containing monitor information
* @param {string} address - Email address to send the notification to
* @param {Array} [alerts=[]] - List of hardware alerts to include in the email
* @returns {Promise<boolean>} - Indicates whether email was sent successfully
* @throws {Error}
*/
async sendHardwareEmail(networkResponse, address, alerts = []) {
if (alerts.length === 0) return false;
const { monitor, status, prevStatus } = networkResponse;
const template = "hardwareIncidentTemplate";
const context = { monitor: monitor.name, url: monitor.url, alerts };
const subject = `Monitor ${monitor.name} infrastructure alerts`;
this.emailService.buildAndSendEmail(template, context, address, subject);
return true;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Yo! This email sender's looking clean, but let's catch those errors like mom's spaghetti!

The method looks solid, but we should add error handling for the email sending operation.

 async sendHardwareEmail(networkResponse, address, alerts = []) {
   if (alerts.length === 0) return false;
   const { monitor, status, prevStatus } = networkResponse;
   const template = "hardwareIncidentTemplate";
   const context = { monitor: monitor.name, url: monitor.url, alerts };
   const subject = `Monitor ${monitor.name} infrastructure alerts`;
-  this.emailService.buildAndSendEmail(template, context, address, subject);
+  try {
+    await this.emailService.buildAndSendEmail(template, context, address, subject);
+  } catch (error) {
+    this.logger.error({
+      message: 'Failed to send hardware alert email',
+      service: this.SERVICE_NAME,
+      method: 'sendHardwareEmail',
+      error
+    });
+    return false;
+  }
   return true;
 }

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

Comment on lines +90 to +97
notificationService.sendEmail = sinon.stub();
const res = await notificationService.handleNotifications({
monitor: {
type: "hardware",
address: "www.google.com",
},
});
expect(res).to.be.true;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Stub 'sendHardwareEmail' instead of 'sendEmail' for hardware notifications

Don't let the beat drop—ensure you're stubbing sendHardwareEmail rather than sendEmail when testing hardware notifications to keep your tests in rhythm.

Apply this diff to stub sendHardwareEmail:

 it("should handle hardware notifications", async () => {
-    notificationService.sendEmail = sinon.stub();
+    notificationService.sendHardwareEmail = sinon.stub();
     const res = await notificationService.handleNotifications({
         monitor: {
             type: "hardware",
             address: "www.google.com",
         },
     });
     expect(res).to.be.true;
 });
📝 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
notificationService.sendEmail = sinon.stub();
const res = await notificationService.handleNotifications({
monitor: {
type: "hardware",
address: "www.google.com",
},
});
expect(res).to.be.true;
notificationService.sendHardwareEmail = sinon.stub();
const res = await notificationService.handleNotifications({
monitor: {
type: "hardware",
address: "www.google.com",
},
});
expect(res).to.be.true;

Comment on lines +81 to +85
const res = await notificationService.handleNotifications({
monitor: {
type: "email",
address: "www.google.com",
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the 'address' field to match the notification type

Before you lose yourself in the code, ensure the 'address' field matches the notification type. For an 'email' notification, the 'address' should be an email address, not a URL.

Apply this diff to fix the 'address' field:

 const res = await notificationService.handleNotifications({
     monitor: {
         type: "email",
-        address: "www.google.com",
+        address: "[email protected]",
     },
 });
📝 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 res = await notificationService.handleNotifications({
monitor: {
type: "email",
address: "www.google.com",
},
const res = await notificationService.handleNotifications({
monitor: {
type: "email",
address: "test@test.com",
},

Comment on lines +182 to +189
const testError = new Error("Test Error");
db.getNotificationsByMonitorId.rejects(testError);
try {
await notificationService.handleStatusNotifications(networkResponse);
} catch (error) {
expect(error).to.be.an.instanceOf(Error);
expect(error.message).to.equal("Test Error");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle errors internally without throwing in 'handleStatusNotifications'

Keep the flow steady—handleStatusNotifications should catch errors internally and not throw them. Adjust the test to reflect this error-handling behaviour.

Apply this diff to correct the test:

 it("should handle an error", async () => {
     const testError = new Error("Test Error");
     db.getNotificationsByMonitorId.rejects(testError);
-    try {
-        await notificationService.handleStatusNotifications(networkResponse);
-    } catch (error) {
-        expect(error).to.be.an.instanceOf(Error);
-        expect(error.message).to.equal("Test Error");
-    }
+    const res = await notificationService.handleStatusNotifications(networkResponse);
+    expect(notificationService.logger.warn.calledOnce).to.be.true;
+    expect(res).to.be.false;
 });

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

@ajhollid ajhollid merged commit dd82201 into develop Nov 25, 2024
3 checks passed
@ajhollid ajhollid deleted the feat/be/threshold-notifications branch November 25, 2024 02:31
@coderabbitai coderabbitai bot mentioned this pull request Nov 25, 2024
6 tasks
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