class ReviewPanel
ReviewPanel is a UI component class for managing document review workflows, providing interfaces for viewing review details, submitting review decisions, and managing review cycles.
/tf/active/vicechatdev/CDocs single class/ui/review_panel.py
42 - 434
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 renderingsession_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 reviewersection: 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 closeupdate_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
loggingtracebacktypingdatetimepandaspanelCDocs.configCDocs.models.user_extensionsCDocs.controllersCDocs.controllers.document_controllerCDocs.controllers.review_controllerCDocs.auth.session_managerCDocs.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
Optionalfrom 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
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ReviewPanel_v1 81.8% similar
-
function create_review_panel_v1 79.0% similar
-
class WorkflowPanelBase 76.8% similar
-
function create_workflow_panel 76.1% similar
-
function create_review_panel 73.8% similar