Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 48 additions & 48 deletions docs/refactors/plans/standardize-command-view-folder-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ harder to review (logic changes + file moves mixed). The correct order is:
**Total Active Proposals**: 3
**Total Postponed**: 0
**Total Discarded**: 0
**Completed**: 0
**Completed**: 3
**In Progress**: 0
**Not Started**: 3
**Not Started**: 0

### Phase Summary

- **Phase 0 - Fix `run` module layout (High Impact, Low Effort)**: ⏳ 0/2 completed (0%)
- **Phase 1 - Fix `list` and `show` module layout (Medium Impact, Low Effort)**: ⏳ 0/1 completed (0%)
- **Phase 0 - Fix `run` module layout (High Impact, Low Effort)**: ✅ 2/2 completed (100%)
- **Phase 1 - Fix `list` and `show` module layout (Medium Impact, Low Effort)**: ✅ 1/1 completed (100%)

### Discarded Proposals

Expand Down Expand Up @@ -134,13 +134,13 @@ For `list` and `show`, this means creating thin wrapper or re-export modules in

### Proposal #1: Move `run` View Files Into `views/` Subfolder

**Status**: ⏳ Not Started
**Status**: ✅ Completed
**Impact**: 🟢🟢🟢 High
**Effort**: 🔵 Low
**Priority**: P0
**Depends On**: None
**Completed**: -
**Commit**: -
**Completed**: 2026-02-28
**Commit**: `8e3f8a41`

#### Problem

Expand Down Expand Up @@ -193,13 +193,13 @@ All public exports from `run/mod.rs` remain the same — callers are unaffected.

#### Implementation Checklist

- [ ] Create directory `src/presentation/cli/views/commands/run/views/`
- [ ] Move `run/json_view.rs` to `run/views/json_view.rs`
- [ ] Move `run/text_view.rs` to `run/views/text_view.rs`
- [ ] Create `run/views/mod.rs` exporting `JsonView` and `TextView`
- [ ] Update `run/mod.rs` to use `mod views` and re-export through it
- [ ] Verify all tests pass
- [ ] Run `cargo run --bin linter all` and fix issues
- [x] Create directory `src/presentation/cli/views/commands/run/views/`
- [x] Move `run/json_view.rs` to `run/views/json_view.rs`
- [x] Move `run/text_view.rs` to `run/views/text_view.rs`
- [x] Create `run/views/mod.rs` exporting `JsonView` and `TextView`
- [x] Update `run/mod.rs` to use `mod views` and re-export through it
- [x] Verify all tests pass
- [x] Run `cargo run --bin linter all` and fix issues

#### Testing Strategy

Expand All @@ -210,13 +210,13 @@ E2E tests must pass unchanged.

### Proposal #2: Extract `run` Inline DTO to `view_data/`

**Status**: ⏳ Not Started
**Status**: ✅ Completed
**Impact**: 🟢🟢 Medium
**Effort**: 🔵 Low
**Priority**: P0
**Depends On**: Proposal #1
**Completed**: -
**Commit**: -
**Completed**: 2026-02-28
**Commit**: `ff0c6d7b`

#### Problem

Expand Down Expand Up @@ -279,14 +279,14 @@ in other commands (e.g., `ConfigureDetailsData`, `DestroyDetailsData`).

#### Implementation Checklist

- [ ] Create `src/presentation/cli/views/commands/run/view_data/run_details.rs` with `RunDetailsData`
- [ ] Create `src/presentation/cli/views/commands/run/view_data/mod.rs`
- [ ] Update `run/mod.rs` to declare `pub mod view_data`
- [ ] Update `run/views/json_view.rs` to use `RunDetailsData` from `super::super::view_data`
- [x] Create `src/presentation/cli/views/commands/run/view_data/run_details.rs` with `RunDetailsData`
- [x] Create `src/presentation/cli/views/commands/run/view_data/mod.rs`
- [x] Update `run/mod.rs` to declare `pub mod view_data`
- [x] Update `run/views/json_view.rs` to use `RunDetailsData` from `super::super::view_data`
(or the crate-level path) and remove the inline struct
- [ ] Update all call sites constructing the old inline struct to construct `RunDetailsData`
- [ ] Verify all tests pass
- [ ] Run `cargo run --bin linter all` and fix issues
- [x] Update all call sites constructing the old inline struct to construct `RunDetailsData`
- [x] Verify all tests pass
- [x] Run `cargo run --bin linter all` and fix issues

#### Testing Strategy

Expand All @@ -299,13 +299,13 @@ old struct. Observable JSON output is unaffected (field names do not change).

### Proposal #3: Add `view_data/` to `list` and `show`

**Status**: ⏳ Not Started
**Status**: ✅ Completed
**Impact**: 🟢🟢 Medium
**Effort**: 🔵 Low
**Priority**: P1
**Depends On**: None (can be parallel with Phase 0)
**Completed**: -
**Commit**: -
**Completed**: 2026-02-28
**Commit**: `3d63edc6`

