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

Mail channel first iteration #1620

Merged
merged 27 commits into from
Dec 9, 2024

Conversation

hasinaxp
Copy link
Contributor

@hasinaxp hasinaxp commented Dec 9, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced mail channel processing and reading commands in the CLI.
    • Added new API endpoints for retrieving mail channel logs and scheduling mail reading.
    • Implemented batch processing of messages via a specified bot.
  • Bug Fixes

    • Enhanced validation and error handling in various components related to email processing.
  • Tests

    • Comprehensive test coverage added for mail processing and reading functionalities, including unit tests for new methods and endpoints.
  • Documentation

    • Updated configuration files to include new parameters for mail handling.
  • Chores

    • Added new dependencies for email processing capabilities.

Copy link

coderabbitai bot commented Dec 9, 2024

Walkthrough

This pull request introduces significant enhancements to the Kairon framework, particularly in the area of email handling. It adds new command-line interface (CLI) commands for processing and reading emails, integrates new API endpoints for retrieving mail channel logs, and introduces several new classes and methods for managing email events and processing. Additionally, it includes extensive test coverage for the new functionalities, ensuring robustness and reliability in handling email-related tasks.

Changes

File Change Summary
kairon/__init__.py Added imports and methods for mail_channel_process and mail_channel_read subparsers.
kairon/api/app/routers/bot/bot.py Introduced new endpoint get_mail_channel_logs for retrieving mail channel logs.
kairon/chat/agent_processor.py Added method get_agent_without_cache to retrieve agents without using cache.
kairon/chat/utils.py Added method process_messages_via_bot for processing multiple messages through a bot.
kairon/cli/mail_channel_process.py New file defining CLI command for processing mail channels with associated methods.
kairon/cli/mail_channel_read.py New file defining CLI command for reading emails with associated methods.
kairon/events/definitions/factory.py Added new event mappings for MailProcessEvent and MailReadEvent.
kairon/events/definitions/mail_channel.py Introduced MailProcessEvent and MailReadEvent classes with relevant methods.
kairon/events/server.py Added new endpoint for scheduling mail reading associated with a bot.
kairon/events/utility.py Added methods for scheduling mail reading events and rescheduling for all bots.
kairon/shared/channels/mail/constants.py Introduced MailConstants class with email configuration constants.
kairon/shared/channels/mail/data_objects.py New file defining classes for mail channel state and logging.
kairon/shared/channels/mail/processor.py New file defining MailProcessor class for handling email operations.
kairon/shared/channels/mail/scheduler.py New file defining MailScheduler class for scheduling mail reading.
kairon/shared/chat/processor.py Modified save_channel_config to handle MAIL connector type.
kairon/shared/constants.py Added new enumeration members related to email handling.
kairon/shared/data/data_models.py Added ScheduleActionRequest class with validation logic.
metadata/integrations.yml Added new mail integration channel with required fields.
requirements/prod.txt Added new dependencies for email handling.
system.yaml Updated URL for the pyscript evaluator.
tests/integration_test/event_service_test.py Added tests for the new mail scheduling endpoint.
tests/integration_test/services_test.py Updated assertions in various test functions.
tests/testing_data/system.yaml Added new configuration parameters for mail handling.
tests/unit_test/channels/mail_channel_test.py Added unit tests for MailProcessor class functionalities.
tests/unit_test/channels/mail_scheduler_test.py Added unit tests for MailScheduler functionality.
tests/unit_test/chat/chat_test.py Added tests for processing messages via a bot.
tests/unit_test/cli_test.py Introduced tests for mail channel CLI handling.
tests/unit_test/data_processor/agent_processor_test.py Added test for get_agent_without_cache method.
tests/unit_test/events/definitions_test.py Added tests for MailProcessEvent and MailReadEvent.
tests/unit_test/utility_test.py Enhanced tests for email verification and message converters.

Possibly related PRs

  • content importer fix #1527: The changes in this PR also modify the create_argument_parser function in kairon/__init__.py by adding a new subparser for the content_importer, similar to how the main PR adds subparsers for mail_channel_process and mail_channel_read.

Suggested reviewers

  • hiteshghuge

Poem

🐰 In the meadow where emails flow,
New features sprout and start to grow.
With a hop and a skip, we process with glee,
Mail channels now dance, oh what a spree!
So let’s cheer for the code, both bright and bold,
In Kairon’s world, new stories unfold! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

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

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

🧹 Outside diff range and nitpick comments (32)
tests/unit_test/data_processor/data_processor_test.py (2)

3988-3997: Remove duplicate assertion and optimize dict operations

There are a few improvements that can be made to this test segment:

  1. Line 3999 contains a duplicate assertion for domain.forms.__len__() == 2
  2. The .keys() method call is unnecessary when checking length

Apply this diff to improve the code:

-assert domain.responses.keys().__len__() == 29
+assert len(domain.responses) == 29
-assert domain.forms.__len__() == 2
-assert domain.forms.__len__() == 2
+assert domain.forms.__len__() == 2
🧰 Tools
🪛 Ruff (0.8.0)

3992-3992: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


3994-3994: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7133-7142: Consider refactoring repetitive test patterns

There are multiple test methods with similar assertion patterns. Consider:

  1. Creating helper methods for common assertion patterns
  2. Using parameterized tests for different scenarios
  3. Consistently using len() instead of __len__() and avoiding .keys()

Example helper method:

def assert_domain_properties(self, domain, expected_counts):
    """
    Helper to verify domain properties
    
    Args:
        domain: Domain instance to verify
        expected_counts: dict with expected counts for slots, entities, etc.
    """
    assert len(domain.slots) == expected_counts['slots']
    assert len(domain.entities) == expected_counts['entities']
    assert len(domain.responses) == expected_counts['responses']
    assert len(domain.intents) == expected_counts['intents']

Example usage:

self.assert_domain_properties(domain, {
    'slots': 27,
    'entities': 27,
    'responses': 29,
    'intents': 32
})

Also applies to: 7198-7200, 7278-7287, 7343-7352, 7393-7402, 7451-7460, 7496-7505, 7551-7554, 7629-7632

🧰 Tools
🪛 Ruff (0.8.0)

7137-7137: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7139-7139: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

kairon/events/definitions/mail_channel.py (2)

90-90: Replace print statement with appropriate logging

Using print statements is not recommended for logging in production code. Replace it with a logging call to maintain consistent logging practices.

Apply this diff to fix the issue:

