alwrity chatbot assistant, content scheduler, and content repurposing
This commit is contained in:
392
lib/content_scheduler/ui/views/timeline_view.py
Normal file
392
lib/content_scheduler/ui/views/timeline_view.py
Normal file
@@ -0,0 +1,392 @@
|
||||
"""
|
||||
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)}")
|
||||
Reference in New Issue
Block a user