class ApprovalComment_v2
A model class representing a comment made during document approval workflows, with support for resolution tracking, replies, and database persistence.
/tf/active/vicechatdev/CDocs single class/models/approval.py
25 - 266
complex
Purpose
ApprovalComment manages comments created during document approval cycles. It handles comment creation, storage, retrieval, resolution tracking, and relationships with approval cycles and users. The class provides backward compatibility with legacy data formats and supports threaded replies. It integrates with the database layer for persistence and maintains relationships between comments, approval cycles, and users.
Source Code
class ApprovalComment(CommentBase, BaseModel):
"""Model representing a comment made during document approval."""
def __init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None):
"""
Initialize an approval 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 'comment_text' in self._data:
self._data['text'] = self._data['comment_text']
if 'author_uid' not in self._data and 'approver_uid' in self._data:
self._data['author_uid'] = self._data['approver_uid']
@classmethod
def create(cls, approval_cycle_uid: str,
approver: Union[DocUser, str],
text: str,
requires_resolution: bool = False,
properties: Optional[Dict[str, Any]] = None) -> Optional['ApprovalComment']:
"""
Create a new approval comment.
Args:
approval_cycle_uid: UID of the approval cycle this comment belongs to
approver: 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 ApprovalComment instance or None if creation failed
"""
try:
# Get approver info
approver_uid = approver.uid if isinstance(approver, DocUser) else approver
approver_name = None
if isinstance(approver, DocUser):
approver_name = approver.name
else:
user_info = db.run_query(
"""
MATCH (u:User {UID: $uid})
RETURN u.Name as name
""",
{"uid": approver_uid}
)
if user_info and 'name' in user_info[0]:
approver_name = user_info[0]['name']
# Prepare properties
props = properties or {}
props.update({
'text': text,
'timestamp': datetime.now(),
'requiresResolution': requires_resolution,
'approver_uid': approver_uid,
'author_uid': approver_uid,
'author_name': approver_name,
'comment_type': props.get('comment_type', CommentType.GENERAL),
'approval_cycle_uid': approval_cycle_uid
})
# 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 approval comment for approval cycle {approval_cycle_uid}")
return None
# Create relationship: Comment -> COMMENTED_ON -> Approval Cycle
rel_success = db.create_relationship(
comment_uid,
approval_cycle_uid,
RelTypes.COMMENTED_ON
)
if not rel_success:
logger.error(f"Failed to create relationship between comment {comment_uid} and approval cycle {approval_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 approval 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):
return datetime.fromisoformat(resolved_at)
# 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 approver_uid(self) -> Optional[str]:
"""Get UID of user who made the comment."""
return self._data.get('approver_uid') or self._data.get('author_uid')
@property
def approver(self) -> Optional[DocUser]:
"""Get user who made the comment."""
approver_uid = self.approver_uid
if approver_uid:
return DocUser(uid=approver_uid)
return None
@property
def approval_cycle_uid(self) -> Optional[str]:
"""Get the UID of the approval cycle this comment belongs to."""
# First check if we already have it in properties
if self._data.get('approval_cycle_uid'):
return self._data.get('approval_cycle_uid')
# Otherwise query the database
result = db.run_query(
"""
MATCH (c:ReviewComment {UID: $uid})-[:COMMENTED_ON]->(a:Approval)
RETURN a.UID as approval_uid
""",
{"uid": self.uid}
)
if result and 'approval_uid' in result[0]:
return result[0]['approval_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):
return db.create_node(NodeLabels.REVIEW_COMMENT, self._data)
# Otherwise update existing node
return db.update_node(self.uid, self._data)
except Exception as e:
logger.error(f"Error saving approval comment: {e}")
return False
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary representation."""
result = BaseModel.to_dict(self)
# Add approver information if available
approver = self.approver
if approver:
result['approver'] = {
'UID': approver.uid,
'name': approver.name,
'email': approver.email
}
return result
def get_replies(self) -> List['ApprovalComment']:
"""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 [ApprovalComment(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 (text, timestamp, author_uid, etc.). If provided, initializes the comment with these properties. If None and uid is provided, data will be fetched from database.
uid: Optional string UID to load an existing comment from the database. Used when data is None to retrieve comment data by its unique identifier.
Return Value
Instantiation returns an ApprovalComment object. The create() class method returns an Optional[ApprovalComment] - either a new comment instance on success or None if creation failed. Properties return their respective types (datetime, bool, str, DocUser, etc.). The save() method returns bool indicating success/failure. The to_dict() method returns a Dict[str, Any] with the comment's data. The get_replies() method returns a List[ApprovalComment] of reply comments.
Class Interface
Methods
__init__(self, data: Optional[Dict[str, Any]] = None, uid: Optional[str] = None)
Purpose: Initialize an approval comment from data dictionary or by loading from database using UID
Parameters:
data: Dictionary of comment propertiesuid: Comment UID to load from database if data not provided
Returns: None (constructor)
create(cls, approval_cycle_uid: str, approver: Union[DocUser, str], text: str, requires_resolution: bool = False, properties: Optional[Dict[str, Any]] = None) -> Optional['ApprovalComment']
Purpose: Create a new approval comment with database persistence and relationships
Parameters:
approval_cycle_uid: UID of the approval cycle this comment belongs toapprover: DocUser object or UID string of the user making the commenttext: The comment text contentrequires_resolution: Whether this comment requires resolution before approval can proceedproperties: Additional properties to store with the comment
Returns: New ApprovalComment 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 of when comment was made, or None if not set
@property requires_resolution(self) -> bool
property
Purpose: Check if the comment requires resolution before approval can proceed
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, persisting to database
Parameters:
text: The resolution text explaining how the comment was addressed
Returns: None (setter)
@property resolution_date(self) -> Optional[datetime]
property
Purpose: Get the datetime when the comment was resolved
Returns: datetime object of 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 resolved, False otherwise
@property approver_uid(self) -> Optional[str]
property
Purpose: Get the UID of the user who made the comment
Returns: String UID of the approver/author, or None if not set
@property approver(self) -> Optional[DocUser]
property
Purpose: Get the DocUser object of the user who made the comment
Returns: DocUser instance of the approver, or None if approver_uid not set
@property approval_cycle_uid(self) -> Optional[str]
property
Purpose: Get the UID of the approval cycle this comment belongs to
Returns: String UID of the approval cycle, querying database if not cached, or None if not found
save(self) -> bool
Purpose: Persist comment changes to the database, creating node if it doesn't exist
Returns: True if save successful, False if save failed
to_dict(self) -> Dict[str, Any]
Purpose: Convert comment to dictionary representation with enriched approver information
Returns: Dictionary containing all comment data plus approver details (UID, name, email)
get_replies(self) -> List['ApprovalComment']
Purpose: Get all reply comments to this comment, ordered by timestamp
Returns: List of ApprovalComment instances that are replies to this comment
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
_data |
Dict[str, Any] | Internal dictionary storing all comment properties including text, timestamp, resolution, author info, etc. 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, register_model
Usage Example
# Create a new approval comment
from CDocs.models.approval import ApprovalComment
from CDocs.models.user_extensions import DocUser
# Method 1: Create new comment
approver = DocUser(uid='user-123')
comment = ApprovalComment.create(
approval_cycle_uid='cycle-456',
approver=approver,
text='This section needs revision',
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}')
# Resolve the comment
comment.resolution = 'Updated section per feedback'
print(f'Resolved: {comment.is_resolved}')
print(f'Resolution date: {comment.resolution_date}')
# Get replies
replies = comment.get_replies()
print(f'Number of replies: {len(replies)}')
# Convert to dictionary
comment_dict = comment.to_dict()
# Method 2: Load existing comment
existing_comment = ApprovalComment(uid='comment-789')
if existing_comment:
print(f'Author: {existing_comment.approver.name}')
print(f'Text: {existing_comment.text}')
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 resolution property setter to resolve comments, not direct data manipulation, as it handles both old and new data formats
- The class maintains backward compatibility with legacy data formats (comment_text vs text, approver_uid vs author_uid)
- Call save() after modifying comment properties to persist changes to the database
- The approval_cycle_uid property may query the database if not cached, so cache the result if using multiple times
- When loading by UID, the constructor will fetch data from database automatically
- Resolution tracking supports both old and new data formats for backward compatibility
- The class inherits from both CommentBase and BaseModel, providing shared functionality for comments and database operations
- Use to_dict() for serialization, as it includes enriched approver information
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ApprovalComment_v1 95.6% similar
-
class ApprovalComment 94.4% similar
-
class ReviewComment 81.1% similar
-
function add_approval_comment_v2 80.7% similar
-
class ReviewComment_v1 79.3% similar