merge: panel intake pod rows
This commit is contained in:
commit
2fcbd6aefb
|
|
@ -48,11 +48,11 @@ use crate::workspace_panel::{
|
|||
ActionPriority, CompanionLifecyclePlan, CompanionPanelState, CompanionPanelStatus,
|
||||
CompanionPodPresence, ComposerTarget, NextUserAction, OrchestratorLifecyclePlan,
|
||||
OrchestratorPanelState, OrchestratorPanelStatus, OrchestratorPodPresence, PanelRow,
|
||||
PanelRowKey, TicketConfigAvailability, TicketLocalClaimStatus, WorkspacePanelViewModel,
|
||||
bounded_panel_diagnostic, build_current_ticket_row, build_workspace_panel,
|
||||
companion_pod_presence, decide_companion_lifecycle, decide_orchestrator_lifecycle,
|
||||
local_claim_status_for_pod, orchestrator_pod_presence, ticket_config_availability,
|
||||
workspace_companion_pod_name, workspace_orchestrator_pod_name,
|
||||
PanelRowKey, PanelRowKind, TicketConfigAvailability, TicketLocalClaimStatus,
|
||||
WorkspacePanelViewModel, bounded_panel_diagnostic, build_current_ticket_row,
|
||||
build_workspace_panel, companion_pod_presence, decide_companion_lifecycle,
|
||||
decide_orchestrator_lifecycle, local_claim_status_for_pod, orchestrator_pod_presence,
|
||||
ticket_config_availability, workspace_companion_pod_name, workspace_orchestrator_pod_name,
|
||||
};
|
||||
|
||||
const MAX_ENTRIES: usize = 50;
|
||||
|
|
@ -958,6 +958,13 @@ fn panel_e2e_row_key(key: &PanelRowKey) -> PanelE2eRowKey {
|
|||
kind: "ticket",
|
||||
id: id.clone(),
|
||||
},
|
||||
PanelRowKey::TicketIntakePod {
|
||||
ticket_id,
|
||||
pod_name,
|
||||
} => PanelE2eRowKey {
|
||||
kind: "ticket_intake_pod",
|
||||
id: format!("{ticket_id}:{pod_name}"),
|
||||
},
|
||||
PanelRowKey::Pod(name) => PanelE2eRowKey {
|
||||
kind: "pod",
|
||||
id: name.clone(),
|
||||
|
|
@ -1207,12 +1214,8 @@ impl MultiPodApp {
|
|||
}
|
||||
|
||||
fn selected_pod_entry(&self) -> Option<&PodListEntry> {
|
||||
match self.selected_row.as_ref() {
|
||||
Some(PanelRowKey::Pod(name)) => {
|
||||
self.list.entries.iter().find(|entry| &entry.name == name)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
let name = self.selected_row.as_ref().and_then(PanelRowKey::pod_name)?;
|
||||
self.list.entries.iter().find(|entry| entry.name == name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -1238,11 +1241,14 @@ impl MultiPodApp {
|
|||
}),
|
||||
);
|
||||
}
|
||||
let entry = self.selected_pod_entry()?;
|
||||
if entry.actions.can_open {
|
||||
return None;
|
||||
if let Some(entry) = self.selected_pod_entry() {
|
||||
if entry.actions.can_open {
|
||||
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) {
|
||||
|
|
@ -1354,6 +1360,9 @@ impl MultiPodApp {
|
|||
None => match &hit.key {
|
||||
PanelRowKey::Pod(name) => (name.clone(), None, None),
|
||||
PanelRowKey::Ticket(id) => (id.clone(), None, None),
|
||||
PanelRowKey::TicketIntakePod { pod_name, .. } => {
|
||||
(pod_name.clone(), None, None)
|
||||
}
|
||||
},
|
||||
};
|
||||
PanelE2eRenderedRow {
|
||||
|
|
@ -1406,7 +1415,7 @@ impl MultiPodApp {
|
|||
}
|
||||
if let Some(key) = visible.iter().find(|key| match key {
|
||||
PanelRowKey::Pod(name) => Some(name.as_str()) != orchestrator_pod_name,
|
||||
PanelRowKey::Ticket(_) => true,
|
||||
PanelRowKey::Ticket(_) | PanelRowKey::TicketIntakePod { .. } => true,
|
||||
}) {
|
||||
self.select_panel_key(key.clone());
|
||||
return;
|
||||
|
|
@ -4677,6 +4686,13 @@ fn selected_ticket_notice(row: Option<&PanelRow>) -> String {
|
|||
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(),
|
||||
}
|
||||
}
|
||||
|
|
@ -4793,7 +4809,7 @@ fn visible_panel_keys(panel: &WorkspacePanelViewModel, list: &PodList) -> Vec<Pa
|
|||
let mut keys = panel
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|row| row.is_ticket_action())
|
||||
.filter(|row| row.is_ticket_section_row())
|
||||
.map(|row| row.key.clone())
|
||||
.collect::<Vec<_>>();
|
||||
keys.extend(
|
||||
|
|
@ -5145,7 +5161,7 @@ fn panel_action_rows(
|
|||
let rows = panel
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|row| row.is_ticket_action())
|
||||
.filter(|row| row.is_ticket_section_row())
|
||||
.collect::<Vec<_>>();
|
||||
if rows.is_empty() {
|
||||
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 POD_STATUS_COLUMN_WIDTH: usize = 18;
|
||||
|
||||
fn panel_row_lines(row: &PanelRow, selected: bool, width: u16) -> [Line<'static>; 2] {
|
||||
[
|
||||
panel_row_title_line(row, selected, width),
|
||||
panel_row_detail_line(row, selected, width),
|
||||
]
|
||||
fn panel_row_lines(row: &PanelRow, selected: bool, width: u16) -> Vec<Line<'static>> {
|
||||
if row.kind == PanelRowKind::TicketIntakePod {
|
||||
vec![panel_row_title_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> {
|
||||
|
|
@ -5242,6 +5262,21 @@ fn push_ticket_marker_span(spans: &mut Vec<Span<'static>>, selected: bool, remai
|
|||
}
|
||||
|
||||
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)];
|
||||
if let Some(blocked_reason) = row
|
||||
.ticket
|
||||
|
|
@ -5303,6 +5338,7 @@ fn panel_ticket_reference(row: &PanelRow) -> String {
|
|||
.map(|ticket| ticket.id.clone())
|
||||
.unwrap_or_else(|| match &row.key {
|
||||
PanelRowKey::Ticket(id) => id.clone(),
|
||||
PanelRowKey::TicketIntakePod { ticket_id, .. } => ticket_id.clone(),
|
||||
PanelRowKey::Pod(name) => name.clone(),
|
||||
})
|
||||
}
|
||||
|
|
@ -7321,7 +7357,8 @@ branch = "orchestration/custom-panel"
|
|||
"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 detail_line = plain_line(&detail);
|
||||
let state_start = 2;
|
||||
|
|
@ -7352,7 +7389,8 @@ branch = "orchestration/custom-panel"
|
|||
"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 detail_line = plain_line(&detail);
|
||||
let state_start = 2;
|
||||
|
|
@ -7377,7 +7415,8 @@ branch = "orchestration/custom-panel"
|
|||
"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 detail_line = plain_line(&detail);
|
||||
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.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);
|
||||
|
||||
assert!(detail_line.contains("Gate: waiting for BLOCKER-1 via depends_on"));
|
||||
|
|
@ -8602,6 +8642,7 @@ branch = "orchestration/custom-panel"
|
|||
blocked_reason: None,
|
||||
related_pods: Vec::new(),
|
||||
local_claim: None,
|
||||
intake_pods: Vec::new(),
|
||||
};
|
||||
PanelRow {
|
||||
key: PanelRowKey::Ticket(ticket.id.clone()),
|
||||
|
|
|
|||
|
|
@ -182,15 +182,26 @@ impl OrchestratorPanelStatus {
|
|||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) enum PanelRowKey {
|
||||
Ticket(String),
|
||||
TicketIntakePod { ticket_id: String, pod_name: 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)]
|
||||
pub(crate) enum PanelRowKind {
|
||||
Planning,
|
||||
Ticket,
|
||||
Review,
|
||||
ActiveWork,
|
||||
TicketIntakePod,
|
||||
Pod,
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +247,30 @@ pub(crate) struct TicketPanelEntry {
|
|||
pub(crate) blocked_reason: Option<String>,
|
||||
pub(crate) related_pods: Vec<String>,
|
||||
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)]
|
||||
|
|
@ -279,11 +314,22 @@ pub(crate) struct PanelRow {
|
|||
|
||||
impl PanelRow {
|
||||
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_ASSOCIATED_INTAKE_ROWS_PER_TICKET: usize = 3;
|
||||
const ORCHESTRATOR_SUFFIX: &str = "-orchestrator";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
@ -574,12 +620,6 @@ fn build_workspace_panel_with_registry_model(
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -628,13 +668,13 @@ fn build_ticket_rows(
|
|||
pods: &PodList,
|
||||
registry: &PanelRegistrySnapshot,
|
||||
) -> ticket::Result<Vec<PanelRow>> {
|
||||
let mut rows = Vec::new();
|
||||
let mut ticket_rows = Vec::new();
|
||||
for summary in backend.list(TicketFilter::all())? {
|
||||
if summary.workflow_state == TicketWorkflowState::Closed {
|
||||
continue;
|
||||
}
|
||||
let ticket = backend.show(TicketIdOrSlug::Query(summary.id.clone()))?;
|
||||
rows.push(ticket_row(
|
||||
ticket_rows.push(ticket_row(
|
||||
summary,
|
||||
&ticket.events,
|
||||
&ticket.relations.blockers,
|
||||
|
|
@ -642,6 +682,19 @@ fn build_ticket_rows(
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
@ -653,7 +706,17 @@ fn ticket_row(
|
|||
registry: &PanelRegistrySnapshot,
|
||||
) -> PanelRow {
|
||||
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 latest_event = events.last();
|
||||
let entry = TicketPanelEntry {
|
||||
|
|
@ -669,6 +732,7 @@ fn ticket_row(
|
|||
blocked_reason: derived.blocked_reason.clone(),
|
||||
related_pods: related_pods.clone(),
|
||||
local_claim,
|
||||
intake_pods,
|
||||
};
|
||||
let subtitle = ticket_subtitle(&entry);
|
||||
PanelRow {
|
||||
|
|
@ -802,32 +866,111 @@ fn derive_ticket_state(
|
|||
}
|
||||
}
|
||||
|
||||
fn related_pods_for_ticket(
|
||||
fn associated_intake_entries_for_ticket(
|
||||
summary: &TicketSummary,
|
||||
pods: &PodList,
|
||||
registry: &PanelRegistrySnapshot,
|
||||
) -> Vec<String> {
|
||||
let id = lowercase(&summary.id);
|
||||
let mut names = Vec::new();
|
||||
if let Some(claim) = registry.claim_for_ticket(&summary.id) {
|
||||
names.push(claim.pod_name.clone());
|
||||
local_claim: Option<&TicketLocalClaimEntry>,
|
||||
) -> Vec<TicketAssociatedIntakeEntry> {
|
||||
let mut entries = Vec::new();
|
||||
if let Some(claim) = local_claim.filter(|claim| is_intake_role(&claim.role)) {
|
||||
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);
|
||||
if !id.is_empty() && name.contains(&id) {
|
||||
Some(pod.name.clone())
|
||||
} else {
|
||||
None
|
||||
|
||||
let mut related_sessions = registry
|
||||
.sessions
|
||||
.iter()
|
||||
.filter(|session| {
|
||||
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;
|
||||
}
|
||||
}) {
|
||||
if !names.iter().any(|existing| existing == &pod) {
|
||||
names.push(pod);
|
||||
}
|
||||
if names.len() >= 5 {
|
||||
entries.push(TicketAssociatedIntakeEntry {
|
||||
ticket_id: summary.id.clone(),
|
||||
status: local_claim_status_for_pod(&pod_name, pods),
|
||||
pod_name,
|
||||
source: TicketAssociatedIntakeSource::RelatedSession,
|
||||
});
|
||||
if entries.len() >= MAX_ASSOCIATED_INTAKE_ROWS_PER_TICKET {
|
||||
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(
|
||||
|
|
@ -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)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::pod_list::{LivePodInfo, PodEntrySummary};
|
||||
use crate::role_session_registry::{PanelRegistryStore, RelatedTicketRef, RoleSessionOrigin};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tempfile::TempDir;
|
||||
|
|
@ -1196,6 +1336,97 @@ mod tests {
|
|||
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, ®istry.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]
|
||||
fn workspace_panel_displays_local_ticket_claim_status() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user