class ReviewComment
A model class representing a comment made during document review, with support for resolution tracking, replies, and integration with review cycles.
/tf/active/vicechatdev/CDocs single class/models/review.py
24 - 283
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 withuid: 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 tocommenter: DocUser instance or user UID string of the person making the commenttext: The comment text contentrequires_resolution: Whether this comment requires resolution before proceedingproperties: 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
logginguuidtypingdatetimeCDocs.dbCDocs.configCDocs.db.schema_managerCDocs.models.user_extensionsCDocs.models.workflow_baseCDocs.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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ReviewComment_v1 97.2% similar
-
class ApprovalComment 81.3% similar
-
class ApprovalComment_v2 81.1% similar
-
class ApprovalComment_v1 80.3% similar
-
function add_review_comment_v1 75.6% similar