Skip to content

[OSDEV-1347] Moderation. Implement GET api/v1/moderation-events/{moderation_id}#405

Merged
mazursasha1990 merged 100 commits intomainfrom
OSDEV-1347-implement-get-moderation-event-by-id
Nov 15, 2024
Merged

[OSDEV-1347] Moderation. Implement GET api/v1/moderation-events/{moderation_id}#405
mazursasha1990 merged 100 commits intomainfrom
OSDEV-1347-implement-get-moderation-event-by-id

Conversation

@roman-stolar
Copy link
Contributor

@roman-stolar roman-stolar commented Nov 6, 2024

OSDEV-1347 Moderation. Implement GET api/v1/moderation-events/{moderation_id}

  • Created new GET endpoint api/v1/moderation-events/{moderation_id}
  • Added possibility to query search by moderation_id in opensearch
  • Updated error handling

vladsha-dev and others added 30 commits October 24, 2024 15:59
# Conflicts:
#	doc/release/RELEASE-NOTES.md
#	src/django/api/views/v1/moderation_events.py
#	src/django/api/views/v1/opensearch_query_builder/moderation_events_query_builder.py
#	src/django/api/views/v1/opensearch_query_builder/opensearch_query_builder.py
#	src/django/api/views/v1/opensearch_query_builder/opensearch_query_builder_interface.py
#	src/django/api/views/v1/opensearch_query_builder/production_locations_query_builder.py
#	src/tests/v1/test_moderation_events.py
@coderabbitai
Copy link

coderabbitai bot commented Nov 14, 2024

Walkthrough

This pull request updates the RELEASE-NOTES.md file for version 1.25.0 of the Open Supply Hub project, documenting the addition of a new GET request for the v1/moderation-events/{moderation_id} endpoint. It also includes migration changes related to the api_moderationevent table and emphasizes the inclusion of specific commands in the post_deployment command. Additionally, a new retrieve method is introduced in the ModerationEvents class to handle requests for specific moderation events, and a corresponding URL pattern is added. Tests for this new functionality are also included.

Changes

File Change Summary
doc/release/RELEASE-NOTES.md Updated for version 1.25.0: added GET request for v1/moderation-events/{moderation_id}, migration changes, and emphasized post_deployment commands.
src/django/api/views/v1/moderation_events.py Added retrieve method in ModerationEvents class to handle GET requests for moderation events by ID, modified patch method to accept pk.
src/django/oar/urls.py Added new URL pattern for moderation events: path('api/v1/moderation-events/<str:moderation_id>/', ...).
src/tests/v1/test_moderation_events.py Added test_moderation_events_exact method to test the new moderation events retrieval functionality.

Possibly related PRs

Suggested reviewers

  • vladsha-dev
  • mazursasha1990
  • VadimKovalenkoSNF

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

🧹 Outside diff range and nitpick comments (7)
src/django/api/views/v1/utils.py (2)

114-119: Consider specifying the UUID version.

The implementation is correct, but consider being explicit about the UUID version if the moderation IDs follow a specific format (e.g., UUIDv4).

 def is_valid_uuid(uuid_string):
     try:
-        uuid.UUID(uuid_string)
+        # Explicitly validate UUIDv4 if that's what you're using
+        uuid_obj = uuid.UUID(uuid_string)
+        # Verify it's a v4 UUID
+        return uuid_obj.version == 4
-        return True
     except ValueError:
         return False

122-134: Add type hints and make the error message configurable.

The implementation is good, but could be improved for maintainability.