-90     print(vals)
+90     logger.debug(f"Mail read values: {vals}")

57-59: Correct typo in docstring

There's a typo in the docstring. Replace "tp" with "to" for clarity.

Apply this diff to fix the issue:

57     """
-58     Event to read mails from mail channel and create events for each mail tp process them via bot
+58     Event to read mails from mail channel and create events for each mail to process them via bot
59     """
kairon/shared/channels/mail/processor.py (1)

127-131: Simplify complex list comprehension for readability

The multi-level list comprehension used to parse slots reduces readability. Consider refactoring it into a standard loop for better clarity.

Suggested refactor:

slots = {}
for slot_str in rasa_chat_response.get('slots', []):
    split_result = slot_str.split(":", 1)
    if len(split_result) == 2:
        key, value = split_result
        slots[key.strip()] = value.strip()
tests/unit_test/channels/mail_channel_test.py (4)

1-1: Remove unused import 'asyncio'

The asyncio module is imported but not used in this file. Consider removing it to clean up the imports.

Apply this diff to fix the issue:

-1     import asyncio
🧰 Tools
🪛 Ruff (0.8.0)

1-1: asyncio imported but unused

Remove unused import: asyncio

(F401)


9-9: Remove unused import 'uuid7'

The uuid7 function is imported but not used in this file. Consider removing it to clean up the imports.

Apply this diff to fix the issue:

-9     from uuid6 import uuid7
🧰 Tools
🪛 Ruff (0.8.0)

9-9: uuid6.uuid7 imported but unused

Remove unused import: uuid6.uuid7

(F401)


14-14: Use capitalized environment variable name

Environment variable names are conventionally uppercase. Replace system_file with SYSTEM_FILE for consistency.

Apply this diff to fix the issue:

-14     os.environ["system_file"] = "./tests/testing_data/system.yaml"
+14     os.environ["SYSTEM_FILE"] = "./tests/testing_data/system.yaml"
🧰 Tools
🪛 Ruff (0.8.0)

14-14: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)


484-485: Simplify nested with statements and remove unused variable

You can combine multiple context managers into a single with statement for better readability. Also, the variable mock_save is assigned but not used; consider removing it.

Apply this diff to fix the issue:

-484     with patch.object(MailChannelStateData, 'save', return_value=None) as mock_save:
-485         with patch('kairon.shared.channels.mail.data_objects.MailChannelStateData', return_value=mock_state):
+484     with patch.object(MailChannelStateData, 'save', return_value=None), \
+          patch('kairon.shared.channels.mail.data_objects.MailChannelStateData', return_value=mock_state):
🧰 Tools
🪛 Ruff (0.8.0)

484-485: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


484-484: Local variable mock_save is assigned to but never used

Remove assignment to unused variable mock_save

(F841)

tests/unit_test/utility_test.py (2)

Line range hint 2668-2669: Consider adding an assertion to confirm expected behavior

In the test_verify_email_disable method, consider adding an assertion to explicitly verify that email verification passes without exceptions when verification is disabled. This will enhance test clarity and ensure the test accurately reflects the expected outcome.


Line range hint 2750-2785: Consider adding an assertion to confirm successful email verification

In the test_verify_email_enable_valid_email method, consider adding an explicit assertion to confirm that no exception is raised for valid emails. While the absence of an exception implies success, an assertion improves test readability and clarity.

kairon/cli/mail_channel_read.py (1)

20-26: Add input validation for bot and user arguments

The CLI arguments for bot and user are accepted without any validation. Consider adding validation patterns to ensure the input meets expected formats.

     mail_parser.add_argument('bot',
                              type=str,
-                             help="Bot id for which command is executed", action='store')
+                             help="Bot id for which command is executed (alphanumeric)",
+                             action='store',
+                             pattern=r'^[a-zA-Z0-9_-]+$')

     mail_parser.add_argument('user',
                             type=str,
-                             help="Kairon user who is initiating the command", action='store')
+                             help="Kairon user who is initiating the command (email format)",
+                             action='store',
+                             pattern=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
kairon/shared/channels/mail/data_objects.py (2)

9-19: Add data validation and index optimization

The MailChannelStateData class could benefit from additional validation and index optimization.

 class MailChannelStateData(Document):
-    event_id = StringField()
+    event_id = StringField(required=True, unique_with='bot')
     last_email_uid = IntField(default=0)
     bot = StringField(required=True)
     timestamp = FloatField(default=time.time())

-    meta = {"indexes": ["bot"]}
+    meta = {
+        "indexes": [
+            "bot",
+            {"fields": ["bot", "timestamp"], "expire_after_seconds": 2592000}  # 30 days
+        ],
+        "allow_inheritance": True
+    }

21-24: Consider adding intermediate status for better tracking

The MailStatus enum could benefit from additional states for better tracking of email processing stages.

 class MailStatus(Enum):
     Processing = "processing"
+    VALIDATING = "validating"
+    SENDING = "sending"
     SUCCESS = "success"
     FAILED = "failed"
+    RETRYING = "retrying"
kairon/events/definitions/factory.py (1)

24-26: Consider grouping related events together

The mail channel events could be grouped with other channel-related events for better organization.

Consider reordering the events:

     __events = {
         EventClass.model_training: ModelTrainingEvent,
         EventClass.model_testing: ModelTestingEvent,
         EventClass.delete_history: DeleteHistoryEvent,
         EventClass.data_importer: TrainingDataImporterEvent,
         EventClass.multilingual: MultilingualEvent,
         EventClass.faq_importer: FaqDataImporterEvent,
         EventClass.message_broadcast: MessageBroadcastEvent,
+        EventClass.mail_channel_read_mails: MailReadEvent,
+        EventClass.mail_channel_process_mails: MailProcessEvent,
         EventClass.content_importer: DocContentImporterEvent,
-        EventClass.mail_channel_read_mails: MailReadEvent,
-        EventClass.mail_channel_process_mails: MailProcessEvent
     }
metadata/integrations.yml (1)

78-87: Consider enhancing mail channel configuration for security and completeness

The mail channel configuration could benefit from additional fields for secure and robust email handling:

  1. Security:

    • Add SSL/TLS configuration options for both IMAP and SMTP
    • Consider adding OAuth2 support as an alternative to password authentication
  2. Missing configurations:

    • Add IMAP port configuration (currently only SMTP port is specified)
    • Add connection timeout settings
    • Add max email size limit
  3. Documentation needed:

    • Clarify the purpose and format of the 'interval' field (is it polling interval in seconds?)
    • Clarify the purpose of the 'intent' field

