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/risk_rule_logic #2472

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

feature/risk_rule_logic #2472

wants to merge 13 commits into from

Conversation

Blokh
Copy link
Collaborator

@Blokh Blokh commented Jun 24, 2024

Bal 2130 epic rule engine

Summary by CodeRabbit

  • New Features

    • Introduced Notion API integration for retrieving and transforming database records.
    • Added a new RiskRuleService for validating risk rule records from Notion.
    • Implemented a Rule Engine module with custom error handling, operators, and validation logic.
    • Enhanced application with a new RuleEngineService for executing rules on form data.
  • Bug Fixes

    • Addressed dependency management by updating and adding necessary packages.
  • Tests

    • Added unit tests for the Rule Engine, covering various validation scenarios and error handling.
  • Chores

    • Updated environment configuration to include NOTION_API_KEY.

Copy link

changeset-bot bot commented Jun 24, 2024

⚠️ No Changeset found

Latest commit: b9d54e6

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 Jun 24, 2024

Walkthrough

The changes introduce a new integration with Notion API within the workflows-service. This involves adding a new environment variable, NOTION_API_KEY, updating dependencies, and implementing new services like NotionService and RiskRuleService to interact with Notion databases. Additionally, a rule engine module has been added to validate and evaluate complex business rules using custom operators and schemas, complete with error handling and validations.

Changes

Files Change Summary
.env.example Added NOTION_API_KEY environment variable.
package.json Updated dependencies to include @notionhq/client.
prisma/data-migrations Updated subproject commit reference.
src/app.module.ts Imported and included RuleEngineModule.
src/env.ts Added NOTION_API_KEY to serverEnvSchema.
src/notion.service.ts Introduced NotionService for interacting with Notion API.
src/risk-rule.service.ts Added RiskRuleService to handle and validate risk rules from Notion.
src/rule-engine/core/errors.ts Introduced custom error classes for rule engine.
src/rule-engine/core/operators/constants.ts Added operation helpers for various operations.
src/rule-engine/core/operators/enums.ts Defined constants for operations and operators.
src/rule-engine/core/operators/helpers.ts Added base operator classes with evaluation methods for specific conditions.
src/rule-engine/core/operators/schemas.ts Introduced schema definitions using zod library.
src/rule-engine/core/operators/types.ts Defined types for rule engine conditions and helpers.
src/rule-engine/rule-engine.module.ts Introduced RuleEngineModule with RuleEngineService and RuleStoreService.
src/rule-engine/rule-engine.service.ts Added RuleEngineService to execute rules on form data.
src/rule-engine/core/rule-engine.ts Added functions to validate and run rule sets.
src/rule-engine/core/test/data-helper.ts Introduced context with business information.
src/rule-engine/core/test/rule-engine.unit.test.ts Introduced unit tests for validating rule engine scenarios.

Sequence Diagrams

sequenceDiagram
    participant App
    participant NotionService
    participant NotionAPI as Notion API
    participant RuleEngineService
    participant RuleEngine
    
    App->>NotionService: getAllDatabaseRecordsValues({ databaseId })
    NotionService->>NotionAPI: Query database with databaseId
    NotionAPI-->>NotionService: Return database records
    NotionService-->>App: Return transformed records
    
    App->>RuleEngineService: run(storeOptions, formData)
    RuleEngineService->>RuleEngine: Execute rules with formData
    RuleEngine-->>RuleEngineService: Rule execution results
    RuleEngineService-->>App: Return results
Loading

Poem

In the realm of code, a new dawn appears,
With Notion and rules, dispelling dark fears.
Data from pages, in orderly queues,
Engines of logic, with value imbues.
A service now smarter, with wisdom to share,
In workflows’ embrace, code rabbits do care. 🐰


Tip

Early access features: enabled

We are currently testing the following features in early access:

  • OpenAI gpt-4o model for code reviews and chat: OpenAI claims that this model is better at understanding and generating code than the previous models. We seek your feedback over the next few weeks before making it generally available.

Note:

  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.
  • Please join our Discord Community to provide feedback and report issues.
  • OSS projects are currently opted into early access features by default.

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>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration 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: 9

