Files
ALwrity/ToBeMigrated/content_scheduler/ui/views/timeline_view.py
2025-08-06 16:29:49 +05:30

392 lines
14 KiB
Python

"""
Timeline view implementation for the Content Scheduler.
Provides interactive Gantt charts and progress tracking visualization.
"""
import streamlit as st
import plotly.figure_factory as ff
import plotly.graph_objects as go
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
import pandas as pd
import json
# Use unified database models
from lib.database.models import ContentItem, Schedule, ScheduleStatus, get_session
class TimelineView:
"""Interactive timeline view with Gantt charts and progress tracking."""
def __init__(self):
"""Initialize the timeline view."""
self.session = get_session()
def render(self):
"""Render the timeline view."""
st.header("Schedule Timeline")
# Timeline controls
self._render_timeline_controls()
# Main timeline view
self._render_timeline()
# Progress tracking
self._render_progress_tracking()
def _render_timeline_controls(self):
"""Render timeline control options."""
col1, col2, col3 = st.columns([2, 2, 1])
with col1:
view_type = st.selectbox(
"View Type",
["Gantt Chart", "Timeline", "List View"],
help="Select the type of timeline visualization"
)
with col2:
date_range = st.date_input(
"Date Range",
value=(
datetime.now().date(),
datetime.now().date() + timedelta(days=7)
),
help="Select the date range to display"
)
with col3:
if st.button("Export", help="Export timeline data"):
self._export_timeline_data()
def _render_timeline(self):
"""Render the main timeline visualization."""
# Get schedules for the selected date range
schedules = self._get_schedules_for_timeline()
if not schedules:
st.info("No schedules found for the selected date range.")
return
# Create Gantt chart data
gantt_data = self._create_gantt_data(schedules)
# Create and display Gantt chart
fig = self._create_gantt_chart(gantt_data)
st.plotly_chart(fig, use_container_width=True)
# Display schedule details
self._render_schedule_details(schedules)
def _render_progress_tracking(self):
"""Render progress tracking visualization."""
st.subheader("Progress Tracking")
# Progress metrics
col1, col2, col3 = st.columns(3)
with col1:
self._render_progress_metric(
"Completed",
self._get_completed_count(),
"green"
)
with col2:
self._render_progress_metric(
"In Progress",
self._get_in_progress_count(),
"orange"
)
with col3:
self._render_progress_metric(
"Pending",
self._get_pending_count(),
"blue"
)
# Progress chart
self._render_progress_chart()
def _get_schedules_for_timeline(self) -> List[Schedule]:
"""Get schedules for the timeline view."""
try:
# Get date range from session state or use default
if hasattr(st.session_state, 'date_range') and st.session_state.date_range:
start_date, end_date = st.session_state.date_range
else:
start_date = datetime.now().date()
end_date = start_date + timedelta(days=7)
# Convert to datetime
start_datetime = datetime.combine(start_date, datetime.min.time())
end_datetime = datetime.combine(end_date, datetime.max.time())
# Query schedules from unified database
schedules = self.session.query(Schedule).filter(
Schedule.scheduled_time >= start_datetime,
Schedule.scheduled_time <= end_datetime
).all()
return schedules
except Exception as e:
st.error(f"Failed to get schedules: {str(e)}")
return []
def _create_gantt_data(self, schedules: List[Schedule]) -> List[Dict[str, Any]]:
"""Create data for Gantt chart."""
gantt_data = []
for schedule in schedules:
# Get content item details
content_item = self.session.query(ContentItem).filter(
ContentItem.id == schedule.content_item_id
).first()
if content_item:
# Calculate task duration
duration = timedelta(hours=1) # Default duration
# Create task data
task = {
'Task': content_item.title[:50] + "..." if len(content_item.title) > 50 else content_item.title,
'Start': schedule.scheduled_time,
'Finish': schedule.scheduled_time + duration,
'Resource': schedule.status.value,
'Status': schedule.status.value,
'Progress': self._calculate_progress(schedule)
}
gantt_data.append(task)
return gantt_data
def _create_gantt_chart(self, gantt_data: List[Dict[str, Any]]) -> go.Figure:
"""Create Gantt chart visualization."""
if not gantt_data:
# Return empty figure
fig = go.Figure()
fig.update_layout(
title='Content Schedule Timeline',
xaxis_title='Timeline',
yaxis_title='Status',
height=400
)
return fig
# Convert data to DataFrame
df = pd.DataFrame(gantt_data)
# Create Gantt chart
fig = ff.create_gantt(
df,
index_col='Resource',
show_colorbar=True,
group_tasks=True,
showgrid_x=True,
showgrid_y=True
)
# Update layout
fig.update_layout(
title='Content Schedule Timeline',
xaxis_title='Timeline',
yaxis_title='Status',
height=400,
showlegend=True
)
return fig
def _render_schedule_details(self, schedules: List[Schedule]):
"""Render detailed schedule information."""
st.subheader("Schedule Details")
for schedule in schedules:
# Get content item details
content_item = self.session.query(ContentItem).filter(
ContentItem.id == schedule.content_item_id
).first()
if content_item:
with st.expander(f"{content_item.title} - {schedule.status.value}"):
col1, col2 = st.columns(2)
with col1:
st.write("**Schedule Information**")
st.write(f"Content Type: {content_item.content_type.value if content_item.content_type else 'Unknown'}")
st.write(f"Status: {schedule.status.value}")
st.write(f"Scheduled Time: {schedule.scheduled_time}")
st.write(f"Priority: {schedule.priority}")
if schedule.recurrence:
st.write(f"Recurrence: {schedule.recurrence}")
with col2:
st.write("**Progress**")
progress = self._calculate_progress(schedule)
st.progress(progress / 100)
st.write(f"Progress: {progress:.1f}%")
# Action buttons
col2a, col2b = st.columns(2)
with col2a:
if st.button(f"Edit {schedule.id}", key=f"edit_{schedule.id}"):
st.session_state.edit_schedule_id = schedule.id
with col2b:
if st.button(f"Cancel {schedule.id}", key=f"cancel_{schedule.id}"):
self._cancel_schedule(schedule.id)
def _render_progress_metric(self, label: str, value: int, color: str):
"""Render a progress metric."""
st.metric(label, value)
def _render_progress_chart(self):
"""Render progress chart visualization."""
try:
# Get progress data
progress_data = self._get_progress_data()
if progress_data:
# Create pie chart
labels = list(progress_data.keys())
values = list(progress_data.values())
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
fig.update_layout(
title="Schedule Status Distribution",
height=300
)
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No progress data available.")
except Exception as e:
st.error(f"Error rendering progress chart: {str(e)}")
def _calculate_progress(self, schedule: Schedule) -> float:
"""Calculate progress percentage for a schedule."""
try:
if schedule.status == ScheduleStatus.COMPLETED:
return 100.0
elif schedule.status == ScheduleStatus.RUNNING:
return 50.0
elif schedule.status == ScheduleStatus.FAILED:
return 0.0
else: # PENDING
return 0.0
except Exception as e:
st.error(f"Error calculating progress: {str(e)}")
return 0.0
def _get_completed_count(self) -> int:
"""Get count of completed schedules."""
try:
return self.session.query(Schedule).filter(
Schedule.status == ScheduleStatus.COMPLETED
).count()
except Exception as e:
st.error(f"Error getting completed count: {str(e)}")
return 0
def _get_in_progress_count(self) -> int:
"""Get count of in-progress schedules."""
try:
return self.session.query(Schedule).filter(
Schedule.status == ScheduleStatus.RUNNING
).count()
except Exception as e:
st.error(f"Error getting in-progress count: {str(e)}")
return 0
def _get_pending_count(self) -> int:
"""Get count of pending schedules."""
try:
return self.session.query(Schedule).filter(
Schedule.status == ScheduleStatus.PENDING
).count()
except Exception as e:
st.error(f"Error getting pending count: {str(e)}")
return 0
def _get_progress_data(self) -> Dict[str, int]:
"""Get progress data for visualization."""
try:
progress_data = {}
# Count schedules by status
for status in ScheduleStatus:
count = self.session.query(Schedule).filter(
Schedule.status == status
).count()
progress_data[status.value] = count
return progress_data
except Exception as e:
st.error(f"Error getting progress data: {str(e)}")
return {}
def _cancel_schedule(self, schedule_id: int):
"""Cancel a schedule."""
try:
schedule = self.session.query(Schedule).filter(
Schedule.id == schedule_id
).first()
if schedule:
schedule.status = ScheduleStatus.CANCELLED
self.session.commit()
st.success(f"Schedule {schedule_id} cancelled successfully!")
st.experimental_rerun()
else:
st.error("Schedule not found.")
except Exception as e:
st.error(f"Error cancelling schedule: {str(e)}")
self.session.rollback()
def _export_timeline_data(self):
"""Export timeline data."""
try:
schedules = self._get_schedules_for_timeline()
if schedules:
# Prepare export data
export_data = []
for schedule in schedules:
content_item = self.session.query(ContentItem).filter(
ContentItem.id == schedule.content_item_id
).first()
if content_item:
export_data.append({
'Schedule ID': schedule.id,
'Title': content_item.title,
'Content Type': content_item.content_type.value if content_item.content_type else 'Unknown',
'Scheduled Time': schedule.scheduled_time.isoformat(),
'Status': schedule.status.value,
'Priority': schedule.priority,
'Recurrence': schedule.recurrence or 'None'
})
# Convert to CSV
df = pd.DataFrame(export_data)
csv = df.to_csv(index=False)
# Provide download
st.download_button(
label="Download Timeline Data",
data=csv,
file_name=f"timeline_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv"
)
else:
st.warning("No data to export.")
except Exception as e:
st.error(f"Error exporting data: {str(e)}")