Would you like me to propose an enhanced configuration structure with these additions?

kairon/__init__.py (2)

47-48: Consider splitting long import statement for better readability

The import statement is becoming lengthy. Consider splitting it across multiple lines:

-from kairon.cli import importer, training, testing, conversations_deletion, translator, delete_logs,\
-        message_broadcast,content_importer, mail_channel_process, mail_channel_read
+from kairon.cli import (
+    importer,
+    training,
+    testing,
+    conversations_deletion,
+    translator,
+    delete_logs,
+    message_broadcast,
+    content_importer,
+    mail_channel_process,
+    mail_channel_read,
+)

Line range hint 1-46: Update CLI documentation to include mail channel commands

The docstring needs to be updated to include documentation for the new mail channel commands.

Add the following to the docstring:

    Message broadcast:
        kairon broadcast <botid> <userid> <eventid> <is_resend>
    
+   Mail channel processing:
+       kairon mail-channel-process <botid> <userid>
+   
+   Mail channel reading:
+       kairon mail-channel-read <botid> <userid>
+
    Delete audit logs:
        kairon delete-logs
kairon/chat/agent_processor.py (1)

82-99: Consider performance implications of uncached agents

The method bypasses the cache mechanism which could lead to:

  1. Increased memory usage if called frequently
  2. Slower response times due to loading the model each time
  3. Potential resource exhaustion

Consider:

  1. Adding a warning log when this method is called
  2. Documenting the performance implications
  3. Adding metrics to track usage
tests/unit_test/channels/mail_scheduler_test.py (4)

8-8: Use uppercase for environment variable name

Environment variables should use uppercase naming convention.

-os.environ["system_file"] = "./tests/testing_data/system.yaml"
+os.environ["SYSTEM_FILE"] = "./tests/testing_data/system.yaml"
🧰 Tools
🪛 Ruff (0.8.0)

8-8: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)


23-23: Extract magic numbers into named constants

The mock response contains magic numbers that should be extracted into named constants for better maintainability.

+# At the top of the file
+DEFAULT_MAIL_CHECK_INTERVAL = 60
+DEFAULT_TEST_EMAIL = '[email protected]'

-        mock_read_mails.return_value = ([], '[email protected]', 60)  # Mock responses and next_delay
+        mock_read_mails.return_value = ([], DEFAULT_TEST_EMAIL, DEFAULT_MAIL_CHECK_INTERVAL)

37-45: Add docstrings to test functions

Test functions should have docstrings describing the test scenario and expected behavior.

Example for the first test:

 def test_request_epoch_success(mock_execute_http_request, mock_get_event_server_url, mock_imp, mock_smpt):
+    """
+    Test successful execution of request_epoch:
+    - Given: A valid bot ID and successful HTTP response
+    - When: request_epoch is called
+    - Then: No exception should be raised
+    """

Also applies to: 50-55, 60-65


83-83: Remove commented line number

Remove the commented line number as it's not providing any value.

-#     # Test case when event_id is None
+    # Test case when event_id is None
tests/unit_test/data_processor/agent_processor_test.py (1)

138-143: Add docstring to the test method.

The test implementation looks good and follows the existing patterns. Consider adding a docstring to document the test's purpose and expectations.

 def test_get_agent_no_cache(self):
+    """
+    Test retrieval of agent without using cache.
+    
+    Verifies that:
+    - Agent is successfully retrieved
+    - Training history exists
+    - Model version is valid
+    """
     model = AgentProcessor.get_agent_without_cache(pytest.bot, False)
     assert model
     assert len(list(ModelProcessor.get_training_history(pytest.bot))) == 1
     assert not Utility.check_empty_string(model.model_ver)
tests/integration_test/event_service_test.py (1)

536-545: Add test cases for error scenarios.

The current test only covers the success path. Consider adding test cases for:

  1. Invalid bot ID
  2. Failed scheduling scenarios
  3. Error response structure
@patch('kairon.events.utility.EventUtility.schedule_channel_mail_reading')
def test_request_epoch_invalid_bot(mock_epoch):
    mock_epoch.side_effect = ValueError("Invalid bot")
    response = client.get('/api/mail/schedule/invalid_bot')
    assert response.status_code == 400
    resp = response.json()
    assert not resp['success']
    assert "Invalid bot" in resp['message']

@patch('kairon.events.utility.EventUtility.schedule_channel_mail_reading')
def test_request_epoch_scheduling_failed(mock_epoch):
    mock_epoch.side_effect = Exception("Scheduling failed")
    response = client.get('/api/mail/schedule/test_bot')
    assert response.status_code == 500
    resp = response.json()
    assert not resp['success']
    assert "Scheduling failed" in resp['message']
tests/unit_test/cli_test.py (3)

321-326: Use capitalized environment variable name.

Follow Python naming conventions for environment variables.

-os.environ["system_file"] = "./tests/testing_data/system.yaml"
+os.environ["SYSTEM_FILE"] = "./tests/testing_data/system.yaml"
🧰 Tools
🪛 Ruff (0.8.0)

323-323: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)


340-352: Combine nested with statements.

Improve code readability by combining the nested with statements.

-        with patch('argparse.ArgumentParser.parse_args',
-                   return_value=argparse.Namespace(func=process_channel_mails,
-                                                   bot="test_bot",
-                                                    user="test_user", mails=data)):
-            with pytest.raises(ValueError):
-                cli()
+        with (
+            patch('argparse.ArgumentParser.parse_args',
+                  return_value=argparse.Namespace(func=process_channel_mails,
+                                                bot="test_bot",
+                                                user="test_user", mails=data)),
+            pytest.raises(ValueError)
+        ):
+            cli()
🧰 Tools
🪛 Ruff (0.8.0)

345-349: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


353-361: Add test cases for mail read failures.

Consider adding test cases for:

  1. Invalid bot configuration
  2. Mail server connection failures
  3. Authentication errors
@mock.patch("kairon.cli.mail_channel_read.MailReadEvent.execute")
def test_start_mail_channel_read_invalid_config(self, mock_execute):
    mock_execute.side_effect = ValueError("Invalid mail configuration")
    with patch('argparse.ArgumentParser.parse_args',
               return_value=argparse.Namespace(func=read_channel_mails,
                                             bot="test_bot",
                                             user="test_user")):
        with pytest.raises(ValueError):
            cli()
    mock_execute.assert_called_once()

