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

Delete slot and slot mapping #1605

Merged
merged 1 commit into from
Nov 26, 2024

Conversation

maheshsattala
Copy link
Contributor

@maheshsattala maheshsattala commented Nov 25, 2024

Changed logic related to Delete Slot and Slot Mapping and added related test cases.

Summary by CodeRabbit

  • New Features

    • Enhanced validation for slot deletion, preventing deletion if the slot is still in use.
  • Bug Fixes

    • Improved accuracy in checking for associated forms when deleting slots.
  • Tests

    • Added new tests for adding and deleting slots and their mappings, ensuring expected API behavior.

Copy link

coderabbitai bot commented Nov 25, 2024

Walkthrough

The changes in this pull request primarily involve updates to the delete_slot method within the MongoProcessor class in kairon/shared/data/processor.py. The method's logic for checking associated forms with a slot has been refined to improve accuracy. Additionally, new test functions have been added to tests/integration_test/services_test.py to enhance coverage for slot and slot mapping functionalities, specifically for adding and deleting slots and their mappings.

Changes

File Path Change Summary
kairon/shared/data/processor.py Updated delete_slot method to change query logic from required_slots__contains=slot_name to required_slots__in=[slot_name]. Added exception handling for slots attached to forms, actions, stories, etc.
tests/integration_test/services_test.py Added three new test functions: test_add_slot_mapping_2, test_delete_slot_mapping_exists, and test_delete_slots_exists to test slot and mapping functionalities.

Possibly related PRs

  • - slot mapping #1346: Modifies the __save_slot_mapping method in processor.py, related to slot management.
  • slot mapped actions fix #1588: Involves changes to the MongoProcessor class in processor.py, focusing on slot mapping and actions.
  • Delete slot mapping fix #1602: Addresses the deletion of slot mappings and includes related test cases, connecting directly to the delete_slot method functionality.

Suggested reviewers

  • hiteshghuge
  • sushantpatade

🐇 "In the meadow where slots play,
A deletion dance brightens the day.
With tests to ensure all's well and clear,
No slot shall linger, no need to fear!
Hopping high, we celebrate this feat,
In code we trust, our work is sweet!" 🐇


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

🧹 Outside diff range and nitpick comments (8)
tests/integration_test/services_test.py (3)

19589-19612: Enhance test robustness and clarity

While the test covers the basic flow, consider these improvements:

  1. Rename test to better reflect its purpose (current name suggests a duplicate)
  2. Add cleanup in case of failure between operations
  3. Add validation of actual database state
  4. Document the significance of "na" and the mapping configuration
  5. Add negative test cases (e.g., invalid mapping types)

Consider refactoring to:

-def test_add_slot_mapping_2():
+def test_add_text_slot_with_from_text_mapping():
+    """Test creation of a text slot and adding a from_text mapping to it.
+    The slot 'na' represents [explain significance].
+    """
     try:
         response = client.post(
             f"/api/bot/{pytest.bot}/slots",
             json={"name": "na", "type": "text"},
             headers={"Authorization": pytest.token_type + " " + pytest.access_token},
         )
         
         actual = response.json()
         assert actual["message"] == "Slot added successfully!"
         assert actual["success"]
         assert actual["error_code"] == 0
+        
+        # Verify slot exists in database
+        slot = mongo_processor.get_slot("na")
+        assert slot is not None
         
         response = client.post(
             f"/api/bot/{pytest.bot}/slots/mapping",
             json={
                 "slot": "na",
                 "mapping": {"type": "from_text", "value": "user", "entity": "na"},
             },
             headers={"Authorization": pytest.token_type + " " + pytest.access_token},
         )
         actual = response.json()
         assert actual["message"] == "Slot mapping added"
         assert actual["success"]
         assert actual["error_code"] == 0
+        
+        # Verify mapping exists in database
+        mapping = mongo_processor.get_slot_mapping("na")
+        assert mapping is not None
+    finally:
+        # Cleanup in case of failure
+        mongo_processor.delete_slot("na")

19614-19623: Add database state validation and negative test cases

The test verifies the API response but doesn't confirm the actual database state.

Consider enhancing the test:

 def test_delete_slot_mapping_exists():
