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

Feature/bull mq integration 2 #2983

Open
wants to merge 23 commits into
base: dev
Choose a base branch
from
Open

Conversation

alonp99
Copy link
Collaborator

@alonp99 alonp99 commented Jan 21, 2025

Summary by CodeRabbit

Based on the comprehensive changes, here are the release notes:

  • New Features

    • Added Redis service configuration for improved data management
    • Introduced job queue management with BullMQ and Bull Board
    • Enhanced webhook processing with queuing and monitoring capabilities
  • Improvements

    • Restructured webhook handling for incoming and outgoing webhooks
    • Added environment configuration for Redis and queue system
    • Implemented more robust error handling for webhook operations
  • Dependencies

    • Added BullMQ and Bull Board libraries for job queue management
    • Updated dependencies to support new queuing infrastructure
  • Performance

    • Introduced asynchronous webhook processing
    • Added dead-letter queue support for failed jobs

Copy link

changeset-bot bot commented Jan 21, 2025

⚠️ No Changeset found

Latest commit: 1a7acc9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Jan 21, 2025

Walkthrough

This pull request introduces a comprehensive implementation of a Redis-based queue system for managing webhooks in the workflows service. The changes include setting up a Docker Compose configuration for Redis, adding BullMQ and Bull Board for queue management, and refactoring webhook handling to support both synchronous and asynchronous processing. The implementation adds environment-based configuration for queue system enablement, introduces new services for incoming and outgoing webhooks, and provides a flexible infrastructure for job processing with error handling and logging.

Changes