@mock.patch("kairon.cli.mail_channel_read.MailReadEvent.execute")
def test_start_mail_channel_read_connection_error(self, mock_execute):
    mock_execute.side_effect = ConnectionError("Failed to connect to mail server")
    with patch('argparse.ArgumentParser.parse_args',
               return_value=argparse.Namespace(func=read_channel_mails,
                                             bot="test_bot",
                                             user="test_user")):
        with pytest.raises(ConnectionError):
            cli()
    mock_execute.assert_called_once()
kairon/shared/data/data_models.py (2)

1343-1344: Class documentation needed

Add docstring to explain the purpose and usage of ScheduleActionRequest class.

class ScheduleActionRequest(BaseModel):
+    """
+    Data model for validating schedule action requests.
+    
+    Attributes:
+        name: Name of the schedule action
+        schedule_time: Time when action should be scheduled
+        timezone: Timezone for scheduling
+        schedule_action: Action to be executed
+        response_text: Optional response text
+        params_list: Optional list of parameters
+        dispatch_bot_response: Whether to dispatch bot response
+    """

Line range hint 1343-1363: Consider adding field descriptions and type hints

Add field descriptions using Pydantic's Field class to improve code documentation and type safety.

class ScheduleActionRequest(BaseModel):
-    name: constr(to_lower=True, strip_whitespace=True)
-    schedule_time: CustomActionDynamicParameterModel
-    timezone: str = None
-    schedule_action: str
-    response_text: Optional[str]
-    params_list: Optional[List[HttpActionParameters]]
-    dispatch_bot_response: bool = True
+    name: constr(to_lower=True, strip_whitespace=True) = Field(
+        ..., description="Name of the schedule action")
+    schedule_time: CustomActionDynamicParameterModel = Field(
+        ..., description="Time when action should be scheduled")
+    timezone: str = Field(
+        None, description="Timezone for scheduling")
+    schedule_action: str = Field(
+        ..., description="Action to be executed")
+    response_text: Optional[str] = Field(
+        None, description="Optional response text")
+    params_list: Optional[List[HttpActionParameters]] = Field(
+        None, description="Optional list of parameters")
+    dispatch_bot_response: bool = Field(
+        True, description="Whether to dispatch bot response")
tests/unit_test/events/definitions_test.py (2)

1258-1259: Remove unused import

The patch import is already available from the top-level imports.

-        from unittest.mock import patch
🧰 Tools
🪛 Ruff (0.8.0)

1258-1258: unittest.mock.patch imported but unused

Remove unused import: unittest.mock.patch

(F401)


1307-1333: Add docstrings to test methods

Add descriptive docstrings to test methods to explain test scenarios and expectations.

    @patch('kairon.shared.channels.mail.processor.MailProcessor.read_mails')
    @patch('kairon.events.definitions.mail_channel.MailProcessEvent.enqueue')
    @patch('kairon.events.definitions.mail_channel.MailProcessEvent.validate')
    def test_mail_read_event_execute(self, mock_validate, mock_enqueue, mock_read_mails):
+        """
+        Test successful execution of mail read event:
+        1. Verify mails are read correctly
+        2. Verify validation is performed
+        3. Verify enqueue is called with correct parameters
+        """
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d183b03 and 97752a2.

📒 Files selected for processing (32)
  • kairon/__init__.py (2 hunks)
  • kairon/api/app/routers/bot/bot.py (2 hunks)
  • kairon/chat/agent_processor.py (1 hunks)
  • kairon/chat/utils.py (1 hunks)
  • kairon/cli/mail_channel_process.py (1 hunks)
  • kairon/cli/mail_channel_read.py (1 hunks)
  • kairon/events/definitions/factory.py (2 hunks)
  • kairon/events/definitions/mail_channel.py (1 hunks)
  • kairon/events/server.py (2 hunks)
  • kairon/events/utility.py (2 hunks)
  • kairon/shared/channels/mail/constants.py (1 hunks)
  • kairon/shared/channels/mail/data_objects.py (1 hunks)
  • kairon/shared/channels/mail/processor.py (1 hunks)
  • kairon/shared/channels/mail/scheduler.py (1 hunks)
  • kairon/shared/chat/processor.py (1 hunks)
  • kairon/shared/constants.py (3 hunks)
  • kairon/shared/data/data_models.py (1 hunks)
  • metadata/integrations.yml (1 hunks)
  • requirements/prod.txt (1 hunks)
  • system.yaml (1 hunks)
  • tests/integration_test/action_service_test.py (3 hunks)
  • tests/integration_test/event_service_test.py (1 hunks)
  • tests/integration_test/services_test.py (6 hunks)
  • tests/testing_data/system.yaml (2 hunks)
  • tests/unit_test/channels/mail_channel_test.py (1 hunks)
  • tests/unit_test/channels/mail_scheduler_test.py (1 hunks)
  • tests/unit_test/chat/chat_test.py (2 hunks)
  • tests/unit_test/cli_test.py (2 hunks)
  • tests/unit_test/data_processor/agent_processor_test.py (1 hunks)
  • tests/unit_test/data_processor/data_processor_test.py (19 hunks)
  • tests/unit_test/events/definitions_test.py (1 hunks)
  • tests/unit_test/utility_test.py (1 hunks)
🔥 Files not summarized due to errors (2)
  • tests/integration_test/action_service_test.py: Error: Server error: no LLM provider could handle the message
  • tests/unit_test/data_processor/data_processor_test.py: Error: Server error: no LLM provider could handle the message
✅ Files skipped from review due to trivial changes (2)
  • system.yaml
  • kairon/shared/channels/mail/constants.py
🧰 Additional context used
🪛 Ruff (0.8.0)
tests/integration_test/action_service_test.py

6-6: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)

tests/unit_test/data_processor/data_processor_test.py

3992-3992: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


3994-3994: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7137-7137: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7139-7139: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7282-7282: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7284-7284: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7347-7347: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7349-7349: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7397-7397: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7399-7399: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7455-7455: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7457-7457: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7500-7500: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)


7502-7502: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

kairon/events/server.py

151-151: Do not perform function call Path in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

kairon/events/utility.py

62-62: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


72-72: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/unit_test/channels/mail_scheduler_test.py

8-8: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)

kairon/api/app/routers/bot/bot.py

1666-1667: Do not perform function call Security in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

tests/unit_test/channels/mail_channel_test.py

1-1: asyncio imported but unused