+    """Test deletion of a slot mapping while preserving the slot itself."""
     response = client.delete(
         f"/api/bot/{pytest.bot}/slots/mapping/na",
         headers={"Authorization": pytest.token_type + " " + pytest.access_token},
     )
     actual = response.json()
     assert actual["success"]
     assert actual["error_code"] == 0
     assert actual["message"] == "Slot mapping deleted"
+    
+    # Verify mapping is deleted from database
+    mapping = mongo_processor.get_slot_mapping("na")
+    assert mapping is None
+    
+    # Verify slot still exists
+    slot = mongo_processor.get_slot("na")
+    assert slot is not None

Also consider adding a negative test case:

def test_delete_nonexistent_slot_mapping():
    """Test deletion of a mapping that doesn't exist."""
    response = client.delete(
        f"/api/bot/{pytest.bot}/slots/mapping/nonexistent",
        headers={"Authorization": pytest.token_type + " " + pytest.access_token},
    )
    actual = response.json()
    assert not actual["success"]
    assert actual["error_code"] != 0

19589-19635: Address test dependencies and structure

The current tests form an implicit sequence (add slot -> add mapping -> delete mapping -> delete slot) without proper dependency management. This could lead to flaky tests and maintenance issues.

Consider these structural improvements:

  1. Use pytest's dependency management:
@pytest.mark.dependency()
@pytest.mark.dependency(name="slot_created")
def test_add_text_slot_with_from_text_mapping():
    # ... existing code ...

@pytest.mark.dependency(depends=["slot_created"])
@pytest.mark.dependency(name="mapping_deleted")
def test_delete_slot_mapping_exists():
    # ... existing code ...

@pytest.mark.dependency(depends=["mapping_deleted"])
def test_delete_slots_exists():
    # ... existing code ...
  1. Consider using fixtures for setup/teardown:
@pytest.fixture
def text_slot():
    """Fixture to create and clean up a text slot."""
    response = client.post(
        f"/api/bot/{pytest.bot}/slots",
        json={"name": "test_slot", "type": "text"},
        headers={"Authorization": pytest.token_type + " " + pytest.access_token},
    )
    yield response
    # Cleanup
    client.delete(
        f"/api/bot/{pytest.bot}/slots/test_slot",
        headers={"Authorization": pytest.token_type + " " + pytest.access_token},
    )
  1. Group related tests in a class:
@pytest.mark.slots
class TestSlotOperations:
    """Tests for slot CRUD operations."""
    
    def test_add_text_slot_with_from_text_mapping(self):
        # ... existing code ...
    
    def test_delete_slot_mapping_exists(self):
        # ... existing code ...
    
    def test_delete_slots_exists(self):
        # ... existing code ...
kairon/shared/data/processor.py (3)

Line range hint 4791-4924: Comprehensive validation logic for slot deletion, but consider a few improvements

The validation logic for slot deletion is thorough and checks for slot usage across multiple entities. However, there are a few suggestions to enhance the implementation:

  1. Consider extracting the validation logic into a separate method for better readability and reusability:
def validate_slot_dependencies(self, slot_name: Text, bot: Text):
    """
    Validates if a slot can be deleted by checking its dependencies.
    
    :param slot_name: Name of the slot to validate
    :param bot: Bot ID
    :raises: AppException if slot has dependencies
    """
    forms_with_slot = Forms.objects(bot=bot, status=True, required_slots__in=[slot_name])
    if len(forms_with_slot) > 0:
        raise AppException(f'Slot is attached to form: {[form["name"] for form in forms_with_slot]}')
    
    # Add other validation checks...
  1. Consider adding batch validation capability to check multiple slots at once:
def validate_slots_dependencies(self, slot_names: List[Text], bot: Text):
    """
    Validates if multiple slots can be deleted by checking their dependencies.
    
    :param slot_names: List of slot names to validate
    :param bot: Bot ID
    :returns: Dict mapping slot names to their dependencies
    """
    dependencies = {}
    for slot_name in slot_names:
        try:
            self.validate_slot_dependencies(slot_name, bot)
        except AppException as e:
            dependencies[slot_name] = str(e)
    return dependencies

Consider implementing a dependency graph to track relationships between slots and other components. This would make it easier to:

  1. Visualize dependencies
  2. Identify orphaned slots
  3. Perform impact analysis before deletions

