class TrainingDashboard
Training dashboard for users to view and complete training requirements.
/tf/active/vicechatdev/CDocs/ui/training_dashboard.py
22 - 939
moderate
Purpose
Training dashboard for users to view and complete training requirements.
Source Code
class TrainingDashboard(BaseUIComponent):
"""Training dashboard for users to view and complete training requirements."""
def __init__(self, parent_app=None, **params):
super().__init__(parent_app, **params)
self.current_user = None
self.training_data = {}
# UI components
self.summary_panel = None
self.required_table = None
self.completed_table = None
self.expired_table = None
self.main_content = pn.Column(sizing_mode='stretch_width')
self._build_ui()
def _build_ui(self):
"""Build the training dashboard UI."""
try:
# Create header
self.header = pn.pane.Markdown("# Training Dashboard", sizing_mode='stretch_width')
# Create summary panel
self.summary_panel = pn.pane.HTML(
"<div class='alert alert-info'>Loading training data...</div>",
sizing_mode='stretch_width'
)
# Create tabs for different training categories
self.tabs = pn.Tabs(
("Required Training", pn.Column()),
("Completed Training", pn.Column()),
("Expired Training", pn.Column()),
sizing_mode='stretch_width'
)
# Add components to main content
self.main_content.extend([
self.header,
self.summary_panel,
self.tabs
])
except Exception as e:
logger.error(f"Error building training dashboard UI: {e}")
self.main_content.append(pn.pane.Markdown(f"**Error:** {str(e)}"))
def set_user(self, user: DocUser):
"""Set the current user and load their training data."""
try:
self.current_user = user
self._load_training_data()
except Exception as e:
logger.error(f"Error setting user in training dashboard: {e}")
self.show_error(f"Error loading training data: {str(e)}")
def _load_training_data(self):
"""Load training data for the current user."""
try:
if not self.current_user:
return
# Get training dashboard data
result = training_controller.get_user_training_dashboard(self.current_user)
if result.get('success'):
# Convert any Neo4j DateTime objects to Python datetime objects
converted_result = self._convert_neo4j_datetimes(result)
self.training_data = converted_result
logger.info(f"Loaded training data for user {self.current_user.uid}: "
f"Required: {len(converted_result.get('required_training', []))}, "
f"Completed: {len(converted_result.get('completed_training', []))}, "
f"Expired: {len(converted_result.get('expired_training', []))}")
self._update_summary()
self._update_tables()
else:
error_msg = result.get('message', 'Unknown error')
logger.error(f"Error loading training data: {error_msg}")
self.show_error(f"Error loading training data: {error_msg}")
except Exception as e:
logger.error(f"Error loading training data: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
self.show_error(f"Error loading training data: {str(e)}")
def _update_summary(self):
"""Update the summary panel with training statistics."""
try:
summary = self.training_data.get('summary', {})
required_count = summary.get('required_count', 0)
completed_count = summary.get('completed_count', 0)
expired_count = summary.get('expired_count', 0)
total_count = summary.get('total_count', 0)
# Calculate completion percentage
completion_pct = (completed_count / total_count * 100) if total_count > 0 else 0
# Create summary HTML
summary_html = f"""
<div class="row">
<div class="col-md-3">
<div class="card text-white bg-warning">
<div class="card-body">
<h5 class="card-title">Required</h5>
<h2>{required_count}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-success">
<div class="card-body">
<h5 class="card-title">Completed</h5>
<h2>{completed_count}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-danger">
<div class="card-body">
<h5 class="card-title">Expired</h5>
<h2>{expired_count}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-info">
<div class="card-body">
<h5 class="card-title">Completion</h5>
<h2>{completion_pct:.1f}%</h2>
</div>
</div>
</div>
</div>
"""
self.summary_panel.object = summary_html
except Exception as e:
logger.error(f"Error updating summary: {e}")
self.summary_panel.object = f"<div class='alert alert-danger'>Error updating summary: {str(e)}</div>"
def _update_tables(self):
"""Update the training tables with current data."""
try:
# Required training
required_training = self.training_data.get('required_training', [])
self._update_required_table(required_training)
# Completed training
completed_training = self.training_data.get('completed_training', [])
self._update_completed_table(completed_training)
# Expired training
expired_training = self.training_data.get('expired_training', [])
self._update_expired_table(expired_training)
except Exception as e:
logger.error(f"Error updating tables: {e}")
def _update_required_table(self, training_list: List[Dict[str, Any]]):
"""Update the required training table."""
if not training_list:
self.tabs[0] = ("Required Training", pn.pane.Markdown("No required training found."))
return
# Prepare data for table
table_data = []
for training in training_list:
table_data.append({
'Document': training.get('doc_number', 'Unknown'),
'Title': training.get('doc_title', 'Unknown'),
'Assigned Date': self._format_date(training.get('assigned_date')),
'Quiz Required': 'Yes' if training.get('quiz_required') else 'No',
'UID': training.get('document_uid', '') # Hidden column for actions
})
# Initialize table_with_actions to None
table_with_actions = None
# Create table using DataFrame if pandas is available
try:
import pandas as pd
df = pd.DataFrame(table_data)
# Create Tabulator with DataFrame
required_table = pn.widgets.Tabulator(
df,
pagination='remote',
page_size=10,
sizing_mode='stretch_width',
hidden_columns=['UID'],
configuration={
'selectable': True,
'layout': 'fitData'
}
)
# Create action buttons using DocumentAccessControls for each training item
action_section = pn.Column()
for i, training in enumerate(training_list):
document_uid = training.get('document_uid', '')
if document_uid and hasattr(self, 'current_user') and self.current_user:
try:
from CDocs.ui.components.document_access_controls import DocumentAccessControls
# Create access controls for this document
access_controls = DocumentAccessControls(
document_uid=document_uid,
user_uid=self.current_user.uid,
show_access_indicator=False # Don't show indicator in dashboard
)
# Create a compact row for this training item
training_row = pn.Row(
pn.pane.Markdown(f"**{training.get('doc_number')}**"),
access_controls.view(),
pn.layout.HSpacer(),
self._create_start_training_button(document_uid),
sizing_mode='stretch_width'
)
action_section.append(training_row)
except Exception as e:
logger.warning(f"Error creating access controls for {document_uid}: {e}")
# Fallback to simple button - FIX: Use proper closure
def create_button_handler(uid):
def handler(event):
self._start_training(uid)
return handler
btn = pn.widgets.Button(
name="Start Training",
button_type="primary",
width=120,
height=30
)
btn.on_click(create_button_handler(document_uid))
action_section.append(btn)
# Combine table and actions - SET the variable here
table_with_actions = pn.Column(
required_table,
pn.pane.Markdown("### Actions"),
action_section,
sizing_mode='stretch_width'
)
except ImportError:
# Fallback to HTML table if pandas not available - SET the variable here too
table_html = self._create_html_table_with_access_controls(training_list)
table_with_actions = pn.pane.HTML(table_html, sizing_mode='stretch_width')
# Check if table_with_actions was set, if not create a simple fallback
if table_with_actions is None:
table_with_actions = self._create_simple_training_table(training_list)
# Update tab
self.tabs[0] = ("Required Training", pn.Column(
pn.pane.Markdown(f"### Required Training ({len(training_list)} items)"),
table_with_actions
))
def _update_completed_table(self, training_list: List[Dict[str, Any]]):
"""Update the completed training table."""
try:
if not training_list:
self.tabs[1] = ("Completed Training", pn.pane.Markdown("No completed training found."))
return
# Convert Neo4j data and prepare data for table
converted_training_list = self._convert_neo4j_datetimes(training_list)
table_data = []
for training in converted_training_list:
table_data.append({
'Document': training.get('doc_number', 'Unknown'),
'Title': training.get('doc_title', 'Unknown'),
'Completed Date': self._format_date(training.get('trained_on')),
'Expires Date': self._format_date(training.get('expires_on')),
'Valid': 'Yes' if training.get('effective_status') == 'TRAINED' else 'No'
})
# Create table using DataFrame if pandas is available
try:
import pandas as pd
df = pd.DataFrame(table_data)
completed_table = pn.widgets.Tabulator(
df,
pagination='remote',
page_size=10,
sizing_mode='stretch_width',
configuration={
'layout': 'fitData'
}
)
except ImportError:
# Fallback to HTML table if pandas not available
table_html = "<table class='table table-striped'>"
table_html += "<thead><tr><th>Document</th><th>Title</th><th>Completed Date</th><th>Expires Date</th><th>Valid</th></tr></thead>"
table_html += "<tbody>"
for training in table_data:
table_html += f"""
<tr>
<td>{training['Document']}</td>
<td>{training['Title']}</td>
<td>{training['Completed Date']}</td>
<td>{training['Expires Date']}</td>
<td>{training['Valid']}</td>
</tr>
"""
table_html += "</tbody></table>"
completed_table = pn.pane.HTML(table_html, sizing_mode='stretch_width')
# Update tab
self.tabs[1] = ("Completed Training", pn.Column(
pn.pane.Markdown(f"### Completed Training ({len(training_list)} items)"),
completed_table
))
except Exception as e:
logger.error(f"Error updating completed table: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
self.tabs[1] = ("Completed Training", pn.pane.Markdown(f"Error loading completed training: {str(e)}"))
def _update_expired_table(self, training_list: List[Dict[str, Any]]):
"""Update the expired training table."""
if not training_list:
self.tabs[2] = ("Expired Training", pn.pane.Markdown("No expired training found."))
return
# Convert Neo4j data and prepare data for table
converted_training_list = self._convert_neo4j_datetimes(training_list)
table_data = []
for training in converted_training_list:
table_data.append({
'Document': training.get('doc_number', 'Unknown'),
'Title': training.get('doc_title', 'Unknown'),
'Completed Date': self._format_date(training.get('trained_on')),
'Expired Date': self._format_date(training.get('expires_on')),
'UID': training.get('document_uid', '') # Hidden column for actions
})
# Create table using DataFrame if pandas is available
try:
import pandas as pd
df = pd.DataFrame(table_data)
# Create Tabulator with DataFrame
expired_table = pn.widgets.Tabulator(
df,
pagination='remote',
page_size=10,
sizing_mode='stretch_width',
hidden_columns=['UID'],
configuration={
'selectable': True,
'layout': 'fitData'
}
)
# Create action buttons - FIX: Use proper closure for document_uid
action_buttons = pn.Column()
for i, training in enumerate(converted_training_list):
document_uid = training.get('document_uid', '')
if document_uid:
def create_retake_handler(uid):
def handler(event):
self._start_training(uid)
return handler
btn = pn.widgets.Button(
name="Retake Training",
button_type="warning",
width=120,
height=30
)
btn.on_click(create_retake_handler(document_uid))
action_buttons.append(btn)
# Combine table and buttons
table_with_actions = pn.Row(
expired_table,
pn.Column(
pn.pane.Markdown("**Actions**"),
action_buttons
),
sizing_mode='stretch_width'
)
except ImportError:
# Fallback to HTML table if pandas not available
table_html = "<table class='table table-striped'>"
table_html += "<thead><tr><th>Document</th><th>Title</th><th>Completed Date</th><th>Expired Date</th><th>Actions</th></tr></thead>"
table_html += "<tbody>"
for i, training in enumerate(table_data):
document_uid = converted_training_list[i].get('document_uid', '')
table_html += f"""
<tr>
<td>{training['Document']}</td>
<td>{training['Title']}</td>
<td>{training['Completed Date']}</td>
<td>{training['Expired Date']}</td>
<td><button class='btn btn-warning btn-sm' onclick='startTraining(\"{document_uid}\")'>Retake Training</button></td>
</tr>
"""
table_html += "</tbody></table>"
table_with_actions = pn.pane.HTML(table_html, sizing_mode='stretch_width')
# Update tab
self.tabs[2] = ("Expired Training", pn.Column(
pn.pane.Markdown(f"### Expired Training ({len(training_list)} items)"),
table_with_actions
))
def _create_html_table_with_access_controls(self, training_list: List[Dict[str, Any]]) -> str:
"""Create HTML table with access controls for training items."""
try:
table_html = "<table class='table table-striped'>"
table_html += "<thead><tr><th>Document</th><th>Title</th><th>Assigned Date</th><th>Quiz Required</th><th>Actions</th></tr></thead>"
table_html += "<tbody>"
for i, training in enumerate(training_list):
document_uid = training.get('document_uid', '')
table_html += f"""
<tr>
<td>{training.get('doc_number', 'Unknown')}</td>
<td>{training.get('doc_title', 'Unknown')}</td>
<td>{self._format_date(training.get('assigned_date'))}</td>
<td>{'Yes' if training.get('quiz_required') else 'No'}</td>
<td>
<button class='btn btn-primary btn-sm' onclick='startTraining(\"{document_uid}\")'>Start Training</button>
<button class='btn btn-secondary btn-sm' onclick='viewDocument(\"{document_uid}\")'>View Document</button>
</td>
</tr>
"""
table_html += "</tbody></table>"
# Add JavaScript for button clicks
table_html += """
<script>
function startTraining(documentUid) {
// This would need to be connected to the Panel backend
console.log('Start training for:', documentUid);
}
function viewDocument(documentUid) {
// This would need to be connected to the Panel backend
console.log('View document:', documentUid);
}
</script>
"""
return table_html
except Exception as e:
logger.error(f"Error creating HTML table: {e}")
return f"<div class='alert alert-danger'>Error creating table: {str(e)}</div>"
def _create_simple_training_table(self, training_list: List[Dict[str, Any]]) -> pn.viewable.Viewable:
"""Create a simple training table as fallback."""
try:
# Create simple table without pandas
content = [pn.pane.Markdown("### Training Requirements")]
for training in training_list:
training_card = pn.Column(
pn.pane.Markdown(f"**{training.get('doc_number', 'Unknown')}** - {training.get('doc_title', 'Unknown')}"),
pn.pane.Markdown(f"*Assigned: {self._format_date(training.get('assigned_date'))}*"),
pn.pane.Markdown(f"*Quiz Required: {'Yes' if training.get('quiz_required') else 'No'}*"),
pn.Row(
self._create_start_training_button(training.get('document_uid', '')),
self._create_view_document_button(training.get('document_uid', '')),
align='start'
),
styles={'background': '#f8f9fa', 'margin': '10px 0'},
css_classes=['p-3', 'border', 'rounded'],
sizing_mode='stretch_width'
)
content.append(training_card)
return pn.Column(*content, sizing_mode='stretch_width')
except Exception as e:
logger.error(f"Error creating simple training table: {e}")
return pn.pane.Markdown(f"Error creating training table: {str(e)}")
def _create_view_document_button(self, document_uid: str) -> pn.viewable.Viewable:
"""Create a view document button for a specific document."""
try:
if not document_uid or not hasattr(self, 'current_user') or not self.current_user:
return pn.pane.Markdown("")
try:
from CDocs.ui.components.document_access_controls import DocumentAccessControls
# Create access controls for this document
access_controls = DocumentAccessControls(
document_uid=document_uid,
user_uid=self.current_user.uid,
show_access_indicator=False
)
return access_controls.view()
except Exception as e:
logger.warning(f"Error creating access controls for {document_uid}: {e}")
# Fallback to simple button
btn = pn.widgets.Button(
name="View Document",
button_type="secondary",
width=120,
height=30
)
btn.on_click(lambda event, uid=document_uid: self._view_document_fallback(uid))
return btn
except Exception as e:
logger.error(f"Error creating view document button: {e}")
return pn.pane.Markdown("")
def _view_document_fallback(self, document_uid: str):
"""Fallback method to view document when access controls fail."""
try:
if self.parent_app and hasattr(self.parent_app, 'navigate_to'):
try:
# Try different navigation approaches for document detail
if hasattr(self.parent_app, 'set_current_document_uid'):
self.parent_app.set_current_document_uid(document_uid)
self.parent_app.navigate_to('document_detail')
else:
try:
# Try with document_uid as parameter
self.parent_app.navigate_to('document_detail', document_uid)
except TypeError:
# Try setting context and navigate
if hasattr(self.parent_app, 'current_document_uid'):
self.parent_app.current_document_uid = document_uid
self.parent_app.navigate_to('document_detail')
except Exception as e:
logger.warning(f"Document navigation failed: {e}")
self.show_info(f"View document {document_uid} - opening in new window")
self._try_direct_document_view(document_uid)
else:
self.show_info(f"View document {document_uid}")
except Exception as e:
logger.error(f"Error in view document fallback: {e}")
self.show_error(f"Error viewing document: {str(e)}")
def _try_direct_document_view(self, document_uid: str):
"""Try to open document directly using controllers."""
try:
if not self.current_user:
return
# Try to get document view URL directly
from CDocs.controllers.document_controller import get_document_edit_url
from CDocs.models.document import ControlledDocument
document = ControlledDocument(uid=document_uid)
if document and document.current_version:
result = get_document_edit_url(
document_uid=document_uid,
user=self.current_user,
version_uid=document.current_version.uid
)
if result.get('success'):
view_url = result.get('view_url')
if view_url:
# Create a popup or new window
notification_html = f"""
<div class='alert alert-info'>
<strong>Opening document...</strong><br>
<a href='{view_url}' target='_blank' class='btn btn-primary'>Click here to view document</a>
</div>
<script>
window.open('{view_url}', '_blank');
</script>
"""
if hasattr(self, 'summary_panel') and self.summary_panel:
self.summary_panel.object = notification_html
except Exception as e:
logger.warning(f"Direct document view failed: {e}")
def refresh(self):
"""Refresh the training dashboard data."""
if self.current_user:
self._load_training_data()
def get_view(self) -> pn.viewable.Viewable:
"""Get the main view component."""
return self.main_content
def show_error(self, message: str):
"""Show error message in the summary panel."""
try:
if hasattr(self, 'summary_panel') and self.summary_panel:
self.summary_panel.object = f"<div class='alert alert-danger'>{message}</div>"
else:
logger.error(f"Error: {message}")
except Exception as e:
logger.error(f"Error showing error message: {e}")
logger.error(f"Original error: {message}")
def show_info(self, message: str):
"""Show info message in the summary panel."""
try:
if hasattr(self, 'summary_panel') and self.summary_panel:
self.summary_panel.object = f"<div class='alert alert-info'>{message}</div>"
else:
logger.info(f"Info: {message}")
except Exception as e:
logger.error(f"Error showing info message: {e}")
logger.info(f"Original info: {message}")
def _format_date(self, date_value) -> str:
"""
Format date string or object for display, handling multiple input formats including Neo4j DateTime.
Args:
date_value: Date input which could be:
- String in various formats
- datetime.datetime object
- Neo4j DateTime object
- None
Returns:
Formatted date string (YYYY-MM-DD HH:MM) or "N/A" if input is invalid
"""
if not date_value:
return "N/A"
try:
# Handle Neo4j DateTime objects
if hasattr(date_value, '__class__') and date_value.__class__.__name__ == 'DateTime':
try:
# Convert Neo4j DateTime to Python datetime
date_obj = datetime(
year=date_value.year,
month=date_value.month,
day=date_value.day,
hour=getattr(date_value, 'hour', 0),
minute=getattr(date_value, 'minute', 0),
second=getattr(date_value, 'second', 0),
microsecond=getattr(date_value, 'nanosecond', 0) // 1000
)
return date_obj.strftime('%Y-%m-%d %H:%M')
except (AttributeError, ValueError, TypeError) as e:
logger.warning(f"Error converting Neo4j DateTime: {e}")
return str(date_value)
# Handle Python datetime objects
if isinstance(date_value, datetime):
return date_value.strftime('%Y-%m-%d %H:%M')
# Handle string dates
if isinstance(date_value, str):
# Remove any timezone info and try to parse
clean_date = date_value.replace('Z', '+00:00').replace('T', ' ')
# Try different parsing strategies
try:
# Try ISO format first
date_obj = datetime.fromisoformat(clean_date)
return date_obj.strftime('%Y-%m-%d %H:%M')
except (ValueError, AttributeError):
try:
# Try without timezone
if '+' in clean_date or 'Z' in date_value:
clean_date = clean_date.split('+')[0].split('Z')[0]
date_obj = datetime.fromisoformat(clean_date)
return date_obj.strftime('%Y-%m-%d %H:%M')
except (ValueError, AttributeError):
# If it already looks like a formatted date, return as-is
if len(clean_date) >= 10 and '-' in clean_date:
return clean_date[:16] # Take first 16 chars (YYYY-MM-DD HH:MM)
return str(date_value)
# For any other type, try to convert to string
return str(date_value)
except Exception as e:
logger.warning(f"Error formatting date {date_value}: {e}")
return str(date_value) if date_value else "N/A"
def _convert_neo4j_datetimes(self, data):
"""
Recursively convert all Neo4j DateTime objects to Python datetime objects.
Args:
data: Any data structure potentially containing Neo4j DateTime objects
Returns:
Same data structure with Neo4j DateTime objects converted to Python datetime
"""
if data is None:
return None
# Handle Neo4j DateTime objects
if hasattr(data, '__class__') and data.__class__.__name__ == 'DateTime':
try:
# Try to convert to Python datetime
py_datetime = datetime(
year=data.year,
month=data.month,
day=data.day,
hour=getattr(data, 'hour', 0),
minute=getattr(data, 'minute', 0),
second=getattr(data, 'second', 0),
microsecond=getattr(data, 'nanosecond', 0) // 1000
)
return py_datetime
except (AttributeError, ValueError):
# If conversion fails, return as string
return str(data)
# Handle dictionaries
elif isinstance(data, dict):
return {k: self._convert_neo4j_datetimes(v) for k, v in data.items()}
# Handle lists
elif isinstance(data, list):
return [self._convert_neo4j_datetimes(item) for item in data]
# Handle tuples
elif isinstance(data, tuple):
return tuple(self._convert_neo4j_datetimes(item) for item in data)
# Return other types unchanged
return data
def _start_training(self, document_uid: str):
"""Start training for a specific document."""
try:
if not document_uid:
self.show_error("No document specified for training")
return
logger.info(f"Starting training for document {document_uid}")
# Validate document_uid
if not isinstance(document_uid, str) or len(document_uid.strip()) == 0:
self.show_error("Invalid document ID provided")
return
document_uid = document_uid.strip()
# Try smart navigation first with explicit document_uid
if self._smart_navigate('training_completion', document_uid):
logger.info(f"Successfully navigated to training completion for {document_uid}")
return
# Fallback approaches
if self.parent_app:
try:
# Try simple navigation with document context
if hasattr(self.parent_app, 'current_document_uid'):
logger.info(f"Setting current_document_uid to {document_uid}")
self.parent_app.current_document_uid = document_uid
if hasattr(self.parent_app, 'navigate_to'):
logger.info("Calling navigate_to with training_completion")
self.parent_app.navigate_to('training_completion')
return
except Exception as e:
logger.warning(f"Simple navigation failed: {e}")
# Try direct content replacement
try:
logger.info(f"Attempting direct training completion for {document_uid}")
self._show_training_completion_direct(document_uid)
return
except Exception as e:
logger.warning(f"Direct training completion failed: {e}")
# Final fallback
self.show_info(f"Starting training for document {document_uid} - please use the main navigation")
except Exception as e:
logger.error(f"Error starting training for {document_uid}: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
self.show_error(f"Error starting training: {str(e)}")
def _show_training_completion_direct(self, document_uid: str):
"""Directly show training completion when navigation fails."""
try:
from CDocs.ui.training_completion import TrainingCompletion
# Create training completion instance
training_completion = TrainingCompletion(parent_app=self.parent_app)
# Set user and document
training_completion.set_user_and_document(self.current_user, document_uid)
# Replace current content with training completion
if hasattr(self.parent_app, 'set_main_content'):
self.parent_app.set_main_content(training_completion.get_view())
elif hasattr(self.parent_app, 'main_content'):
self.parent_app.main_content.clear()
self.parent_app.main_content.append(training_completion.get_view())
else:
self.show_info(f"Training completion for document {document_uid} - navigation not available")
except Exception as e:
logger.error(f"Error showing training completion directly: {e}")
self.show_error(f"Error opening training: {str(e)}")
def _create_start_training_button(self, document_uid: str) -> pn.viewable.Viewable:
"""Create a start training button for a specific document."""
try:
if not document_uid:
return pn.pane.Markdown("")
# Create button with improved click handler that captures document_uid properly
btn = pn.widgets.Button(
name="Start Training",
button_type="success",
width=120,
height=30
)
# Use a closure to capture document_uid properly
def handle_click(event):
try:
logger.info(f"Start training button clicked for document: {document_uid}")
self._start_training(document_uid)
except Exception as e:
logger.error(f"Error in button click handler: {e}")
self.show_error(f"Error starting training: {str(e)}")
btn.on_click(handle_click)
return btn
except Exception as e:
logger.error(f"Error creating start training button: {e}")
return pn.pane.Markdown("")
def _detect_navigation_method(self):
"""Detect the correct navigation method signature."""
try:
if not self.parent_app or not hasattr(self.parent_app, 'navigate_to'):
return None
# Check method signature
import inspect
sig = inspect.signature(self.parent_app.navigate_to)
params = list(sig.parameters.keys())
logger.info(f"Navigation method signature: {params}")
# Return navigation method info
return {
'method': 'navigate_to',
'params': params,
'accepts_kwargs': any(p.kind == p.VAR_KEYWORD for p in sig.parameters.values())
}
except Exception as e:
logger.warning(f"Could not detect navigation method: {e}")
return None
def _smart_navigate(self, view_name: str, document_uid: str = None):
"""Smart navigation that adapts to the app's navigation method."""
try:
if not self.parent_app:
return False
nav_info = self._detect_navigation_method()
if not nav_info:
logger.warning("No navigation method available")
return False
# Try different navigation patterns
try:
params = nav_info['params']
if len(params) == 1:
# Only view name accepted - set document context first
if hasattr(self.parent_app, 'current_document_uid') and document_uid:
self.parent_app.current_document_uid = document_uid
self.parent_app.navigate_to(view_name)
return True
elif len(params) >= 2:
# Accepts additional parameters - pass document_uid as second parameter
self.parent_app.navigate_to(view_name, document_uid)
return True
elif nav_info['accepts_kwargs']:
# Accepts keyword arguments
self.parent_app.navigate_to(view_name, document_uid=document_uid)
return True
except Exception as e:
logger.warning(f"Smart navigation failed: {e}")
return False
except Exception as e:
logger.error(f"Error in smart navigation: {e}")
return False
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
BaseUIComponent | - |
Parameter Details
bases: Parameter of type BaseUIComponent
Return Value
Returns unspecified type
Class Interface
Methods
__init__(self, parent_app)
Purpose: Internal method: init
Parameters:
parent_app: Parameter
Returns: None
_build_ui(self)
Purpose: Build the training dashboard UI.
Returns: None
set_user(self, user)
Purpose: Set the current user and load their training data.
Parameters:
user: Type: DocUser
Returns: None
_load_training_data(self)
Purpose: Load training data for the current user.
Returns: None
_update_summary(self)
Purpose: Update the summary panel with training statistics.
Returns: None
_update_tables(self)
Purpose: Update the training tables with current data.
Returns: None
_update_required_table(self, training_list)
Purpose: Update the required training table.
Parameters:
training_list: Type: List[Dict[str, Any]]
Returns: None
_update_completed_table(self, training_list)
Purpose: Update the completed training table.
Parameters:
training_list: Type: List[Dict[str, Any]]
Returns: None
_update_expired_table(self, training_list)
Purpose: Update the expired training table.
Parameters:
training_list: Type: List[Dict[str, Any]]
Returns: None
_create_html_table_with_access_controls(self, training_list) -> str
Purpose: Create HTML table with access controls for training items.
Parameters:
training_list: Type: List[Dict[str, Any]]
Returns: Returns str
_create_simple_training_table(self, training_list) -> pn.viewable.Viewable
Purpose: Create a simple training table as fallback.
Parameters:
training_list: Type: List[Dict[str, Any]]
Returns: Returns pn.viewable.Viewable
_create_view_document_button(self, document_uid) -> pn.viewable.Viewable
Purpose: Create a view document button for a specific document.
Parameters:
document_uid: Type: str
Returns: Returns pn.viewable.Viewable
_view_document_fallback(self, document_uid)
Purpose: Fallback method to view document when access controls fail.
Parameters:
document_uid: Type: str
Returns: None
_try_direct_document_view(self, document_uid)
Purpose: Try to open document directly using controllers.
Parameters:
document_uid: Type: str
Returns: None
refresh(self)
Purpose: Refresh the training dashboard data.
Returns: None
get_view(self) -> pn.viewable.Viewable
Purpose: Get the main view component.
Returns: Returns pn.viewable.Viewable
show_error(self, message)
Purpose: Show error message in the summary panel.
Parameters:
message: Type: str
Returns: None
show_info(self, message)
Purpose: Show info message in the summary panel.
Parameters:
message: Type: str
Returns: None
_format_date(self, date_value) -> str
Purpose: Format date string or object for display, handling multiple input formats including Neo4j DateTime. Args: date_value: Date input which could be: - String in various formats - datetime.datetime object - Neo4j DateTime object - None Returns: Formatted date string (YYYY-MM-DD HH:MM) or "N/A" if input is invalid
Parameters:
date_value: Parameter
Returns: Returns str
_convert_neo4j_datetimes(self, data)
Purpose: Recursively convert all Neo4j DateTime objects to Python datetime objects. Args: data: Any data structure potentially containing Neo4j DateTime objects Returns: Same data structure with Neo4j DateTime objects converted to Python datetime
Parameters:
data: Parameter
Returns: See docstring for return details
_start_training(self, document_uid)
Purpose: Start training for a specific document.
Parameters:
document_uid: Type: str
Returns: None
_show_training_completion_direct(self, document_uid)
Purpose: Directly show training completion when navigation fails.
Parameters:
document_uid: Type: str
Returns: None
_create_start_training_button(self, document_uid) -> pn.viewable.Viewable
Purpose: Create a start training button for a specific document.
Parameters:
document_uid: Type: str
Returns: Returns pn.viewable.Viewable
_detect_navigation_method(self)
Purpose: Detect the correct navigation method signature.
Returns: None
_smart_navigate(self, view_name, document_uid)
Purpose: Smart navigation that adapts to the app's navigation method.
Parameters:
view_name: Type: strdocument_uid: Type: str
Returns: None
Required Imports
import logging
from typing import Dict
from typing import List
from typing import Any
from typing import Optional
Usage Example
# Example usage:
# result = TrainingDashboard(bases)
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class TrainingCompletion 70.8% similar
-
function get_user_training_dashboard 68.6% similar
-
class TrainingManagement 61.7% similar
-
class DocumentTraining 58.9% similar
-
class UserTraining 58.8% similar