Remove unused import: asyncio

(F401)


9-9: uuid6.uuid7 imported but unused

Remove unused import: uuid6.uuid7

(F401)


14-14: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)


484-485: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


484-484: Local variable mock_save is assigned to but never used

Remove assignment to unused variable mock_save

(F841)

tests/unit_test/cli_test.py

323-323: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)


345-349: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

kairon/events/definitions/mail_channel.py

39-39: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


51-51: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


82-82: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


98-98: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/unit_test/events/definitions_test.py

1214-1215: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


1214-1214: Local variable mp is assigned to but never used

Remove assignment to unused variable mp

(F841)


1215-1215: Local variable mock_login is assigned to but never used

Remove assignment to unused variable mock_login

(F841)


1216-1216: Local variable mock_logout is assigned to but never used

Remove assignment to unused variable mock_logout

(F841)


1258-1258: unittest.mock.patch imported but unused

Remove unused import: unittest.mock.patch

(F401)


1286-1288: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

kairon/shared/channels/mail/processor.py

50-50: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


165-165: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


219-219: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (25)
tests/unit_test/data_processor/data_processor_test.py (2)

3883-3889: LGTM! Comprehensive verification of mail-related domain configuration

The test properly verifies the integration of mail channel slots and entities into the domain configuration.


9378-9387: LGTM! Well-structured mail slot configurations

The test data properly defines the new mail-related slots with appropriate properties:

  • All mail slots (mail_id, subject, body) are correctly configured to influence conversation
  • Default status is properly set
  • Deep comparison ensures exact property matching
tests/integration_test/action_service_test.py (3)

24-25: LGTM!

The import organization with visual separation looks good.


11758-11758: LGTM! Well-structured test setup

The test function is well-organized with appropriate mocks and follows good testing practices.

Also applies to: 11758-11761


11758-11761: Verify mail channel test coverage

Given that this PR implements mail channel functionality, ensure that corresponding test cases for mail channel features are also included.

✅ Verification successful

Mail channel test coverage is comprehensive

The codebase includes extensive test coverage for mail channel functionality:

  • mail_channel_test.py contains 20+ test cases covering IMAP/SMTP operations, mail processing, connection validation, and state management
  • mail_scheduler_test.py includes 7 test cases for scheduling and epoch-related functionality
  • Tests cover both success and failure scenarios for critical operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for mail channel related test files and test functions

# Search for mail channel related test files
echo "Searching for mail channel test files..."
fd -e py "mail.*test" "tests/"

# Search for mail channel related test functions
echo "Searching for mail channel test functions..."
rg -l "def test.*mail" "tests/"

Length of output: 800


Script:

#!/bin/bash
# Let's check the content of mail channel test files to verify coverage

echo "Content of mail_channel_test.py:"
rg "def test_" "tests/unit_test/channels/mail_channel_test.py" -A 1

echo -e "\nContent of mail_scheduler_test.py:"
rg "def test_" "tests/unit_test/channels/mail_scheduler_test.py" -A 1

Length of output: 3875

tests/unit_test/utility_test.py (7)

Line range hint 2671-2709: Test test_verify_email_enable_disposable_email correctly handles disposable emails

The test accurately simulates a disposable email scenario and asserts that an exception is raised as expected when a disposable email is provided.


Line range hint 2711-2748: Test test_verify_email_enable_invalid_email correctly handles invalid emails

This test effectively verifies that an exception is raised for invalid emails, ensuring robust validation in the email verification process.


2851-2852: Update to include new channels in expected_channels

The addition of "line" and "mail" to the expected_channels list in test_get_channels aligns with the newly integrated channels and ensures the test accurately reflects the updated functionality.


Line range hint 2861-2874: test_get_channels method correctly validates channel list

The test ensures that Utility.get_channels() returns the expected list of channels, including the new "line" and "mail" channels. This effectively verifies the integration of the new channels.


Line range hint 2869-2877: Timezone conversion test accurately validates functionality

The test_convertdatetime_with_timezone method correctly tests the Utility.convert_utcdate_with_timezone function, confirming that the datetime conversion from UTC to "Asia/Kolkata" timezone is accurate.


Line range hint 2808-2820: Test test_get_llm_hyperparameters validates hyperparameters correctly

The test asserts that Utility.get_llm_hyperparameters("openai") returns the correct hyperparameters, ensuring that the LLM configuration is as expected.


Line range hint 2822-2826: Proper exception handling for unsupported LLMs

The test_get_llm_hyperparameters_not_found method correctly verifies that an AppException is raised when requesting hyperparameters for an unsupported LLM, such as "gemini".

tests/integration_test/services_test.py (6)

4409-4410: Cosmetic changes look good.


4751-4751: Cosmetic changes look good.


23869-23871: Cosmetic changes look good.


8301-8301: Verify documentation for all 17 entities.

The test now expects 17 entities instead of 14, which aligns with the addition of mail-related fields. Please ensure all entities are properly documented.


9068-9069: Mail-related entity fields look good.

The addition of mail_id, subject, and body entities aligns with the mail channel implementation.

✅ Verification successful

Let me gather more information about the mail-related entities and their implementation.


Let me search for the mail entity implementation to verify the fields.


Mail-related entity fields are properly implemented

The verification confirms that mail_id, subject, and body fields are correctly implemented in the codebase:

  • MailResponseLog class in kairon/shared/channels/mail/data_objects.py defines the required fields:
    • subject = StringField()
    • body = StringField()
  • The fields are also defined as constants in kairon/shared/constants.py:
    • mail_id = "mail_id"
    • subject = "subject"
    • body = "body"
  • The fields are properly used in the mail processor (kairon/shared/channels/mail/processor.py) for handling mail responses and processing incoming mails.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of mail-related entities
# Expected: Find corresponding entity definitions and handlers

# Search for mail entity definitions
ast-grep --pattern 'class $_(Entity|Constant):
  $$$
  mail_id = $$$
  $$$
'

# Search for mail entity handlers
rg -A 5 "def (process|handle).*mail"

Length of output: 1145


Script:

#!/bin/bash
# Search for mail-related entity definitions and fields
rg -A 10 "class.*Mail" 

# Search for mail_id, subject, and body fields in test files
rg -A 5 "(mail_id|subject|body)\s*=\s*" "tests/"

# Search for mail-related constants or configurations
rg -A 3 "MAIL_" 

Length of output: 83347


Script:

