major-domo-v2/commands/examples/migration_example.py
Cal Corum e6a30af604 CLAUDE: SUCCESSFUL STARTUP - Discord Bot v2.0 fully operational
 **MAJOR MILESTONE**: Bot successfully starts and loads all commands

🔧 **Key Fixes Applied**:
- Fixed Pydantic configuration (SettingsConfigDict vs ConfigDict)
- Resolved duplicate logging with hybrid propagation approach
- Enhanced console logging with detailed format (function:line)
- Eliminated redundant .log file handler (kept console + JSON)
- Fixed Pylance type errors across views and modals
- Added newline termination to JSON logs for better tool compatibility
- Enabled league commands package in bot.py
- Enhanced command tree hashing for proper type support

📦 **New Components Added**:
- Complete views package (base.py, common.py, embeds.py, modals.py)
- League service and commands integration
- Comprehensive test coverage improvements
- Enhanced decorator functionality with proper signature preservation

🎯 **Architecture Improvements**:
- Hybrid logging: detailed console for dev + structured JSON for monitoring
- Type-safe command tree handling for future extensibility
- Proper optional parameter handling in Pydantic models
- Eliminated duplicate log messages while preserving third-party library logs

🚀 **Ready for Production**: Bot loads all command packages successfully with no errors

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 07:36:47 -05:00

311 lines
11 KiB
Python

