🔍 Code Extractor

class ReviewComment

Maturity: 54

A model class representing a comment made during document review, with support for resolution tracking, replies, and integration with review cycles.

File:
/tf/active/vicechatdev/CDocs single class/models/review.py
Lines:
24 - 283
Complexity:
complex

Purpose

ReviewComment manages comments created during document review cycles. It handles comment creation, storage, resolution tracking, and relationships with review cycles and users. The class supports both legacy and new-style data formats for backward compatibility, manages comment-to-review-cycle relationships in the database, and provides methods for saving, retrieving, and managing comment data including replies and resolution status.

Source Code

class ReviewComment(CommentBase, BaseModel):
    """Model representing a comment made during document review."""
    
    def __init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None):
        """
        Initialize a review comment.
        
        Args:
            data: Dictionary of comment properties
            uid: Comment UID to load from database (if data not provided)
        """
        if data is None and uid is not None:
            # Fetch comment data from database
            data = db.get_node_by_uid(uid)
            
        BaseModel.__init__(self, data or {})
        CommentBase.__init__(self, data or {})
        
        # Ensure backward compatibility
        if 'text' not in self._data and 'text' in self._data:
            self._data['text'] = self._data['text']
            
        if 'author_uid' not in self._data and 'commenterUID' in self._data:
            self._data['author_uid'] = self._data['commenterUID']
    
    @classmethod
    def create(cls, review_cycle_uid: str, 
              commenter: Union[DocUser, str], 
              text: str,
              requires_resolution: bool = False,
              properties: Optional[Dict[str, Any]] = None) -> Optional['ReviewComment']:
        """
        Create a new review comment.
        
        Args:
            review_cycle_uid: UID of the review cycle this comment belongs to
            commenter: User making the comment or their UID
            text: Comment text
            requires_resolution: Whether this comment requires resolution
            properties: Additional properties for the comment
            
        Returns:
            New ReviewComment instance or None if creation failed
        """
        try:
            # Get commenter info
            commenter_uid = commenter.uid if isinstance(commenter, DocUser) else commenter
            commenter_name = None
            if isinstance(commenter, DocUser):
                commenter_name = commenter.name
            else:
                user_info = db.run_query(
                    """
                    MATCH (u:User {UID: $uid})
                    RETURN u.Name as name
                    """,
                    {"uid": commenter_uid}
                )
                if user_info and 'name' in user_info[0]:
                    commenter_name = user_info[0]['name']
            
            # Prepare properties
            props = properties or {}
            props.update({
                'text': text,
                'timestamp': datetime.now(),
                'requiresResolution': requires_resolution,
                'commenterUID': commenter_uid,  # For backward compatibility
                'author_uid': commenter_uid,
                'author_name': commenter_name,
                'comment_type': props.get('comment_type', CommentType.GENERAL),
                'review_cycle_uid': review_cycle_uid  # For easier lookup
            })
            
            # Create the comment node
            comment_uid = str(uuid.uuid4())
            props['UID'] = comment_uid
            
            # Create node in database
            success = db.create_node(NodeLabels.REVIEW_COMMENT, props)
            
            if not success:
                logger.error(f"Failed to create review comment for review cycle {review_cycle_uid}")
                return None
            
            # Create relationship: Comment -> COMMENTED_ON -> Review Cycle
            rel_success = db.create_relationship(
                comment_uid, 
                review_cycle_uid,
                RelTypes.COMMENTED_ON
            )
            
            if not rel_success:
                logger.error(f"Failed to create relationship between comment {comment_uid} and review cycle {review_cycle_uid}")
                # Clean up the orphaned comment node
                db.delete_node(comment_uid)
                return None
                
            # Create the comment instance
            comment = cls(props)
            
            return comment
            
        except Exception as e:
            logger.error(f"Error creating review comment: {e}")
            return None

    @property
    def timestamp(self) -> Optional[datetime]:
        """Get when comment was made."""
        return self._data.get('timestamp')
    
    @property
    def requires_resolution(self) -> bool:
        """Whether comment requires resolution."""
        return self._data.get('requiresResolution', False)
    
    @property
    def resolution(self) -> Optional[str]:
        """Get resolution text."""
        # First check new style (workflow_base style)
        if 'context' in self._data and 'resolution' in self._data['context'] and 'text' in self._data['context']['resolution']:
            return self._data['context']['resolution']['text']
        # Then check old style
        return self._data.get('resolution')
    
    @resolution.setter
    def resolution(self, text: str) -> None:
        """Set resolution text."""
        # Update both styles for compatibility
        self._data['resolution'] = text
        self._data['resolutionDate'] = datetime.now()
        
        # Also update in workflow_base style
        if 'context' not in self._data:
            self._data['context'] = {}
        if 'resolution' not in self._data['context']:
            self._data['context']['resolution'] = {}
        
        self._data['context']['resolution']['text'] = text
        self._data['context']['resolution']['resolved_at'] = datetime.now().isoformat()
        
        # Save to database
        db.update_node(self.uid, {
            'resolution': text,
            'resolutionDate': self._data['resolutionDate'],
            'context': self._data['context'],
            'resolved': True
        })
    
    @property
    def resolution_date(self) -> Optional[datetime]:
        """Get when comment was resolved."""
        # Check new style
        if 'context' in self._data and 'resolution' in self._data['context'] and 'resolved_at' in self._data['context']['resolution']:
            resolved_at = self._data['context']['resolution']['resolved_at']
            if isinstance(resolved_at, str):
                try:
                    return datetime.fromisoformat(resolved_at.replace('Z', '+00:00'))
                except:
                    pass
        # Check old style
        return self._data.get('resolutionDate')
    
    @property
    def is_resolved(self) -> bool:
        """Whether comment has been resolved."""
        # Check both styles
        return self.resolution is not None or self.resolved
    
    @property
    def commenter_uid(self) -> Optional[str]:
        """Get UID of user who made the comment."""
        return self._data.get('commenterUID') or self._data.get('author_uid')
    
    @property
    def commenter(self) -> Optional[DocUser]:
        """Get user who made the comment."""
        commenter_uid = self.commenter_uid
        if commenter_uid:
            return DocUser(uid=commenter_uid)
        return None
    
    @property
    def review_cycle_uid(self) -> Optional[str]:
        """Get the UID of the review cycle this comment belongs to."""
        # First check if we already have it in properties
        if self._data.get('review_cycle_uid'):
            return self._data.get('review_cycle_uid')
        
        # Otherwise query the database
        result = db.run_query(
            """
            MATCH (c:ReviewComment {UID: $uid})-[:COMMENTED_ON]->(r:ReviewCycle)
            RETURN r.UID as review_uid
            """,
            {"uid": self.uid}
        )
        
        if result and 'review_uid' in result[0]:
            # Cache it for future use
            self._data['review_cycle_uid'] = result[0]['review_uid']
            return result[0]['review_uid']
            
        return None
    
    def save(self) -> bool:
        """Save changes to database."""
        try:
            # If node doesn't exist, create it
            if not db.node_exists(self.uid):
                created = db.create_node_with_uid(
                    NodeLabels.REVIEW_COMMENT,
                    self._data,
                    self.uid
                )
                
                if created and self._data.get('review_cycle_uid'):
                    # Create relationship between comment and review cycle
                    db.create_relationship(
                        self.uid,
                        self._data['review_cycle_uid'],
                        RelTypes.COMMENTED_ON
                    )
                return created
            
            # Otherwise update existing node
            return db.update_node(self.uid, self._data)
        except Exception as e:
            logger.error(f"Error saving review comment: {e}")
            return False
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary representation."""
        result = BaseModel.to_dict(self)
        
        # Add commenter information if available
        commenter = self.commenter
        if commenter:
            result['commenter'] = {
                'UID': commenter.uid,
                'name': commenter.name,
                'email': commenter.email
            }
            
        return result
    
    def get_replies(self) -> List['ReviewComment']:
        """Get replies to this comment."""
        result = db.run_query(
            """
            MATCH (c:ReviewComment)
            WHERE c.parent_comment_uid = $comment_uid
            RETURN c
            ORDER BY c.timestamp
            """,
            {"comment_uid": self.uid}
        )
        
        return [ReviewComment(record['c']) for record in result if 'c' in record]

Parameters

Name Type Default Kind
bases CommentBase, BaseModel -

Parameter Details

data: Optional dictionary containing comment properties. If provided, initializes the comment with these properties. Properties can include 'text', 'timestamp', 'requiresResolution', 'author_uid', 'commenterUID', 'comment_type', 'review_cycle_uid', 'resolution', 'context', and other metadata.

uid: Optional string UID to load an existing comment from the database. If provided and data is None, the comment data will be fetched from the database using this UID.

Return Value

The __init__ method returns a ReviewComment instance. The create() class method returns a new ReviewComment instance if successful, or None if creation failed. Properties return their respective types (datetime, bool, str, DocUser, List). The save() method returns a boolean indicating success. The to_dict() method returns a dictionary representation of the comment.

Class Interface

Methods

__init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None)

Purpose: Initialize a review comment either from provided data or by loading from database using uid

Parameters:

  • data: Dictionary of comment properties to initialize with
  • uid: Comment UID to load from database if data not provided

Returns: None (constructor)

create(cls, review_cycle_uid: str, commenter: Union[DocUser, str], text: str, requires_resolution: bool = False, properties: Optional[Dict[str, Any]] = None) -> Optional['ReviewComment']

Purpose: Create a new review comment in the database with all necessary relationships

Parameters:

  • review_cycle_uid: UID of the review cycle this comment belongs to
  • commenter: DocUser instance or user UID string of the person making the comment
  • text: The comment text content
  • requires_resolution: Whether this comment requires resolution before proceeding
  • properties: Additional properties to store with the comment

Returns: New ReviewComment instance if successful, None if creation failed

@property timestamp(self) -> Optional[datetime] property

Purpose: Get the datetime when the comment was created

Returns: datetime object representing when comment was made, or None if not set

@property requires_resolution(self) -> bool property

Purpose: Check if the comment requires resolution before proceeding

Returns: True if comment requires resolution, False otherwise

@property resolution(self) -> Optional[str] property

Purpose: Get the resolution text for this comment

Returns: Resolution text string if resolved, None otherwise. Checks both new and old data formats.

@resolution.setter resolution(self, text: str) -> None property

Purpose: Set the resolution text and mark comment as resolved, updating both old and new data formats and saving to database

Parameters:

  • text: The resolution text explaining how the comment was addressed

Returns: None

@property resolution_date(self) -> Optional[datetime] property

Purpose: Get the datetime when the comment was resolved

Returns: datetime object representing when comment was resolved, or None if not resolved

@property is_resolved(self) -> bool property

Purpose: Check if the comment has been resolved

Returns: True if comment has resolution text or is marked as resolved, False otherwise

@property commenter_uid(self) -> Optional[str] property

Purpose: Get the UID of the user who made the comment

Returns: User UID string, checking both old (commenterUID) and new (author_uid) field names

@property commenter(self) -> Optional[DocUser] property

Purpose: Get the DocUser instance of the user who made the comment

Returns: DocUser instance if commenter_uid exists, None otherwise

@property review_cycle_uid(self) -> Optional[str] property

Purpose: Get the UID of the review cycle this comment belongs to, querying database if not cached

Returns: Review cycle UID string if found, None otherwise. Result is cached after first query.

save(self) -> bool

Purpose: Save or update the comment in the database, creating node and relationships if needed

Returns: True if save was successful, False otherwise

to_dict(self) -> Dict[str, Any]

Purpose: Convert the comment to a dictionary representation with expanded commenter information

Returns: Dictionary containing all comment data plus expanded commenter details (UID, name, email) if available

get_replies(self) -> List['ReviewComment']

Purpose: Get all reply comments to this comment, ordered by timestamp

Returns: List of ReviewComment instances that are replies to this comment, ordered chronologically

Attributes

Name Type Description Scope
_data Dict[str, Any] Internal dictionary storing all comment properties including text, timestamp, resolution, author info, and metadata. Inherited from BaseModel. instance
uid str Unique identifier for the comment. Inherited from BaseModel. instance
text str The comment text content. Inherited from CommentBase. instance
author_uid str UID of the comment author. Inherited from CommentBase. instance
author_name str Name of the comment author. Inherited from CommentBase. instance
comment_type CommentType Type/category of the comment. Inherited from CommentBase. instance
resolved bool Whether the comment is marked as resolved. Inherited from CommentBase. instance

Dependencies

  • logging
  • uuid
  • typing
  • datetime
  • CDocs.db
  • CDocs.config
  • CDocs.db.schema_manager
  • CDocs.models.user_extensions
  • CDocs.models.workflow_base
  • CDocs.models.document

Required Imports

import logging
import uuid
from typing import Dict, List, Any, Optional, Union
from datetime import datetime
from CDocs import db
from CDocs.db.schema_manager import NodeLabels, RelTypes
from CDocs.models.user_extensions import DocUser
from CDocs.models.workflow_base import CommentBase, CommentType
from CDocs.models import BaseModel

Usage Example

# Create a new review comment
from CDocs.models.review import ReviewComment
from CDocs.models.user_extensions import DocUser

# Method 1: Create using class method
commenter = DocUser(uid='user-123')
comment = ReviewComment.create(
    review_cycle_uid='cycle-456',
    commenter=commenter,
    text='This section needs clarification',
    requires_resolution=True,
    properties={'comment_type': CommentType.GENERAL}
)

if comment:
    print(f'Comment created: {comment.uid}')
    print(f'Timestamp: {comment.timestamp}')
    print(f'Requires resolution: {comment.requires_resolution}')

# Method 2: Load existing comment
existing_comment = ReviewComment(uid='comment-789')
print(f'Comment text: {existing_comment.text}')
print(f'Author: {existing_comment.commenter.name}')

# Resolve a comment
existing_comment.resolution = 'Clarification added in section 3.2'
print(f'Resolved: {existing_comment.is_resolved}')
print(f'Resolution date: {existing_comment.resolution_date}')

# Get replies to a comment
replies = existing_comment.get_replies()
for reply in replies:
    print(f'Reply: {reply.text}')

# Save changes
if existing_comment.save():
    print('Comment saved successfully')

# Convert to dictionary
comment_dict = existing_comment.to_dict()
print(comment_dict)

Best Practices

  • Always use the create() class method to create new comments rather than direct instantiation, as it handles database relationships and validation
  • Check if create() returns None before using the comment object, as creation can fail
  • Use the uid parameter when loading existing comments from the database
  • The class maintains backward compatibility with legacy field names (commenterUID vs author_uid, requiresResolution vs requires_resolution)
  • Resolution data is stored in both old and new formats for compatibility - use the resolution property setter to ensure both are updated
  • Call save() after modifying comment properties to persist changes to the database
  • The review_cycle_uid property caches the result after first query - this is safe as the relationship shouldn't change
  • When checking resolution status, use is_resolved property which checks both old and new data formats
  • The commenter property returns a DocUser instance which may trigger additional database queries
  • Use get_replies() to retrieve threaded replies to a comment
  • The class inherits from both CommentBase and BaseModel, providing workflow and base model functionality
  • Database operations may fail silently in some cases - check return values from save() and create()
  • The to_dict() method includes expanded commenter information if available

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class ReviewComment_v1 97.2% similar

    A model class representing a comment made during document review, with support for resolution tracking and database persistence.

    From: /tf/active/vicechatdev/CDocs/models/review.py
  • class ApprovalComment 81.3% similar

    Model class representing a comment made during a document approval cycle, with support for resolution tracking and database persistence.

    From: /tf/active/vicechatdev/CDocs/models/approval_bis.py
  • class ApprovalComment_v2 81.1% similar

    A model class representing a comment made during document approval workflows, with support for resolution tracking, replies, and database persistence.

    From: /tf/active/vicechatdev/CDocs single class/models/approval.py
  • class ApprovalComment_v1 80.3% similar

    A model class representing a comment made during a document approval cycle, with support for resolution tracking and database persistence.

    From: /tf/active/vicechatdev/CDocs/models/approval.py
  • function add_review_comment_v1 75.6% similar

    Adds a comment to a document review cycle, with options to mark it as requiring resolution and specify comment type.

    From: /tf/active/vicechatdev/CDocs single class/controllers/review_controller.py
← Back to Browse