#!/bin/bash
# Search for mail entity and response log definitions
rg -A 10 "class.*Mail.*Response" "kairon/"

# Search for mail_id, subject, and body fields in data objects
rg -A 5 "(mail_id|subject|body)\s*=\s*" "kairon/shared/"

Length of output: 33714


9707-9707: Verify documentation for all slots.

The test now expects:

  • 24 total slots (increased)
  • 17 default slots (increased)

These increases align with the mail channel implementation.

Also applies to: 9712-9712

requirements/prod.txt (1)

Line range hint 70-73: Fix dependency version constraints

Several dependencies lack version constraints, which could lead to breaking changes or security issues:

  1. google-generativeai should have a fixed version
  2. more-itertools should have a fixed version
  3. Consider updating imap-tools to latest stable version for security fixes

Apply these version constraints:

-imap-tools==1.7.4
+imap-tools==1.8.0  # Latest stable version with security fixes
-google-generativeai
+google-generativeai==0.3.2  # Latest stable version
-more-itertools
+more-itertools==10.2.0  # Latest stable version
 python-multipart>=0.0.18

Let's verify the latest stable versions and any security advisories:

✅ Verification successful

Update dependency version constraints with latest stable versions

Based on the PyPI data and security advisories check:

-imap-tools==1.7.4
-google-generativeai
-more-itertools
+imap-tools==1.7.4  # Current version is latest stable
+google-generativeai==0.8.3  # Latest stable version
+more-itertools==10.5.0  # Latest stable version
 python-multipart>=0.0.18  # Correct minimum version for security patch
  • The current imap-tools==1.7.4 is actually the latest version, no security issues reported
  • google-generativeai and more-itertools should be pinned to their latest stable versions
  • The python-multipart>=0.0.18 constraint is correct as it ensures the version is above the security patch (CVE fixes in 0.0.18)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check PyPI for latest versions and potential security issues

for pkg in "imap-tools" "google-generativeai" "more-itertools" "python-multipart"; do
  echo "Checking $pkg..."
  curl -s "https://pypi.org/pypi/$pkg/json" | jq -r '.info.version'
  
  # Check for security advisories
  gh api graphql -f query="{
    securityVulnerabilities(first: 5, ecosystem: PIP, package: \"$pkg\") {
      nodes {
        advisory {
          summary
          severity
          publishedAt
        }
        vulnerableVersionRange
        firstPatchedVersion {
          identifier
        }
      }
    }
  }"
done

Length of output: 2872

tests/testing_data/system.yaml (1)

124-124: LGTM! Configuration follows best practices.

The mail-related configurations are well-structured:

  • Follow existing naming patterns
  • Use environment variables with sensible defaults
  • Properly placed in relevant sections

Also applies to: 144-144

kairon/chat/utils.py (1)

46-66: LGTM! Well-documented method signature.

The method signature is clear and the docstring provides good documentation of parameters and return value.

tests/unit_test/cli_test.py (1)

327-338: LGTM! Good test coverage for successful mail processing.

The test properly verifies the successful execution of mail channel processing.

kairon/api/app/routers/bot/bot.py (3)

28-28: LGTM!

The import statement for MailProcessor is correctly placed and necessary for the new mail channel logs functionality.


1664-1672: LGTM! The endpoint is well-implemented.

The implementation follows the established patterns in the codebase:

  • Proper pagination parameters
  • Consistent security scope (TESTER_ACCESS)
  • Clear docstring
  • Appropriate delegation to the MailProcessor
🧰 Tools
🪛 Ruff (0.8.0)

1666-1667: Do not perform function call Security in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


1671-1671: Verify error handling and response structure.

Please ensure that:

  1. The MailProcessor.get_log method handles potential errors and exceptions appropriately
  2. The response structure matches other similar endpoints in the codebase (e.g., pagination metadata)
✅ Verification successful

Based on the search results, I can see the implementation of MailProcessor.get_log in kairon/shared/channels/mail/processor.py and similar log retrieval endpoints across the codebase. Let me check the error handling and response structure in more detail.


Based on the code analysis, I can now provide a final response regarding the error handling and response structure verification:

Error handling and response structure are properly implemented.

  1. The MailProcessor.get_log method has proper error handling:

    • Uses try-catch block to handle exceptions
    • Raises AppException with the error message
    • Returns a consistent response structure with logs array and total count
  2. The endpoint implementation follows the codebase patterns:

    • Uses standard pagination parameters (start_idx, page_size)
    • Returns data wrapped in a Response model
    • Consistent with other log retrieval endpoints in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check response structure consistency across similar endpoints

# Test: Search for similar log retrieval endpoints
rg -A 5 'def get_.*_logs.*page_size.*Response' 

Length of output: 48


Script:

#!/bin/bash
# Let's check the implementation of MailProcessor.get_log and similar log retrieval methods

# Search for MailProcessor class and get_log method
ast-grep --pattern 'class MailProcessor:
  $$$
  def get_log($$$):
    $$$'

# Search for similar log retrieval endpoints with different patterns
rg -A 5 'def get_.*log.*:' 

# Check error handling patterns in the file
rg -B 2 -A 5 'except.*:' kairon/api/app/routers/bot/bot.py

Length of output: 14024


Script:

#!/bin/bash
# Check the implementation of get_mail_channel_logs endpoint and MailProcessor.get_log
rg -B 5 -A 10 'async def get_mail_channel_logs' kairon/api/app/routers/bot/bot.py

# Check the complete implementation of MailProcessor.get_log
cat kairon/shared/channels/mail/processor.py

Length of output: 11777

@@ -2,6 +2,10 @@
import datetime
import os
from urllib.parse import urlencode, urljoin
from kairon.shared.utils import Utility
os.environ["system_file"] = "./tests/testing_data/system.yaml"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use uppercase for environment variable name

Following Python conventions, environment variables should be uppercase.

-os.environ["system_file"] = "./tests/testing_data/system.yaml"
+os.environ["SYSTEM_FILE"] = "./tests/testing_data/system.yaml"
📝 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
os.environ["system_file"] = "./tests/testing_data/system.yaml"
os.environ["SYSTEM_FILE"] = "./tests/testing_data/system.yaml"
🧰 Tools
🪛 Ruff (0.8.0)

6-6: Use capitalized environment variable SYSTEM_FILE instead of system_file

Replace system_file with SYSTEM_FILE

(SIM112)

