use super::*;
impl MissionControlState {
pub(crate) fn activity_is_descendant_of(&self, id: &ActivityId, ancestor: &ActivityId) -> bool {
if id == ancestor {
return false;
}
let Some(_) = self.nodes.get(id) else {
return id.is_path_descendant_of(ancestor);
};
let mut current = id;
let mut visited = HashSet::new();
let mut has_visual_parent = false;
loop {
if !visited.insert(current.clone()) {
return false;
}
let Some(node) = self.nodes.get(current) else {
return false;
};
let Some(parent_id) = node.parent_id.as_ref() else {
return !has_visual_parent
&& matches!(
&node.parent_provenance,
ActivityParentProvenance::Unavailable
)
&& id.is_path_descendant_of(ancestor);
};
has_visual_parent = true;
if parent_id == ancestor {
return true;
}
if !self.nodes.contains_key(parent_id) {
return false;
}
current = parent_id;
}
}
pub(super) fn parent_assignment_would_cycle(
&self,
id: &ActivityId,
parent: &ActivityId,
) -> bool {
let mut current = parent;
let mut visited = HashSet::new();
loop {
if current == id || !visited.insert(current.clone()) {
return true;
}
let Some(node) = self.nodes.get(current) else {
return false;
};
let Some(next) = node.parent_id.as_ref() else {
return false;
};
current = next;
}
}
pub(super) fn reattach_waiting_children(&mut self, parent_id: &ActivityId) -> bool {
let waiting_children = self
.nodes
.iter()
.filter(|(_, node)| {
node.parent_id.is_none()
&& matches!(
&node.parent_provenance,
ActivityParentProvenance::Declared(declared_parent)
if declared_parent == parent_id
)
})
.map(|(child_id, _)| child_id.clone())
.collect::<Vec<_>>();
let mut changed = false;
for child_id in waiting_children {
if self.parent_assignment_would_cycle(&child_id, parent_id) {
continue;
}
self.attach(&child_id, Some(parent_id));
changed |= self
.nodes
.get(&child_id)
.is_some_and(|node| node.parent_id.as_ref() == Some(parent_id));
}
changed
}
pub(in crate::tui::state) fn attach(&mut self, id: &ActivityId, parent: Option<&ActivityId>) {
let parent = parent
.filter(|parent| self.nodes.contains_key(*parent))
.cloned();
if let Some(parent) = parent.as_ref()
&& self.parent_assignment_would_cycle(id, parent)
{
return;
}
match parent.as_ref() {
Some(parent) => {
self.roots.retain(|root| root != id);
let children = self.children.entry(parent.clone()).or_default();
if !children.contains(id) {
children.push(id.clone());
}
}
None => {
if !self.roots.contains(id) {
self.roots.push(id.clone());
}
}
}
if let Some(node) = self.nodes.get_mut(id) {
node.parent_id = parent;
}
}
pub(super) fn detach(&mut self, id: &ActivityId, parent: Option<&ActivityId>) {
self.mark_activity_updated(id);
self.roots.retain(|root| root != id);
if let Some(parent) = parent
&& let Some(children) = self.children.get_mut(parent)
{
children.retain(|child| child != id);
}
if let Some(node) = self.nodes.get_mut(id) {
node.parent_id = None;
}
}
pub(super) fn aggregate_parent_status(&mut self, id: &ActivityId) {
let mut parent = self.nodes.get(id).and_then(|node| node.parent_id.clone());
let mut visited = HashSet::new();
while let Some(parent_id) = parent {
if !visited.insert(parent_id.clone()) {
break;
}
let statuses = self.children.get(&parent_id).cloned().unwrap_or_default();
let failed = statuses.iter().any(|child| {
self.nodes.get(child).is_some_and(|node| {
!matches!(
node.kind,
ActivityKind::Hook | ActivityKind::ProviderContextInjection
) && node.status == ActivityStatus::Failed
})
});
if failed
&& let Some(node) = self.nodes.get_mut(&parent_id)
&& !is_active_status(node.status)
{
node.status = ActivityStatus::Failed;
}
parent = self
.nodes
.get(&parent_id)
.and_then(|node| node.parent_id.clone());
}
}
}