#### Problem

Expand Down Expand Up @@ -379,17 +379,17 @@ file in the command.

#### Implementation Checklist

- [ ] Create `src/presentation/cli/views/commands/list/view_data/list_details.rs`
- [ ] Create `src/presentation/cli/views/commands/list/view_data/mod.rs`
- [ ] Update `list/mod.rs` to declare `pub mod view_data`
- [ ] Update imports in `list/views/json_view.rs` and `list/views/text_view.rs`
- [ ] Create `src/presentation/cli/views/commands/show/view_data/show_details.rs`
- [ ] Create `src/presentation/cli/views/commands/show/view_data/mod.rs`
- [ ] Update `show/mod.rs` to declare `pub mod view_data`
- [ ] Update imports in `show/views/json_view.rs`, `show/views/text_view.rs`,
- [x] Create `src/presentation/cli/views/commands/list/view_data/list_details.rs`
- [x] Create `src/presentation/cli/views/commands/list/view_data/mod.rs`
- [x] Update `list/mod.rs` to declare `pub mod view_data`
- [x] Update imports in `list/views/json_view.rs` and `list/views/text_view.rs`
- [x] Create `src/presentation/cli/views/commands/show/view_data/show_details.rs`
- [x] Create `src/presentation/cli/views/commands/show/view_data/mod.rs`
- [x] Update `show/mod.rs` to declare `pub mod view_data`
- [x] Update imports in `show/views/json_view.rs`, `show/views/text_view.rs`,
and all other files under `show/views/` that import application-layer types
- [ ] Verify all tests pass
- [ ] Run `cargo run --bin linter all` and fix issues
- [x] Verify all tests pass
- [x] Run `cargo run --bin linter all` and fix issues

#### Testing Strategy

Expand All @@ -406,18 +406,18 @@ Pure import path changes. Compilation is the primary test. No behavior changes.

### Approval Criteria

- [ ] Technical feasibility validated
- [ ] Aligns with [Development Principles](../development-principles.md)
- [ ] Implementation plan is clear and actionable
- [ ] Priorities are correct (high-impact/low-effort first)
- [x] Technical feasibility validated
- [x] Aligns with [Development Principles](../development-principles.md)
- [x] Implementation plan is clear and actionable
- [x] Priorities are correct (high-impact/low-effort first)

### Completion Criteria

- [ ] All active proposals implemented
- [ ] All tests passing
- [ ] All linters passing
- [ ] Documentation updated
- [ ] Changes merged to main branch
- [x] All active proposals implemented
- [x] All tests passing
- [x] All linters passing
- [x] Documentation updated
- [x] Changes merged to main branch

## 📚 Related Documentation

Expand All @@ -442,5 +442,5 @@ The `show` command's `views/` subfolder already contains multiple sub-views
---

**Created**: 2026-02-27
**Last Updated**: 2026-02-27
**Status**: 📋 Planning
**Last Updated**: 2026-02-28
**Status**: ✅ Completed
14 changes: 6 additions & 8 deletions src/presentation/cli/controllers/run/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::domain::environment::name::EnvironmentName;
use crate::domain::environment::repository::EnvironmentRepository;
use crate::domain::environment::state::AnyEnvironmentState;
use crate::presentation::cli::input::cli::OutputFormat;
use crate::presentation::cli::views::commands::run::{JsonView, TextView};
use crate::presentation::cli::views::commands::run::{JsonView, RunDetailsData, TextView};
use crate::presentation::cli::views::progress::ProgressReporter;
use crate::presentation::cli::views::UserOutput;
use crate::shared::clock::Clock;
Expand Down Expand Up @@ -266,17 +266,15 @@ impl RunCommandController {
let grafana =
grafana_config.map(|config| GrafanaInfo::from_config(config, instance_ip));

let data = RunDetailsData::new(any_env.name().to_string(), services, grafana);

// Render using appropriate view based on output format (Strategy Pattern)
let output = match output_format {
OutputFormat::Text => {
TextView::render(any_env.name().as_str(), &services, grafana.as_ref())
}
OutputFormat::Json => {
JsonView::render(any_env.name().as_str(), &services, grafana.as_ref())
}
OutputFormat::Text => TextView::render(&data),
OutputFormat::Json => JsonView::render(&data),
};

// Pipeline: ServiceInfo + GrafanaInfo → render → output to stdout
// Pipeline: RunDetailsData → render → output to stdout
self.progress.result(&output)?;
}

Expand Down
2 changes: 2 additions & 0 deletions src/presentation/cli/views/commands/list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//! - `text_view.rs`: Human-readable table rendering
//! - `json_view.rs`: JSON output for automation workflows