Comment on lines +97 to +98
except Exception as e:
raise AppException(f"Failed to schedule mail reading for bot {self.bot}. Error: {str(e)}")
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use exception chaining when re-raising exceptions

When re-raising an exception inside an except block, use exception chaining with raise ... from e to preserve the original traceback and provide better context.

Apply this diff to fix the issue:

97     except Exception as e:
-98         raise AppException(f"Failed to schedule mail reading for bot {self.bot}. Error: {str(e)}")
+98         raise AppException(f"Failed to schedule mail reading for bot {self.bot}. Error: {str(e)}") from e
📝 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
except Exception as e:
raise AppException(f"Failed to schedule mail reading for bot {self.bot}. Error: {str(e)}")
except Exception as e:
raise AppException(f"Failed to schedule mail reading for bot {self.bot}. Error: {str(e)}") from e
🧰 Tools
🪛 Ruff (0.8.0)

98-98: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

Comment on lines +49 to +51
except Exception as e:
logger.error(str(e))
raise AppException(e)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use exception chaining when re-raising exceptions

When re-raising an exception inside an except block, use exception chaining with raise ... from e to preserve the original traceback and provide better context.

Apply this diff to fix the issue:

49     except Exception as e:
50         logger.error(str(e))
-51         raise AppException(e)
+51         raise AppException(e) from e
📝 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
except Exception as e:
logger.error(str(e))
raise AppException(e)
except Exception as e:
logger.error(str(e))
raise AppException(e) from e
🧰 Tools
🪛 Ruff (0.8.0)

51-51: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

Comment on lines +81 to +83
logger.error(str(e))
raise AppException(e)

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use exception chaining when re-raising exceptions

When re-raising an exception inside an except block, use exception chaining with raise ... from e to preserve the original traceback and provide better context.

Apply this diff to fix the issue:

81     except Exception as e:
82         logger.error(str(e))
-83         raise AppException(e)
+83         raise AppException(e) from e
📝 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
logger.error(str(e))
raise AppException(e)
logger.error(str(e))
raise AppException(e) from e
🧰 Tools
🪛 Ruff (0.8.0)

82-82: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

Comment on lines +37 to +39
except Exception as e:
logger.error(str(e))
raise AppException(e)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use exception chaining when re-raising exceptions

When re-raising an exception inside an except block, it's recommended to use exception chaining with raise ... from e to preserve the original traceback and provide better context.

Apply this diff to fix the issue:

37     except Exception as e:
38         logger.error(str(e))
-39         raise AppException(e)
+39         raise AppException(e) from e
📝 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
except Exception as e:
logger.error(str(e))
raise AppException(e)
except Exception as e:
logger.error(str(e))
raise AppException(e) from e
🧰 Tools
🪛 Ruff (0.8.0)

39-39: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

Comment on lines +42 to +44
if configuration['connector_type'] == ChannelTypes.MAIL.value:
from kairon.shared.channels.mail.scheduler import MailScheduler
MailScheduler.request_epoch(bot)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for mail scheduler initialization.

While the implementation is correct, consider adding error handling for potential exceptions from MailScheduler.request_epoch(). This ensures graceful handling of SMTP/IMAP connection issues during mail channel configuration.

 if configuration['connector_type'] == ChannelTypes.MAIL.value:
     from kairon.shared.channels.mail.scheduler import MailScheduler
-    MailScheduler.request_epoch(bot)
+    try:
+        MailScheduler.request_epoch(bot)
+    except Exception as e:
+        logger.error(f"Error initializing mail scheduler for bot {bot}: {str(e)}")
+        raise AppException(f"Failed to initialize mail channel: {str(e)}")
📝 Committable suggestion

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

Suggested change
if configuration['connector_type'] == ChannelTypes.MAIL.value:
from kairon.shared.channels.mail.scheduler import MailScheduler
MailScheduler.request_epoch(bot)
if configuration['connector_type'] == ChannelTypes.MAIL.value:
from kairon.shared.channels.mail.scheduler import MailScheduler
try:
MailScheduler.request_epoch(bot)
except Exception as e:
logger.error(f"Error initializing mail scheduler for bot {bot}: {str(e)}")
raise AppException(f"Failed to initialize mail channel: {str(e)}")

Comment on lines +67 to +74
responses = []
uncached_model = AgentProcessor.get_agent_without_cache(bot, False)
metadata = ChatUtils.get_metadata(account, bot, is_integration_user, metadata)
for message in messages:
msg = UserMessage(message, sender_id=user, metadata=metadata)
chat_response = await uncached_model.handle_message(msg)
responses.append(chat_response)
return responses
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and input validation.

Consider adding:

  1. Input validation for empty messages list
  2. Error handling for failed message processing
  3. Type validation for message strings
@staticmethod
async def process_messages_via_bot(
        messages: [str],
        account: int,
        bot: str,
        user: str,
        is_integration_user: bool = False,
        metadata: Dict = None,
):
    """Process a list of messages through the bot.
     Args:
         messages: List of messages to process
         account: Account ID
         bot: Bot ID
         user: User ID
         is_integration_user: Flag indicating if user is integration user
         metadata: Additional metadata

     Returns:
         List of responses from the bot
    """
+   if not messages:
+       raise ValueError("Messages list cannot be empty")
+   if not all(isinstance(msg, str) for msg in messages):
+       raise ValueError("All messages must be strings")
    
    responses = []
    uncached_model = AgentProcessor.get_agent_without_cache(bot, False)
    metadata = ChatUtils.get_metadata(account, bot, is_integration_user, metadata)
    for message in messages:
+       try:
            msg = UserMessage(message, sender_id=user, metadata=metadata)
            chat_response = await uncached_model.handle_message(msg)
            responses.append(chat_response)
+       except Exception as e:
+           logger.error(f"Error processing message '{message}': {str(e)}")
+           responses.append({"error": f"Failed to process message: {str(e)}"})
    return responses
📝 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
responses = []
uncached_model = AgentProcessor.get_agent_without_cache(bot, False)
metadata = ChatUtils.get_metadata(account, bot, is_integration_user, metadata)
for message in messages:
msg = UserMessage(message, sender_id=user, metadata=metadata)
chat_response = await uncached_model.handle_message(msg)
responses.append(chat_response)
return responses
if not messages:
raise ValueError("Messages list cannot be empty")
if not all(isinstance(msg, str) for msg in messages):
raise ValueError("All messages must be strings")
responses = []
uncached_model = AgentProcessor.get_agent_without_cache(bot, False)
metadata = ChatUtils.get_metadata(account, bot, is_integration_user, metadata)
for message in messages:
try:
msg = UserMessage(message, sender_id=user, metadata=metadata)
chat_response = await uncached_model.handle_message(msg)
responses.append(chat_response)
except Exception as e:
logger.error(f"Error processing message '{message}': {str(e)}")
responses.append({"error": f"Failed to process message: {str(e)}"})
return responses