Outside diff range and nitpick comments (2)
services/workflows-service/src/rule-engine/core/operators/helpers.ts (1)

188-188: Error handling in LastYear.eval.

The error thrown for unsupported data types in LastYear.eval is a good practice. However, consider adding more specific error messages or handling more types if applicable. This would enhance the robustness and user-friendliness of the rule engine.

services/workflows-service/src/rule-engine/core/rule-engine.unit.test.ts (1)

12-194: Comprehensive review of unit tests in rule-engine.unit.test.ts.

The unit tests are well-structured and cover a variety of rule evaluation scenarios. However, consider adding more detailed comments explaining the purpose of each test case, especially for complex rule sets. This would improve the maintainability and understandability of the tests.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between a9026b4 and 44e77c8.

Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing (19)
  • services/workflows-service/.env.example (1 hunks)
  • services/workflows-service/package.json (3 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
  • services/workflows-service/src/app.module.ts (2 hunks)
  • services/workflows-service/src/env.ts (1 hunks)
  • services/workflows-service/src/notion.service.ts (1 hunks)
  • services/workflows-service/src/risk-rule.service.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/errors.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/constants.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/enums.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/helpers.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/schemas.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/types.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/rule-engine.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/rule-engine.unit.test.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/types.ts (1 hunks)
  • services/workflows-service/src/rule-engine/rule-engine.module.ts (1 hunks)
  • services/workflows-service/src/rule-engine/rule-engine.service.ts (1 hunks)
  • services/workflows-service/src/rule-engine/rule-store.service.ts (1 hunks)
Files skipped from review due to trivial changes (4)
  • services/workflows-service/prisma/data-migrations
  • services/workflows-service/src/rule-engine/core/operators/constants.ts
  • services/workflows-service/src/rule-engine/core/operators/enums.ts
  • services/workflows-service/src/rule-engine/rule-engine.module.ts
Additional context used
Gitleaks
services/workflows-service/.env.example

36-37: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key)

Biome
services/workflows-service/src/rule-engine/core/rule-engine.ts

[error] 54-56: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)

Unsafe fix: Omit the else clause.

Additional comments not posted (18)
services/workflows-service/src/rule-engine/core/operators/types.ts (4)

1-1: Type definition for primitive values.

The definition of primitive as number | string | boolean is accurate and covers the typical primitive data types used in condition checks. This is essential for ensuring that the rule engine can handle the most common types of data it will encounter.


3-6: Type definition for BetweenParams.

The BetweenParams type is correctly defined with min and max as numbers. This is appropriate for conditions that check if a value falls between two numbers. The definition is clear and correctly restricts the parameters to numeric types, which is suitable for most between operations.
[APROVED]


8-10: Type definition for LastYearsParams.

The LastYearsParams type is well-defined with a single years property of type number. This type is likely used for operations that need to calculate or check values relative to a number of years, which is a common scenario in date-related operations.
[APROVED]


12-16: Definition of ConditionFn and IConditionHelpers.

The ConditionFn generic type is well-designed, allowing for a function that takes a primitive and a parameter of type T, and returns a boolean. This is a flexible setup that can accommodate various types of conditions. The IConditionHelpers interface maps strings to these condition functions, providing a structured way to access different condition checks by name. This setup promotes modularity and reusability in the rule engine.
[APROVED]

services/workflows-service/src/rule-engine/rule-store.service.ts (1)

1-7: Imports and type definitions.

The Injectable decorator from NestJS is correctly used, and the RuleSet type is imported from the appropriate location. The RuleStoreServiceFindAllOptions type is appropriately defined with databaseId and source, which are necessary for fetching rules from a specific source like Notion.

services/workflows-service/src/rule-engine/core/operators/schemas.ts (2)

1-3: Imports and constant definitions.

The import of the Zod library (z) and the OPERATION enum from enums.ts are correct and necessary for defining the schemas. This setup correctly establishes the dependencies needed for the schema definitions.


4-9: Definition of PrimitiveSchema and BetweenSchema.

The PrimitiveSchema is correctly defined as a union of number, string, and boolean, which are the basic data types handled by the rule engine. The BetweenSchema is well-defined with min and max as PrimitiveSchema, ensuring that the values for these properties adhere to the defined primitive types. This is crucial for validating 'between' operations accurately.
[APROVED]

