merge: panel intake pod rows

This commit is contained in:
Keisuke Hirata 2026-06-15 00:54:44 +09:00
commit 2fcbd6aefb
No known key found for this signature in database
2 changed files with 331 additions and 59 deletions

View File

@ -48,11 +48,11 @@ use crate::workspace_panel::{
ActionPriority, CompanionLifecyclePlan, CompanionPanelState, CompanionPanelStatus, ActionPriority, CompanionLifecyclePlan, CompanionPanelState, CompanionPanelStatus,
CompanionPodPresence, ComposerTarget, NextUserAction, OrchestratorLifecyclePlan, CompanionPodPresence, ComposerTarget, NextUserAction, OrchestratorLifecyclePlan,
OrchestratorPanelState, OrchestratorPanelStatus, OrchestratorPodPresence, PanelRow, OrchestratorPanelState, OrchestratorPanelStatus, OrchestratorPodPresence, PanelRow,
PanelRowKey, TicketConfigAvailability, TicketLocalClaimStatus, WorkspacePanelViewModel, PanelRowKey, PanelRowKind, TicketConfigAvailability, TicketLocalClaimStatus,
bounded_panel_diagnostic, build_current_ticket_row, build_workspace_panel, WorkspacePanelViewModel, bounded_panel_diagnostic, build_current_ticket_row,
companion_pod_presence, decide_companion_lifecycle, decide_orchestrator_lifecycle, build_workspace_panel, companion_pod_presence, decide_companion_lifecycle,
local_claim_status_for_pod, orchestrator_pod_presence, ticket_config_availability, decide_orchestrator_lifecycle, local_claim_status_for_pod, orchestrator_pod_presence,
workspace_companion_pod_name, workspace_orchestrator_pod_name, ticket_config_availability, workspace_companion_pod_name, workspace_orchestrator_pod_name,
}; };
const MAX_ENTRIES: usize = 50; const MAX_ENTRIES: usize = 50;
@ -958,6 +958,13 @@ fn panel_e2e_row_key(key: &PanelRowKey) -> PanelE2eRowKey {
kind: "ticket", kind: "ticket",
id: id.clone(), id: id.clone(),
}, },
PanelRowKey::TicketIntakePod {
ticket_id,
pod_name,
} => PanelE2eRowKey {
kind: "ticket_intake_pod",
id: format!("{ticket_id}:{pod_name}"),
},
PanelRowKey::Pod(name) => PanelE2eRowKey { PanelRowKey::Pod(name) => PanelE2eRowKey {
kind: "pod", kind: "pod",
id: name.clone(), id: name.clone(),
@ -1207,12 +1214,8 @@ impl MultiPodApp {
} }
fn selected_pod_entry(&self) -> Option<&PodListEntry> { fn selected_pod_entry(&self) -> Option<&PodListEntry> {
match self.selected_row.as_ref() { let name = self.selected_row.as_ref().and_then(PanelRowKey::pod_name)?;
Some(PanelRowKey::Pod(name)) => { self.list.entries.iter().find(|entry| entry.name == name)
self.list.entries.iter().find(|entry| &entry.name == name)
}
_ => None,
}
} }
#[cfg(test)] #[cfg(test)]
@ -1238,11 +1241,14 @@ impl MultiPodApp {
}), }),
); );
} }
let entry = self.selected_pod_entry()?; if let Some(entry) = self.selected_pod_entry() {
if entry.actions.can_open { if entry.actions.can_open {
return None; return None;
}
return Some(open_disabled_reason(entry));
} }
Some(open_disabled_reason(entry)) self.selected_panel_row()
.and_then(|row| row.disabled_reason.clone().or_else(|| row.key_hint.clone()))
} }
pub(crate) fn select_next(&mut self) { pub(crate) fn select_next(&mut self) {
@ -1354,6 +1360,9 @@ impl MultiPodApp {
None => match &hit.key { None => match &hit.key {
PanelRowKey::Pod(name) => (name.clone(), None, None), PanelRowKey::Pod(name) => (name.clone(), None, None),
PanelRowKey::Ticket(id) => (id.clone(), None, None), PanelRowKey::Ticket(id) => (id.clone(), None, None),
PanelRowKey::TicketIntakePod { pod_name, .. } => {
(pod_name.clone(), None, None)
}
}, },
}; };
PanelE2eRenderedRow { PanelE2eRenderedRow {
@ -1406,7 +1415,7 @@ impl MultiPodApp {
} }
if let Some(key) = visible.iter().find(|key| match key { if let Some(key) = visible.iter().find(|key| match key {
PanelRowKey::Pod(name) => Some(name.as_str()) != orchestrator_pod_name, PanelRowKey::Pod(name) => Some(name.as_str()) != orchestrator_pod_name,
PanelRowKey::Ticket(_) => true, PanelRowKey::Ticket(_) | PanelRowKey::TicketIntakePod { .. } => true,
}) { }) {
self.select_panel_key(key.clone()); self.select_panel_key(key.clone());
return; return;
@ -4677,6 +4686,13 @@ fn selected_ticket_notice(row: Option<&PanelRow>) -> String {
row.title row.title
) )
} }
Some(row) if row.kind == PanelRowKind::TicketIntakePod => row
.disabled_reason
.clone()
.or_else(|| row.key_hint.clone())
.unwrap_or_else(|| {
"Open/attach this Ticket's Intake Pod from the associated row.".to_string()
}),
_ => "No Pod is selected.".to_string(), _ => "No Pod is selected.".to_string(),
} }
} }
@ -4793,7 +4809,7 @@ fn visible_panel_keys(panel: &WorkspacePanelViewModel, list: &PodList) -> Vec<Pa
let mut keys = panel let mut keys = panel
.rows .rows
.iter() .iter()
.filter(|row| row.is_ticket_action()) .filter(|row| row.is_ticket_section_row())
.map(|row| row.key.clone()) .map(|row| row.key.clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
keys.extend( keys.extend(
@ -5145,7 +5161,7 @@ fn panel_action_rows(
let rows = panel let rows = panel
.rows .rows
.iter() .iter()
.filter(|row| row.is_ticket_action()) .filter(|row| row.is_ticket_section_row())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if rows.is_empty() { if rows.is_empty() {
return Vec::new(); return Vec::new();
@ -5181,11 +5197,15 @@ fn panel_action_header_line(total: usize, width: u16) -> Line<'static> {
const TICKET_STATE_COLUMN_WIDTH: usize = 10; const TICKET_STATE_COLUMN_WIDTH: usize = 10;
const POD_STATUS_COLUMN_WIDTH: usize = 18; const POD_STATUS_COLUMN_WIDTH: usize = 18;
fn panel_row_lines(row: &PanelRow, selected: bool, width: u16) -> [Line<'static>; 2] { fn panel_row_lines(row: &PanelRow, selected: bool, width: u16) -> Vec<Line<'static>> {
[ if row.kind == PanelRowKind::TicketIntakePod {
panel_row_title_line(row, selected, width), vec![panel_row_title_line(row, selected, width)]
panel_row_detail_line(row, selected, width), } else {
] vec![
panel_row_title_line(row, selected, width),
panel_row_detail_line(row, selected, width),
]
}
} }
fn panel_row_title_line(row: &PanelRow, selected: bool, width: u16) -> Line<'static> { fn panel_row_title_line(row: &PanelRow, selected: bool, width: u16) -> Line<'static> {
@ -5242,6 +5262,21 @@ fn push_ticket_marker_span(spans: &mut Vec<Span<'static>>, selected: bool, remai
} }
fn panel_ticket_detail(row: &PanelRow) -> String { fn panel_ticket_detail(row: &PanelRow) -> String {
if row.kind == PanelRowKind::TicketIntakePod {
let mut parts = row
.subtitle
.as_ref()
.map(|subtitle| vec![subtitle.clone()])
.unwrap_or_else(|| vec![panel_ticket_reference(row)]);
if let Some(action) = row.next_action {
parts.push(format!("Action: {}", action.label()));
}
if let Some(reason) = panel_ticket_reason(row) {
parts.push(format!("Reason: {reason}"));
}
return parts.join(" · ");
}
let mut parts = vec![panel_ticket_reference(row)]; let mut parts = vec![panel_ticket_reference(row)];
if let Some(blocked_reason) = row if let Some(blocked_reason) = row
.ticket .ticket
@ -5303,6 +5338,7 @@ fn panel_ticket_reference(row: &PanelRow) -> String {
.map(|ticket| ticket.id.clone()) .map(|ticket| ticket.id.clone())
.unwrap_or_else(|| match &row.key { .unwrap_or_else(|| match &row.key {
PanelRowKey::Ticket(id) => id.clone(), PanelRowKey::Ticket(id) => id.clone(),
PanelRowKey::TicketIntakePod { ticket_id, .. } => ticket_id.clone(),
PanelRowKey::Pod(name) => name.clone(), PanelRowKey::Pod(name) => name.clone(),
}) })
} }
@ -7321,7 +7357,8 @@ branch = "orchestration/custom-panel"
"inprogress", "inprogress",
); );
let [title, detail] = panel_row_lines(&row, true, 160); let lines = panel_row_lines(&row, true, 160);
let (title, detail) = (&lines[0], &lines[1]);
let title_line = plain_line(&title); let title_line = plain_line(&title);
let detail_line = plain_line(&detail); let detail_line = plain_line(&detail);
let state_start = 2; let state_start = 2;
@ -7352,7 +7389,8 @@ branch = "orchestration/custom-panel"
"ready", "ready",
); );
let [title, detail] = panel_row_lines(&row, false, 160); let lines = panel_row_lines(&row, false, 160);
let (title, detail) = (&lines[0], &lines[1]);
let title_line = plain_line(&title); let title_line = plain_line(&title);
let detail_line = plain_line(&detail); let detail_line = plain_line(&detail);
let state_start = 2; let state_start = 2;
@ -7377,7 +7415,8 @@ branch = "orchestration/custom-panel"
"ready", "ready",
); );
let [title, detail] = panel_row_lines(&row, false, 42); let lines = panel_row_lines(&row, false, 42);
let (title, detail) = (&lines[0], &lines[1]);
let title_line = plain_line(&title); let title_line = plain_line(&title);
let detail_line = plain_line(&detail); let detail_line = plain_line(&detail);
let title_start = 2 + TICKET_STATE_COLUMN_WIDTH + 1; let title_start = 2 + TICKET_STATE_COLUMN_WIDTH + 1;
@ -7402,7 +7441,8 @@ branch = "orchestration/custom-panel"
row.disabled_reason = Some("Queue disabled: waiting for BLOCKER-1".to_string()); row.disabled_reason = Some("Queue disabled: waiting for BLOCKER-1".to_string());
row.ticket.as_mut().unwrap().blocked_reason = Some("BLOCKER-1 via depends_on".to_string()); row.ticket.as_mut().unwrap().blocked_reason = Some("BLOCKER-1 via depends_on".to_string());
let [_title, detail] = panel_row_lines(&row, true, 160); let lines = panel_row_lines(&row, true, 160);
let detail = &lines[1];
let detail_line = plain_line(&detail); let detail_line = plain_line(&detail);
assert!(detail_line.contains("Gate: waiting for BLOCKER-1 via depends_on")); assert!(detail_line.contains("Gate: waiting for BLOCKER-1 via depends_on"));
@ -8602,6 +8642,7 @@ branch = "orchestration/custom-panel"
blocked_reason: None, blocked_reason: None,
related_pods: Vec::new(), related_pods: Vec::new(),
local_claim: None, local_claim: None,
intake_pods: Vec::new(),
}; };
PanelRow { PanelRow {
key: PanelRowKey::Ticket(ticket.id.clone()), key: PanelRowKey::Ticket(ticket.id.clone()),

View File

@ -182,15 +182,26 @@ impl OrchestratorPanelStatus {
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum PanelRowKey { pub(crate) enum PanelRowKey {
Ticket(String), Ticket(String),
TicketIntakePod { ticket_id: String, pod_name: String },
Pod(String), Pod(String),
} }
impl PanelRowKey {
pub(crate) fn pod_name(&self) -> Option<&str> {
match self {
Self::Pod(name) | Self::TicketIntakePod { pod_name: name, .. } => Some(name),
Self::Ticket(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PanelRowKind { pub(crate) enum PanelRowKind {
Planning, Planning,
Ticket, Ticket,
Review, Review,
ActiveWork, ActiveWork,
TicketIntakePod,
Pod, Pod,
} }
@ -236,6 +247,30 @@ pub(crate) struct TicketPanelEntry {
pub(crate) blocked_reason: Option<String>, pub(crate) blocked_reason: Option<String>,
pub(crate) related_pods: Vec<String>, pub(crate) related_pods: Vec<String>,
pub(crate) local_claim: Option<TicketLocalClaimEntry>, pub(crate) local_claim: Option<TicketLocalClaimEntry>,
pub(crate) intake_pods: Vec<TicketAssociatedIntakeEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TicketAssociatedIntakeEntry {
pub(crate) ticket_id: String,
pub(crate) pod_name: String,
pub(crate) status: TicketLocalClaimStatus,
pub(crate) source: TicketAssociatedIntakeSource,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TicketAssociatedIntakeSource {
LocalClaim,
RelatedSession,
}
impl TicketAssociatedIntakeSource {
pub(crate) fn label(self) -> &'static str {
match self {
Self::LocalClaim => "local claim",
Self::RelatedSession => "related session",
}
}
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -279,11 +314,22 @@ pub(crate) struct PanelRow {
impl PanelRow { impl PanelRow {
pub(crate) fn is_ticket_action(&self) -> bool { pub(crate) fn is_ticket_action(&self) -> bool {
!matches!(self.kind, PanelRowKind::Pod) matches!(
self.kind,
PanelRowKind::Planning
| PanelRowKind::Ticket
| PanelRowKind::Review
| PanelRowKind::ActiveWork
)
}
pub(crate) fn is_ticket_section_row(&self) -> bool {
self.is_ticket_action() || matches!(self.kind, PanelRowKind::TicketIntakePod)
} }
} }
const MAX_POD_NAME_CHARS: usize = 80; const MAX_POD_NAME_CHARS: usize = 80;
const MAX_ASSOCIATED_INTAKE_ROWS_PER_TICKET: usize = 3;
const ORCHESTRATOR_SUFFIX: &str = "-orchestrator"; const ORCHESTRATOR_SUFFIX: &str = "-orchestrator";
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -574,12 +620,6 @@ fn build_workspace_panel_with_registry_model(
} }
model.rows.extend(pod_rows(pods)); model.rows.extend(pod_rows(pods));
model.rows.sort_by(|a, b| {
a.priority
.cmp(&b.priority)
.then_with(|| row_updated_at(b).cmp(row_updated_at(a)))
.then_with(|| a.title.cmp(&b.title))
});
model model
} }
@ -628,13 +668,13 @@ fn build_ticket_rows(
pods: &PodList, pods: &PodList,
registry: &PanelRegistrySnapshot, registry: &PanelRegistrySnapshot,
) -> ticket::Result<Vec<PanelRow>> { ) -> ticket::Result<Vec<PanelRow>> {
let mut rows = Vec::new(); let mut ticket_rows = Vec::new();
for summary in backend.list(TicketFilter::all())? { for summary in backend.list(TicketFilter::all())? {
if summary.workflow_state == TicketWorkflowState::Closed { if summary.workflow_state == TicketWorkflowState::Closed {
continue; continue;
} }
let ticket = backend.show(TicketIdOrSlug::Query(summary.id.clone()))?; let ticket = backend.show(TicketIdOrSlug::Query(summary.id.clone()))?;
rows.push(ticket_row( ticket_rows.push(ticket_row(
summary, summary,
&ticket.events, &ticket.events,
&ticket.relations.blockers, &ticket.relations.blockers,
@ -642,6 +682,19 @@ fn build_ticket_rows(
registry, registry,
)); ));
} }
ticket_rows.sort_by(|a, b| {
a.priority
.cmp(&b.priority)
.then_with(|| row_updated_at(b).cmp(row_updated_at(a)))
.then_with(|| a.title.cmp(&b.title))
});
let mut rows = Vec::new();
for row in ticket_rows {
let intake_rows = ticket_intake_pod_rows(&row);
rows.push(row);
rows.extend(intake_rows);
}
Ok(rows) Ok(rows)
} }
@ -653,7 +706,17 @@ fn ticket_row(
registry: &PanelRegistrySnapshot, registry: &PanelRegistrySnapshot,
) -> PanelRow { ) -> PanelRow {
let local_claim = local_claim_for_ticket(&summary, pods, registry); let local_claim = local_claim_for_ticket(&summary, pods, registry);
let related_pods = related_pods_for_ticket(&summary, pods, registry); let intake_pods =
associated_intake_entries_for_ticket(&summary, pods, registry, local_claim.as_ref());
let mut related_pods = Vec::new();
if let Some(claim) = local_claim.as_ref() {
related_pods.push(claim.pod_name.clone());
}
for pod_name in intake_pods.iter().map(|intake| intake.pod_name.clone()) {
if !related_pods.iter().any(|existing| existing == &pod_name) {
related_pods.push(pod_name);
}
}
let derived = derive_ticket_state(&summary, relation_blockers); let derived = derive_ticket_state(&summary, relation_blockers);
let latest_event = events.last(); let latest_event = events.last();
let entry = TicketPanelEntry { let entry = TicketPanelEntry {
@ -669,6 +732,7 @@ fn ticket_row(
blocked_reason: derived.blocked_reason.clone(), blocked_reason: derived.blocked_reason.clone(),
related_pods: related_pods.clone(), related_pods: related_pods.clone(),
local_claim, local_claim,
intake_pods,
}; };
let subtitle = ticket_subtitle(&entry); let subtitle = ticket_subtitle(&entry);
PanelRow { PanelRow {
@ -802,32 +866,111 @@ fn derive_ticket_state(
} }
} }
fn related_pods_for_ticket( fn associated_intake_entries_for_ticket(
summary: &TicketSummary, summary: &TicketSummary,
pods: &PodList, pods: &PodList,
registry: &PanelRegistrySnapshot, registry: &PanelRegistrySnapshot,
) -> Vec<String> { local_claim: Option<&TicketLocalClaimEntry>,
let id = lowercase(&summary.id); ) -> Vec<TicketAssociatedIntakeEntry> {
let mut names = Vec::new(); let mut entries = Vec::new();
if let Some(claim) = registry.claim_for_ticket(&summary.id) { if let Some(claim) = local_claim.filter(|claim| is_intake_role(&claim.role)) {
names.push(claim.pod_name.clone()); entries.push(TicketAssociatedIntakeEntry {
ticket_id: summary.id.clone(),
pod_name: claim.pod_name.clone(),
status: claim.status,
source: TicketAssociatedIntakeSource::LocalClaim,
});
} }
for pod in pods.entries.iter().filter_map(|pod| {
let name = lowercase(&pod.name); let mut related_sessions = registry
if !id.is_empty() && name.contains(&id) { .sessions
Some(pod.name.clone()) .iter()
} else { .filter(|session| {
None is_intake_role(&session.role)
&& session
.related_tickets
.iter()
.any(|related| related.id == summary.id.as_str())
})
.map(|session| session.pod_name.clone())
.collect::<Vec<_>>();
related_sessions.sort();
related_sessions.dedup();
for pod_name in related_sessions {
if entries.iter().any(|entry| entry.pod_name == pod_name) {
continue;
} }
}) { entries.push(TicketAssociatedIntakeEntry {
if !names.iter().any(|existing| existing == &pod) { ticket_id: summary.id.clone(),
names.push(pod); status: local_claim_status_for_pod(&pod_name, pods),
} pod_name,
if names.len() >= 5 { source: TicketAssociatedIntakeSource::RelatedSession,
});
if entries.len() >= MAX_ASSOCIATED_INTAKE_ROWS_PER_TICKET {
break; break;
} }
} }
names
entries.truncate(MAX_ASSOCIATED_INTAKE_ROWS_PER_TICKET);
entries
}
fn is_intake_role(role: &str) -> bool {
role.eq_ignore_ascii_case("intake")
}
fn ticket_intake_pod_rows(row: &PanelRow) -> Vec<PanelRow> {
row.ticket
.as_ref()
.map(|ticket| {
ticket
.intake_pods
.iter()
.map(ticket_intake_pod_row)
.collect()
})
.unwrap_or_default()
}
fn ticket_intake_pod_row(intake: &TicketAssociatedIntakeEntry) -> PanelRow {
let stale = intake.status == TicketLocalClaimStatus::Stale;
PanelRow {
key: PanelRowKey::TicketIntakePod {
ticket_id: intake.ticket_id.clone(),
pod_name: intake.pod_name.clone(),
},
kind: PanelRowKind::TicketIntakePod,
title: format!("↳ Intake Pod: {}", intake.pod_name),
subtitle: Some(format!(
"Ticket {} · {} · {}",
intake.ticket_id,
intake.source.label(),
intake.status.label()
)),
status: intake.status.label().to_string(),
priority: ActionPriority::ActiveWork,
next_action: if stale {
None
} else {
Some(NextUserAction::OpenPod)
},
ticket: None,
related_pods: vec![intake.pod_name.clone()],
disabled_reason: if stale {
Some(
"Associated Intake Pod is stale; no live or restorable Pod entry is available."
.to_string(),
)
} else {
None
},
key_hint: Some(if stale {
"Stale Intake claim/session; restore is unavailable".to_string()
} else {
"Open/attach this Ticket's Intake Pod".to_string()
}),
}
} }
fn local_claim_for_ticket( fn local_claim_for_ticket(
@ -962,15 +1105,12 @@ fn excerpt(markdown: &str, max_chars: usize) -> Option<String> {
} }
} }
fn lowercase(value: &str) -> String {
value.to_ascii_lowercase()
}
#[allow(dead_code)] #[allow(dead_code)]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::pod_list::{LivePodInfo, PodEntrySummary}; use crate::pod_list::{LivePodInfo, PodEntrySummary};
use crate::role_session_registry::{PanelRegistryStore, RelatedTicketRef, RoleSessionOrigin};
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tempfile::TempDir; use tempfile::TempDir;
@ -1196,6 +1336,97 @@ mod tests {
assert_eq!(done.next_action, Some(NextUserAction::Close)); assert_eq!(done.next_action, Some(NextUserAction::Close));
} }
#[test]
fn workspace_panel_shows_ticket_associated_intake_pods_adjacent_to_ticket() {
let temp = TempDir::new().unwrap();
write_ticket_config(temp.path());
let backend = LocalTicketBackend::new(temp.path().join(".yoi/tickets"));
create_ticket(&backend, "Ticket With Intake", |input| {
input.workflow_state = Some(TicketWorkflowState::Ready);
});
let ticket_id = backend
.list(TicketFilter::all())
.unwrap()
.into_iter()
.find(|ticket| ticket.title == "Ticket With Intake")
.unwrap()
.id;
let preticket_pod = format!("pre-{ticket_id}-intake");
let registry = PanelRegistryStore::from_root(temp.path().join("registry"));
registry
.claim_ticket(&ticket_id, None, "claimed-intake", "intake")
.unwrap();
registry
.record_session(
"shared-intake",
"intake",
RoleSessionOrigin::RoleLaunch,
None,
[RelatedTicketRef {
id: ticket_id.clone(),
slug: None,
}],
)
.unwrap();
registry
.record_session(
&preticket_pod,
"intake",
RoleSessionOrigin::PreTicketIntake,
None,
[],
)
.unwrap();
let pods = live_pods(&["claimed-intake", "shared-intake", &preticket_pod]);
let model =
build_workspace_panel_with_registry(temp.path(), &pods, &registry.snapshot().unwrap());
let ticket_index = model
.rows
.iter()
.position(|row| row.key == PanelRowKey::Ticket(ticket_id.clone()))
.unwrap();
let ticket_row = &model.rows[ticket_index];
let ticket = ticket_row.ticket.as_ref().unwrap();
assert_eq!(
ticket
.intake_pods
.iter()
.map(|entry| entry.pod_name.as_str())
.collect::<Vec<_>>(),
vec!["claimed-intake", "shared-intake"]
);
assert_eq!(ticket.related_pods, vec!["claimed-intake", "shared-intake"]);
assert_eq!(
model.rows[ticket_index + 1].key,
PanelRowKey::TicketIntakePod {
ticket_id: ticket_id.clone(),
pod_name: "claimed-intake".to_string(),
}
);
assert_eq!(
model.rows[ticket_index + 1].kind,
PanelRowKind::TicketIntakePod
);
assert_eq!(model.rows[ticket_index + 1].status, "live");
assert_eq!(
model.rows[ticket_index + 1].next_action,
Some(NextUserAction::OpenPod)
);
assert_eq!(
model.rows[ticket_index + 2].key,
PanelRowKey::TicketIntakePod {
ticket_id: ticket_id.clone(),
pod_name: "shared-intake".to_string(),
}
);
assert!(model.rows.iter().all(|row| {
row.kind != PanelRowKind::TicketIntakePod
|| row.key.pod_name() != Some(preticket_pod.as_str())
}));
}
#[test] #[test]
fn workspace_panel_displays_local_ticket_claim_status() { fn workspace_panel_displays_local_ticket_claim_status() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();