Comment on lines +942 to +966
@patch("kairon.chat.utils.AgentProcessor.get_agent_without_cache")
@patch("kairon.chat.utils.ChatUtils.get_metadata")
async def test_process_messages_via_bot(mock_get_metadata, mock_get_agent_without_cache):
messages = ["/greet", "/bye"]
account = 1
bot = "test_bot"
user = "test_user"
is_integration_user = False
metadata = {"key": "value"}

mock_get_metadata.return_value = metadata
mock_model = MagicMock()
mock_get_agent_without_cache.return_value = mock_model
mock_model.handle_message = AsyncMock(side_effect=[{"text": "Hello"}, {"text": "Goodbye"}])
from kairon.chat.utils import ChatUtils

responses = await ChatUtils.process_messages_via_bot(messages, account, bot, user, is_integration_user, metadata)

assert len(responses) == 2
assert responses[0] == {"text": "Hello"}
assert responses[1] == {"text": "Goodbye"}
mock_get_metadata.assert_called_once_with(account, bot, is_integration_user, metadata)
mock_get_agent_without_cache.assert_called_once_with(bot, False)
assert mock_model.handle_message.call_count == 2
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add more test scenarios for error handling

The current test only covers the happy path. Consider adding test cases for:

  • Invalid messages
  • Failed message processing
  • Empty message list
  • Invalid metadata

Example test case for error handling:

@pytest.mark.asyncio
@patch("kairon.chat.utils.AgentProcessor.get_agent_without_cache")
@patch("kairon.chat.utils.ChatUtils.get_metadata")
async def test_process_messages_via_bot_error(mock_get_metadata, mock_get_agent_without_cache):
    messages = ["/greet"]
    mock_get_metadata.return_value = {"key": "value"}
    mock_model = MagicMock()
    mock_get_agent_without_cache.return_value = mock_model
    mock_model.handle_message = AsyncMock(side_effect=Exception("Failed to process message"))

    with pytest.raises(Exception, match="Failed to process message"):
        await ChatUtils.process_messages_via_bot(
            messages, 1, "test_bot", "test_user", False, {"key": "value"}
        )

Comment on lines +1214 to +1217
with patch('kairon.shared.channels.mail.processor.MailProcessor.__init__', return_value=None) as mp:
with patch('kairon.shared.channels.mail.processor.MailProcessor.login_smtp', return_value=None) as mock_login:
with patch('kairon.shared.channels.mail.processor.MailProcessor.logout_smtp', return_value=None) as mock_logout:

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify nested context managers and remove unused variables

Combine nested with statements and remove unused mock variables.

-        with patch('kairon.shared.channels.mail.processor.MailProcessor.__init__', return_value=None) as mp:
-            with patch('kairon.shared.channels.mail.processor.MailProcessor.login_smtp', return_value=None) as mock_login:
-                with patch('kairon.shared.channels.mail.processor.MailProcessor.logout_smtp', return_value=None) as mock_logout:
+        with (
+            patch('kairon.shared.channels.mail.processor.MailProcessor.__init__', return_value=None),
+            patch('kairon.shared.channels.mail.processor.MailProcessor.login_smtp', return_value=None),
+            patch('kairon.shared.channels.mail.processor.MailProcessor.logout_smtp', return_value=None)
+        ):
📝 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
with patch('kairon.shared.channels.mail.processor.MailProcessor.__init__', return_value=None) as mp:
with patch('kairon.shared.channels.mail.processor.MailProcessor.login_smtp', return_value=None) as mock_login:
with patch('kairon.shared.channels.mail.processor.MailProcessor.logout_smtp', return_value=None) as mock_logout:
with (
patch('kairon.shared.channels.mail.processor.MailProcessor.__init__', return_value=None),
patch('kairon.shared.channels.mail.processor.MailProcessor.login_smtp', return_value=None),
patch('kairon.shared.channels.mail.processor.MailProcessor.logout_smtp', return_value=None)
):
🧰 Tools
🪛 Ruff (0.8.0)

1214-1215: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


1214-1214: Local variable mp is assigned to but never used

Remove assignment to unused variable mp

(F841)


1215-1215: Local variable mock_login is assigned to but never used

Remove assignment to unused variable mock_login

(F841)


1216-1216: Local variable mock_logout is assigned to but never used

Remove assignment to unused variable mock_logout

(F841)

Comment on lines +1286 to +1289
with patch("kairon.shared.channels.mail.processor.MailProcessor.process_message_task",
side_effect=Exception("Test")):
with pytest.raises(AppException, match="Test"):
MailProcessEvent("", "").execute(mails=["[email protected]"])
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify nested context managers

Combine nested with statements for better readability.

-        with patch("kairon.shared.channels.mail.processor.MailProcessor.process_message_task",
-                   side_effect=Exception("Test")):
-            with pytest.raises(AppException, match="Test"):
-                MailProcessEvent("", "").execute(mails=["[email protected]"])
+        with (
+            patch("kairon.shared.channels.mail.processor.MailProcessor.process_message_task",
+                  side_effect=Exception("Test")),
+            pytest.raises(AppException, match="Test")
+        ):
+            MailProcessEvent("", "").execute(mails=["[email protected]"])
📝 Committable suggestion

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

Suggested change
with patch("kairon.shared.channels.mail.processor.MailProcessor.process_message_task",
side_effect=Exception("Test")):
with pytest.raises(AppException, match="Test"):
MailProcessEvent("", "").execute(mails=["[email protected]"])
with (
patch("kairon.shared.channels.mail.processor.MailProcessor.process_message_task",
side_effect=Exception("Test")),
pytest.raises(AppException, match="Test")
):
MailProcessEvent("", "").execute(mails=["[email protected]"])
🧰 Tools
🪛 Ruff (0.8.0)

1286-1288: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

Copy link
Collaborator

@hiteshghuge hiteshghuge left a comment

Choose a reason for hiding this comment

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

approved

@hiteshghuge hiteshghuge merged commit b3e781f into digiteinfotech:master Dec 9, 2024
8 checks passed
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.

2 participants