🔍 Code Extractor

class ApprovalComment_v2

Maturity: 48

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

File:
/tf/active/vicechatdev/CDocs single class/models/approval.py
Lines:
25 - 266
Complexity:
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 properties
  • uid: 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 to
  • approver: DocUser object or UID string of the user making the comment
  • text: The comment text content
  • requires_resolution: Whether this comment requires resolution before approval can proceed
  • properties: 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

  • 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, 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

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class ApprovalComment_v1 95.6% 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
  • class ApprovalComment 94.4% 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 ReviewComment 81.1% similar

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

    From: /tf/active/vicechatdev/CDocs single class/models/review.py
  • function add_approval_comment_v2 80.7% similar

    Adds a comment to an approval cycle in a document management system, with optional resolution requirements and comment type specification.

    From: /tf/active/vicechatdev/CDocs single class/controllers/approval_controller.py
  • class ReviewComment_v1 79.3% 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
← Back to Browse