services/workflows-service/src/rule-engine/rule-engine.service.ts (2)

1-5: Imports and service decoration.

The use of the Injectable decorator is correct, ensuring that the service can be managed by NestJS's dependency injection system. The imports are appropriate, bringing in necessary classes and helpers for the service's functionality.


6-18: Implementation of RuleEngineService and run method.

The run method is well-implemented, using the RuleStoreService to fetch rules and the RuleEngine to execute these rules against provided formData. This method effectively ties together the rule fetching and execution logic, providing a clean and functional interface for running rule sets.

services/workflows-service/src/rule-engine/core/types.ts (1)

23-23: Clarify the optional rules field in PassedRuleResult.

The comment suggests that the rules field should always be present, which conflicts with its optional declaration (rules?). Please confirm if this field is optional or required, and adjust the type accordingly.

services/workflows-service/src/risk-rule.service.ts (1)

19-22: Validate recursive schema definition.

Ensure that the recursive definition in RuleSetSchema is correctly implemented and will not cause runtime issues due to infinite recursion.

services/workflows-service/src/notion.service.ts (2)

14-18: Secure API key usage.

Ensure that the API key used in the Notion client is securely handled and not logged or exposed in any logs or error messages.
[SECURITY]


34-52: Optimize database content extraction logic.

Consider optimizing the loop for querying database pages to handle large datasets more efficiently and prevent potential performance issues.
[PERFORMANCE]

- do {
-   database = await this.client.databases.query({
-     database_id: databaseId,
-     ...(database?.next_cursor && { start_cursor: database.next_cursor }),
-   });
-   records.push(...database.results);
- } while (database.next_cursor);
+ // Proposed refactor to use a more efficient querying strategy
services/workflows-service/src/env.ts (1)

78-78: Validate new environment variable addition.

Ensure that the addition of NOTION_API_KEY to the environment schema is correctly implemented and documented.

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

48-48: Confirm integration of new module.

Ensure that the integration of RuleEngineModule is properly configured and does not introduce any circular dependencies or configuration issues.

Verification successful

Confirm integration of new module.

The RuleEngineModule is properly integrated in app.module.ts without introducing any circular dependencies or configuration issues.

  • services/workflows-service/src/app.module.ts: RuleEngineModule is imported and added to the imports array.
  • services/workflows-service/src/rule-engine/rule-engine.module.ts: RuleEngineModule provides and exports RuleEngineService.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify module integration and check for circular dependencies.

rg --type ts 'RuleEngineModule' -C 10

Length of output: 4295

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

64-64: Added dependency @notionhq/client.

The addition of the Notion client is in alignment with the PR's objective to integrate Notion API functionality. Ensure that the usage of this client is covered by appropriate unit tests and that its integration is secure and efficient.


77-77: Re-added dependency ballerine-nestjs-typebox.

The re-addition of this dependency should be justified in the PR description or documentation, especially if it was previously removed and then added back. This action might indicate dependency management issues or version conflicts that were resolved.

Verification successful

Re-added dependency ballerine-nestjs-typebox.

The commit history shows multiple entries related to ballerine-nestjs-typebox, including fixes and integrations. It appears that the re-addition was necessary to resolve issues and improve the workflows service.

  • Commit e34070518522975e92f80846b08edf2cbc266e5e: Updated ballerine-nestjs-typebox.
  • Commit b5ad350be4ba5c35811e13c522ed9ed4e0a83dd3: Integrated ballerine-nestjs-typebox with multiple changes and fixes.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Search commit history for removal and re-addition reasons.

# Test: Search for commits related to 'ballerine-nestjs-typebox'. Expect: Commit messages explaining the reasons.
git log --grep='ballerine-nestjs-typebox'

Length of output: 1057

services/workflows-service/src/rule-engine/core/operators/helpers.ts (1)

55-67: Review of specific operator implementations.

Each operator class (Equals, GreaterThan, LessThan, etc.) correctly implements the eval method with appropriate logic. However, ensure that these implementations are thoroughly tested, especially edge cases like type mismatches and boundary values.

