|
1 | 1 | import json |
2 | 2 | import logging |
3 | 3 | from datetime import datetime |
4 | | -from enum import Enum |
5 | 4 | from pathlib import Path |
6 | | -from typing import Any, Dict, List, Optional, Union |
| 5 | +from typing import Any, Dict, Optional, Union |
7 | 6 |
|
8 | 7 | from py_trees import common |
9 | 8 | from py_trees.common import Status |
10 | 9 | from pydantic import BaseModel, Field |
11 | | -from rich.console import Console |
12 | | -from rich.table import Table |
13 | 10 |
|
14 | 11 | from redis_release.models import ( |
15 | 12 | PackageType, |
@@ -265,220 +262,3 @@ def reset_model_to_defaults(target: BaseModel, default: BaseModel) -> None: |
265 | 262 | else: |
266 | 263 | # Simple value, copy directly |
267 | 264 | 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) |
0 commit comments