Skip to content

Commit 37b94c2

Browse files
committed
Move state print into separate class and file
1 parent 7aed606 commit 37b94c2

File tree

5 files changed

+281
-231
lines changed

5 files changed

+281
-231
lines changed

src/redis_release/bht/state.py

Lines changed: 1 addition & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import json
22
import logging
33
from datetime import datetime
4-
from enum import Enum
54
from pathlib import Path
6-
from typing import Any, Dict, List, Optional, Union
5+
from typing import Any, Dict, Optional, Union
76

87
from py_trees import common
98
from py_trees.common import Status
109
from pydantic import BaseModel, Field
11-
from rich.console import Console
12-
from rich.table import Table
1310

1411
from redis_release.models import (
1512
PackageType,
@@ -265,220 +262,3 @@ def reset_model_to_defaults(target: BaseModel, default: BaseModel) -> None:
265262
else:
266263
# Simple value, copy directly
267264
setattr(target, field_name, default_value)
268-
269-
270-
def print_state_table(state: ReleaseState, console: Optional[Console] = None) -> None:
271-
"""Print table showing the release state.
272-
273-
Args:
274-
state: The ReleaseState to display
275-
console: Optional Rich Console instance (creates new one if not provided)
276-
"""
277-
if console is None:
278-
console = Console()
279-
280-
# Create table with title
281-
table = Table(
282-
title=f"[bold cyan]Release State: {state.meta.tag or 'N/A'}[/bold cyan]",
283-
show_header=True,
284-
show_lines=True,
285-
header_style="bold magenta",
286-
border_style="bright_blue",
287-
title_style="bold cyan",
288-
)
289-
290-
# Add columns
291-
table.add_column("Package", style="cyan", no_wrap=True, min_width=20, width=20)
292-
table.add_column("Build", justify="center", no_wrap=True, min_width=20, width=20)
293-
table.add_column("Publish", justify="center", no_wrap=True, min_width=20, width=20)
294-
table.add_column("Details", style="yellow", width=100)
295-
296-
# Process each package
297-
for package_name, package in sorted(state.packages.items()):
298-
# Determine build status
299-
build_status = _get_workflow_status_display(package, package.build)
300-
301-
# Determine publish status
302-
publish_status = _get_workflow_status_display(package, package.publish)
303-
304-
# Collect details from workflows
305-
details = _collect_details(package)
306-
307-
# Add row to table
308-
table.add_row(
309-
package_name,
310-
build_status,
311-
publish_status,
312-
details,
313-
)
314-
315-
# Print the table
316-
console.print()
317-
console.print(table)
318-
console.print()
319-
320-
321-
class StepStatus(str, Enum):
322-
NOT_STARTED = "not_started"
323-
RUNNING = "in_progress"
324-
FAILED = "failed"
325-
SUCCEEDED = "succeeded"
326-
INCORRECT = "incorrect"
327-
328-
329-
# Decision table for step status
330-
# See WorkflowEphemeral for more details on the flags
331-
_step_status_mapping = {
332-
None: {False: StepStatus.NOT_STARTED, True: StepStatus.SUCCEEDED},
333-
Status.RUNNING: {False: StepStatus.RUNNING},
334-
Status.FAILURE: {False: StepStatus.FAILED},
335-
Status.SUCCESS: {True: StepStatus.SUCCEEDED},
336-
}
337-
338-
339-
def _get_step_status(
340-
step_result: bool, step_status_flag: Optional[common.Status]
341-
) -> StepStatus:
342-
"""Get step status based on result and ephemeral flag.
343-
344-
See WorkflowEphemeral for more details on the flags.
345-
"""
346-
if step_status_flag in _step_status_mapping:
347-
if step_result in _step_status_mapping[step_status_flag]:
348-
return _step_status_mapping[step_status_flag][step_result]
349-
return StepStatus.INCORRECT
350-
351-
352-
def _get_workflow_status(
353-
package: Package, workflow: Workflow
354-
) -> tuple[StepStatus, List[tuple[StepStatus, str, Optional[str]]]]:
355-
"""Get workflow status based on ephemeral and result fields.
356-
357-
Returns tuple of overall status and list of step statuses.
358-
359-
See WorkflowEphemeral for more details on the flags.
360-
"""
361-
steps_status: List[tuple[StepStatus, str, Optional[str]]] = []
362-
steps = [
363-
(
364-
package.meta.ref is not None,
365-
package.meta.ephemeral.identify_ref,
366-
"Identify target ref",
367-
None,
368-
),
369-
(
370-
workflow.triggered_at is not None,
371-
workflow.ephemeral.trigger_workflow,
372-
"Trigger workflow",
373-
None,
374-
),
375-
(
376-
workflow.run_id is not None,
377-
workflow.ephemeral.identify_workflow,
378-
"Find workflow run",
379-
None,
380-
),
381-
(
382-
workflow.conclusion == WorkflowConclusion.SUCCESS,
383-
workflow.ephemeral.wait_for_completion,
384-
"Wait for completion",
385-
workflow.ephemeral.wait_for_completion_message,
386-
),
387-
(
388-
workflow.artifacts is not None,
389-
workflow.ephemeral.download_artifacts,
390-
"Download artifacts",
391-
None,
392-
),
393-
(
394-
workflow.result is not None,
395-
workflow.ephemeral.extract_artifact_result,
396-
"Get result",
397-
None,
398-
),
399-
]
400-
for result, status_flag, name, status_msg in steps:
401-
s = _get_step_status(result, status_flag)
402-
steps_status.append((s, name, status_msg))
403-
if s != StepStatus.SUCCEEDED:
404-
return (s, steps_status)
405-
return (StepStatus.SUCCEEDED, steps_status)
406-
407-
408-
def _get_workflow_status_display(package: Package, workflow: Workflow) -> str:
409-
"""Get a rich-formatted status display for a workflow.
410-
411-
Args:
412-
package: The package containing the workflow
413-
workflow: The workflow to check
414-
415-
Returns:
416-
Rich-formatted status string
417-
"""
418-
workflow_status = _get_workflow_status(package, workflow)
419-
if workflow_status[0] == StepStatus.SUCCEEDED:
420-
return "[bold green]✓ Success[/bold green]"
421-
elif workflow_status[0] == StepStatus.RUNNING:
422-
return "[bold yellow]⏳ In Progress[/bold yellow]"
423-
elif workflow_status[0] == StepStatus.NOT_STARTED:
424-
return "[dim]Not Started[/dim]"
425-
elif workflow_status[0] == StepStatus.INCORRECT:
426-
return "[bold red]✗ Invalid state![/bold red]"
427-
428-
return "[bold red]✗ Failed[/bold red]"
429-
430-
431-
def _collect_workflow_details(
432-
package: Package, workflow: Workflow, prefix: str
433-
) -> List[str]:
434-
"""Collect details from a workflow using bottom-up approach.
435-
436-
Shows successes until the first failure, then stops.
437-
Bottom-up means: trigger → identify → timeout → conclusion → artifacts → result
438-
439-
Args:
440-
workflow: The workflow to check
441-
prefix: Prefix for detail messages (e.g., "Build" or "Publish")
442-
443-
Returns:
444-
List of detail strings
445-
"""
446-
details: List[str] = []
447-
448-
workflow_status = _get_workflow_status(package, workflow)
449-
if workflow_status[0] == StepStatus.NOT_STARTED:
450-
return details
451-
452-
details.append(f"{prefix} Workflow")
453-
indent = " " * 2
454-
455-
for step_status, step_name, step_message in workflow_status[1]:
456-
if step_status == StepStatus.SUCCEEDED:
457-
details.append(f"{indent}[green]✓ {step_name}[/green]")
458-
elif step_status == StepStatus.RUNNING:
459-
details.append(f"{indent}[yellow]⏳ {step_name}[/yellow]")
460-
elif step_status == StepStatus.NOT_STARTED:
461-
details.append(f"{indent}[dim]Not started: {step_name}[/dim]")
462-
else:
463-
msg = f" ({step_message})" if step_message else ""
464-
details.append(f"{indent}[red]✗ {step_name} failed[/red]{msg}")
465-
break
466-
467-
return details
468-
469-
470-
def _collect_details(package: Package) -> str:
471-
"""Collect and format all details from package and workflows.
472-
473-
Args:
474-
package: The package to check
475-
476-
Returns:
477-
Formatted string of details
478-
"""
479-
details: List[str] = []
480-
481-
details.extend(_collect_workflow_details(package, package.build, "Build"))
482-
details.extend(_collect_workflow_details(package, package.publish, "Publish"))
483-
484-
return "\n".join(details)

src/redis_release/bht/tree.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from ..config import Config
2222
from ..github_client_async import GitHubClientAsync
2323
from ..models import ReleaseArgs
24+
from ..state_display import print_state_table
2425
from ..state_manager import S3StateStorage, StateManager, StateStorage
2526
from .backchain import latch_chains
2627
from .behaviours import NeedToPublishRelease
@@ -43,14 +44,7 @@
4344
create_workflow_completion_ppa,
4445
create_workflow_success_ppa,
4546
)
46-
from .state import (
47-
Package,
48-
PackageMeta,
49-
ReleaseMeta,
50-
ReleaseState,
51-
Workflow,
52-
print_state_table,
53-
)
47+
from .state import Package, PackageMeta, ReleaseMeta, ReleaseState, Workflow
5448

5549
logger = logging.getLogger(__name__)
5650

src/redis_release/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import typer
99
from py_trees.display import render_dot_tree, unicode_tree
1010

11-
from redis_release.bht.state import print_state_table
1211
from redis_release.models import ReleaseType
12+
from redis_release.state_display import print_state_table
1313
from redis_release.state_manager import (
1414
InMemoryStateStorage,
1515
S3StateStorage,

0 commit comments

Comments
 (0)