Also applies to: 69-81, 83-95, 97-117, 119-139, 142-165, 167-189

@@ -34,3 +34,4 @@ COLLECTION_FLOW_URL=http://localhost:5201
WEB_UI_SDK_URL=http://localhost:5202
#HASHING_KEY_SECRET="$2b$10$FovZTB91/QQ4Yu28nvL8e."
HASHING_KEY_SECRET_BASE64=JDJiJDEwJDNFeWtwWEs4QkdiczlRaWFwLkM4Vk8=
NOTION_API_KEY=secret
Copy link
Contributor

Choose a reason for hiding this comment

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

Potential security risk with the hardcoded API key.

The addition of NOTION_API_KEY with a hardcoded value in the .env.example file poses a security risk. It's essential to ensure that sensitive keys are not exposed in version control. Consider using a placeholder or a more secure method of handling sensitive keys.

- NOTION_API_KEY=secret
+ NOTION_API_KEY=<YOUR_NOTION_API_KEY_HERE>
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
NOTION_API_KEY=secret
NOTION_API_KEY=<YOUR_NOTION_API_KEY_HERE>
Tools
Gitleaks

36-37: Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (generic-api-key)

Comment on lines +24 to +40
export class ValidationFailedError extends Error {
errors:
| Array<{
message: string;
path: string;
}>
| undefined;

constructor(key: string, operation: string, error?: ZodError) {
super(`Validation failed for '${key}', message: ${operation}`);
this.name = 'ValidationFailedError';

this.errors = error?.errors.map(zodIssue => ({
message: zodIssue.message,
path: zodIssue.path.join('.'),
}));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Improve error handling in ValidationFailedError.

The ValidationFailedError class could be improved by ensuring that the errors field is always an array, even when there are no errors to report. This can avoid potential issues in error handling where the consumer expects an array but receives undefined.

-   | undefined;
+   = [];
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 class ValidationFailedError extends Error {
errors:
| Array<{
message: string;
path: string;
}>
| undefined;
constructor(key: string, operation: string, error?: ZodError) {
super(`Validation failed for '${key}', message: ${operation}`);
this.name = 'ValidationFailedError';
this.errors = error?.errors.map(zodIssue => ({
message: zodIssue.message,
path: zodIssue.path.join('.'),
}));
}
export class ValidationFailedError extends Error {
errors:
| Array<{
message: string;
path: string;
}>
= [];
constructor(key: string, operation: string, error?: ZodError) {
super(`Validation failed for '${key}', message: ${operation}`);
this.name = 'ValidationFailedError';
this.errors = error?.errors.map(zodIssue => ({
message: zodIssue.message,
path: zodIssue.path.join('.'),
}));
}

Comment on lines +7 to +52
export abstract class BaseOperator<T = primitive> {
operator: string;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;

constructor(options: {
operator: TOperation;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
}) {
const { operator, conditionValueSchema, dataValueSchema } = options;

this.operator = operator;
this.conditionValueSchema = conditionValueSchema;
this.dataValueSchema = dataValueSchema;
}

abstract eval(dataValue: primitive, conditionValue: T): boolean;

execute(dataValue: primitive, conditionValue: T): boolean {
this.validate({ dataValue, conditionValue });

return this.eval(dataValue, conditionValue);
}

protected validate(args: { dataValue: unknown; conditionValue: unknown }) {
if (this.conditionValueSchema) {
this.validateSchema(
this.conditionValueSchema,
args.conditionValue,
`Invalid condition value`,
);
}

if (this.dataValueSchema) {
this.validateSchema(this.dataValueSchema, args.dataValue, `Invalid data value`);
}
}

protected validateSchema(schema: ZodSchema<any>, value: unknown, message: string) {
const result = schema.safeParse(value);

if (!result.success) {
throw new ValidationFailedError(this.operator, message, result.error);
}
}
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 for BaseOperator class.

The BaseOperator class is well-structured with clear separation of concerns. However, consider using generics more effectively to enforce type safety across different operator implementations. This would reduce the risk of runtime type errors and improve maintainability.

- export abstract class BaseOperator<T = primitive> {
+ export abstract class BaseOperator<T extends primitive> {
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 abstract class BaseOperator<T = primitive> {
operator: string;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
constructor(options: {
operator: TOperation;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
}) {
const { operator, conditionValueSchema, dataValueSchema } = options;
this.operator = operator;
this.conditionValueSchema = conditionValueSchema;
this.dataValueSchema = dataValueSchema;
}
abstract eval(dataValue: primitive, conditionValue: T): boolean;
execute(dataValue: primitive, conditionValue: T): boolean {
this.validate({ dataValue, conditionValue });
return this.eval(dataValue, conditionValue);
}
protected validate(args: { dataValue: unknown; conditionValue: unknown }) {
if (this.conditionValueSchema) {
this.validateSchema(
this.conditionValueSchema,
args.conditionValue,
`Invalid condition value`,
);
}
if (this.dataValueSchema) {
this.validateSchema(this.dataValueSchema, args.dataValue, `Invalid data value`);
}
}
protected validateSchema(schema: ZodSchema<any>, value: unknown, message: string) {
const result = schema.safeParse(value);
if (!result.success) {
throw new ValidationFailedError(this.operator, message, result.error);
}
}
export abstract class BaseOperator<T extends primitive> {
operator: string;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
constructor(options: {
operator: TOperation;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
}) {
const { operator, conditionValueSchema, dataValueSchema } = options;
this.operator = operator;
this.conditionValueSchema = conditionValueSchema;
this.dataValueSchema = dataValueSchema;
}
abstract eval(dataValue: primitive, conditionValue: T): boolean;
execute(dataValue: primitive, conditionValue: T): boolean {
this.validate({ dataValue, conditionValue });
return this.eval(dataValue, conditionValue);
}
protected validate(args: { dataValue: unknown; conditionValue: unknown }) {
if (this.conditionValueSchema) {
this.validateSchema(
this.conditionValueSchema,
args.conditionValue,
`Invalid condition value`,
);
}
if (this.dataValueSchema) {
this.validateSchema(this.dataValueSchema, args.dataValue, `Invalid data value`);
}
}
protected validateSchema(schema: ZodSchema<any>, value: unknown, message: string) {
const result = schema.safeParse(value);
if (!result.success) {
throw new ValidationFailedError(this.operator, message, result.error);
}
}

Comment on lines +68 to +96
public async findAll({ databaseId, source }: { databaseId: string; source: 'notion' }) {
if (source === 'notion') {
const records = await this.notionService.getAllDatabaseRecordsValues({
databaseId,
});

const validatedRecords = [];

for (const record of records) {
const validatedRecord = NotionRiskRuleRecordSchema.safeParse(record);

if (!validatedRecord.success) {
this.logger.error('Notion risk rule record schema validation failed', {
databaseId,
record,
error: validatedRecord.error,
});

continue;
}

validatedRecords.push(validatedRecord.data);
}

return validatedRecords;
}

throw new Error('Unsupported source');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Error handling and logging enhancement.

When handling errors from external services, consider adding retry logic or more detailed error handling to improve resilience and diagnostic capabilities.

- throw new Error('Unsupported source');
+ throw new Error(`Unsupported source: ${source}. Please check the configuration.`);

Committable suggestion was skipped due to low confidence.

Comment on lines +1 to +4
import { Injectable } from '@nestjs/common';
import { NotionService } from '@/notion.service';
import z from 'zod';
import { AppLoggerService } from '@/common/app-logger/app-logger.service';
Copy link
Contributor

Choose a reason for hiding this comment

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

Organize imports for better readability.

Consider grouping imports from the same library together to enhance readability and maintainability.

- import { Injectable } from '@nestjs/common';
- import { NotionService } from '@/notion.service';
- import z from 'zod';
- import { AppLoggerService } from '@/common/app-logger/app-logger.service';
+ import { Injectable } from '@nestjs/common';
+ import z from 'zod';
+ import { NotionService, AppLoggerService } from '@/common';

Committable suggestion was skipped due to low confidence.

Comment on lines +9 to +13
@Injectable()
export class RuleStoreService {
public async findAll({ databaseId, source }: RuleStoreServiceFindAllOptions): Promise<RuleSet> {
throw new Error('Unsupported source');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Implementation of RuleStoreService and findAll method.

The findAll method currently throws an 'Unsupported source' error, which suggests that the implementation is not yet complete or is a placeholder. This needs to be addressed to ensure the service can actually retrieve data as expected.

Please implement the findAll method or handle different sources appropriately.

Comment on lines +11 to +22
export const LastYearsSchema = z.object({
years: z.number().positive(),
});

// TODO: TBD
const ruleSchema = z.object({
key: z.string(),
operation: z.literal(OPERATION.LAST_YEAR),
value: z.object({
years: z.number(),
}),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Definition of LastYearsSchema and ruleSchema.

The LastYearsSchema is appropriately defined with a positive number for years, which makes sense for operations dealing with year calculations. The ruleSchema includes a TODO comment, suggesting it is not finalized. This schema is crucial for validating rule data, and it should be completed and thoroughly tested to ensure robustness.

Please address the TODO in ruleSchema and ensure it is fully implemented and tested.

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

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 44e77c8 and f02ffc0.

Files selected for processing (4)
  • services/workflows-service/src/rule-engine/core/rule-engine.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/test/data-helper.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/test/rule-engine.unit.test.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/types.ts (1 hunks)
Additional context used
Biome
services/workflows-service/src/rule-engine/core/rule-engine.ts

[error] 53-55: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)

Unsafe fix: Omit the else clause.

Additional comments not posted (2)
services/workflows-service/src/rule-engine/core/test/data-helper.ts (1)

1-64: Well-structured test data for context.

The context object is well-defined and includes comprehensive details necessary for testing various scenarios in the rule engine. This should provide a robust basis for unit tests.

services/workflows-service/src/rule-engine/core/test/rule-engine.unit.test.ts (1)

1-246: Comprehensive unit tests for the rule engine.

The tests in rule-engine.unit.test.ts are well-structured and cover a broad range of scenarios, which is crucial for ensuring the reliability of the rule engine. Good job on covering edge cases and using mock data effectively.

export type Rule = {
key: string;
operation: TOperation;
value: any; // This should be a union of all possible values for the operation
Copy link
Contributor

Choose a reason for hiding this comment

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

Specify more precise types for the value field in the Rule type.

Currently, the value field in the Rule type is annotated with any, which is too broad and can lead to potential bugs. Considering the operations you support, it would be beneficial to define a more specific union type that includes all possible values for the operation.

// Suggested change:
value: string | number | boolean | BetweenParams | LastYearsParams;

Comment on lines +53 to +55
} else {
throw error;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove unnecessary else clause.

The else clause in the runRuleSet function is unnecessary because the previous catch block always returns early. Removing it can simplify the control flow and improve code readability.

-        } else {
-          throw error;
-        }
Committable suggestion

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

Suggested change
} else {
throw error;
}
Tools
Biome

[error] 53-55: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)

Unsafe fix: Omit the else clause.

Copy link

vercel bot commented Jul 2, 2024

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

Name Status Preview Comments Updated (UTC)
ballerine-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 2, 2024 9:32am

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

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between f02ffc0 and b9d54e6.

Files selected for processing (5)
  • services/workflows-service/src/rule-engine/core/operators/constants.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/enums.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/helpers.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/operators/types.ts (1 hunks)
  • services/workflows-service/src/rule-engine/core/test/rule-engine.unit.test.ts (1 hunks)
Files skipped from review due to trivial changes (1)
  • services/workflows-service/src/rule-engine/core/operators/enums.ts
Additional comments not posted (24)
services/workflows-service/src/rule-engine/core/operators/types.ts (6)

3-3: Consider renaming primitive to Primitive.

In TypeScript, types are usually capitalized to distinguish them from variables.

- export type primitive = number | string | boolean;
+ export type Primitive = number | string | boolean;

5-8: Consider renaming BetweenParams to BetweenParamsType.

This will help to clearly indicate that it is a type definition.

- export type BetweenParams = {
+ export type BetweenParamsType = {

10-12: Consider renaming LastYearsParams to LastYearsParamsType.

This will help to clearly indicate that it is a type definition.

- export type LastYearsParams = {
+ export type LastYearsParamsType = {

14-16: Consider renaming ExistsParams to ExistsParamsType.

This will help to clearly indicate that it is a type definition.

- export type ExistsParams = {
+ export type ExistsParamsType = {

18-18: Consider renaming ConditionFn to ConditionFunction.

This will help to clearly indicate that it is a function type.

- export type ConditionFn<T = primitive> = (value: primitive, param: T) => boolean;
+ export type ConditionFunction<T = primitive> = (value: primitive, param: T) => boolean;

20-22: Consider renaming IConditionHelpers to ConditionHelpers.

The I prefix is not necessary and does not add value.

- export interface IConditionHelpers {
+ export interface ConditionHelpers {
services/workflows-service/src/rule-engine/core/operators/constants.ts (1)

1-1: Consider removing EXISTS from the import statement.

The EXISTS constant is not used in this file.

- import { EQUALS, BETWEEN, LT, LTE, GT, GTE, LAST_YEAR, EXISTS } from './helpers';
+ import { EQUALS, BETWEEN, LT, LTE, GT, GTE, LAST_YEAR } from './helpers';
services/workflows-service/src/rule-engine/core/operators/helpers.ts (8)

56-68: No issues found in the Equals class.

The class is well-structured and implements the eval method correctly.


70-82: No issues found in the GreaterThan class.

The class is well-structured and implements the eval method correctly.


84-96: No issues found in the LessThan class.

The class is well-structured and implements the eval method correctly.


98-118: No issues found in the GreaterThanOrEqual class.

The class is well-structured and implements the eval method correctly.


120-141: No issues found in the LessThanOrEqual class.

The class is well-structured and implements the eval method correctly.


143-165: No issues found in the Between class.

The class is well-structured and implements the eval method correctly.


168-191: No issues found in the LastYear class.

The class is well-structured and implements the eval method correctly.


193-211: No issues found in the Exists class.

The class is well-structured and implements the eval method correctly.

services/workflows-service/src/rule-engine/core/test/rule-engine.unit.test.ts (9)

19-64: No issues found in the should validate a simple rule set test case.

The test case is well-structured and verifies the validation of a simple rule set correctly.


66-84: No issues found in the should handle missing key in rule test case.

The test case is well-structured and verifies the handling of a missing key in the rule correctly.


86-112: No issues found in the should throw an error for unknown operation test case.

The test case is well-structured and verifies the handling of an unknown operation correctly.


114-129: No issues found in the should fail for incorrect value test case.

The test case is well-structured and verifies the handling of an incorrect value correctly.


131-146: No issues found in the should validate custom operation with additional params test case.

The test case is well-structured and verifies the validation of a custom operation with additional params correctly.


148-165: No issues found in the should fail custom operation with missing additional params test case.

The test case is well-structured and verifies the handling of a custom operation with missing additional params correctly.


167-192: No issues found in the should throw MissingKeyError when rule is missing key field test case.

The test case is well-structured and verifies the handling of a missing key field in the rule correctly.


194-246: No issues found in the should resolve a nested property from context test case.

The test case is well-structured and verifies the resolution of a nested property from the context correctly.


247-378: No issues found in the exists operator test suite.

The test suite is well-structured and verifies the functionality of the exists operator correctly.

Comment on lines +5 to +14
export const operationHelpers = {
[OPERATION.EQUALS]: EQUALS,
[OPERATION.BETWEEN]: BETWEEN,
[OPERATION.GT]: GT,
[OPERATION.GTE]: GTE,
[OPERATION.LT]: LT,
[OPERATION.LTE]: LTE,
[OPERATION.LAST_YEAR]: LAST_YEAR,
[OPERATION.EXISTS]: EXISTS,
} 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.

Consider removing EXISTS from the operationHelpers constant.

The EXISTS operation is not defined in the OPERATION enum.

-  [OPERATION.EXISTS]: EXISTS,
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 operationHelpers = {
[OPERATION.EQUALS]: EQUALS,
[OPERATION.BETWEEN]: BETWEEN,
[OPERATION.GT]: GT,
[OPERATION.GTE]: GTE,
[OPERATION.LT]: LT,
[OPERATION.LTE]: LTE,
[OPERATION.LAST_YEAR]: LAST_YEAR,
[OPERATION.EXISTS]: EXISTS,
} as const;
export const operationHelpers = {
[OPERATION.EQUALS]: EQUALS,
[OPERATION.BETWEEN]: BETWEEN,
[OPERATION.GT]: GT,
[OPERATION.GTE]: GTE,
[OPERATION.LT]: LT,
[OPERATION.LTE]: LTE,
[OPERATION.LAST_YEAR]: LAST_YEAR,
} as const;

Comment on lines +8 to +54
export abstract class BaseOperator<T = primitive> {
operator: string;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;

constructor(options: {
operator: TOperation;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
}) {
const { operator, conditionValueSchema, dataValueSchema } = options;

this.operator = operator;
this.conditionValueSchema = conditionValueSchema;
this.dataValueSchema = dataValueSchema;
}

abstract eval(dataValue: primitive, conditionValue: T): boolean;

execute(dataValue: primitive, conditionValue: T): boolean {
this.validate({ dataValue, conditionValue });

return this.eval(dataValue, conditionValue);
}

protected validate(args: { dataValue: unknown; conditionValue: unknown }) {
if (this.conditionValueSchema) {
this.validateSchema(
this.conditionValueSchema,
args.conditionValue,
`Invalid condition value`,
);
}

if (this.dataValueSchema) {
this.validateSchema(this.dataValueSchema, args.dataValue, `Invalid data value`);
}
}

protected validateSchema(schema: ZodSchema<any>, value: unknown, message: string) {
const result = schema.safeParse(value);

if (!result.success) {
throw new ValidationFailedError(this.operator, message, result.error);
}
}
}
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 for BaseOperator class.

Consider using generics more effectively to enforce type safety across different operator implementations.

- export abstract class BaseOperator<T = primitive> {
+ export abstract class BaseOperator<T extends primitive> {
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 abstract class BaseOperator<T = primitive> {
operator: string;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
constructor(options: {
operator: TOperation;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
}) {
const { operator, conditionValueSchema, dataValueSchema } = options;
this.operator = operator;
this.conditionValueSchema = conditionValueSchema;
this.dataValueSchema = dataValueSchema;
}
abstract eval(dataValue: primitive, conditionValue: T): boolean;
execute(dataValue: primitive, conditionValue: T): boolean {
this.validate({ dataValue, conditionValue });
return this.eval(dataValue, conditionValue);
}
protected validate(args: { dataValue: unknown; conditionValue: unknown }) {
if (this.conditionValueSchema) {
this.validateSchema(
this.conditionValueSchema,
args.conditionValue,
`Invalid condition value`,
);
}
if (this.dataValueSchema) {
this.validateSchema(this.dataValueSchema, args.dataValue, `Invalid data value`);
}
}
protected validateSchema(schema: ZodSchema<any>, value: unknown, message: string) {
const result = schema.safeParse(value);
if (!result.success) {
throw new ValidationFailedError(this.operator, message, result.error);
}
}
}
export abstract class BaseOperator<T extends primitive> {
operator: string;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
constructor(options: {
operator: TOperation;
conditionValueSchema?: ZodSchema<any>;
dataValueSchema?: ZodSchema<any>;
}) {
const { operator, conditionValueSchema, dataValueSchema } = options;
this.operator = operator;
this.conditionValueSchema = conditionValueSchema;
this.dataValueSchema = dataValueSchema;
}
abstract eval(dataValue: primitive, conditionValue: T): boolean;
execute(dataValue: primitive, conditionValue: T): boolean {
this.validate({ dataValue, conditionValue });
return this.eval(dataValue, conditionValue);
}
protected validate(args: { dataValue: unknown; conditionValue: unknown }) {
if (this.conditionValueSchema) {
this.validateSchema(
this.conditionValueSchema,
args.conditionValue,
`Invalid condition value`,
);
}
if (this.dataValueSchema) {
this.validateSchema(this.dataValueSchema, args.dataValue, `Invalid data value`);
}
}
protected validateSchema(schema: ZodSchema<any>, value: unknown, message: string) {
const result = schema.safeParse(value);
if (!result.success) {
throw new ValidationFailedError(this.operator, message, result.error);
}
}
}

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.

None yet

4 participants