-def handle_path_error(field, message, status_code):
+from typing import Any
+from rest_framework.status import HTTP_400_BAD_REQUEST
+
+def handle_path_error(
+    field: str,
+    message: str,
+    status_code: int = HTTP_400_BAD_REQUEST,
+    error_message: str = "The request path parameter is invalid."
+) -> Response:
     return Response(
         {
-            "message": "The request path parameter is invalid.",
+            "message": error_message,
             "errors": [
                 {
                     "field": field,
src/django/oar/urls.py (1)

105-109: Consider adding UUID path converter for moderation_id validation.

The URL pattern follows RESTful conventions and matches the structure of other v1 endpoints. However, since moderation_id is likely a UUID, consider using Django's UUID path converter for automatic validation:

-    path(
-        'api/v1/moderation-events/<str:moderation_id>/',
-        ModerationEvents.as_view({'get': 'retrieve'}),
-        name='moderation-events-details'
-    ),
+    path(
+        'api/v1/moderation-events/<uuid:moderation_id>/',
+        ModerationEvents.as_view({'get': 'retrieve'}),
+        name='moderation-events-details'
+    ),
src/tests/v1/test_moderation_events.py (2)

8-27: LGTM with suggestions for improved test coverage.

The test implementation correctly covers both successful and unsuccessful scenarios for the new endpoint. However, consider these improvements:

  1. Add test cases for:

    • Malformed UUIDs (non-UUID strings)
    • Invalid UUID format
    • Complete response structure validation
  2. Use meaningful test data:

    • Add comments explaining the significance of the test UUIDs
    • Consider using test fixtures or constants for test data

Here's a suggested enhancement:

+    # Test constants
+    NONEXISTENT_MODERATION_ID = '0f35a90f-70a0-4c3e-8e06-2ed8e1fc6800'
+    VALID_MODERATION_ID = '1f35a90f-70a0-4c3e-8e06-2ed8e1fc6800'
+
     def test_moderation_events_exact(self):
-        moderation_id_wrong = '0f35a90f-70a0-4c3e-8e06-2ed8e1fc6800'
         response = requests.get(
-                f"{self.root_url}/api/v1/moderation-events/{moderation_id_wrong}",
+                f"{self.root_url}/api/v1/moderation-events/{self.NONEXISTENT_MODERATION_ID}",
                 headers=self.basic_headers,
             )

         result = response.json()
         self.assertEqual(result['count'], 0)

-        moderation_id = '1f35a90f-70a0-4c3e-8e06-2ed8e1fc6800'
         response = requests.get(
-                f"{self.root_url}/api/v1/moderation-events/{moderation_id}",
+                f"{self.root_url}/api/v1/moderation-events/{self.VALID_MODERATION_ID}",
                 headers=self.basic_headers,
             )

         result = response.json()
         self.assertEqual(result['count'], 1)
-        self.assertEqual(result['data'][0]['moderation_id'], moderation_id)
+        self.assertEqual(result['data'][0]['moderation_id'], self.VALID_MODERATION_ID)
+        # Verify complete response structure
+        self.assertIn('status', result['data'][0])
+        self.assertIn('request_type', result['data'][0])
+        self.assertIn('source', result['data'][0])
+        self.assertIn('created_at', result['data'][0])

+    def test_moderation_events_malformed_id(self):
+        """Test handling of malformed moderation IDs."""
+        malformed_ids = [
+            'not-a-uuid',           # Non-UUID string
+            '123',                  # Number
+            'g35h-invalid-uuid'     # Invalid format
+        ]
+        
+        for malformed_id in malformed_ids:
+            response = requests.get(
+                f"{self.root_url}/api/v1/moderation-events/{malformed_id}",
+                headers=self.basic_headers,
+            )
+            
+            self.assertEqual(response.status_code, 400)
+            result = response.json()
+            self.assertIn('errors', result)
+            error = result['errors'][0]
+            self.assertEqual(error['field'], 'Moderation_Id')

11-13: Fix indentation to use spaces instead of tabs.

The indentation in the URL formatting uses tabs instead of spaces. This is inconsistent with Python's style guide (PEP 8).

Apply this fix:

     def test_moderation_events_exact(self):
         moderation_id_wrong = '0f35a90f-70a0-4c3e-8e06-2ed8e1fc6800'
         response = requests.get(
-                f"{self.root_url}/api/v1/moderation-events/{moderation_id_wrong}",
-                headers=self.basic_headers,
-            )
+            f"{self.root_url}/api/v1/moderation-events/{moderation_id_wrong}",
+            headers=self.basic_headers,
+        )

Also applies to: 20-22

doc/release/RELEASE-NOTES.md (1)

Line range hint 1-1161: Consider adding a table of contents for better navigation.

The release notes document is becoming quite long. To improve readability and navigation, consider adding:

  1. A table of contents at the top listing all releases
  2. Quick links to jump to specific releases

Example addition at the top:

# Release Notes
All notable changes to this project will be documented in this file.

This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). The format is based on the `RELEASE-NOTES-TEMPLATE.md` file.

+## Table of Contents
+- [Release 1.25.0](#release-1250) - November 30, 2024
+- [Release 1.24.0](#release-1240) - November 16, 2024
+- [Release 1.23.0](#release-1230) - November 02, 2024
+...
src/django/api/views/v1/moderation_events.py (1)

76-85: Handle empty search results gracefully

If no moderation events match the provided moderation_id and user_id, the current implementation will return an empty response. Consider adding a check to return a 404 status code when no matching moderation event is found.

You can add the following logic before returning the response:

         response = self.opensearch_service.search_index(
             OpenSearchIndexNames.MODERATION_EVENTS_INDEX,
             query_body
         )
+        if not response['hits']['hits']:
+            return Response(
+                {"detail": "Moderation event not found."},
+                status=status.HTTP_404_NOT_FOUND
+            )
         return Response(response)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1b8c3b7 and 32bb09c.

📒 Files selected for processing (5)
  • doc/release/RELEASE-NOTES.md (1 hunks)
  • src/django/api/views/v1/moderation_events.py (2 hunks)
  • src/django/api/views/v1/utils.py (2 hunks)
  • src/django/oar/urls.py (1 hunks)
  • src/tests/v1/test_moderation_events.py (1 hunks)
🔇 Additional comments (5)
src/django/api/views/v1/utils.py (1)

114-119: Consider security implications when logging UUIDs.

Since UUIDs might contain sensitive information or be used as security tokens, ensure that invalid UUIDs are not logged in their entirety. Consider masking or truncating them in logs.

src/django/oar/urls.py (1)

105-109: Verify potential routing conflict with existing moderation-events registration.

There might be a routing conflict since moderation-events is registered twice:

  1. In the default router: router.register('moderation-events', views.ModerationEventsViewSet, 'moderation-event')
  2. In the v1 router: Through OpenSearchIndexNames.MODERATION_EVENTS_INDEX

Let's verify the potential conflict:

src/tests/v1/test_moderation_events.py (1)

Line range hint 1-999: Test coverage aligns well with PR objectives.

The test suite provides comprehensive coverage for the new GET endpoint api/v1/moderation-events/{moderation_id}. The integration with existing tests maintains consistency in testing patterns and coverage levels across the API.

doc/release/RELEASE-NOTES.md (1)

Line range hint 1-24: LGTM! The latest release (1.25.0) is well documented.

The release notes for version 1.25.0 clearly document:

  • The new GET endpoint for moderation events
  • Database migration changes
  • Release instructions
src/django/api/views/v1/moderation_events.py (1)

7-9: LGTM on new utility imports

The newly imported functions handle_errors_decorator, is_valid_uuid, and handle_path_error are appropriately used in the retrieve method.

mazursasha1990
mazursasha1990 previously approved these changes Nov 14, 2024
Copy link
Contributor

@mazursasha1990 mazursasha1990 left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@VadimKovalenkoSNF VadimKovalenkoSNF left a comment

Choose a reason for hiding this comment

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

Left few suggestions.

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

🧹 Outside diff range and nitpick comments (1)
src/django/api/views/v1/utils.py (1)

114-126: Add type hints and docstring

The function implementation looks good and follows the established error handling pattern. Consider adding type hints and documentation for better maintainability.

-def handle_path_error(field, message, status_code):
+def handle_path_error(field: str, message: str, status_code: int) -> Response:
+    """Handle invalid path parameter errors with a consistent response format.
+
+    Args:
+        field: The name of the invalid path parameter field
+        message: The error message describing the validation failure
+        status_code: The HTTP status code to return
+
+    Returns:
+        Response: A DRF Response object with structured error details
+    """
     return Response(
         {
             "message": "The request path parameter is invalid.",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 32bb09c and d009821.

📒 Files selected for processing (2)
  • src/django/api/views/v1/moderation_events.py (3 hunks)
  • src/django/api/views/v1/utils.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/django/api/views/v1/moderation_events.py
🧰 Additional context used
🪛 Ruff
src/django/api/views/v1/utils.py

1-1: uuid imported but unused

Remove unused import: uuid

(F401)

🔇 Additional comments (1)
src/django/api/views/v1/utils.py (1)

1-1: Verify if the uuid import is needed

The uuid import is currently unused in this file. If UUID validation is handled in src/django/api/serializers/v1/opensearch_common_validators/moderation_id_validator.py as mentioned in the previous review, this import can be removed.

🧰 Tools
🪛 Ruff

1-1: uuid imported but unused

Remove unused import: uuid

(F401)

# Conflicts:
#	doc/release/RELEASE-NOTES.md
#	src/django/api/views/v1/moderation_events.py
#	src/django/oar/urls.py
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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f7d65c1 and 0551943.

📒 Files selected for processing (3)
  • doc/release/RELEASE-NOTES.md (1 hunks)
  • src/django/api/views/v1/moderation_events.py (2 hunks)
  • src/django/oar/urls.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/django/oar/urls.py
🔇 Additional comments (2)
src/django/api/views/v1/moderation_events.py (1)

71-75: Verify OpenSearch query optimization

The current query might not be optimized for retrieving a single document by ID.

Let's analyze the query being generated:

✅ Verification successful

Based on the gathered information, I can now provide the final response:

OpenSearch query implementation is appropriate for ID lookups

The current implementation using the query director pattern is correct and optimized. The moderation_id parameter is handled through the standard query building process which uses a match query, which is appropriate for exact ID matches in OpenSearch. The query builder automatically sets track_total_hits to true and includes proper query structure with bool/must clauses, ensuring efficient retrieval.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the query builder is using the most efficient method for ID lookups

# Look for term/match queries in the query builder
ast-grep --pattern 'def build_query($params):
  $$$
  term($$$)
  $$$'

# Check for any existing index configurations
fd -g '*opensearch*.py' -x rg -A 5 'MODERATION_EVENTS_INDEX'

Length of output: 392


Script:

#!/bin/bash
# Let's check the query director implementation more thoroughly
rg -l "OpenSearchQueryDirector" | xargs rg -A 10 "build_query"

# Also check for any term/match query implementations
rg -A 5 "term\(" 

# Look for the specific query director class being used
rg -A 5 "opensearch_query_director.*=.*OpenSearchQueryDirector"

Length of output: 5571


Script:

#!/bin/bash
# Let's check the query builder template fields and implementation
rg -A 10 "moderation_events_query_builder.*=" 

# Check the actual query builder implementation
rg -A 15 "class.*QueryBuilder"

# Look for any direct ID-based query implementations
rg -A 5 "get_by_id|find_by_id|search_by_id"

Length of output: 10331

doc/release/RELEASE-NOTES.md (1)

24-24: LGTM! Documentation follows standards

The new API endpoint is properly documented in the release notes with the correct JIRA ticket reference and consistent formatting.

@sonarqubecloud
Copy link

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 (1)
src/django/api/views/v1/moderation_events.py (1)

82-97: Standardize error message formatting

The error messages in the patch method use inconsistent string concatenation styles. Consider using parentheses for multi-line strings consistently across the codebase.

Apply this diff to standardize the formatting:

-                "message": 'The request body contains '
-                'invalid or missing fields.',
+                "message": (
+                    "The request body contains "
+                    "invalid or missing fields."
+                ),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0551943 and f287ed1.

📒 Files selected for processing (1)
  • src/django/api/views/v1/moderation_events.py (2 hunks)

Copy link
Contributor

@mazursasha1990 mazursasha1990 left a comment

Choose a reason for hiding this comment

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

LGTM

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.

4 participants