File Change Summary
docker-compose.redis.yml Added Redis service configuration with persistent storage, password, and network setup
package.json Added BullMQ and Bull Board dependencies, new scripts for Redis service management
src/env.ts Added new environment variables for Redis and queue system configuration
src/app.module.ts Integrated new webhook and BullMQ modules
src/bull-mq/* Added new modules, services, and types for queue management
src/webhooks/* Refactored webhook handling with new incoming and outgoing webhook services

Sequence Diagram

sequenceDiagram
    participant Client
    participant WebhookController
    participant OutgoingWebhookService
    participant OutgoingWebhookQueueService
    participant Redis

    alt Queue System Enabled
        Client->>WebhookController: Send Webhook Request
        WebhookController->>OutgoingWebhookQueueService: Add Job
        OutgoingWebhookQueueService->>Redis: Enqueue Job
    else Queue System Disabled
        Client->>WebhookController: Send Webhook Request
        WebhookController->>OutgoingWebhookService: Invoke Webhook
        OutgoingWebhookService->>Client: Send Webhook Directly
    end
Loading

Possibly related PRs

Suggested reviewers

  • tomer-shvadron
  • MatanYadaev

Poem

🐰 Webhooks dance in Redis' embrace,
Queues spinning with algorithmic grace,
Async magic, jobs take flight,
Ballerine's system shining bright!
Hop, hop, hooray for queue delight! 🚀

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

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

🔭 Outside diff range comments (3)
services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (1)

Line range hint 86-90: Avoid logging sensitive data in error logs

In the error handling, the this.logger.error call logs the entire data object, which may contain sensitive or personal information. Logging sensitive data can lead to security vulnerabilities and PII leakage.

Consider sanitizing the data object before logging or logging only non-sensitive information. For example:

-     this.logger.error('Webhook error data', {
-       data,
-       error,
-       webhook,
-     });
+     this.logger.error('Webhook error data', {
+       error,
+       webhookId: webhook.id,
+       // Include any additional non-sensitive information if necessary
+     });
🧰 Tools
🪛 Biome (1.9.4)

[error] 84-84: Catch clause variable type annotation must be 'any' or 'unknown' if specified.

(parse)

services/workflows-service/src/events/workflow-completed-webhook-caller.ts (1)

Line range hint 147-194: Remove duplicate webhook sending logic.

There's duplicate code for constructing the webhook payload and configuration. Consider extracting this logic into a separate method.

Example refactor:

private buildWebhookPayload(data: WebhookData, id: string, environment: string, apiVersion: string) {
  const { runtimeData, correlationId, entityId, ...restData } = data;
  const { createdAt, resolvedAt, workflowDefinitionId, id: runtimeDataId, ...restRuntimeData } = runtimeData;
  
  return {
    id,
    eventName: 'workflow.completed',
    apiVersion,
    // ... rest of the payload construction
  };
}

private buildWebhookArgs(url: string, payload: any, webhookSharedSecret: string) {
  return {
    requestConfig: {
      url,
      method: 'POST',
      headers: {},
      body: payload,
      timeout: this.configService.get('WEBHOOK_TIMEOUT') ?? 15_000,
    },
    customerConfig: {
      webhookSharedSecret,
    },
  };
}
services/workflows-service/package.json (1)

Line range hint 1-1: Update lockfile to match package.json dependencies.

The pipeline is failing because the lockfile is out of sync with package.json.

Run the following command to update the lockfile:

pnpm install
🧰 Tools
🪛 GitHub Actions: CI

[error] Lock file (pnpm-lock.yaml) is out of sync with package.json dependencies. The package specifications in the lockfile do not match the specifications in package.json. Run 'pnpm install' without the frozen-lockfile option to update the lock file.

🧹 Nitpick comments (18)
services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (1)

15-15: Consider removing the empty WebhookHttpService class

The WebhookHttpService class is currently empty and does not extend any other class or provide any implementation. If it is no longer needed, you may consider removing it to clean up the codebase.

services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1)

19-20: Redundant initializeWorker() call

The initializeWorker() method is called both in the base class constructor and the subclass constructor. This redundancy might lead to unexpected behavior or multiple initializations. Consider removing the call in the subclass if it's already handled by the base class.

services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (1)

98-110: Specify the type of webhookArgs for clarity

Adding an explicit type annotation to webhookArgs improves readability and ensures type safety.

Apply this diff to specify the type:

      const webhookArgs = {
+     const webhookArgs: WebhookJobData = {
        requestConfig: {
services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1)

73-75: Use generic log messages for better reusability

The log messages refer to "Webhook job", which may not be appropriate for all subclasses of the base class. Using generic terms like "Job" improves the base class's reusability.

Apply this diff to update the log messages:

    this.logger.log(`Webhook job ${job.id} is active`);
+   this.logger.log(`Job ${job.id} is active`);

    this.logger.warn(
-     `Webhook job ${job?.id} failed. Attempt: ${currentAttempts}. Error: ${error.message}`,
+     `Job ${job?.id} failed. Attempt: ${currentAttempts}. Error: ${error.message}`,
    );

Also applies to: 98-100

services/workflows-service/src/bull-mq/consts.ts (1)

8-13: Consider optimizing retry configurations.

The current retry settings might need adjustment:

  1. 10-15 retry attempts with exponential backoff could lead to very long processing times
  2. Using the same backoff delay (1000ms) for all queues might not be optimal for different types of operations

Consider:

  • Reducing the number of attempts
  • Adjusting backoff delays based on queue purpose
  • Adding maximum delay to prevent excessive wait times
     config: {
-      attempts: 15,
+      attempts: 5,
       backoff: {
         type: 'exponential',
         delay: 1000,
+        maxDelay: 60000,
       },
     },

Also applies to: 18-23, 28-33

services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts (1)

11-15: Add health check method.

Consider adding a health check method to monitor queue health and connection status.

   constructor(protected readonly logger: AppLoggerService) {
     super(QUEUES.INCOMING_WEBHOOKS_QUEUE.name, logger);
   }
 
+  async isHealthy(): Promise<boolean> {
+    try {
+      const isPaused = await this.queue.isPaused();
+      const workerInfo = await this.worker.getWorkerInfo();
+      return !isPaused && !!workerInfo;
+    } catch (error) {
+      this.logger.error('Health check failed:', error);
+      return false;
+    }
+  }
services/workflows-service/src/business-report/business-report-list.dto.ts (2)

7-18: Consider adding validation constraints for pagination.

While the DTO includes validation for businessId and search, the page property lacks explicit validation constraints. Consider adding validation to ensure proper pagination parameters.

 export class BusinessReportListRequestParamDto {
   @IsOptional()
   @IsString()
   businessId?: string;

   @IsOptional()
   @ApiProperty({ type: String, required: false })
   search?: string;

   @ApiProperty({ type: PageDto })
+  @ValidateNested()
+  @Type(() => PageDto)
   page!: PageDto;
 }

28-37: Consider adding example values for the data array.

The response DTO is well-structured with appropriate Swagger decorations. Consider enhancing the data property's API documentation with example values for better API documentation.

-  @ApiProperty({ type: [BusinessReportDto] })
+  @ApiProperty({
+    type: [BusinessReportDto],
+    example: [{
+      id: 'report-123',
+      status: 'COMPLETED',
+      // ... other example fields
+    }]
+  })
   data!: BusinessReportDto[];
services/workflows-service/src/business-report/business-report.module.ts (1)

11-11: Consider restructuring to eliminate circular dependencies.

The module has multiple circular dependencies that are being handled with forwardRef. While this works, it might indicate a need to restructure the modules to have clearer boundaries and responsibilities.

Consider:

  1. Extracting shared functionality into a separate module
  2. Using events/message bus for cross-module communication
  3. Implementing the mediator pattern
services/workflows-service/src/business-report/business-report.dto.ts (2)

11-23: Consider adding URL validation.

The WebsiteDto should include URL format validation to ensure data integrity.

 export class WebsiteDto {
   @ApiProperty({ type: String })
   id!: string;

   @ApiProperty({ type: String })
+  @IsUrl({
+    require_tld: true,
+    require_protocol: true,
+  })
   url!: string;

   @ApiProperty({ type: String })
   createdAt!: string;

   @ApiProperty({ type: String })
   updatedAt!: string;
 }

65-67: Consider typing the data property more strictly.

Using Record<string, unknown> for the data property might be too permissive and could lead to runtime errors. Consider defining a more specific type or interface for the expected data structure.

-  @ApiProperty({ type: Object })
-  data!: Record<string, unknown>;
+  @ApiProperty({
+    type: 'object',
+    additionalProperties: true,
+    description: 'Dynamic data specific to the report type'
+  })
+  data!: Record<string, string | number | boolean | null>;
services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (1)

10-24: Consider adding request validation and timeout constraints.

While the type definitions are comprehensive, consider adding:

  1. URL validation to ensure it's a valid HTTP/HTTPS URL
  2. Reasonable constraints on the timeout value to prevent excessive waiting
   requestConfig: {
-    url: string;
+    url: z.string().url();
     method: Method;
     headers?: Record<string, string>;
     body?: AnyRecord | string;
-    timeout?: number;
+    timeout?: z.number().min(1000).max(30000);
   };
services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (1)

41-41: Consider organizing providers into feature modules.

The module has a large number of providers and dependencies. Consider:

  1. Grouping related providers into feature modules
  2. Using forwardRef only where necessary to break circular dependencies

Also applies to: 79-79, 82-82, 84-84

services/workflows-service/src/env.ts (1)

96-106: Enhance Redis configuration validation.

Consider the following improvements:

  1. Add port number validation
  2. Add database number validation
  3. Consider using URL format for Redis connection string
-  REDIS_PORT: z.string().transform(value => Number(value)),
+  REDIS_PORT: z.string().transform(value => Number(value))
+    .refine(port => port > 0 && port < 65536, 'Port must be between 1 and 65535'),
-  REDIS_DB: z
-    .string()
-    .transform(value => Number(value))
-    .optional(),
+  REDIS_DB: z.string()
+    .transform(value => Number(value))
+    .refine(db => db >= 0 && db <= 15, 'Redis DB must be between 0 and 15')
+    .optional(),
services/workflows-service/src/events/workflow-completed-webhook-caller.ts (1)

134-145: Extract timeout to configuration.

The timeout value is hardcoded. Consider moving it to the configuration service for better maintainability.

-        timeout: 15_000,
+        timeout: this.configService.get('WEBHOOK_TIMEOUT') ?? 15_000,
services/workflows-service/src/events/document-changed-webhook-caller.ts (1)

206-217: Consider adding retry configuration.

The webhook configuration is well-structured but lacks retry settings which are crucial for handling temporary network issues.

Add retry configuration to improve reliability:

 const webhookArgs = {
   requestConfig: {
     url,
     method: 'POST',
     headers: {},
     body: payload,
     timeout: 15_000,
+    retry: {
+      maxRetries: 3,
+      initialDelayMs: 1000,
+      backoffFactor: 2,
+    },
   },
   customerConfig: {
     webhookSharedSecret,
   },
 } as const;
services/workflows-service/docker-compose.redis.yml (1)

1-22: LGTM! Consider enhancing security with additional Redis configuration.

The Redis configuration looks good, particularly:

  • Using the lightweight Alpine-based image
  • Implementing password authentication
  • Configuring data persistence with AOF
  • Using local driver for volume (as per previous feedback)

Consider adding these security-focused Redis configurations:

   command: >
     --requirepass ${REDIS_PASSWORD}
     --appendonly yes
+    --protected-mode yes
+    --tcp-backlog 128
+    --maxmemory-policy allkeys-lru
+    --maxmemory 100mb
services/workflows-service/package.json (1)

60-60: Consider using stricter version constraints for BullMQ dependencies.

The caret (^) version constraint allows minor version updates which could introduce breaking changes.

Apply this diff to use exact versions:

-    "@nestjs/bullmq": "^10.2.1",
+    "@nestjs/bullmq": "10.2.1",
-    "bullmq": "^5.13.2",
+    "bullmq": "5.13.2",

Also applies to: 87-87

🧰 Tools
🪛 GitHub Actions: CI

[error] Lock file (pnpm-lock.yaml) is out of sync with package.json dependencies. The package specifications in the lockfile do not match the specifications in package.json. Run 'pnpm install' without the frozen-lockfile option to update the lock file.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b220184 and fb5a5cd.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • apps/backoffice-v2/src/lib/blocks/components/EditableDetails/EditableDetails.tsx (1 hunks)
  • apps/backoffice-v2/src/lib/blocks/hooks/useDocumentBlocks/useDocumentBlocks.tsx (1 hunks)
  • apps/backoffice-v2/src/pages/MerchantMonitoring/components/MerchantMonitoringTable/columns.tsx (1 hunks)
  • apps/backoffice-v2/src/pages/MerchantMonitoring/get-merchant-monitoring-search-schema.ts (1 hunks)
  • apps/kyb-app/src/pages/CollectionFlow/CollectionFlow.tsx (1 hunks)
  • packages/ui/src/components/templates/report/adapters/report-adapter/report-adapter.ts (1 hunks)
  • packages/workflow-core/src/lib/plugins/external-plugin/vendor-consts.ts (2 hunks)
  • services/workflows-service/docker-compose.redis.yml (1 hunks)
  • services/workflows-service/package.json (4 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
  • services/workflows-service/src/alert/alert.module.ts (3 hunks)
  • services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (2 hunks)
  • services/workflows-service/src/app.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/consts.ts (1 hunks)
  • services/workflows-service/src/bull-mq/incoming-webhook/incoming-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/incoming-webhook/types/types.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/types/types.ts (1 hunks)
  • services/workflows-service/src/bull-mq/types.ts (1 hunks)
  • services/workflows-service/src/business-report/business-report-list.dto.ts (1 hunks)
  • services/workflows-service/src/business-report/business-report.dto.ts (1 hunks)
  • services/workflows-service/src/business-report/business-report.module.ts (1 hunks)
  • services/workflows-service/src/business-report/dto/create-business-report-batch-body.dto.ts (1 hunks)
  • services/workflows-service/src/business-report/dto/get-business-report.dto.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
  • services/workflows-service/src/events/document-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-completed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/redis/const/redis-config.ts (1 hunks)
  • services/workflows-service/src/redis/redis.module.ts (1 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.controller.ts (2 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (3 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.service.ts (1 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.module.ts (1 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (1 hunks)
  • services/workflows-service/src/worker-app.module.ts (1 hunks)
  • services/workflows-service/src/worker-main.ts (1 hunks)
  • services/workflows-service/src/workflow/workflow.module.ts (2 hunks)
  • services/workflows-service/src/workflow/workflow.service.ts (1 hunks)
  • services/workflows-service/src/workflow/workflow.service.unit.test.ts (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • services/workflows-service/src/redis/redis.module.ts
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.service.ts
  • apps/backoffice-v2/src/lib/blocks/components/EditableDetails/EditableDetails.tsx
  • services/workflows-service/prisma/data-migrations
🧰 Additional context used
📓 Learnings (1)
services/workflows-service/docker-compose.redis.yml (1)
Learnt from: alonp99
PR: ballerine-io/ballerine#2735
File: services/workflows-service/docker-compose.redis.yml:17-23
Timestamp: 2024-11-12T05:57:16.983Z
Learning: For Redis volumes in local development, prefer using `driver: local` in Docker Compose configurations.
🪛 Biome (1.9.4)
services/workflows-service/src/workflow/workflow.service.ts

[error] 1599-1599: Shouldn't redeclare 'collectionFlow'. Consider to delete it or rename it.

'collectionFlow' is defined here:

(lint/suspicious/noRedeclare)

apps/backoffice-v2/src/lib/blocks/hooks/useDocumentBlocks/useDocumentBlocks.tsx

[error] 412-412: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

apps/backoffice-v2/src/pages/MerchantMonitoring/components/MerchantMonitoringTable/columns.tsx

[error] 32-32: Shouldn't redeclare 'MERCHANT_REPORT_STATUSES_MAP'. Consider to delete it or rename it.

'MERCHANT_REPORT_STATUSES_MAP' is defined here:

(lint/suspicious/noRedeclare)


[error] 33-33: Shouldn't redeclare 'MERCHANT_REPORT_TYPES_MAP'. Consider to delete it or rename it.

'MERCHANT_REPORT_TYPES_MAP' is defined here:

(lint/suspicious/noRedeclare)

🪛 GitHub Actions: CI
services/workflows-service/package.json

[error] Lock file (pnpm-lock.yaml) is out of sync with package.json dependencies. The package specifications in the lockfile do not match the specifications in package.json. Run 'pnpm install' without the frozen-lockfile option to update the lock file.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (25)
services/workflows-service/src/bull-mq/bull-mq.module.ts (1)

1-70: LGTM! The module is well-structured and integrates BullMQ effectively.

The code is well-organized and correctly sets up the BullMQ queues and Bull Board monitoring interface.

services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (1)

50-63: Ensure QUEUE_SYSTEM_ENABLED is properly converted to a boolean

When checking env.QUEUE_SYSTEM_ENABLED, ensure that the environment variable is correctly parsed as a boolean value, especially if the variable is set as a string (e.g., 'true' or 'false'). This will prevent unexpected behavior due to truthy or falsy string values.

Consider explicitly parsing the environment variable to a boolean, for example:

- if (env.QUEUE_SYSTEM_ENABLED) {
+ if (env.QUEUE_SYSTEM_ENABLED === 'true') {

Alternatively, update the configuration to ensure QUEUE_SYSTEM_ENABLED is a boolean value.

services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (1)

12-14: Code changes enhance modularity and flexibility

The additions of imports, injected services, and the queuing logic based on env.QUEUE_SYSTEM_ENABLED enhance the modularity and configurability of webhook handling. The implementation aligns well with the application's architecture.

Also applies to: 26-27, 98-114

packages/ui/src/components/templates/report/adapters/report-adapter/report-adapter.ts (3)

14-24: Effective use of utility function toRiskLabels

The toRiskLabels function efficiently standardizes risk indicators into a consistent format, enhancing code reusability and maintainability.


26-33: Consistent risk level normalization with normalizeRiskLevel

The normalizeRiskLevel function provides a streamlined approach to mapping risk levels, ensuring consistent severity representation across the application.


35-185: Comprehensive and robust reportAdapter.DEFAULT implementation

The reportAdapter.DEFAULT method thoroughly processes and adapts the report data, handling various data sources with appropriate optional chaining and type safety. This enhances the robustness and reliability of the data transformation.

services/workflows-service/src/bull-mq/types.ts (1)

1-4: Well-defined TJobPayloadMetadata type

The TJobPayloadMetadata type is correctly defined with optional properties projectId and customerName, enhancing type safety and clarity in job payload handling.

services/workflows-service/src/bull-mq/incoming-webhook/types/types.ts (1)

1-5: Appropriate IncomingWebhookData interface definition

The IncomingWebhookData interface is well-structured, clearly defining the expected properties for handling incoming webhooks. The inclusion of a service function property promotes flexibility in processing webhook payloads asynchronously.

services/workflows-service/src/bull-mq/outgoing-webhook/types/types.ts (1)

1-3: LGTM! Well-structured type definition.

The use of the Parameters utility type to extract the parameter types from OutgoingWebhooksService.invokeWebhook is a great practice. This ensures type safety and automatic updates if the method signature changes.

services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.module.ts (1)

5-10: Verify HTTP module dependency.

The module looks well-structured, but please verify if HttpModule is imported either here or within OutgoingWebhooksService for making HTTP webhook calls.

services/workflows-service/src/business-report/dto/get-business-report.dto.ts (1)

6-28: LGTM! Well-structured DTO with proper validation and documentation.

The DTO implementation follows best practices:

  • Comprehensive swagger documentation
  • Proper validation decorators
  • Type-safe orderBy field using template literals
apps/backoffice-v2/src/pages/MerchantMonitoring/get-merchant-monitoring-search-schema.ts (1)

6-28: LGTM! Well-implemented search schema with proper type safety.

The schema implementation is solid:

  • Good use of type safety with satisfies operator
  • Reasonable fallback for sortBy
  • Clean extension of base schema
services/workflows-service/src/business-report/business-report-list.dto.ts (1)

20-26: LGTM! Robust schema validation.

The Zod schema properly validates pagination parameters with appropriate constraints:

  • Coerces and validates numbers
  • Ensures positive integers
  • Sets a reasonable maximum page size
services/workflows-service/src/business-report/dto/create-business-report-batch-body.dto.ts (1)

18-36: LGTM! Well-structured enum validation.

The type and version properties are properly validated using:

  • Appropriate enum constraints
  • Clear default values
  • Comprehensive Swagger documentation
services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (2)

1-8: LGTM! Clean class setup with proper dependency injection.

The class is correctly decorated with @Injectable() and uses constructor injection for the logger service.


27-40: Enhance security headers and CORS configuration.

The current implementation has a few security considerations:

  1. Access-Control-Allow-Origin: '*' is too permissive for a backend service
  2. HMAC signature implementation looks secure, but consider adding a timestamp to prevent replay attacks
services/workflows-service/src/workflow/workflow.module.ts (1)

49-50: LGTM! Verify BullMQ configuration.

The integration of BullMQ and OutgoingWebhooks modules looks good. The modules are properly imported and added to the module's dependencies.

Let's verify the BullMQ configuration:

Also applies to: 68-69

✅ Verification successful

BullMQ integration verified and properly configured

The BullMQ setup includes:

  • Redis connection configuration
  • Queue registration with Dead Letter Queue (DLQ) support
  • BullBoard monitoring UI at /queues
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for BullMQ configuration in the codebase

# Check for Redis configuration which is required for BullMQ
rg -l "createBullBoard|BullModule.forRoot|registerQueue" -g "*.ts"

Length of output: 125


Script:

#!/bin/bash
# Check the content of bull-mq.module.ts
cat services/workflows-service/src/bull-mq/bull-mq.module.ts

# Also check for Redis configuration in environment files
rg -l "REDIS" -g "*.{ts,env}*"

Length of output: 2376

services/workflows-service/src/app.module.ts (1)

135-135: Verify the removal of WebhooksModule.

The replacement of WebhooksModule with BullMqModule looks good, but we should ensure all webhook functionality has been properly migrated.

Let's verify no critical webhook functionality is broken:

✅ Verification successful

WebhooksModule removal is safe - functionality preserved in specialized modules

The webhook functionality has been properly migrated and modularized into IncomingWebhooksModule and OutgoingWebhooksModule, with complete BullMQ integration for queue management. All webhook capabilities (incoming endpoints, outgoing notifications, queue processing) are maintained through the new architecture.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining WebhooksModule dependencies

# Search for any remaining references to WebhooksModule
rg "WebhooksModule|@UseWebhooks|@Webhook" -g "*.ts"

# Search for any webhook-related services that might need updating
rg "implements.*WebhookHandler|extends.*WebhookBase" -g "*.ts"

Length of output: 1443


Script:

#!/bin/bash
# Search for any direct usage of WebhooksModule (not OutgoingWebhooksModule or IncomingWebhooksModule)
rg "WebhooksModule(?!(\.|\s*}|,))" -g "*.ts"

# Check for any webhook-related services or controllers that might be affected
ast-grep --pattern 'class $_ {
  $$$
  @Webhook($$$)
  $$$
}'

Length of output: 357


Script:

#!/bin/bash
# Search for WebhooksModule (exact match)
rg "\bWebhooksModule\b" -g "*.ts"

# Search for webhook decorators and implementations
rg "@Webhook|webhook" -i -g "*.ts" -A 2

Length of output: 65822

services/workflows-service/src/events/workflow-completed-webhook-caller.ts (1)

106-132: LGTM! Clear payload structure.

The new payload structure is well-organized and properly separates workflow metadata from the actual data.

services/workflows-service/src/events/document-changed-webhook-caller.ts (2)

182-204: LGTM! Well-structured webhook payload.

The payload structure is comprehensive and includes all necessary information for webhook processing.


219-224: LGTM! Good separation of concerns.

The conditional logic for queue-based processing is clean and follows the single responsibility principle.

packages/workflow-core/src/lib/plugins/external-plugin/vendor-consts.ts (1)

661-705: LGTM! Good UTC time handling.

The implementation properly handles UTC time conversion for the UBO vendor test configuration.

services/workflows-service/src/webhooks/incoming/incoming-webhooks.controller.ts (1)

1-8: LGTM! Clean refactoring of webhook handling.

The changes improve code organization by:

  • Separating incoming webhook handling into its own controller
  • Updating import paths to reflect the new structure
  • Maintaining consistent functionality while improving modularity
services/workflows-service/package.json (2)

8-8: LGTM! Setup script updated to manage Redis service.

The setup script correctly orchestrates the database and Redis services.

🧰 Tools
🪛 GitHub Actions: CI

[error] Lock file (pnpm-lock.yaml) is out of sync with package.json dependencies. The package specifications in the lockfile do not match the specifications in package.json. Run 'pnpm install' without the frozen-lockfile option to update the lock file.


35-36: LGTM! Redis Docker management scripts added.

The scripts provide clear commands for managing the Redis service lifecycle.

🧰 Tools
🪛 GitHub Actions: CI

[error] Lock file (pnpm-lock.yaml) is out of sync with package.json dependencies. The package specifications in the lockfile do not match the specifications in package.json. Run 'pnpm install' without the frozen-lockfile option to update the lock file.

Comment on lines 15 to 38
export const validate = async (config: Record<string, unknown>) => {
const zodEnvSchema = z
.object(serverEnvSchema)
.refine(data => data.HASHING_KEY_SECRET || data.HASHING_KEY_SECRET_BASE64, {
message: 'At least one of HASHING_KEY_SECRET or HASHING_KEY_SECRET_BASE64 should be present',
path: ['HASHING_KEY_SECRET', 'HASHING_KEY_SECRET_BASE64'],
});

const result = zodEnvSchema.safeParse(config);

if (!result.success) {
const errors = result.error.errors.map(zodIssue => ({
message: `❌ ${zodIssue.message}`,
path: zodIssue.path.join('.'), // Backwards compatibility - Legacy code message excepts array
}));

throw new Error(JSON.stringify(errors, null, 2));
}

// validate salt value
await hashKey('check salt value');

return result.data;
};
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 the validate function in ConfigModule.forRoot is synchronous

The validate function provided to ConfigModule.forRoot should be synchronous. Declaring it as an async function and using await inside may cause unexpected behavior since ConfigModule.forRoot expects validate to return synchronously. Asynchronous operations inside validate will not complete as expected.

To fix this issue, consider making the validate function synchronous and handle any asynchronous validation outside of it. For example, you can perform the salt value validation in a separate initialization step.

Comment on lines 23 to 37
async handleJob(job: Job<TJobArgs>) {
this.logger.log(`Processing webhook job ${job.id}`);

const response = await this.webhookService.invokeWebhook({
...job.data.jobData,
});

this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);

if (response.status >= 200 && response.status < 300) {
return;
}

await this.handleRetryStrategy(response.status, job);
}
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

Handle exceptions from invokeWebhook to prevent job crashes

The invokeWebhook method may throw exceptions, but there is no error handling around its invocation. Without a try-catch block, any errors will cause the job to fail without proper logging or retry logic.

Apply this diff to handle exceptions appropriately:

  async handleJob(job: Job<TJobArgs>) {
    this.logger.log(`Processing webhook job ${job.id}`);

+   try {
      const response = await this.webhookService.invokeWebhook({
        ...job.data.jobData,
      });

      this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);

      if (response.status >= 200 && response.status < 300) {
        return;
      }

      await this.handleRetryStrategy(response.status, job);
+   } catch (error) {
+     this.logger.error(`Error processing webhook job ${job.id}: ${error.message}`);
+     await this.handleRetryStrategy(500, job);
+   }
  }
📝 Committable suggestion

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

Suggested change
async handleJob(job: Job<TJobArgs>) {
this.logger.log(`Processing webhook job ${job.id}`);
const response = await this.webhookService.invokeWebhook({
...job.data.jobData,
});
this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);
if (response.status >= 200 && response.status < 300) {
return;
}
await this.handleRetryStrategy(response.status, job);
}
async handleJob(job: Job<TJobArgs>) {
this.logger.log(`Processing webhook job ${job.id}`);
try {
const response = await this.webhookService.invokeWebhook({
...job.data.jobData,
});
this.logger.log(`Webhook job ${job.id} completed with status: ${response.status}`);
if (response.status >= 200 && response.status < 300) {
return;
}
await this.handleRetryStrategy(response.status, job);
} catch (error) {
this.logger.error(`Error processing webhook job ${job.id}: ${error.message}`);
await this.handleRetryStrategy(500, job);
}
}

Comment on lines 39 to 68
private async handleRetryStrategy(status: number, job: Job<TJobArgs>) {
if (job.opts.attempts && job.attemptsMade >= job.opts.attempts) {
this.logger.warn(`Job ${job.id} reached the maximum retry attempts (${job.opts.attempts})`);
throw new Error(`Job ${job.id} failed after reaching max attempts`);
}

let delayMs: number;

switch (status) {
case HttpStatusCode.TooManyRequests:
case HttpStatusCode.InternalServerError:
case HttpStatusCode.BadGateway:
case HttpStatusCode.ServiceUnavailable:
case HttpStatusCode.GatewayTimeout:
delayMs = Math.pow(2, job.attemptsMade + 1) * 1000; // Exponential backoff
break;

case HttpStatusCode.RequestTimeout:
delayMs = 1000 * 60 * (job.attemptsMade + 1); // Linear backoff in minutes
break;

case HttpStatusCode.BadRequest:
throw new Error(`Webhook job failed with status ${status}: Bad Request`);

default:
throw new Error(`Webhook job failed with status ${status}: Unexpected Error`);
}

await this.retryJob(job, delayMs);
}
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

Utilize BullMQ's built-in retry and backoff features

The custom retry logic in handleRetryStrategy and retryJob duplicates functionality that BullMQ provides natively. Leveraging BullMQ's attempts and backoff options simplifies the code and reduces complexity.

Consider specifying attempts and backoff in the job options or queue configuration and remove the custom retry logic. Apply this diff to simplify the handleJob method:

    if (response.status >= 200 && response.status < 300) {
      return;
    }

-   await this.handleRetryStrategy(response.status, job);
+   throw new Error(`Webhook job failed with status ${response.status}`);

And remove the handleRetryStrategy and retryJob methods if they are no longer needed.

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

Comment on lines 129 to 142
protected setQueueListener<T extends keyof QueueListener<any, any, any>>({
queue,
eventName,
listener,
}: {
queue: Queue | undefined;
eventName: T;
listener: QueueListener<any, any, any>[T];
}) {
return async () => {
queue?.removeAllListeners(eventName);
queue?.on(eventName, listener);
};
}
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

setQueueListener does not attach the event listener

The setQueueListener method returns an async function that is not called, so the event listener is never attached to the queue. This prevents the listener from functioning as intended.

Apply this diff to fix the issue:

  protected setQueueListener<T extends keyof QueueListener<any, any, any>>({
    queue,
    eventName,
    listener,
  }: {
    queue: Queue | undefined;
    eventName: T;
    listener: QueueListener<any, any, any>[T];
  }) {
-   return async () => {
      queue?.removeAllListeners(eventName);
      queue?.on(eventName, listener);
-   };
  }

By removing the returned function, the listener will be correctly attached when setQueueListener is called.

📝 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
protected setQueueListener<T extends keyof QueueListener<any, any, any>>({
queue,
eventName,
listener,
}: {
queue: Queue | undefined;
eventName: T;
listener: QueueListener<any, any, any>[T];
}) {
return async () => {
queue?.removeAllListeners(eventName);
queue?.on(eventName, listener);
};
}
protected setQueueListener<T extends keyof QueueListener<any, any, any>>({
queue,
eventName,
listener,
}: {
queue: Queue | undefined;
eventName: T;
listener: QueueListener<any, any, any>[T];
}) {
queue?.removeAllListeners(eventName);
queue?.on(eventName, listener);
}

Comment on lines 4 to 9
export const REDIS_CONFIG = {
host: env.REDIS_HOST || 'localhost',
port: env.REDIS_PORT || 7381,
...(env.REDIS_PASSWORD ? { password: env.REDIS_PASSWORD } : {}),
...(env.REDIS_DB ? { db: env.REDIS_DB } : {}),
} satisfies RedisOptions;
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

Enhance Redis configuration with security and reliability settings.

While the basic configuration is good, consider adding these essential settings:

  1. The default port (7381) is non-standard - consider using the standard Redis port 6379
  2. Add connection timeout settings
  3. Consider TLS configuration for secure environments
  4. Add retry strategy for connection resilience
 export const REDIS_CONFIG = {
   host: env.REDIS_HOST || 'localhost',
-  port: env.REDIS_PORT || 7381,
+  port: env.REDIS_PORT || 6379,
   ...(env.REDIS_PASSWORD ? { password: env.REDIS_PASSWORD } : {}),
   ...(env.REDIS_DB ? { db: env.REDIS_DB } : {}),
+  tls: env.REDIS_TLS_ENABLED ? {
+    rejectUnauthorized: true,
+    ca: env.REDIS_TLS_CA,
+  } : undefined,
+  connectTimeout: 10000,
+  maxRetriesPerRequest: 3,
+  retryStrategy: (times: number) => Math.min(times * 50, 2000),
 } satisfies RedisOptions;
📝 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 REDIS_CONFIG = {
host: env.REDIS_HOST || 'localhost',
port: env.REDIS_PORT || 7381,
...(env.REDIS_PASSWORD ? { password: env.REDIS_PASSWORD } : {}),
...(env.REDIS_DB ? { db: env.REDIS_DB } : {}),
} satisfies RedisOptions;
export const REDIS_CONFIG = {
host: env.REDIS_HOST || 'localhost',
port: env.REDIS_PORT || 6379,
...(env.REDIS_PASSWORD ? { password: env.REDIS_PASSWORD } : {}),
...(env.REDIS_DB ? { db: env.REDIS_DB } : {}),
tls: env.REDIS_TLS_ENABLED ? {
rejectUnauthorized: true,
ca: env.REDIS_TLS_CA,
} : undefined,
connectTimeout: 10000,
maxRetriesPerRequest: 3,
retryStrategy: (times: number) => Math.min(times * 50, 2000),
} satisfies RedisOptions;

@@ -205,6 +205,7 @@ export const CollectionFlow = withSessionProtected(() => {
<div className="flex w-full justify-end">
<AppShell.LanguagePicker />
</div>
<AppShell.Navigation />
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

Remove duplicate navigation component.

The AppShell.Navigation component is duplicated, which could lead to UI inconsistencies and confusion.

Apply this diff to remove the duplicate:

-                                      <AppShell.Navigation />

Comment on lines 510 to 551
[COMPANY_SCREENING_VENDORS['test']]: (options: CompanySanctionsAsiaVerifyOptions) => ({
name: 'companySanctions',
pluginKind: 'company-sanctions',
vendor: 'test',
url: {
url: `{secret.UNIFIED_API_URL}/companies/{country}/{entity.data.companyName}/sanctions`,
options: {
country: options.defaultCountry ?? '{entity.data.country}',
},
},
headers: { Authorization: 'Bearer {secret.UNIFIED_API_TOKEN}' },
method: 'GET' as const,
displayName: 'Company Sanctions',
persistResponseDestination: 'pluginsOutput.companySanctions',
request: {
transform: [
{
mapping: "{ vendor: 'test' }",
transformer: 'jmespath',
},
],
},
response: {
transform: [
{
mapping:
"merge({ name: 'companySanctions', status: contains(['NOT_IMPLEMENTED', 'NOT_AVAILABLE'], reason) && 'CANCELED' || error != `null` && 'ERROR' || 'SUCCESS' }, @)",
transformer: 'jmespath',
},
{
mapping: [
{
method: 'setTimeToRecordUTC',
source: 'invokedAt',
target: 'invokedAt',
},
],
transformer: 'helper',
},
],
},
}),
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

Remove duplicate vendor implementation.

The implementation of COMPANY_SCREENING_VENDORS['test'] is duplicated, which could lead to confusion and maintenance issues.

Remove the duplicate implementation (lines 510-551) as it's already defined above.

Comment on lines 1599 to 1615
const collectionFlow = buildCollectionFlowState({
apiUrl: env.APP_API_URL,
steps: uiDefinition?.definition
? getOrderedSteps(
(uiDefinition?.definition as Prisma.JsonObject)?.definition as Record<
string,
Record<string, unknown>
>,
{ finalStates: [...WORKFLOW_FINAL_STATES] },
).map(stepName => ({
stateName: stepName,
}))
: [],
additionalInformation: {
customerCompany: customer.displayName,
},
});
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

Remove duplicate variable declaration.

The collectionFlow variable is declared twice, which is a syntax error and will cause runtime issues.

Remove the duplicate declaration (lines 1599-1615) as it's already defined above.

🧰 Tools
🪛 Biome (1.9.4)

[error] 1599-1599: Shouldn't redeclare 'collectionFlow'. Consider to delete it or rename it.

'collectionFlow' is defined here:

(lint/suspicious/noRedeclare)

Comment on lines 31 to 34
import {
MERCHANT_REPORT_STATUSES_MAP,
MERCHANT_REPORT_TYPES_MAP,
} from '@/domains/business-reports/constants';
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

Remove duplicate imports.

The constants MERCHANT_REPORT_STATUSES_MAP and MERCHANT_REPORT_TYPES_MAP are already imported at lines 17-20.

Apply this diff to remove the duplicate imports:

-import {
-  MERCHANT_REPORT_STATUSES_MAP,
-  MERCHANT_REPORT_TYPES_MAP,
-} from '@/domains/business-reports/constants';
🧰 Tools
🪛 Biome (1.9.4)

[error] 32-32: Shouldn't redeclare 'MERCHANT_REPORT_STATUSES_MAP'. Consider to delete it or rename it.

'MERCHANT_REPORT_STATUSES_MAP' is defined here:

(lint/suspicious/noRedeclare)


[error] 33-33: Shouldn't redeclare 'MERCHANT_REPORT_TYPES_MAP'. Consider to delete it or rename it.

'MERCHANT_REPORT_TYPES_MAP' is defined here:

(lint/suspicious/noRedeclare)

@@ -409,6 +409,7 @@ export const useDocumentBlocks = ({
}

if (ocrResult?.parsedData?.[title]) {
debugger;
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

Remove debugger statement.

Debugger statements should not be committed to production code.

Apply this diff to remove the debugger statement:

-                          debugger;
🧰 Tools
🪛 Biome (1.9.4)

[error] 412-412: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
services/workflows-service/src/bull-mq/bull-mq.module.ts (2)

12-28: Consider aligning with arrow function consistency.
You’ve explicitly disabled the prefer-arrow/prefer-arrow-functions rule for composeQueueAndDlqBoard. If the rest of the codebase consistently uses arrow functions, consider removing the ESLint disable directive and converting this to an arrow function for consistency.


30-39: Clarify the queue configuration logic.
composeInitiateQueueWithDlq cleverly handles the creation of both the main queue and the DLQ in a concise way. However, the chained logical checks can be slightly opaque for newcomers. Adding a descriptive comment near the filter(Boolean) step would aid readability and maintainability.

services/workflows-service/src/bull-mq/consts.ts (1)

3-37: Centralize and document backoff and retry strategies.
The DEFAULT, INCOMING_WEBHOOKS_QUEUE, and OUTGOING_WEBHOOKS_QUEUE each define attempts and backoff. Consider placing these shared retry strategies in a separate config or constants file for consistency across all queues and future expansions.

services/workflows-service/package.json (1)

8-8: Consider improving script organization.

The Redis management scripts could be better organized:

  1. Consider combining the Redis setup commands into a single script.
  2. Consider moving Redis management scripts to a separate namespace (e.g., redis:up, redis:down).
-    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis:down && npm run docker:redis && npm run db:reset && npm run seed",
-    "docker:redis": "docker compose -f docker-compose.redis.yml up -d --wait",
-    "docker:redis:down": "docker compose -f docker-compose.redis.yml down --volumes",
+    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run redis:setup && npm run db:reset && npm run seed",
+    "redis:setup": "npm run redis:down && npm run redis:up",
+    "redis:up": "docker compose -f docker-compose.redis.yml up -d --wait",
+    "redis:down": "docker compose -f docker-compose.redis.yml down --volumes",

Also applies to: 35-36

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fb5a5cd and de13bc9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • services/workflows-service/package.json (4 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
  • services/workflows-service/src/alert/alert.module.ts (3 hunks)
  • services/workflows-service/src/app.module.ts (3 hunks)
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/consts.ts (1 hunks)
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/events/workflow-completed-webhook-caller.ts (4 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • services/workflows-service/prisma/data-migrations
  • services/workflows-service/src/bull-mq/outgoing-webhook/outgoing-webhook-queue.service.ts
  • services/workflows-service/src/bull-mq/base/base-queue-worker.service.ts
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: test_windows
  • GitHub Check: test_linux
  • GitHub Check: build (windows-latest)
  • GitHub Check: spell_check
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: lint
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (5)
services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (1)

10-24: Avoid missing or undefined HTTP methods.
The invokeWebhook method relies on requestConfig.method without enforcing a default or validating it. Consider adding a sanity check or default method to handle scenarios where method might not be supplied or is incorrectly spelled.

services/workflows-service/src/bull-mq/consts.ts (1)

8-13: Confirm that 15 retry attempts is optimal.
For the DEFAULT queue, 15 attempts with exponential backoff can cause extended delays before the job finally fails or lands in the DLQ. Ensure that this is aligned with operational SLAs, as it may significantly postpone error handling for failing jobs.

services/workflows-service/src/alert/alert.module.ts (1)

37-38: Remove duplicate module entries.

BullMqModule and OutgoingWebhooksModule are listed in both imports and providers arrays. They should only be in imports.

services/workflows-service/src/app.module.ts (1)

51-53: LGTM!

The new modules are correctly imported and added to the imports array.

Also applies to: 96-97, 139-139

services/workflows-service/src/events/workflow-completed-webhook-caller.ts (1)

150-152: Add QUEUE_SYSTEM_ENABLED to environment configuration template.

The QUEUE_SYSTEM_ENABLED environment variable is missing from the .env.example file.

Comment on lines 45 to 53
BullModule.forRootAsync({
useFactory: () => {
return {
connection: {
...REDIS_CONFIG,
},
};
},
}),
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 fallback or retry on Redis connection issues.
Currently, BullModule.forRootAsync uses only the basic REDIS_CONFIG. If Redis is unavailable at startup or faces transient failures, the module may fail to initialize. To improve resilience, consider adding connection error handling or a retry strategy.

Comment on lines +59 to +36
BullBoardModule.forRoot({
route: '/queues',
adapter: ExpressAdapter,
}),
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

Secure access to Bull Board.
Exposing the Bull Board at /queues can create security or privacy concerns in production. Consider restricting access via authentication, IP whitelisting, or at least an environment-based guard to prevent unauthorized queue monitoring.

Comment on lines 27 to 33
const signedHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
...headers,
...(body && customerConfig?.webhookSharedSecret
? {
'X-HMAC-Signature': sign({
payload: body,
key: customerConfig.webhookSharedSecret,
}),
}
: {}),
};
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 consistent payload formats for signature generation.
When computing the HMAC signature, if body is sometimes a string and sometimes an object, it’s crucial to ensure consistent serialization. Otherwise, the signature could become invalid. For clarity, make sure you serialize or transform body into a consistent format prior to signing.

Comment on lines 42 to 49
return await axios({
url,
method,
headers: signedHeaders,
data: body,
timeout: timeout || 15000,
});

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 non-2xx responses and exceptions gracefully.
Currently, await axios(...) will throw on 4xx/5xx responses, but there is no retry, logging, or fallback mechanism. Un-commenting your existing error-handling block or implementing custom logic for non-2xx responses can improve observability and resilience.

Comment on lines 109 to 148
// Omit from data properties already sent as part of the webhook payload
const { runtimeData, correlationId, entityId, ...restData } = data;
const {
createdAt,
resolvedAt,
workflowDefinitionId,
id: runtimeDataId,
...restRuntimeData
} = runtimeData;
const payload = {
id,
eventName: 'workflow.completed',
apiVersion,
timestamp: new Date().toISOString(),
workflowCreatedAt: createdAt,
workflowResolvedAt: resolvedAt,
workflowDefinitionId,
workflowRuntimeId: runtimeDataId,
workflowStatus: data.runtimeData.status,
workflowFinalState: data.runtimeData.state,
ballerineEntityId: entityId,
correlationId,
environment,
data: {
...restRuntimeData.context,
},
};

const webhookArgs = {
requestConfig: {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
},
customerConfig: {
webhookSharedSecret,
},
} as const;

if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}
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

Refactor duplicate code blocks.

The code block for queue system enabled check and payload construction is duplicated. Consider refactoring to eliminate the duplication:

  1. Move the payload construction to a separate method.
  2. Remove the duplicate queue system enabled check.
  private async sendWebhook({
    data,
    webhook: { id, url, environment, apiVersion },
    webhookSharedSecret,
  }) {
    this.logger.log('Sending webhook', { id, url });

-    // Omit from data properties already sent as part of the webhook payload
-    const { runtimeData, correlationId, entityId, ...restData } = data;
-    const {
-      createdAt,
-      resolvedAt,
-      workflowDefinitionId,
-      id: runtimeDataId,
-      ...restRuntimeData
-    } = runtimeData;
-    const payload = {
-      id,
-      eventName: 'workflow.completed',
-      apiVersion,
-      timestamp: new Date().toISOString(),
-      workflowCreatedAt: createdAt,
-      workflowResolvedAt: resolvedAt,
-      workflowDefinitionId,
-      workflowRuntimeId: runtimeDataId,
-      workflowStatus: data.runtimeData.status,
-      workflowFinalState: data.runtimeData.state,
-      ballerineEntityId: entityId,
-      correlationId,
-      environment,
-      data: {
-        ...restRuntimeData.context,
-      },
-    };
+    const payload = this.buildWebhookPayload(data, id, apiVersion, environment);

    const webhookArgs = {
      requestConfig: {
        url,
        method: 'POST',
        headers: {},
        body: payload,
        timeout: 15_000,
      },
      customerConfig: {
        webhookSharedSecret,
      },
    } as const;

    if (env.QUEUE_SYSTEM_ENABLED) {
      return await this.outgoingWebhookQueueService.addJob(webhookArgs);
    }

    try {
-      // Omit from data properties already sent as part of the webhook payload
-      const { runtimeData, correlationId, entityId, childWorkflowsRuntimeData, ...restData } = data;
-      const {
-        createdAt,
-        resolvedAt,
-        workflowDefinitionId,
-        id: runtimeDataId,
-        ...restRuntimeData
-      } = runtimeData;
-      const payload = {
-        id,
-        eventName: 'workflow.completed',
-        apiVersion,
-        timestamp: new Date().toISOString(),
-        workflowCreatedAt: createdAt,
-        workflowResolvedAt: resolvedAt,
-        workflowDefinitionId,
-        workflowRuntimeId: runtimeDataId,
-        workflowStatus: data.runtimeData.status,
-        workflowFinalState: data.runtimeData.state,
-        ballerineEntityId: entityId,
-        correlationId,
-        environment,
-        data: {
-          ...restRuntimeData.context,
-          childWorkflowsRuntimeData,
-        },
-      };

-      if (env.QUEUE_SYSTEM_ENABLED) {
-        return await this.outgoingWebhookQueueService.addJob({
-          requestConfig: {
-            url,
-            method: 'POST',
-            headers: {},
-            body: payload,
-            timeout: 15_000,
-          },
-          customerConfig: {
-            webhookSharedSecret,
-          },
-        });
-      }

      const res = await this.#__axios.post(url, payload, {
        headers: {
          'X-Authorization': webhookSharedSecret,
          'X-HMAC-Signature': sign({ payload, key: webhookSharedSecret }),
        },
      });

Add a new private method to handle payload construction:

private buildWebhookPayload(
  data: ExtractWorkflowEventData<'workflow.completed'>,
  id: string,
  apiVersion: string,
  environment: string,
) {
  const { runtimeData, correlationId, entityId, childWorkflowsRuntimeData } = data;
  const {
    createdAt,
    resolvedAt,
    workflowDefinitionId,
    id: runtimeDataId,
    ...restRuntimeData
  } = runtimeData;

  return {
    id,
    eventName: 'workflow.completed',
    apiVersion,
    timestamp: new Date().toISOString(),
    workflowCreatedAt: createdAt,
    workflowResolvedAt: resolvedAt,
    workflowDefinitionId,
    workflowRuntimeId: runtimeDataId,
    workflowStatus: data.runtimeData.status,
    workflowFinalState: data.runtimeData.state,
    ballerineEntityId: entityId,
    correlationId,
    environment,
    data: {
      ...restRuntimeData.context,
      ...(childWorkflowsRuntimeData ? { childWorkflowsRuntimeData } : {}),
    },
  };
}

Also applies to: 184-197

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

🔭 Outside diff range comments (1)
services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (1)

Line range hint 19-19: Remove unused axios instance and service.

The #__axios private field and httpService are no longer used after the refactor to use OutgoingWebhooksService.

-  #__axios: AxiosInstance;
-
   constructor(
-    private httpService: HttpService,
     workflowEventEmitter: WorkflowEventEmitterService,
     // ...
   ) {
-    this.#__axios = this.httpService.axiosRef;
     // ...
   }

Also applies to: 30-30

🧹 Nitpick comments (10)
services/workflows-service/src/bull-mq/bull-mq.module.ts (3)

9-9: Naming consistency for QUEUES.

While QUEUES is descriptive, consider renaming it to something like BULL_QUEUES or QUEUE_CONFIGS to be more explicit about its purpose in the context of BullMQ.


36-49: Handle potential queue registration failures.

While registering multiple queues via a flat map is convenient, consider adding error handling or logging if any queue registration fails. This ensures better debugging and operational clarity in case of misconfiguration or unexpected system states.


52-52: Export only necessary modules.

Currently, BullModule is exported. If no other module depends on it directly, you can keep it internal or export only the necessary services (e.g., OutgoingWebhookQueueService). Restricting exports helps maintain a cleaner API surface.

services/workflows-service/src/bull-mq/queues/base-queue-worker.service.ts (4)

17-24: Log queue skipping reason if QUEUE_SYSTEM_ENABLED is false.

When env.QUEUE_SYSTEM_ENABLED is disabled, the constructor returns early. For improved clarity, log that the queue has been skipped and won't be initialized. This helps with debugging environment-based configurations.

  if (!env.QUEUE_SYSTEM_ENABLED) {
+    this.logger.warn(`Queue ${queueName} not initialized because QUEUE_SYSTEM_ENABLED is false`);
    return;
  }

26-33: Validate the presence of queue configuration.

Throwing an error when the queue configuration is not found is valid. However, consider adding more context or suggestions on how to fix the configuration to reduce confusion for the developer or operator.


64-69: Use a more generic log message for base worker.

The message references "Webhook job," which might not always be accurate if the service is extended for non-webhook jobs. Consider including the this.queueName in the log message or using a more general message to avoid confusion.

- this.logger.log(`Webhook job ${job.id} is active`);
+ this.logger.log(`Queue ${this.queueName} - job ${job.id} is active`);

75-96: Consider capturing stack traces or extended diagnostic info on job failures.

When a job fails, you're logging job IDs and attempts. For deeper diagnostics, capture or link to logs containing the full error stack, particularly when the job is permanently failing. This improves troubleshooting.

services/workflows-service/src/bull-mq/queues/incoming-webhook-queue.service.ts (1)

8-13: Add validation for IncomingWebhookData interface.

The interface lacks validation for required fields and type constraints.

Consider using class-validator decorators:

import { IsString, IsObject, IsFunction } from 'class-validator';

class IncomingWebhookData {
  @IsString()
  source: string;

  @IsObject()
  payload: Record<string, unknown>;

  @IsFunction()
  service: (payload: Record<string, unknown>) => Promise<void>;
}
services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (1)

56-56: Extract timeout value to a constant.

The timeout value of 15000ms is duplicated. Consider extracting it to a constant for better maintainability.

+const WEBHOOK_TIMEOUT_MS = 15_000;
+
 export class WebhookManagerService {
   // ...
   private async sendWebhook<T>({
     // ...
-    timeout: 15_000,
+    timeout: WEBHOOK_TIMEOUT_MS,
     // ...
   }) {
     // ...
-    timeout: 15_000,
+    timeout: WEBHOOK_TIMEOUT_MS,
     // ...
   }
 }

Also applies to: 69-69

services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (1)

98-105: Extract webhook configuration to a reusable constant.

The webhook configuration object is defined inline and could be reused across different webhook calls.

+const createWebhookConfig = (url: string, payload: unknown, secret: string) => ({
+  url,
+  method: 'POST' as const,
+  headers: {},
+  body: payload,
+  timeout: 15_000,
+  secret,
+});
+
-    const webhookArgs = {
-      url,
-      method: 'POST',
-      headers: {},
-      body: payload,
-      timeout: 15_000,
-      secret: webhookSharedSecret,
-    } as const;
+    const webhookArgs = createWebhookConfig(url, payload, webhookSharedSecret);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between de13bc9 and bba7beb.

📒 Files selected for processing (9)
  • services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (2 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/queues/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/queues/incoming-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/queues/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/events/document-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-completed-webhook-caller.ts (4 hunks)
  • services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: test_windows
  • GitHub Check: test_linux
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: lint
🔇 Additional comments (11)
services/workflows-service/src/bull-mq/bull-mq.module.ts (2)

19-27: Add retry or fallback logic to ensure resilient connections to Redis.

This was highlighted in a previous review comment. If Redis is unavailable at startup, the module may fail to initialize. Consider adding retry strategies or graceful fallbacks to ensure the service remains robust.


31-34: Secure access to the Bull Board route in production.

As noted before, exposing queue monitoring at /queues without authentication or IP restriction may lead to security issues. Evaluate environment-based access control or authentication to protect this endpoint in production.

services/workflows-service/src/bull-mq/queues/base-queue-worker.service.ts (1)

136-141: Close the worker first, then pause the queue.

Pausing the queue while the worker is still processing a job may lead to unexpected states. Consider reversing the order: close the worker to ensure no new jobs are processed, then pause the queue to queue up subsequent incoming jobs until fully resumed.

Do you want me to generate a script to confirm that reversing the order doesn't break Worker internals or tests?

services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (2)

39-46: Previous review comments about error handling are still applicable.

The error handling concerns from previous reviews remain unaddressed:

  1. No specific handling for network timeouts
  2. Missing request details in error logs
  3. No retry logic for transient failures

35-37: Ensure consistent payload format for HMAC signing.

When computing the HMAC signature, the body could be either a string or an object. This inconsistency could lead to invalid signatures.

services/workflows-service/src/events/workflow-completed-webhook-caller.ts (3)

16-17: LGTM!

The new imports for environment configuration and queue service are properly scoped and align with the BullMQ integration objectives.


31-31: LGTM!

The constructor properly injects the queue service following dependency injection pattern and TypeScript best practices.


Line range hint 109-189: Critical: Fix code duplication in webhook handling.

The code contains duplicate blocks for:

  1. Payload construction
  2. Queue system enabled check
  3. Webhook arguments construction

Additionally, there's an inconsistency in handling childWorkflowsRuntimeData between the two blocks.

This issue was previously identified. Please refer to the earlier review comment that provided a solution for:

  1. Moving payload construction to a separate method
  2. Removing the duplicate queue system check
  3. Consolidating the webhook argument construction
services/workflows-service/src/events/document-changed-webhook-caller.ts (3)

16-18: LGTM!

The imports correctly reflect the architectural changes:

  1. Addition of queue service and outgoing webhooks service
  2. Removal of direct signing in favor of the service-based approach

40-41: LGTM!

The constructor properly injects both services following dependency injection pattern and TypeScript best practices.


182-220: LGTM!

The webhook handling implementation demonstrates:

  1. Clean payload construction
  2. Proper queue system integration
  3. Good separation of concerns using the outgoing webhook service
  4. Comprehensive error handling

Comment on lines +23 to +29
async handleJob(job: Job<TJobArgs>) {
await this.outgoingWebhookService.invokeWebhook(job.data.jobData);
}
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 exceptions from invokeWebhook calls gracefully.

If an external webhook fails, consider adding a retry, detailed logging, or additional fallback logic. Relying exclusively on max job attempts can obscure granular webhook errors that may be helpful for debugging or alerting.

Comment on lines 19 to 25
super(QUEUES.OUTGOING_WEBHOOKS_QUEUE.name, logger);
this.initializeWorker();
}
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

Remove redundant initializeWorker call

The base class constructor in base-queue-worker.service.ts already calls this.initializeWorker(). Calling it again in the subclass creates duplicate worker instances and event listeners. Remove this redundant call.

🔗 Analysis chain

Avoid calling initializeWorker multiple times.

The base class constructor already calls this.initializeWorker(). Re-invoking it in the subclass constructor can lead to redundant or conflicting event listeners. Confirm if this call is truly necessary.

 constructor(
   protected readonly logger: AppLoggerService,
   protected outgoingWebhookService: OutgoingWebhooksService,
 ) {
   super(QUEUES.OUTGOING_WEBHOOKS_QUEUE.name, logger);
-  this.initializeWorker();
 }
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Find the base class implementation and constructor
ast-grep --pattern 'class $_ {
  constructor($_) {
    $$$
  }
  $$$
}'

# Also search for any direct references to initializeWorker
rg "initializeWorker" -A 5

Length of output: 65840


Script:

#!/bin/bash
# Find the base class and initializeWorker implementation in bull-mq directory
cd services/workflows-service/src/bull-mq && rg -l "class.*extends" .
rg "initializeWorker" . -B 5 -A 5

Length of output: 2415

Comment on lines +21 to +37
async handleJob(job: Job<TJobsWebhookIncoming>) {
this.logger.log(`Processing webhook job ${job.id}`);

const { service: workingService, payload } = job.data.jobData;
// TODO - handle the invoking webhook job internally
}
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

Implement error handling and complete the TODO.

The handleJob method needs implementation and proper error handling:

  1. The TODO comment indicates incomplete implementation
  2. No try-catch block for error handling
  3. No validation of job data
  4. No logging of job completion status

Consider implementing like this:

   async handleJob(job: Job<TJobsWebhookIncoming>) {
     this.logger.log(`Processing webhook job ${job.id}`);
+    try {
+      const { service: workingService, payload } = job.data.jobData;
 
-    const { service: workingService, payload } = job.data.jobData;
-    //   TODO - handle the invoking webhook job internally
+      // Validate job data
+      if (!workingService || !payload) {
+        throw new Error('Invalid job data: missing required fields');
+      }
+
+      // Process the webhook
+      await workingService(payload);
+
+      this.logger.log(`Successfully processed webhook job ${job.id}`);
+    } catch (error) {
+      this.logger.error(`Failed to process webhook job ${job.id}`, error);
+      throw error; // Re-throw to mark job as failed
+    }
   }

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

const headers: RawAxiosRequestHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
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

Remove unnecessary CORS header.

The Access-Control-Allow-Origin: '*' header should not be set on outgoing requests. CORS headers are response headers used by servers to control cross-origin access.

     headers: RawAxiosRequestHeaders = {
       Accept: 'application/json',
       'Content-Type': 'application/json',
-      'Access-Control-Allow-Origin': '*',
       ...argsHeaders,
     };
📝 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
'Access-Control-Allow-Origin': '*',
headers: RawAxiosRequestHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
...argsHeaders,
};

url,
method: 'POST',
headers: {},
body: data as unknown as AnyRecord,
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

Avoid type assertion with unknown.

The type assertion data as unknown as AnyRecord is a code smell. Consider proper type validation or transformation.

-body: data as unknown as AnyRecord,
+body: validateAndTransformData(data),

+function validateAndTransformData<T>(data: T): AnyRecord {
+  // Add validation logic here
+  return data as AnyRecord;
+}

Also applies to: 68-68

Comment on lines +137 to +144
const webhookArgs = {
url,
method: 'POST',
headers: {},
body: payload,
timeout: 15_000,
secret: webhookSharedSecret,
} as const;
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

Standardize webhook arguments structure.

The webhook arguments structure varies between the two implementations. Standardize it to use a single, consistent format throughout the code.

-    const webhookArgs = {
-      url,
-      method: 'POST',
-      headers: {},
-      body: payload,
-      timeout: 15_000,
-      secret: webhookSharedSecret,
-    } as const;
+    const webhookArgs = {
+      requestConfig: {
+        url,
+        method: 'POST',
+        headers: {},
+        body: payload,
+        timeout: 15_000,
+      },
+      customerConfig: {
+        webhookSharedSecret,
+      },
+    } as const;

Also applies to: 181-188

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

♻️ Duplicate comments (1)
services/workflows-service/src/events/workflow-completed-webhook-caller.ts (1)

109-135: 🛠️ Refactor suggestion

Refactor duplicate payload construction.

The payload construction logic is duplicated. Consider extracting it into a separate method as suggested in the previous review.

🧹 Nitpick comments (10)
services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (2)

56-56: Extract timeout value to configuration.

The timeout value of 15,000ms is hardcoded. Consider moving this to a configuration value for better maintainability.

+// In your configuration file or environment variables
+WEBHOOK_TIMEOUT_MS=15000

-timeout: 15_000,
+timeout: env.WEBHOOK_TIMEOUT_MS,

76-76: Improve error type declaration.

The error catch clause uses a union type of Error | any which effectively just becomes any. Consider using a more specific error type or removing the union with any.

-} catch (error: Error | any) {
+} catch (error: Error) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 76-76: Catch clause variable type annotation must be 'any' or 'unknown' if specified.

(parse)

services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (1)

Line range hint 42-55: Consider reducing module coupling.

The module has numerous dependencies which could make it harder to maintain and test. Consider:

  1. Breaking down the module into smaller, more focused sub-modules
  2. Resolving the circular dependency with AuthModule using an interface or event-based approach
services/workflows-service/src/bull-mq/bull-mq.module.ts (1)

38-51: Conditionally register queues based on environment
If you anticipate that certain queues won't be used or want to limit queue overhead in certain deployments, consider a conditional registration approach using an environment toggle (e.g., QUEUE_SYSTEM_ENABLED). This can reduce resource usage and potential misconfigurations.

  ...Object.values(QUEUES).flatMap(queue => {
+   if (!configService.get<boolean>('QUEUE_SYSTEM_ENABLED')) {
+     return [];
+   }
    const queues: Array<Omit<RegisterQueueOptions, 'name'> & { name: string }> = [
      { name: queue.name, ...queue.config },
    ];
services/workflows-service/src/bull-mq/queues/incoming-webhook-queue.service.ts (1)

24-25: Refactor to address circular dependency
The comment hints at a potential circular dependency involving IncomingWebhooksService. Consider restructuring your modules or placing shared interfaces in a separate module to avoid cyclical references.

services/workflows-service/src/bull-mq/queues/base-queue-worker.service.ts (4)

29-31: Enhance job addition with retry options and error handling.

The addJob method should include default retry options and handle potential errors.

Consider this implementation:

-async addJob(jobData: T, metadata: TJobPayloadMetadata = {}, jobOptions = {}): Promise<void> {
-  await this.queue?.add(randomUUID(), { metadata, jobData }, jobOptions);
+async addJob(jobData: T, metadata: TJobPayloadMetadata = {}, jobOptions = {}): Promise<void> {
+  try {
+    const defaultOptions = {
+      attempts: 3,
+      backoff: {
+        type: 'exponential',
+        delay: 1000,
+      },
+    };
+    
+    await this.queue?.add(
+      randomUUID(),
+      { metadata, jobData },
+      { ...defaultOptions, ...jobOptions },
+    );
+  } catch (error) {
+    this.logger.error(`Failed to add job to queue ${this.queue.name}:`, error);
+    throw error;
+  }
+}

33-44: Add worker concurrency and rate limiting options.

The worker initialization could benefit from additional configuration options to control job processing.

Consider adding these options:

 protected initializeWorker() {
   this.worker = new Worker(this.queue.name, this.handleJob.bind(this), {
     connection: {
       host: this.configService.get('REDIS_HOST'),
       port: this.configService.get('REDIS_PORT'),
       password: this.configService.get('REDIS_PASSWORD'),
     },
+    concurrency: this.configService.get<number>('QUEUE_CONCURRENCY', 1),
+    limiter: {
+      max: this.configService.get<number>('QUEUE_RATE_LIMIT_MAX', 100),
+      duration: this.configService.get<number>('QUEUE_RATE_LIMIT_DURATION', 1000),
+    },
+    skipDelayCheck: true,
   });

   this.addWorkerListeners();
   this.addQueueListeners();
 }

58-63: Enhance failed job handling and DLQ processing.

The failed job handling could be more robust with additional context and data transformation.

Consider this implementation:

-listener: async job => {
-  if (!job?.opts.attempts || job.attemptsMade < job.opts.attempts) return;
-
-  this.logger.error(`Job ${job?.id} failed permanently. Moving to DLQ.`);
-  await this.deadLetterQueue.add(randomUUID(), job?.data);
-},
+listener: async (job, err) => {
+  if (!job?.opts.attempts || job.attemptsMade < job.opts.attempts) {
+    this.logger.warn(`Job ${job?.id} failed attempt ${job.attemptsMade}/${job.opts.attempts}:`, err);
+    return;
+  }
+
+  this.logger.error(`Job ${job?.id} failed permanently after ${job.attemptsMade} attempts:`, err);
+  
+  const dlqData = {
+    originalJob: {
+      id: job.id,
+      data: job.data,
+      opts: job.opts,
+    },
+    error: {
+      message: err.message,
+      stack: err.stack,
+    },
+    failedAt: new Date().toISOString(),
+  };
+
+  await this.deadLetterQueue.add(randomUUID(), dlqData, {
+    attempts: 1,
+    removeOnComplete: true,
+  });
+},

103-124: Improve lifecycle hooks with better error handling and cleanup.

The module lifecycle hooks could be more robust with proper error handling and cleanup.

Consider this implementation:

 async onModuleDestroy() {
+  try {
     await this.queue?.pause();
-    await Promise.all([this.worker?.close(), this.queue?.close()]);
+    await Promise.all([
+      this.worker?.close(),
+      this.queue?.close(),
+      this.deadLetterQueue?.close(),
+    ]);
 
     this.logger.log(`Queue ${this.queue.name} is paused and closed`);
+  } catch (error) {
+    this.logger.error(`Failed to cleanup queue ${this.queue.name}:`, error);
+  }
 }

 async onModuleInit() {
-  if (this.queue) {
+  try {
+    if (!this.queue) return;
+
     const isPaused = await this.queue.isPaused();
 
     if (isPaused) {
       await this.queue.resume();
     }
 
     const isPausedAfterResume = await this.queue?.isPaused();
 
     if (isPausedAfterResume) {
       this.logger.error(`Queue ${this.queue.name} is still paused after trying to resume it`);
+      throw new Error(`Failed to resume queue ${this.queue.name}`);
     }
+  } catch (error) {
+    this.logger.error(`Failed to initialize queue ${this.queue.name}:`, error);
+    throw error;
   }
 }
services/workflows-service/package.json (1)

8-8: Add Redis readiness check in setup script.

The setup script should wait for Redis to be ready before proceeding with subsequent commands. Consider adding a wait-on check for Redis similar to the DB check.

-    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis:down && npm run docker:redis && npm run db:reset && npm run seed",
+    "setup": "npm run docker:db:down && npm run docker:db && wait-on tcp:5432 && npm run docker:redis:down && npm run docker:redis && wait-on tcp:${REDIS_PORT:-6379} && npm run db:reset && npm run seed",
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bba7beb and 1a7acc9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • services/workflows-service/docker-compose.redis.yml (1 hunks)
  • services/workflows-service/package.json (4 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
  • services/workflows-service/src/alert/alert.module.ts (2 hunks)
  • services/workflows-service/src/alert/webhook-manager/webhook-http.service.ts (0 hunks)
  • services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (2 hunks)
  • services/workflows-service/src/app.module.ts (3 hunks)
  • services/workflows-service/src/bull-mq/bull-mq.module.ts (1 hunks)
  • services/workflows-service/src/bull-mq/consts.ts (1 hunks)
  • services/workflows-service/src/bull-mq/queues/base-queue-worker.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/queues/incoming-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/queues/outgoing-webhook-queue.service.ts (1 hunks)
  • services/workflows-service/src/bull-mq/types.ts (1 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
  • services/workflows-service/src/events/document-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/events/workflow-completed-webhook-caller.ts (4 hunks)
  • services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts (3 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.controller.ts (2 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (3 hunks)
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.service.ts (1 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.module.ts (1 hunks)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts (1 hunks)
  • services/workflows-service/src/workflow/workflow.module.ts (2 hunks)
  • services/workflows-service/src/workflow/workflow.service.unit.test.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • services/workflows-service/src/alert/webhook-manager/webhook-http.service.ts
🚧 Files skipped from review as they are similar to previous changes (15)
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.module.ts
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.service.ts
  • services/workflows-service/prisma/data-migrations
  • services/workflows-service/src/alert/alert.module.ts
  • services/workflows-service/src/app.module.ts
  • services/workflows-service/src/bull-mq/types.ts
  • services/workflows-service/docker-compose.redis.yml
  • services/workflows-service/src/workflow/workflow.module.ts
  • services/workflows-service/src/env.ts
  • services/workflows-service/src/bull-mq/consts.ts
  • services/workflows-service/src/webhooks/outgoing-webhooks/outgoing-webhooks.service.ts
  • services/workflows-service/src/webhooks/incoming/incoming-webhooks.controller.ts
  • services/workflows-service/src/events/document-changed-webhook-caller.ts
  • services/workflows-service/src/workflow/workflow.service.unit.test.ts
  • services/workflows-service/src/events/workflow-state-changed-webhook-caller.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build (windows-latest)
  • GitHub Check: format
🔇 Additional comments (15)
services/workflows-service/src/events/workflow-completed-webhook-caller.ts (3)

16-17: LGTM: Queue system integration setup.

The imports and constructor injection for the queue system are properly implemented.

Also applies to: 31-31


137-144: Standardize webhook arguments structure.

The webhook arguments structure varies between the two implementations.


146-146: Add QUEUE_SYSTEM_ENABLED to environment configuration template.

The QUEUE_SYSTEM_ENABLED environment variable needs to be added to the configuration template.

services/workflows-service/src/alert/webhook-manager/webhook-manager.service.ts (3)

23-24: LGTM! Clean dependency injection.

The constructor properly injects the new queue and webhook services, following dependency injection best practices.


55-55: Avoid type assertion with unknown.

The type assertion data as unknown as AnyRecord is a code smell. Consider proper type validation or transformation.


69-69: Extract timeout value to configuration.

Same issue as in the queue implementation - the timeout value should be moved to configuration.

services/workflows-service/src/webhooks/incoming/incoming-webhooks.module.ts (3)

29-29: LGTM! Clean import declarations.

The imports follow NestJS conventions and use path aliases effectively.

Also applies to: 38-38


41-41: LGTM! Module configuration follows NestJS patterns.

The controller and service are properly configured, and the service is correctly exported for use in other modules.

Also applies to: 79-79, 82-82


84-84: Verify module rename impact across the codebase.

The rename from WebhooksModule to IncomingWebhooksModule better reflects its responsibility.

Let's verify that all imports of the old module name have been updated:

✅ Verification successful

Module rename verification completed successfully

The module rename from WebhooksModule to IncomingWebhooksModule has been properly implemented across the codebase. All references are using the correct module names with a clear separation between IncomingWebhooksModule and OutgoingWebhooksModule.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to the old module name
rg -l "WebhooksModule" --type ts

Length of output: 409


Script:

#!/bin/bash
# Show the context of WebhooksModule usage in each file
rg "WebhooksModule" --type ts -C 2

Length of output: 4523

services/workflows-service/src/bull-mq/bull-mq.module.ts (2)

21-31: Add fallback or retry on Redis connection issues
Currently BullModule.forRootAsync uses only the basic Redis configuration. If Redis is unavailable or faces transient failures at startup, the module may fail to initialize. Add error handling or a retry strategy to improve connection resilience.


33-36: Secure access to Bull Board
Exposing the Bull Board at /queues can cause security or privacy concerns in a production environment. Consider restricting access via authentication, IP whitelisting, or environment-based guards to prevent unauthorized queue monitoring.

services/workflows-service/src/bull-mq/queues/outgoing-webhook-queue.service.ts (1)

27-29: Handle exceptions from invokeWebhook calls gracefully
If an external webhook fails, consider adding retries, detailed logs, or fallback logic. Relying solely on max job attempts can obscure important errors for debugging and alerting.

services/workflows-service/src/bull-mq/queues/incoming-webhook-queue.service.ts (1)

32-37: Implement error handling and complete the TODO
The handleJob method lacks a try-catch block, validation of job data, and logging of success or failure. Proper error handling ensures failed jobs can be retried or debugged effectively.

services/workflows-service/package.json (2)

35-36: LGTM! Well-structured Redis container management scripts.

The Redis scripts follow good practices:

  • Using --wait flag ensures container readiness
  • Using --volumes flag ensures proper cleanup
  • Follows the same pattern as existing DB scripts

58-60: Verify BullMQ package versions compatibility.

Please verify the compatibility between the following packages:

  • @nestjs/bullmq@^10.2.1 might require a more recent version of bullmq than ^5.13.2
  • Consider using the same version constraint style (^ vs exact) across related packages

Run this script to check the compatibility:

Also applies to: 63-63, 90-90

✅ Verification successful

BullMQ package versions are compatible

The current version of bullmq@^5.13.2 satisfies the peer dependency requirements of @nestjs/bullmq@^10.2.1. The caret (^) version constraint is appropriate as it allows for compatible minor version updates while preventing breaking changes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check BullMQ packages compatibility and latest versions

echo "Checking @nestjs/bullmq peer dependencies..."
npm info @nestjs/[email protected] peerDependencies

echo -e "\nChecking latest versions..."
npm show bullmq versions --json | jq -r '.[-5:]'
npm show @nestjs/bullmq versions --json | jq -r '.[-5:]'
npm show @bull-board/api versions --json | jq -r '.[-5:]'

Length of output: 717

Comment on lines +146 to +148
if (env.QUEUE_SYSTEM_ENABLED) {
return await this.outgoingWebhookQueueService.addJob(webhookArgs);
}
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

Remove duplicate queue system checks.

The queue system enabled check and job addition logic is duplicated. This could lead to maintenance issues and inconsistencies.

Consider restructuring the code to have a single queue system check:

    const webhookArgs = {
      requestConfig: {
        url,
        method: 'POST',
        headers: {},
        body: payload,
        timeout: 15_000,
      },
      customerConfig: {
        webhookSharedSecret,
      },
    } as const;

    if (env.QUEUE_SYSTEM_ENABLED) {
      return await this.outgoingWebhookQueueService.addJob(webhookArgs);
    }

    try {
-      // Duplicate payload construction removed...
-      if (env.QUEUE_SYSTEM_ENABLED) {
-        return await this.outgoingWebhookQueueService.addJob({...});
-      }

      const res = await this.#__axios.post(url, payload, {

Also applies to: 180-189


@common.Injectable()
export abstract class WebhookHttpService extends HttpService {}
export class WebhookHttpService {}
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

Remove empty WebhookHttpService class.

The WebhookHttpService class appears to be empty and serves no purpose. Consider removing it since the webhook functionality has been moved to dedicated services.

-@common.Injectable()
-export class WebhookHttpService {}

Comment on lines +16 to +27
export class OutgoingWebhookQueueService extends BaseQueueWorkerService<WebhookJobData> {
constructor(
@InjectQueue(QUEUES.OUTGOING_WEBHOOKS_QUEUE.name) outgoingQueue: Queue,
@InjectQueue(QUEUES.OUTGOING_WEBHOOKS_QUEUE.dlq) outgoingDLQ: Queue,
protected readonly outgoingWebhookService: OutgoingWebhooksService,
protected readonly logger: AppLoggerService,
protected readonly config: ConfigService,
) {
super(outgoingQueue, outgoingDLQ, logger, config);
}

async handleJob(job: Job<TJobArgs>) {
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

Align queue worker's type parameter with the job payload
Your base class is typed as BaseQueueWorkerService<WebhookJobData>, while your handleJob method uses Job<TJobArgs> where TJobArgs wraps WebhookJobData. This mismatch can cause confusion or type-checking complexities.

-export class OutgoingWebhookQueueService extends BaseQueueWorkerService<WebhookJobData> {
+export class OutgoingWebhookQueueService extends BaseQueueWorkerService<TJobArgs> {
   constructor(
     @InjectQueue(QUEUES.OUTGOING_WEBHOOKS_QUEUE.name) outgoingQueue: Queue,
     ...

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

Comment on lines +35 to +39
connection: {
host: this.configService.get('REDIS_HOST'),
port: this.configService.get('REDIS_PORT'),
password: this.configService.get('REDIS_PASSWORD'),
},
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 validation and error handling for Redis configuration.

The Redis connection parameters should be validated and have default values to prevent runtime errors.

Consider this implementation:

 connection: {
-  host: this.configService.get('REDIS_HOST'),
-  port: this.configService.get('REDIS_PORT'),
-  password: this.configService.get('REDIS_PASSWORD'),
+  host: this.configService.get('REDIS_HOST', 'localhost'),
+  port: this.configService.get<number>('REDIS_PORT', 6379),
+  password: this.configService.get('REDIS_PASSWORD', undefined),
+  maxRetriesPerRequest: 3,
+  enableReadyCheck: 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
connection: {
host: this.configService.get('REDIS_HOST'),
port: this.configService.get('REDIS_PORT'),
password: this.configService.get('REDIS_PASSWORD'),
},
connection: {
host: this.configService.get('REDIS_HOST', 'localhost'),
port: this.configService.get<number>('REDIS_PORT', 6379),
password: this.configService.get('REDIS_PASSWORD', undefined),
maxRetriesPerRequest: 3,
enableReadyCheck: true,
},

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.

3 participants