tui: tighten panel dashboard readiness
This commit is contained in:
+212
-15
@@ -264,6 +264,14 @@ pub struct RenderedPanelRow {
|
||||
pub title: String,
|
||||
pub status: Option<String>,
|
||||
pub action: Option<String>,
|
||||
#[serde(default)]
|
||||
pub disabled_reason: Option<String>,
|
||||
#[serde(default)]
|
||||
pub local_state: Option<String>,
|
||||
#[serde(default)]
|
||||
pub overlay_state: Option<String>,
|
||||
#[serde(default)]
|
||||
pub overlay_detail: Option<String>,
|
||||
pub rect: PanelRect,
|
||||
}
|
||||
|
||||
@@ -272,6 +280,10 @@ pub struct ExpectedPanelTicketRow {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub status: String,
|
||||
pub action: Option<String>,
|
||||
pub disabled_reason: Option<String>,
|
||||
pub local_state: Option<String>,
|
||||
pub overlay_state: Option<String>,
|
||||
}
|
||||
|
||||
impl ExpectedPanelTicketRow {
|
||||
@@ -280,20 +292,64 @@ impl ExpectedPanelTicketRow {
|
||||
id: id.into(),
|
||||
title: title.into(),
|
||||
status: status.into(),
|
||||
action: None,
|
||||
disabled_reason: None,
|
||||
local_state: None,
|
||||
overlay_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_action(mut self, action: impl Into<String>) -> Self {
|
||||
self.action = Some(action.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_disabled_reason(mut self, disabled_reason: impl Into<String>) -> Self {
|
||||
self.disabled_reason = Some(disabled_reason.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_local_state(mut self, local_state: impl Into<String>) -> Self {
|
||||
self.local_state = Some(local_state.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_overlay_state(mut self, overlay_state: impl Into<String>) -> Self {
|
||||
self.overlay_state = Some(overlay_state.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn matches(&self, row: &RenderedPanelRow) -> bool {
|
||||
row.key.kind == "ticket"
|
||||
&& row.key.id == self.id
|
||||
&& row.title == self.title
|
||||
&& row.status.as_deref() == Some(self.status.as_str())
|
||||
&& self.action.as_ref().map_or(true, |action| {
|
||||
row.action.as_deref() == Some(action.as_str())
|
||||
})
|
||||
&& self.disabled_reason.as_ref().map_or(true, |reason| {
|
||||
row.disabled_reason
|
||||
.as_deref()
|
||||
.is_some_and(|actual| actual.contains(reason))
|
||||
})
|
||||
&& self.local_state.as_ref().map_or(true, |state| {
|
||||
row.local_state.as_deref() == Some(state.as_str())
|
||||
})
|
||||
&& self.overlay_state.as_ref().map_or(true, |state| {
|
||||
row.overlay_state.as_deref() == Some(state.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
format!(
|
||||
"ticket row id={} title={:?} status={}",
|
||||
self.id, self.title, self.status
|
||||
"ticket row id={} title={:?} status={} action={:?} disabled_reason={:?} local_state={:?} overlay_state={:?}",
|
||||
self.id,
|
||||
self.title,
|
||||
self.status,
|
||||
self.action,
|
||||
self.disabled_reason,
|
||||
self.local_state,
|
||||
self.overlay_state,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -302,9 +358,20 @@ impl ExpectedPanelTicketRow {
|
||||
pub struct ExpectedDashboardContent {
|
||||
pub tickets: Vec<ExpectedPanelTicketRow>,
|
||||
pub pod_names: Vec<String>,
|
||||
pub companion_status: String,
|
||||
pub orchestrator_status: String,
|
||||
}
|
||||
|
||||
impl ExpectedDashboardContent {
|
||||
pub fn snapshot(&self) -> DashboardContentSnapshot {
|
||||
DashboardContentSnapshot {
|
||||
tickets: self.tickets.clone(),
|
||||
pod_names: self.pod_names.clone(),
|
||||
companion_status: self.companion_status.clone(),
|
||||
orchestrator_status: self.orchestrator_status.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
let tickets = self
|
||||
.tickets
|
||||
@@ -313,7 +380,10 @@ impl ExpectedDashboardContent {
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let pods = self.pod_names.join(", ");
|
||||
format!("tickets=[{tickets}] pods=[{pods}]")
|
||||
format!(
|
||||
"tickets=[{tickets}] pods=[{pods}] companion={} orchestrator={}",
|
||||
self.companion_status, self.orchestrator_status
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,16 +391,42 @@ impl ExpectedDashboardContent {
|
||||
pub struct DashboardContentSnapshot {
|
||||
pub tickets: Vec<ExpectedPanelTicketRow>,
|
||||
pub pod_names: Vec<String>,
|
||||
pub companion_status: String,
|
||||
pub orchestrator_status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardContentReady {
|
||||
pub ticket_configured: bool,
|
||||
pub selected: Option<PanelRowKey>,
|
||||
pub snapshot: DashboardSnapshot,
|
||||
pub categories: DashboardContentCategories,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardSnapshot {
|
||||
pub header: DashboardHeader,
|
||||
pub rows: Vec<RenderedPanelRow>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardHeader {
|
||||
pub ticket_configured: bool,
|
||||
pub companion: Option<DashboardCompanionState>,
|
||||
pub orchestrator: Option<DashboardOrchestratorState>,
|
||||
#[serde(default)]
|
||||
pub diagnostics: Vec<String>,
|
||||
pub rows: Vec<RenderedPanelRow>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardCompanionState {
|
||||
pub pod_name: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DashboardOrchestratorState {
|
||||
pub pod_name: String,
|
||||
pub status: String,
|
||||
pub detail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -345,8 +441,8 @@ pub struct DashboardContentCategories {
|
||||
impl DashboardContentReady {
|
||||
pub fn rows_rendered(&self) -> RowsRendered {
|
||||
RowsRendered {
|
||||
selected: self.selected.clone(),
|
||||
rows: self.rows.clone(),
|
||||
selected: None,
|
||||
rows: self.snapshot.rows.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,19 +454,34 @@ impl DashboardContentReady {
|
||||
tickets: expected
|
||||
.tickets
|
||||
.iter()
|
||||
.filter(|ticket| self.rows.iter().any(|row| ticket.matches(row)))
|
||||
.filter(|ticket| self.snapshot.rows.iter().any(|row| ticket.matches(row)))
|
||||
.cloned()
|
||||
.collect(),
|
||||
pod_names: expected
|
||||
.pod_names
|
||||
.iter()
|
||||
.filter(|pod_name| {
|
||||
self.rows
|
||||
self.snapshot
|
||||
.rows
|
||||
.iter()
|
||||
.any(|row| row.key.kind == "pod" && row.key.id == pod_name.as_str())
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
companion_status: self
|
||||
.snapshot
|
||||
.header
|
||||
.companion
|
||||
.as_ref()
|
||||
.map(|companion| companion.status.clone())
|
||||
.unwrap_or_default(),
|
||||
orchestrator_status: self
|
||||
.snapshot
|
||||
.header
|
||||
.orchestrator
|
||||
.as_ref()
|
||||
.map(|orchestrator| orchestrator.status.clone())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,10 +760,7 @@ impl PanelHarness {
|
||||
expected: &ExpectedDashboardContent,
|
||||
timeout: Duration,
|
||||
) -> Result<DashboardContentReady> {
|
||||
let expected_snapshot = DashboardContentSnapshot {
|
||||
tickets: expected.tickets.clone(),
|
||||
pod_names: expected.pod_names.clone(),
|
||||
};
|
||||
let expected_snapshot = expected.snapshot();
|
||||
let description = expected.description();
|
||||
let event = self.wait_for(
|
||||
format!("dashboard content ready ({description})"),
|
||||
@@ -1173,6 +1281,7 @@ impl FixtureWorkspace {
|
||||
)?;
|
||||
fixture.ready_ticket_id = first;
|
||||
fixture.planning_ticket_id = second;
|
||||
fixture.setup_orchestration_overlay(binary)?;
|
||||
fixture.write_fixture_metadata("ready", None)?;
|
||||
Ok(fixture)
|
||||
}
|
||||
@@ -1183,6 +1292,20 @@ impl FixtureWorkspace {
|
||||
READY_FIXTURE_TICKET_TITLE,
|
||||
"ready",
|
||||
)
|
||||
.with_action("Queue")
|
||||
.with_local_state("ready")
|
||||
}
|
||||
|
||||
pub fn ready_overlay_ticket_row(&self) -> ExpectedPanelTicketRow {
|
||||
ExpectedPanelTicketRow::new(
|
||||
self.ready_ticket_id.clone(),
|
||||
READY_FIXTURE_TICKET_TITLE,
|
||||
"ready→prog",
|
||||
)
|
||||
.with_action("Wait")
|
||||
.with_disabled_reason("orchestration worktree overlay shows Ticket state inprogress")
|
||||
.with_local_state("ready")
|
||||
.with_overlay_state("inprogress")
|
||||
}
|
||||
|
||||
pub fn planning_fixture_ticket_row(&self) -> ExpectedPanelTicketRow {
|
||||
@@ -1191,18 +1314,75 @@ impl FixtureWorkspace {
|
||||
PLANNING_FIXTURE_TICKET_TITLE,
|
||||
"planning",
|
||||
)
|
||||
.with_action("Clarify")
|
||||
.with_disabled_reason("Ticket is still in planning")
|
||||
.with_local_state("planning")
|
||||
}
|
||||
|
||||
pub fn expected_dashboard_content(&self) -> ExpectedDashboardContent {
|
||||
ExpectedDashboardContent {
|
||||
tickets: vec![
|
||||
self.ready_fixture_ticket_row(),
|
||||
self.ready_overlay_ticket_row(),
|
||||
self.planning_fixture_ticket_row(),
|
||||
],
|
||||
pod_names: vec!["workspace".to_string()],
|
||||
companion_status: "unavailable".to_string(),
|
||||
orchestrator_status: "unavailable".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_orchestration_overlay(&self, binary: &Path) -> Result<()> {
|
||||
run_git(&self.workspace, &["init"])?;
|
||||
run_git(&self.workspace, &["checkout", "-B", "develop"])?;
|
||||
run_git(
|
||||
&self.workspace,
|
||||
&["config", "user.email", "fixture@example.invalid"],
|
||||
)?;
|
||||
run_git(&self.workspace, &["config", "user.name", "Yoi E2E Fixture"])?;
|
||||
run_git(&self.workspace, &["add", ".yoi"])?;
|
||||
run_git(&self.workspace, &["commit", "-m", "fixture tickets"])?;
|
||||
let orchestration = self.workspace.join(".worktree/orchestration");
|
||||
run_git(
|
||||
&self.workspace,
|
||||
&[
|
||||
"worktree",
|
||||
"add",
|
||||
"-b",
|
||||
"orchestration",
|
||||
orchestration.to_string_lossy().as_ref(),
|
||||
"HEAD",
|
||||
],
|
||||
)?;
|
||||
run_yoi(
|
||||
binary,
|
||||
&orchestration,
|
||||
&self.home,
|
||||
&self.xdg_data_home,
|
||||
&self.xdg_state_home,
|
||||
&self.xdg_config_home,
|
||||
&self.xdg_runtime_dir,
|
||||
&self.artifacts_dir,
|
||||
&["ticket", "state", &self.ready_ticket_id, "queued"],
|
||||
)?;
|
||||
run_yoi(
|
||||
binary,
|
||||
&orchestration,
|
||||
&self.home,
|
||||
&self.xdg_data_home,
|
||||
&self.xdg_state_home,
|
||||
&self.xdg_config_home,
|
||||
&self.xdg_runtime_dir,
|
||||
&self.artifacts_dir,
|
||||
&["ticket", "state", &self.ready_ticket_id, "inprogress"],
|
||||
)?;
|
||||
run_git(&orchestration, &["add", ".yoi"])?;
|
||||
run_git(
|
||||
&orchestration,
|
||||
&["commit", "-m", "fixture orchestration overlay"],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn panel_config(&self, binary: PathBuf) -> PanelHarnessConfig {
|
||||
PanelHarnessConfig {
|
||||
binary,
|
||||
@@ -1541,6 +1721,23 @@ fn create_ticket(
|
||||
.ok_or_else(|| HarnessError::Protocol(format!("could not parse ticket id from {output:?}")))
|
||||
}
|
||||
|
||||
fn run_git(workspace: &Path, args: &[&str]) -> Result<()> {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(workspace)
|
||||
.output()?;
|
||||
if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
Err(HarnessError::CommandFailed {
|
||||
program: PathBuf::from("git"),
|
||||
args: args.iter().map(|arg| (*arg).to_string()).collect(),
|
||||
status: output.status,
|
||||
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
fn run_yoi(
|
||||
binary: &Path,
|
||||
workspace: &Path,
|
||||
|
||||
+198
-15
@@ -4,28 +4,106 @@ const FIRST_VISIBLE_RENDER_BUDGET: Duration = Duration::from_millis(1500);
|
||||
const DASHBOARD_CONTENT_READY_BUDGET: Duration = Duration::from_secs(5);
|
||||
|
||||
use yoi_e2e::{
|
||||
DashboardCompanionState, DashboardContentCategories, DashboardContentReady, DashboardHeader,
|
||||
DashboardOrchestratorState, DashboardSnapshot, ExpectedDashboardContent,
|
||||
ExpectedPanelTicketRow, FixtureCleanupReport, FixtureWorkspace, KeyPress, PanelHarness,
|
||||
PanelRect, PanelRowKey, RenderedPanelRow, RowsRendered, yoi_binary,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn panel_fixture_ticket_row_matcher_rejects_absent_fixture_data() {
|
||||
let expected = ExpectedPanelTicketRow::new("0000000000000", "Ready E2E Ticket", "ready");
|
||||
let wrong_title = RenderedPanelRow {
|
||||
fn rendered_ticket_row(
|
||||
id: &str,
|
||||
title: &str,
|
||||
status: &str,
|
||||
action: Option<&str>,
|
||||
disabled_reason: Option<&str>,
|
||||
local_state: Option<&str>,
|
||||
overlay_state: Option<&str>,
|
||||
) -> RenderedPanelRow {
|
||||
RenderedPanelRow {
|
||||
key: PanelRowKey {
|
||||
kind: "ticket".to_string(),
|
||||
id: "0000000000000".to_string(),
|
||||
id: id.to_string(),
|
||||
},
|
||||
title: "Different Ticket".to_string(),
|
||||
status: Some("ready".to_string()),
|
||||
action: None,
|
||||
title: title.to_string(),
|
||||
status: Some(status.to_string()),
|
||||
action: action.map(ToOwned::to_owned),
|
||||
disabled_reason: disabled_reason.map(ToOwned::to_owned),
|
||||
local_state: local_state.map(ToOwned::to_owned),
|
||||
overlay_state: overlay_state.map(ToOwned::to_owned),
|
||||
overlay_detail: overlay_state.map(|state| format!("orchestration:{state}")),
|
||||
rect: PanelRect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 10,
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered_pod_row(name: &str) -> RenderedPanelRow {
|
||||
RenderedPanelRow {
|
||||
key: PanelRowKey {
|
||||
kind: "pod".to_string(),
|
||||
id: name.to_string(),
|
||||
},
|
||||
title: name.to_string(),
|
||||
status: None,
|
||||
action: None,
|
||||
disabled_reason: None,
|
||||
local_state: None,
|
||||
overlay_state: None,
|
||||
overlay_detail: None,
|
||||
rect: PanelRect {
|
||||
x: 0,
|
||||
y: 1,
|
||||
width: 10,
|
||||
height: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn ready_snapshot(rows: Vec<RenderedPanelRow>) -> DashboardContentReady {
|
||||
DashboardContentReady {
|
||||
snapshot: DashboardSnapshot {
|
||||
header: DashboardHeader {
|
||||
ticket_configured: true,
|
||||
companion: Some(DashboardCompanionState {
|
||||
pod_name: "workspace".to_string(),
|
||||
status: "unavailable".to_string(),
|
||||
}),
|
||||
orchestrator: Some(DashboardOrchestratorState {
|
||||
pod_name: "workspace-orchestrator".to_string(),
|
||||
status: "unavailable".to_string(),
|
||||
detail: Some("fixture blocks host Pod launch".to_string()),
|
||||
}),
|
||||
diagnostics: vec![],
|
||||
},
|
||||
rows,
|
||||
},
|
||||
categories: DashboardContentCategories {
|
||||
ticket_rows: 2,
|
||||
ready_ticket_rows: 1,
|
||||
planning_ticket_rows: 1,
|
||||
pod_rows: 1,
|
||||
actionable_rows: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_fixture_ticket_row_matcher_rejects_absent_fixture_data() {
|
||||
let expected = ExpectedPanelTicketRow::new("0000000000000", "Ready E2E Ticket", "ready")
|
||||
.with_action("Queue")
|
||||
.with_local_state("ready");
|
||||
let wrong_title = rendered_ticket_row(
|
||||
"0000000000000",
|
||||
"Different Ticket",
|
||||
"ready",
|
||||
Some("Queue"),
|
||||
None,
|
||||
Some("ready"),
|
||||
None,
|
||||
);
|
||||
let wrong_kind = RenderedPanelRow {
|
||||
key: PanelRowKey {
|
||||
kind: "pod".to_string(),
|
||||
@@ -33,7 +111,11 @@ fn panel_fixture_ticket_row_matcher_rejects_absent_fixture_data() {
|
||||
},
|
||||
title: "Ready E2E Ticket".to_string(),
|
||||
status: Some("ready".to_string()),
|
||||
action: None,
|
||||
action: Some("Queue".to_string()),
|
||||
disabled_reason: None,
|
||||
local_state: Some("ready".to_string()),
|
||||
overlay_state: None,
|
||||
overlay_detail: None,
|
||||
rect: PanelRect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -51,6 +133,91 @@ fn panel_fixture_ticket_row_matcher_rejects_absent_fixture_data() {
|
||||
assert!(!rows.has_fixture_ticket_row(&expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashboard_snapshot_rejects_missing_row_wrong_state_missing_overlay_and_missing_action() {
|
||||
let expected_ready = ExpectedPanelTicketRow::new("ready-id", "Ready E2E Ticket", "ready→prog")
|
||||
.with_action("Wait")
|
||||
.with_disabled_reason("orchestration worktree overlay shows Ticket state inprogress")
|
||||
.with_local_state("ready")
|
||||
.with_overlay_state("inprogress");
|
||||
let expected_planning =
|
||||
ExpectedPanelTicketRow::new("planning-id", "Planning E2E Ticket", "planning")
|
||||
.with_action("Clarify")
|
||||
.with_disabled_reason("Ticket is still in planning")
|
||||
.with_local_state("planning");
|
||||
let expected = ExpectedDashboardContent {
|
||||
tickets: vec![expected_ready.clone(), expected_planning.clone()],
|
||||
pod_names: vec!["workspace".to_string()],
|
||||
companion_status: "unavailable".to_string(),
|
||||
orchestrator_status: "unavailable".to_string(),
|
||||
};
|
||||
let complete_rows = || {
|
||||
vec![
|
||||
rendered_ticket_row(
|
||||
"ready-id",
|
||||
"Ready E2E Ticket",
|
||||
"ready→prog",
|
||||
Some("Wait"),
|
||||
Some("orchestration worktree overlay shows Ticket state inprogress"),
|
||||
Some("ready"),
|
||||
Some("inprogress"),
|
||||
),
|
||||
rendered_ticket_row(
|
||||
"planning-id",
|
||||
"Planning E2E Ticket",
|
||||
"planning",
|
||||
Some("Clarify"),
|
||||
Some("Ticket is still in planning"),
|
||||
Some("planning"),
|
||||
None,
|
||||
),
|
||||
rendered_pod_row("workspace"),
|
||||
]
|
||||
};
|
||||
assert_eq!(
|
||||
ready_snapshot(complete_rows()).snapshot_for_expected(&expected),
|
||||
expected.snapshot()
|
||||
);
|
||||
|
||||
let missing_row = ready_snapshot(vec![
|
||||
rendered_ticket_row(
|
||||
"ready-id",
|
||||
"Ready E2E Ticket",
|
||||
"ready→prog",
|
||||
Some("Wait"),
|
||||
Some("orchestration worktree overlay shows Ticket state inprogress"),
|
||||
Some("ready"),
|
||||
Some("inprogress"),
|
||||
),
|
||||
rendered_pod_row("workspace"),
|
||||
]);
|
||||
assert_ne!(
|
||||
missing_row.snapshot_for_expected(&expected),
|
||||
expected.snapshot()
|
||||
);
|
||||
|
||||
let mut wrong_state_rows = complete_rows();
|
||||
wrong_state_rows[0].status = Some("ready".to_string());
|
||||
assert_ne!(
|
||||
ready_snapshot(wrong_state_rows).snapshot_for_expected(&expected),
|
||||
expected.snapshot()
|
||||
);
|
||||
|
||||
let mut missing_overlay_rows = complete_rows();
|
||||
missing_overlay_rows[0].overlay_state = None;
|
||||
assert_ne!(
|
||||
ready_snapshot(missing_overlay_rows).snapshot_for_expected(&expected),
|
||||
expected.snapshot()
|
||||
);
|
||||
|
||||
let mut missing_action_rows = complete_rows();
|
||||
missing_action_rows[0].action = None;
|
||||
assert_ne!(
|
||||
ready_snapshot(missing_action_rows).snapshot_for_expected(&expected),
|
||||
expected.snapshot()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_first_visible_render_arrives_before_background_reload() -> yoi_e2e::Result<()> {
|
||||
let binary = yoi_binary()?;
|
||||
@@ -143,10 +310,23 @@ fn panel_dashboard_content_ready_has_startup_budget() -> yoi_e2e::Result<()> {
|
||||
let content_ready =
|
||||
panel.wait_for_dashboard_content_ready(&expected_content, content_ready_remaining)?;
|
||||
assert!(
|
||||
content_ready.ticket_configured,
|
||||
content_ready.snapshot.header.ticket_configured,
|
||||
"dashboard content ready must include usable Ticket configuration; artifacts at {}",
|
||||
panel.artifacts().dir.display()
|
||||
);
|
||||
assert!(
|
||||
content_ready.snapshot.header.companion.is_some()
|
||||
&& content_ready.snapshot.header.orchestrator.is_some(),
|
||||
"dashboard content ready must include Companion and Orchestrator header status; got {:?}; artifacts at {}",
|
||||
content_ready.snapshot.header,
|
||||
panel.artifacts().dir.display()
|
||||
);
|
||||
assert_eq!(
|
||||
content_ready.snapshot_for_expected(&expected_content),
|
||||
expected_content.snapshot(),
|
||||
"dashboard content ready must match expected Ticket/action/overlay/header snapshot; artifacts at {}",
|
||||
panel.artifacts().dir.display()
|
||||
);
|
||||
assert!(
|
||||
content_ready.categories.ready_ticket_rows > 0
|
||||
&& content_ready.categories.planning_ticket_rows > 0
|
||||
@@ -168,10 +348,13 @@ fn panel_dashboard_content_ready_has_startup_budget() -> yoi_e2e::Result<()> {
|
||||
|
||||
let source_breakdown = panel.expect_dashboard_source_breakdown()?;
|
||||
assert!(
|
||||
source_breakdown.has_source("pod_list.initial")
|
||||
&& source_breakdown.has_source("ticket_config")
|
||||
&& source_breakdown.has_source("workspace_panel.build"),
|
||||
"dashboard source breakdown should include pod, ticket, and panel-build sources; got {:?}; artifacts at {}",
|
||||
source_breakdown.has_source("pod_metadata_status_probe.initial")
|
||||
&& source_breakdown.has_source("ticket_config_probe")
|
||||
&& source_breakdown.has_source("local_claim_scan")
|
||||
&& source_breakdown.has_source("ticket_scan_parse")
|
||||
&& source_breakdown.has_source("orchestration_overlay_validation_read_git")
|
||||
&& source_breakdown.has_source("workspace_panel.build.total"),
|
||||
"dashboard source breakdown should include pod metadata/status, ticket scan/parse, overlay validation/read/git, local claim scan, and panel-build sources; got {:?}; artifacts at {}",
|
||||
source_breakdown,
|
||||
panel.artifacts().dir.display()
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user