Line range hint 4791-4924: Consider enhancing error handling with specific exception types

The current implementation uses generic AppException for all error cases. Consider creating specific exception types for different validation failures to enable more granular error handling by clients.

class SlotInUseError(AppException):
    """Raised when attempting to delete a slot that is in use"""
    pass

class SystemSlotDeletionError(AppException):
    """Raised when attempting to delete a system slot"""
    pass

def delete_slot(self, slot_name: Text, bot: Text, user: Text):
    try:
        if slot_name.lower() in {s.value for s in KaironSystemSlots}:
            raise SystemSlotDeletionError("Default kAIron slot deletion not allowed")
            
        if self.get_row_count(SlotMapping, bot, slot=slot_name, status=True) > 0:
            raise SlotInUseError("Cannot delete slot without removing its mappings!")
        
        # Rest of the validation logic...
        
    except DoesNotExist:
        raise AppException("Slot does not exist.")

This would allow clients to handle different error cases differently:

try:
    processor.delete_slot(slot_name, bot, user)
except SystemSlotDeletionError:
    # Handle system slot deletion attempt
except SlotInUseError:
    # Handle slot in use case
except AppException:
    # Handle other cases

Line range hint 4791-4924: Enhance method documentation with examples and error cases

While the code is well-structured, the documentation could be enhanced to better describe the validation process and possible error scenarios.

Consider updating the docstring to:

def delete_slot(self, slot_name: Text, bot: Text, user: Text):
    """
    Deletes a slot after validating that it's not in use.
    
    Args:
        slot_name: Name of the slot to delete
        bot: Bot ID
        user: User ID
        
    Raises:
        AppException: In the following cases:
            - Attempting to delete a system slot
            - Slot has active mappings
            - Slot is used in forms
            - Slot is used in actions
            - Slot is used in stories
            - Slot is used in training examples
            - Slot is used in multi-flow stories
            - Slot does not exist
            
    Example:
        try:
            processor.delete_slot("user_name", "bot123", "user456")
        except AppException as e:
            print(f"Could not delete slot: {str(e)}")
    """
tests/unit_test/data_processor/data_processor_test.py (2)

9436-9449: Enhance test coverage with edge cases

While the test verifies basic slot creation, consider adding the following test cases for better coverage:

  • Values at boundaries (min_value=0.1, max_value=0.5)
  • Values outside boundaries (<0.1, >0.5)
  • Invalid case where min_value > max_value
def test_add_number_slot_with_invalid_min_max(self):
    processor = MongoProcessor()
    with pytest.raises(AppException, match="min_value cannot be greater than max_value"):
        processor.add_slot(
            {"name": "na", "type": "float", "initial_value": 0.2,
             "max_value": 0.1, "min_value": 0.5,
             "influence_conversation": True},
            'test', 'user'
        )

9461-9476: Consider test naming and error assertion improvements

  1. The test name suffix _1 suggests there might be more test cases. Consider using more descriptive names like test_delete_slot_fails_with_active_mapping and test_delete_slot_succeeds_after_mapping_removal.

  2. The error message assertion could be more specific to ensure the exact error message format doesn't change unexpectedly.

-        with pytest.raises(AppException, match="Cannot delete slot without removing its mappings!"):
+        expected_error = f"Cannot delete slot '{slot_name}' without removing its mappings!"
+        with pytest.raises(AppException, match=re.escape(expected_error)):
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 82e9497 and 702d296.

📒 Files selected for processing (3)
  • kairon/shared/data/processor.py (2 hunks)
  • tests/integration_test/services_test.py (1 hunks)
  • tests/unit_test/data_processor/data_processor_test.py (1 hunks)
🔥 Files not summarized due to errors (1)
  • tests/unit_test/data_processor/data_processor_test.py: Error: Server error: no LLM provider could handle the message
🔇 Additional comments (3)
kairon/shared/data/processor.py (1)

Line range hint 4791-4924: Optimize database queries for slot dependency validation

The current implementation performs multiple separate queries to check slot dependencies. This could be optimized to reduce database round trips.

Consider using MongoDB's aggregation pipeline to check all dependencies in a single query:

def check_slot_dependencies(self, slot_name: Text, bot: Text):
    """
    Checks slot dependencies using aggregation pipeline.
    Returns a dict with counts of dependencies in each collection.
    """
    pipeline = [
        {
            "$facet": {
                "forms": [
                    {"$match": {"bot": bot, "status": True, "required_slots": slot_name}},
                    {"$count": "count"}
                ],
                "google_actions": [
                    {"$match": {"bot": bot, "status": True, "set_slot": slot_name}},
                    {"$count": "count"}
                ],
                "web_search_actions": [
                    {"$match": {"bot": bot, "status": True, "set_slot": slot_name}},
                    {"$count": "count"}
                ],
                "stories": [
                    {
                        "$match": {
                            "bot": bot,
                            "status": True,
                            "events.name": slot_name,
                            "events.type": "slot"
                        }
                    },
                    {"$count": "count"}
                ],
                "training_examples": [
                    {
                        "$match": {
                            "bot": bot,
                            "status": True,
                            "entities.entity": slot_name
                        }
                    },
                    {"$count": "count"}
                ]
            }
        }
    ]
    
    results = Forms.objects().aggregate(pipeline)
    return results

This would:

  1. Reduce the number of database queries
  2. Improve performance for bots with many components
  3. Make it easier to get a complete picture of slot usage
tests/unit_test/data_processor/data_processor_test.py (2)

9450-9460: LGTM! Good coverage of slot deletion

The test effectively verifies that the slot is properly removed from both Slots and Entities collections.


9450-9476: Consider adding these test scenarios

To ensure robust slot deletion functionality, consider adding tests for:

  1. Deleting a non-existent slot
  2. Deleting a slot used in training data or stories
  3. Concurrent deletion attempts
  4. Deletion with invalid bot/user credentials

Comment on lines +19625 to +19634
def test_delete_slots_exists():
response = client.delete(
f"/api/bot/{pytest.bot}/slots/na",
headers={"Authorization": pytest.token_type + " " + pytest.access_token},
)

actual = response.json()
assert actual["message"] == "Slot deleted!"
assert actual["success"]
assert actual["error_code"] == 0
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve test reliability and documentation

The test verifies the API response but lacks database validation and context.

Consider these improvements:

+@pytest.mark.dependency(depends=["test_delete_slot_mapping_exists"])
 def test_delete_slots_exists():
+    """Test deletion of a slot after its mapping has been removed.
+    This test depends on test_delete_slot_mapping_exists having run first
+    to ensure the slot's mapping is already deleted.
+    """
     response = client.delete(
         f"/api/bot/{pytest.bot}/slots/na",
         headers={"Authorization": pytest.token_type + " " + pytest.access_token},
     )
 
     actual = response.json()
     assert actual["message"] == "Slot deleted!"
     assert actual["success"]
     assert actual["error_code"] == 0
+    
+    # Verify slot is deleted from database
+    slot = mongo_processor.get_slot("na")
+    assert slot is None
+    
+    # Verify no orphaned references exist
+    forms = mongo_processor.get_forms()
+    for form in forms:
+        assert "na" not in form["required_slots"]

Also add a test for the error case:

def test_delete_slot_with_existing_mapping():
    """Test that a slot cannot be deleted while it has an existing mapping."""
    # First create a slot with mapping
    client.post(
        f"/api/bot/{pytest.bot}/slots",
        json={"name": "test_slot", "type": "text"},
        headers={"Authorization": pytest.token_type + " " + pytest.access_token},
    )
    client.post(
        f"/api/bot/{pytest.bot}/slots/mapping",
        json={
            "slot": "test_slot",
            "mapping": {"type": "from_text", "value": "user", "entity": "test_slot"},
        },
        headers={"Authorization": pytest.token_type + " " + pytest.access_token},
    )
    
    # Attempt to delete slot with existing mapping
    response = client.delete(
        f"/api/bot/{pytest.bot}/slots/test_slot",
        headers={"Authorization": pytest.token_type + " " + pytest.access_token},
    )
    actual = response.json()
    assert not actual["success"]
    assert "Cannot delete slot with existing mapping" in actual["message"]

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.

reviewed

@hiteshghuge hiteshghuge merged commit 2338dde into digiteinfotech:master Nov 26, 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