pub mod view_data;
pub mod views {
pub mod json_view;
pub mod text_view;
Expand All @@ -24,4 +25,5 @@ pub mod views {
}

// Re-export everything at the module level for backward compatibility
pub use view_data::EnvironmentList;
pub use views::{JsonView, TextView};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! View data for the list command.
//!
//! Re-exports the application-layer DTOs as the canonical view input types.
//! The presentation layer references this module rather than importing directly
//! from the application layer.

pub use crate::application::command_handlers::list::info::EnvironmentList;
pub use crate::application::command_handlers::list::info::EnvironmentSummary;
3 changes: 3 additions & 0 deletions src/presentation/cli/views/commands/list/view_data/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod list_details;

pub use list_details::{EnvironmentList, EnvironmentSummary};
4 changes: 2 additions & 2 deletions src/presentation/cli/views/commands/list/views/json_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! The `JsonView` serializes environment list information to JSON using `serde_json`.
//! The output includes environment summaries, failed environments, and metadata.

use crate::application::command_handlers::list::info::EnvironmentList;
use crate::presentation::cli::views::commands::list::view_data::EnvironmentList;

/// View for rendering environment list as JSON
///
Expand Down Expand Up @@ -115,7 +115,7 @@ mod tests {
use serde_json::Value;

use super::*;
use crate::application::command_handlers::list::info::EnvironmentSummary;
use crate::presentation::cli::views::commands::list::view_data::EnvironmentSummary;

#[test]
fn it_should_render_empty_environment_list_as_json() {
Expand Down
4 changes: 2 additions & 2 deletions src/presentation/cli/views/commands/list/views/text_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! It follows the Strategy Pattern, providing one specific rendering strategy
//! (human-readable text table) for environment lists.

use crate::application::command_handlers::list::info::EnvironmentList;
use crate::presentation::cli::views::commands::list::view_data::EnvironmentList;

/// Text view for rendering environment list
///
Expand Down Expand Up @@ -159,7 +159,7 @@ impl TextView {
#[cfg(test)]
mod tests {
use super::*;
use crate::application::command_handlers::list::info::EnvironmentSummary;
use crate::presentation::cli::views::commands::list::view_data::EnvironmentSummary;

#[test]
fn it_should_render_empty_workspace() {
Expand Down
9 changes: 5 additions & 4 deletions src/presentation/cli/views/commands/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
//! Following the Strategy Pattern, each view (`TextView`, `JsonView`) implements
//! a different output format for the same underlying data (`ServiceInfo` and `GrafanaInfo` DTOs).

mod json_view;
mod text_view;
pub mod view_data;
mod views;

pub use json_view::JsonView;
pub use text_view::TextView;
pub use view_data::RunDetailsData;
pub use views::JsonView;
pub use views::TextView;
8 changes: 8 additions & 0 deletions src/presentation/cli/views/commands/run/view_data/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! View data for the Run Command
//!
//! This module contains data transfer objects (DTOs) used to pass data
//! to run command view renderers.

pub mod run_details;

pub use run_details::RunDetailsData;
69 changes: 69 additions & 0 deletions src/presentation/cli/views/commands/run/view_data/run_details.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Run Details Data Transfer Object
//!
//! This module contains the presentation DTO for the run command output.
//! It serves as the data structure passed to view renderers (`TextView`, `JsonView`, etc.).
//!
//! # Architecture
//!
//! This follows the Strategy Pattern where:
//! - This DTO is the data passed to all rendering strategies
//! - Different views (`TextView`, `JsonView`) consume this data
//! - Adding new formats doesn't modify this DTO or existing views
//!
//! # SOLID Principles
//!
//! - **Single Responsibility**: This file only defines the data structure
//! - **Open/Closed**: New formats extend by adding views, not modifying this
//! - **Separation of Concerns**: Data definition separate from rendering logic

use serde::Serialize;

use crate::application::command_handlers::show::info::{GrafanaInfo, ServiceInfo};

/// Run details data for rendering
///
/// This struct holds all the data needed to render run command output
/// for display to the user. It is consumed by view renderers
/// (`TextView`, `JsonView`) which format it according to their specific output format.
///
/// The `state` field is always `"Running"` — this command only executes
/// when services are being started successfully.
#[derive(Debug, Serialize)]
pub struct RunDetailsData {
/// Name of the environment
pub environment_name: String,

/// Current state — always `"Running"` for this command
pub state: String,

/// Tracker and API service endpoint information
pub services: ServiceInfo,

/// Optional Grafana dashboard information
pub grafana: Option<GrafanaInfo>,
}

impl RunDetailsData {
/// Create a new `RunDetailsData`
///
/// The `state` field is always set to `"Running"`.
///
/// # Arguments
///
/// * `environment_name` - Name of the environment
/// * `services` - Tracker and API service endpoint information
/// * `grafana` - Optional Grafana dashboard information
#[must_use]
pub fn new(
environment_name: String,
services: ServiceInfo,
grafana: Option<GrafanaInfo>,
) -> Self {
Self {
environment_name,
state: "Running".to_string(),
services,
grafana,
}
}
}
Loading
Loading