"""
Migration Example: Before and After Views v2.0
Shows how to upgrade existing commands to use the modern view system.
This demonstrates the transformation from basic embeds to interactive views.
"""
from typing import Optional, List
import discord
from discord.ext import commands
from services.team_service import team_service
from models.team import Team
from constants import SBA_CURRENT_SEASON
from utils.logging import get_contextual_logger
from utils.decorators import logged_command
# Import new view components
from views import (
SBAEmbedTemplate,
EmbedTemplate,
EmbedColors,
TeamSelectionView,
PaginationView,
ConfirmationView,
DetailedInfoView
)
class MigrationExampleCommands(commands.Cog):
"""Example showing before/after migration to Views v2.0."""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.logger = get_contextual_logger(f'{__name__}.MigrationExampleCommands')
# ========================================
# BEFORE: Traditional approach
# ========================================
@discord.app_commands.command(
name="teams-old",
description="List teams (old style - basic embed)"
)
@logged_command("/teams-old")
async def teams_old_style(self, interaction: discord.Interaction, season: Optional[int] = None):
"""Old style team listing - basic embed only."""
await interaction.response.defer()
season = season or SBA_CURRENT_SEASON
teams = await team_service.get_teams_by_season(season)
if not teams:
embed = discord.Embed(
title="No Teams Found",
description=f"No teams found for season {season}",
color=0xff6b6b
)
await interaction.followup.send(embed=embed)
return
# Sort teams by abbreviation
teams.sort(key=lambda t: t.abbrev)
# Create basic embed
embed = discord.Embed(
title=f"SBA Teams - Season {season}",
color=0xa6ce39
)
# Simple list - limited functionality
team_list = "\n".join([f"**{team.abbrev}** - {team.lname}" for team in teams[:20]])
if len(teams) > 20:
team_list += f"\n... and {len(teams) - 20} more teams"
embed.add_field(name="Teams", value=team_list, inline=False)
embed.set_footer(text=f"Total: {len(teams)} teams")
await interaction.followup.send(embed=embed)
# ========================================
# AFTER: Modern Views v2.0 approach
# ========================================
@discord.app_commands.command(
name="teams-new",
description="List teams (new style - interactive with views)"
)
@logged_command("/teams-new")
async def teams_new_style(self, interaction: discord.Interaction, season: Optional[int] = None):
"""New style team listing - interactive with pagination and selection."""
await interaction.response.defer()
season = season or SBA_CURRENT_SEASON
teams = await team_service.get_teams_by_season(season)
if not teams:
embed = EmbedTemplate.warning(
title="No Teams Found",
description=f"No teams found for season {season}"
)
await interaction.followup.send(embed=embed)
return
# Sort teams by abbreviation
teams.sort(key=lambda t: t.abbrev)
# Create paginated view with team selection
await self._create_interactive_team_list(interaction, teams, season)
async def _create_interactive_team_list(
self,
interaction: discord.Interaction,
teams: List[Team],
season: int
):
"""Create interactive team list with pagination and selection."""
teams_per_page = 10
pages = []
# Create pages
for i in range(0, len(teams), teams_per_page):
page_teams = teams[i:i + teams_per_page]
embed = SBAEmbedTemplate.league_status(
season=season,
teams_count=len(teams),
additional_info=f"Showing teams {i + 1}-{min(i + teams_per_page, len(teams))} of {len(teams)}"
)
embed.title = f"🏟️ SBA Teams - Season {season}"
# Group teams by division if available
if any(getattr(team, 'division_id', None) for team in page_teams):
divisions = {}
for team in page_teams:
div_id = getattr(team, 'division_id', 0) or 0
if div_id not in divisions:
divisions[div_id] = []
divisions[div_id].append(team)
for div_id, div_teams in sorted(divisions.items()):
div_name = f"Division {div_id}" if div_id > 0 else "Unassigned"
team_list = "\n".join([
f"**{team.abbrev}** - {team.lname}"
for team in div_teams
])
embed.add_field(name=div_name, value=team_list, inline=True)
else:
# Simple list if no divisions
team_list = "\n".join([
f"**{team.abbrev}** - {team.lname}"
for team in page_teams
])
embed.add_field(name="Teams", value=team_list, inline=False)
pages.append(embed)
# Create pagination view
view = PaginationView(
pages=pages,
user_id=interaction.user.id,
show_page_numbers=True
)
# Add team selection dropdown to first row
if len(teams) <= 25: # Discord limit for select options
team_select = TeamSelectionView(
teams=teams,
user_id=interaction.user.id,
callback=self._handle_team_selection
)
# Combine pagination with selection (would need custom view for this)
# For now, show them separately
await interaction.followup.send(embed=view.get_current_embed(), view=view)
# Also provide team selection if reasonable number
if len(teams) <= 25:
await self._add_team_selection_followup(interaction, teams)
async def _add_team_selection_followup(
self,
interaction: discord.Interaction,
teams: List[Team]
):
"""Add team selection as follow-up message."""
view = TeamSelectionView(
teams=teams,
user_id=interaction.user.id,
callback=self._handle_team_selection
)
embed = EmbedTemplate.info(
title="Team Selection",
description="Select a team below to view detailed information:"
)
await interaction.followup.send(embed=embed, view=view, ephemeral=True)
async def _handle_team_selection(
self,
interaction: discord.Interaction,
team: Team
):
"""Handle team selection from dropdown."""
# Get additional team data
standings_data = await team_service.get_team_standings_position(team.id, team.season)
# Create detailed team embed
embed = SBAEmbedTemplate.team_info(
team_abbrev=team.abbrev,
team_name=team.lname,
season=team.season,
short_name=getattr(team, 'sname', None),
stadium=getattr(team, 'stadium', None),
division=f"Division {team.division_id}" if getattr(team, 'division_id', None) else None,
team_color=getattr(team, 'color', None),
team_thumbnail=getattr(team, 'thumbnail', None)
)
# Add standings info if available
if standings_data:
try:
wins = standings_data.get('wins', 'N/A')
losses = standings_data.get('losses', 'N/A')
pct = standings_data.get('pct', 'N/A')
gb = standings_data.get('gb', 'N/A')
record_text = f"{wins}-{losses}"
if pct != 'N/A':
record_text += f" ({pct:.3f})"
if gb != 'N/A' and gb != 0:
record_text += f"{gb} GB"
embed.add_field(name="Record", value=record_text, inline=False)
except (KeyError, TypeError):
pass
# Create detailed info view with actions
async def refresh_team_data(interaction: discord.Interaction) -> discord.Embed:
"""Refresh team data."""
updated_standings = await team_service.get_team_standings_position(team.id, team.season)
# Recreate embed with updated data
return embed # Simplified for example
async def show_roster(interaction: discord.Interaction):
"""Show team roster."""
roster_embed = EmbedTemplate.info(
title=f"{team.abbrev} Roster",
description="Roster functionality would go here..."
)
await interaction.response.send_message(embed=roster_embed, ephemeral=True)
view = DetailedInfoView(
embed=embed,
user_id=interaction.user.id,
show_refresh=True,
show_details=True,
refresh_callback=refresh_team_data,
details_callback=show_roster
)
await interaction.response.edit_message(embed=embed, view=view)
# ========================================
# Additional Examples
# ========================================
@discord.app_commands.command(
name="confirmation-example",
description="Example of confirmation dialog"
)
@logged_command("/confirmation-example")
async def confirmation_example(self, interaction: discord.Interaction):
"""Example of modern confirmation dialog."""
embed = EmbedTemplate.warning(
title="Confirm Action",
description="This is an example confirmation dialog. Do you want to proceed?"
)
async def handle_confirm(interaction: discord.Interaction):
"""Handle confirmation."""
success_embed = EmbedTemplate.success(
title="Action Confirmed",
description="The action has been completed successfully!"
)
await interaction.response.edit_message(embed=success_embed, view=None)
async def handle_cancel(interaction: discord.Interaction):
"""Handle cancellation."""
cancel_embed = EmbedTemplate.error(
title="Action Cancelled",
description="The action has been cancelled."
)
await interaction.response.edit_message(embed=cancel_embed, view=None)
view = ConfirmationView(
user_id=interaction.user.id,
confirm_callback=handle_confirm,
cancel_callback=handle_cancel,
confirm_label="Yes, Proceed",
cancel_label="No, Cancel"
)
await interaction.response.send_message(embed=embed, view=view)
async def setup(bot: commands.Bot):
"""Load the migration example commands cog."""
await bot.add_cog(MigrationExampleCommands(bot))