🔍 Code Extractor

class ReviewPanel

Maturity: 48

ReviewPanel is a UI component class for managing document review workflows, providing interfaces for viewing review details, submitting review decisions, and managing review cycles.

File:
/tf/active/vicechatdev/CDocs single class/ui/review_panel.py
Lines:
42 - 434
Complexity:
complex

Purpose

ReviewPanel inherits from WorkflowPanelBase to provide a specialized interface for document review management. It displays review metadata, reviewer assignments, comments, and allows reviewers to submit decisions. It handles the complete lifecycle of a review including viewing review details, submitting reviewer feedback with decisions (APPROVED, REJECTED, etc.), adding comments, and closing completed review cycles. The panel adapts its UI based on user permissions and review status.

Source Code

class ReviewPanel(WorkflowPanelBase):
    """Review management interface component inheriting from WorkflowPanelBase"""
    
    def __init__(self, template, session_manager=None, parent_app=None, embedded=False, controller=None, **params):
        """
        Initialize the review panel.
        
        Args:
            template: Panel template for displaying the UI
            session_manager: Authentication session manager
            parent_app: Parent application reference for navigation
            embedded: Whether this panel is embedded in another UI
            controller: Review controller instance
        """
        # Import controller if not provided
        if controller is None:
            from CDocs.controllers.review_controller import _controller
            controller = _controller
            
        # Initialize base class with review-specific settings
        super().__init__(
            template=template,
            session_manager=session_manager,
            parent_app=parent_app,
            embedded=embedded,
            workflow_type='REVIEW',
            **params
        )
        
        # Set controller
        self.controller = controller
    
    def _create_workflow_detail_view(self):
        """Create the review detail view"""
        if not self.workflow_data:
            return
            
        # Extract data
        review = self.workflow_data
        document = self.document_data
        
        # Get review metadata with proper fallbacks for different field names
        status = review.get('status', '')
        review_type = review.get('review_type', '')
        initiated_date = self._format_date(review.get('startDate', review.get('initiated_date', review.get('start_date', ''))))
        due_date = self._format_date(review.get('dueDate', review.get('due_date', '')))
        initiated_by = review.get('initiated_by_name', '')
        instructions = review.get('instructions', '')
        
        # Get reviewer data - handle different data structures
        reviewer_assignments = review.get('reviewer_assignments', [])
        
        # Check for empty or missing assignments and look for reviewers in alternate locations
        if not reviewer_assignments and 'reviewers' in review:
            # Try to convert reviewers list to expected assignment format
            reviewers = review.get('reviewers', [])
            reviewer_assignments = []
            for reviewer in reviewers:
                # Create a simple assignment-like structure
                assignment = {
                    'reviewer_uid': reviewer.get('UID'),
                    'reviewer_name': reviewer.get('name'),
                    'status': 'PENDING'
                }
                reviewer_assignments.append(assignment)
        
        # Find the current user's assignment
        my_assignment = None
        if self.user:
            my_assignment = next((r for r in reviewer_assignments 
                            if r.get('reviewer_uid') == self.user.uid), None)
        
        # Get comment data with fallback options
        comments = review.get('comments', [])

        # Create document header with fallbacks for different field naming
        doc_number = document.get('doc_number', document.get('docNumber', ''))
        doc_title = document.get('title', '')
        doc_revision = document.get('revision', document.get('version', ''))
        
        # Create review detail view
        review_detail = pn.Column(
            sizing_mode='stretch_width'
        )
        
        # Add header with document info
        review_detail.append(pn.pane.Markdown(f"# Review for {doc_number} Rev {doc_revision}"))
        review_detail.append(pn.pane.Markdown(f"## {doc_title}"))
        
        # Create summary card
        summary_card = pn.Column(
            pn.pane.Markdown("### Review Summary"),
            pn.pane.Markdown(f"**Status:** {status}"),
            pn.pane.Markdown(f"**Type:** {review_type}"),
            pn.pane.Markdown(f"**Started:** {initiated_date}"),
            pn.pane.Markdown(f"**Due Date:** {due_date}"),
            pn.pane.Markdown(f"**Initiated By:** {initiated_by}"),
            width=350,
            styles={'background':'#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded']
        )
        
        # Create document info card
        doc_info_card = pn.Column(
            pn.pane.Markdown("### Document Information"),
            pn.pane.Markdown(f"**Number:** {doc_number}"),
            pn.pane.Markdown(f"**Revision:** {doc_revision}"),
            pn.pane.Markdown(f"**Type:** {document.get('doc_type', document.get('docType', ''))}"),
            pn.pane.Markdown(f"**Department:** {document.get('department', '')}"),
            Button(name="View Document", button_type="primary", width=150, on_click=self._view_document),
            width=350,
            styles={'background':'#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded']
        )
        
        # Add cards to layout
        review_detail.append(pn.Row(
            summary_card,
            pn.Column(width=20),  # spacing
            doc_info_card,
            sizing_mode='stretch_width'
        ))
        
        # Add instructions if available
        if instructions:
            review_detail.append(pn.pane.Markdown("### Review Instructions"))
            review_detail.append(pn.pane.Markdown(instructions))
        
        # Create reviewers table with error handling for data structure
        try:
            reviewers_df = self._create_participants_dataframe(reviewer_assignments)
            
            # Create reviewers table
            reviewers_table = Tabulator(
                reviewers_df,
                sizing_mode='stretch_width',
                height=200
            )
            
            # Add reviewers section
            review_detail.append(pn.pane.Markdown("## Reviewers"))
            review_detail.append(reviewers_table)
        except Exception as e:
            logger.error(f"Error creating reviewers table: {e}")
            review_detail.append(pn.pane.Markdown("## Reviewers"))
            review_detail.append(pn.pane.Markdown(f"*Error loading reviewer data: {str(e)}*"))
        
        # Add comments section
        review_detail.append(pn.pane.Markdown("## Comments"))
        
        # Create comments area with error handling
        try:
            comments_area = self._create_comments_area(comments)
            review_detail.append(comments_area)
        except Exception as e:
            logger.error(f"Error creating comments area: {e}")
            review_detail.append(pn.pane.Markdown(f"*Error loading comments: {str(e)}*"))

        # Add close review button if review is completed and user has permission
        if status == 'COMPLETED' and self.user:
            # Check if user is document owner, review initiator, or has manage permission
            is_document_owner = document.get('owner_uid') == self.user.uid
            is_review_initiator = review.get('initiated_by_uid') == self.user.uid
            has_manage_permission = permissions.user_has_permission(self.user, "MANAGE_REVIEWS")
            
            if is_document_owner or is_review_initiator or has_manage_permission:
                close_button = Button(
                    name="Close Review",
                    button_type="danger",
                    width=150
                )
                close_button.on_click(lambda event: self._close_review_cycle(review.get('UID')))
                
                close_section = pn.Row(
                    close_button,
                    sizing_mode='stretch_width',
                    align='end'
                )
                review_detail.append(close_section)
        
        # Add review actions if user is a pending reviewer
        if my_assignment and my_assignment.get('status') == 'PENDING':
            review_actions = self._create_submit_workflow_actions(my_assignment)
            review_detail.append(pn.pane.Markdown("## Your Review"))
            review_detail.append(review_actions)
        
        # Add to review detail area
        self.workflow_detail_area.clear()
        self.workflow_detail_area.append(review_detail)

    def _create_participants_dataframe(self, reviewer_assignments):
        """Create a DataFrame for the reviewers table"""
        # Create data for table
        reviewers_data = []
        
        for assignment in reviewer_assignments:
            # Format dates
            assigned_date = self._format_date(assignment.get('assigned_date', assignment.get('assigned_at', '')))
            decision_date = self._format_date(assignment.get('decision_date', ''))
            
            # Add to data
            reviewers_data.append({
                'reviewer_uid': assignment.get('reviewer_uid'),
                'reviewer_name': assignment.get('reviewer_name', assignment.get('user_name', 'Unknown')),
                'role': assignment.get('role', 'Reviewer'),
                'status': assignment.get('status', 'PENDING'),
                'decision': assignment.get('decision', ''),
                'assigned_date': assigned_date,
                'decision_date': decision_date
            })
        
        # Create DataFrame
        df = pd.DataFrame(reviewers_data)
        
        # Select and rename columns for display
        display_columns = ['reviewer_name', 'role', 'status', 'decision', 
                         'assigned_date', 'decision_date']
        column_names = {
            'reviewer_name': 'Reviewer',
            'role': 'Role',
            'status': 'Status',
            'decision': 'Decision',
            'assigned_date': 'Assigned',
            'decision_date': 'Completed'
        }
        
        # Filter and rename columns
        exist_columns = [col for col in display_columns if col in df.columns]
        df = df[exist_columns]
        rename_dict = {col: column_names[col] for col in exist_columns if col in column_names}
        df = df.rename(columns=rename_dict)
        
        return df
    
    def _create_submit_workflow_actions(self, my_assignment):
        """Create the review actions form"""
        # Create comment input
        comment_input = TextAreaInput(
            name="Comments",
            placeholder="Enter your review comments...",
            rows=5,
            width=600
        )
        
        # Create section input
        section_input = TextInput(
            name="Section",
            placeholder="Optional: specify document section",
            width=300
        )
        
        # Create decision buttons
        decision_options = {d: d for d in settings.REVIEW_DECISIONS}
        decision_group = RadioButtonGroup(
            name='Decision',
            options=decision_options,
            button_type='success',
            value='APPROVED'  # Default selection
        )
        
        # Create submit button
        submit_btn = Button(
            name="Submit Review",
            button_type="primary",
            width=150
        )
        
        # Set up event handler
        submit_btn.on_click(lambda event: self._submit_workflow_action(
            decision_group.value,
            comment_input.value,
            section_input.value
        ))
        
        # Create form layout
        review_actions = Column(
            Row(
                Column(
                    Markdown("### Your Review Decision"),
                    decision_group,
                    width=400
                ),
                Column(
                    Markdown("### Add Comment"),
                    section_input,
                    comment_input,
                    width=600
                ),
                align='start'
            ),
            Row(
                submit_btn,
                align='end'
            ),
            styles={'background':'#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded'],
            sizing_mode='stretch_width'
        )
        
        return review_actions
    
    def _submit_workflow_action(self, decision, comment, section=None):
        """Submit a review decision and comment"""
        try:
            if not self.cycle_uid:
                self.notification_area.object = "Error: No review cycle selected"
                return
                
            if not decision:
                self.notification_area.object = "Please select a decision"
                return
                
            # Set notification
            self.notification_area.object = f"Submitting review with decision: {decision}..."
            
            # Format comment with section if provided
            formatted_comment = comment
            if section:
                formatted_comment = f"**Section: {section}**\n\n{comment}"
            
            # Call API to complete review
            result = complete_review(
                user=self.user,
                review_uid=self.cycle_uid,
                decision=decision,
                comments=formatted_comment
            )
            
            # Handle result
            if result and result.get('success'):
                self.notification_area.object = "Review submitted successfully!"
                # Reload the review after short delay
                pn.state.add_timeout_callback(lambda: self._load_cycle(self.cycle_uid), 1500)
            else:
                self.notification_area.object = f"Error submitting review: {result.get('message', 'Unknown error')}"
                
        except ResourceNotFoundError as e:
            self.notification_area.object = f"**Not Found:** {str(e)}"
        except ValidationError as e:
            self.notification_area.object = f"**Validation Error:** {str(e)}"
        except PermissionError as e:
            self.notification_area.object = f"**Permission Error:** {str(e)}"
        except BusinessRuleError as e:
            self.notification_area.object = f"**Business Rule Error:** {str(e)}"
        except Exception as e:
            logger.error(f"Error submitting review: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** An unexpected error occurred when submitting your review"
    
    def _close_review_cycle(self, review_uid, update_status=True, target_status="DRAFT"):
        """Close a review cycle and optionally update document status"""
        try:
            self.notification_area.object = "Closing review cycle..."
            
            # Call controller to close review cycle
            from CDocs.controllers.review_controller import close_review_cycle
            result = close_review_cycle(
                user=self.user,
                review_uid=review_uid,
                update_document_status=update_status,
                target_status=target_status
            )
            
            if result['success']:
                self.notification_area.object = "Review cycle closed successfully."
                # Reload the review
                self._load_cycle(review_uid)
            else:
                self.notification_area.object = f"Error closing review cycle: {result.get('message', 'Unknown error')}"
        
        except ResourceNotFoundError as e:
            self.notification_area.object = f"**Error:** {str(e)}"
        except ValidationError as e:
            self.notification_area.object = f"**Validation Error:** {str(e)}"
        except PermissionError as e:
            self.notification_area.object = f"**Permission Error:** {str(e)}"
        except BusinessRuleError as e:
            self.notification_area.object = f"**Business Rule Error:** {str(e)}"
        except Exception as e:
            logger.error(f"Error closing review cycle: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** An unexpected error occurred"

    def _load_review(self, review_uid):
        """
        Load a specific review by UID.
        This is a wrapper around _load_cycle for backward compatibility.
        
        Args:
            review_uid: UID of the review to load
        """
        # Call the base class method which handles loading workflow cycles
        return self._load_cycle(review_uid)

Parameters

Name Type Default Kind
bases WorkflowPanelBase -

Parameter Details

template: Panel template object (typically BootstrapTemplate) used for rendering the UI layout and structure

session_manager: SessionManager instance for handling user authentication and session state. Optional, defaults to None

parent_app: Reference to the parent application object for navigation and inter-component communication. Optional, defaults to None

embedded: Boolean flag indicating whether this panel is embedded within another UI component (True) or standalone (False). Defaults to False

controller: Review controller instance that handles business logic and API calls. If None, imports and uses the default _controller from review_controller module

**params: Additional keyword arguments passed to the parent WorkflowPanelBase class constructor

Return Value

Instantiation returns a ReviewPanel object that is a fully configured Panel UI component. The class methods return various types: _create_workflow_detail_view returns None (modifies internal state), _create_participants_dataframe returns a pandas DataFrame, _create_submit_workflow_actions returns a Panel Column layout, _submit_workflow_action returns None (performs side effects), _close_review_cycle returns None (performs side effects), and _load_review returns the result from the parent class _load_cycle method.

Class Interface

Methods

__init__(self, template, session_manager=None, parent_app=None, embedded=False, controller=None, **params)

Purpose: Initialize the ReviewPanel with UI template, authentication, and controller dependencies

Parameters:

  • template: Panel template for UI rendering
  • session_manager: Authentication session manager (optional)
  • parent_app: Parent application reference (optional)
  • embedded: Whether panel is embedded in another UI (default False)
  • controller: Review controller instance (optional, uses default if None)
  • **params: Additional parameters passed to parent class

Returns: None (constructor)

_create_workflow_detail_view(self)

Purpose: Create and populate the detailed review view UI with review metadata, document info, reviewers table, comments, and action forms

Returns: None (modifies self.workflow_detail_area)

_create_participants_dataframe(self, reviewer_assignments)

Purpose: Transform reviewer assignment data into a formatted pandas DataFrame for display in the reviewers table

Parameters:

  • reviewer_assignments: List of reviewer assignment dictionaries containing reviewer_uid, reviewer_name, role, status, decision, and date fields

Returns: pandas DataFrame with formatted columns for display (Reviewer, Role, Status, Decision, Assigned, Completed)

_create_submit_workflow_actions(self, my_assignment)

Purpose: Create the review submission form UI with decision radio buttons, comment input, section input, and submit button

Parameters:

  • my_assignment: Current user's reviewer assignment dictionary containing assignment details

Returns: Panel Column layout containing the complete review submission form

_submit_workflow_action(self, decision, comment, section=None)

Purpose: Submit a review decision with comments to the backend API and reload the review upon success

Parameters:

  • decision: Review decision string (must match settings.REVIEW_DECISIONS values like 'APPROVED', 'REJECTED')
  • comment: Review comment text from reviewer
  • section: Optional document section reference for the comment

Returns: None (performs API call and updates UI via notification_area)

_close_review_cycle(self, review_uid, update_status=True, target_status='DRAFT')

Purpose: Close a completed review cycle and optionally update the associated document status

Parameters:

  • review_uid: UID of the review cycle to close
  • update_status: Boolean flag to update document status (default True)
  • target_status: Target document status after closing review (default 'DRAFT')

Returns: None (performs API call and reloads review)

_load_review(self, review_uid)

Purpose: Load a specific review by UID - wrapper around parent class _load_cycle method for backward compatibility

Parameters:

  • review_uid: UID of the review to load and display

Returns: Result from parent class _load_cycle method

Attributes

Name Type Description Scope
controller ReviewController Review controller instance that handles business logic and API calls for review operations instance
workflow_data Dict Current review cycle data including status, type, dates, reviewers, and comments (inherited from WorkflowPanelBase) instance
document_data Dict Associated document data including doc_number, title, revision, type, and department (inherited from WorkflowPanelBase) instance
cycle_uid str UID of the currently loaded review cycle (inherited from WorkflowPanelBase) instance
user DocUser Current authenticated user object with uid and permissions (inherited from WorkflowPanelBase) instance
workflow_detail_area Panel Container Panel container that holds the detailed review view UI components (inherited from WorkflowPanelBase) instance
notification_area Panel Pane Panel pane for displaying status messages, errors, and notifications to the user (inherited from WorkflowPanelBase) instance
template BootstrapTemplate Panel template used for rendering the UI layout (inherited from WorkflowPanelBase) instance
session_manager SessionManager Session manager for authentication and user session handling (inherited from WorkflowPanelBase) instance
parent_app Any Reference to parent application for navigation and communication (inherited from WorkflowPanelBase) instance
embedded bool Flag indicating if panel is embedded in another UI component (inherited from WorkflowPanelBase) instance

Dependencies

  • logging
  • traceback
  • typing
  • datetime
  • pandas
  • panel
  • CDocs.config
  • CDocs.models.user_extensions
  • CDocs.controllers
  • CDocs.controllers.document_controller
  • CDocs.controllers.review_controller
  • CDocs.auth.session_manager
  • CDocs.ui.workflow_panel_base

Required Imports

import logging
import traceback
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
import pandas as pd
import panel as pn
from panel.template import BootstrapTemplate
from panel.widgets import Button, Select, TextInput, TextAreaInput, DatePicker, FileInput, Tabulator, CheckBoxGroup, RadioButtonGroup
from panel.layout import Column, Row, Card, GridBox, Tabs
from panel.pane import Markdown, HTML, JSON
from CDocs.config import settings, permissions
from CDocs.models.user_extensions import DocUser
from CDocs.controllers import ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError
from CDocs.controllers.document_controller import get_document
from CDocs.controllers.review_controller import get_review_cycle, add_review_comment, complete_review
from CDocs.auth.session_manager import SessionManager
from CDocs.ui.workflow_panel_base import WorkflowPanelBase, create_workflow_panel

Conditional/Optional Imports

These imports are only needed under specific conditions:

from CDocs.controllers.review_controller import _controller

Condition: imported in __init__ if controller parameter is None

Optional
from CDocs.controllers.review_controller import close_review_cycle

Condition: imported in _close_review_cycle method when closing a review

Required (conditional)

Usage Example

# Instantiate ReviewPanel
from panel.template import BootstrapTemplate
from CDocs.auth.session_manager import SessionManager
from CDocs.ui.review_panel import ReviewPanel

# Create template and session manager
template = BootstrapTemplate(title='Review Management')
session_mgr = SessionManager()

# Create review panel
review_panel = ReviewPanel(
    template=template,
    session_manager=session_mgr,
    parent_app=None,
    embedded=False
)

# Load a specific review by UID
review_uid = 'review-123-uid'
review_panel._load_review(review_uid)

# The panel will automatically display:
# - Review metadata (status, type, dates)
# - Document information
# - Reviewer assignments table
# - Comments section
# - Review action form (if user is pending reviewer)
# - Close review button (if completed and user has permission)

# Add panel to template
template.main.append(review_panel)

Best Practices

  • Always provide a valid session_manager with authenticated user for permission checks and user-specific functionality
  • Call _load_review() or _load_cycle() after instantiation to populate the panel with review data
  • Ensure the controller parameter or default _controller has access to review data sources
  • Handle all custom exceptions (ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError) when calling methods
  • The panel modifies its own state through notification_area and workflow_detail_area attributes - avoid external modifications
  • Review decisions must match values in settings.REVIEW_DECISIONS
  • The panel uses automatic reload after submission (1500ms delay) - avoid manual refreshes during this period
  • For embedded usage, set embedded=True to adjust layout and navigation behavior
  • The panel expects specific data structures from the controller with fallback handling for different field naming conventions
  • User permissions are checked dynamically - ensure user object has uid attribute and proper permission assignments

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class ReviewPanel_v1 81.8% similar

    Review management interface component

    From: /tf/active/vicechatdev/CDocs/ui/review_panel.py
  • function create_review_panel_v1 79.0% similar

    Factory function that creates and initializes a review panel UI component for document review workflow management, with error handling and fallback to a minimal panel on failure.

    From: /tf/active/vicechatdev/CDocs single class/ui/review_panel.py
  • class WorkflowPanelBase 76.8% similar

    Base class for workflow panels (review and approval) that provides common functionality and UI components.

    From: /tf/active/vicechatdev/CDocs single class/ui/workflow_panel_base.py
  • function create_workflow_panel 76.1% similar

    Factory function that creates and initializes workflow management panels (Review or Approval) with appropriate configuration, error handling, and fallback mechanisms.

    From: /tf/active/vicechatdev/CDocs single class/ui/workflow_panel_base.py
  • function create_review_panel 73.8% similar

    Factory function that creates and initializes a ReviewPanel instance with error handling, supporting both standalone and embedded modes for document review management.

    From: /tf/active/vicechatdev/CDocs/ui/review_panel.py
← Back to Browse