use std::collections::{BTreeMap, BTreeSet};
use todoapp_core::{
Assignments, ComponentStore, Due, Duration, Estimate, Id, Schedule, Status, TaskEntityStore,
TimeSpent,
};
use crate::service::{Error, Services};
#[derive(Debug, Clone, PartialEq)]
pub struct Aggregate {
pub total: usize,
pub done: usize,
pub progress: f32,
pub time_spent: Duration,
pub estimate: Duration,
pub remaining: Duration,
pub earliest_due: Option<Due>,
pub assignees: BTreeSet<Id>,
pub status: Status,
pub by_status: BTreeMap<Status, usize>,
}
impl Default for Aggregate {
fn default() -> Self {
Self {
total: 0,
done: 0,
progress: 0.0,
time_spent: Duration::ZERO,
estimate: Duration::ZERO,
remaining: Duration::ZERO,
earliest_due: None,
assignees: BTreeSet::new(),
status: Status::Draft,
by_status: BTreeMap::new(),
}
}
}
impl<'a, St: ComponentStore + TaskEntityStore> Services<'a, St> {
pub async fn aggregate(&self, id: &Id) -> Result<Aggregate, Error> {
let mut agg = Aggregate::default();
let mut ids = self.descendants(id).await;
ids.insert(id.clone());
let mut worst: Option<Status> = None;
for tid in ids {
agg.total += 1;
let status = self.store.get::<Status>(&tid).await;
if status == Some(Status::Done) {
agg.done += 1;
}
let status = status.unwrap_or(Status::Draft);
*agg.by_status.entry(status).or_insert(0) += 1;
worst = Some(match worst {
Some(w) if w.rank() <= status.rank() => w,
_ => status,
});
agg.time_spent += self
.store
.get::<TimeSpent>(&tid)
.await
.map_or(Duration::ZERO, |t| t.0);
let estimate = self
.store
.get::<Estimate>(&tid)
.await
.map_or(Duration::ZERO, |e| e.0);
agg.estimate += estimate;
if status != Status::Done {
agg.remaining += estimate;
}
if let Some(Schedule(due)) = self.store.get::<Schedule>(&tid).await {
agg.earliest_due = Some(match agg.earliest_due.take() {
Some(cur) if cur <= due => cur,
_ => due,
});
}
if let Some(Assignments(asg)) = self.store.get::<Assignments>(&tid).await {
agg.assignees.extend(asg.into_iter().map(|a| a.actor));
}
}
agg.progress = if agg.total > 0 {
agg.done as f32 / agg.total as f32
} else {
0.0
};
agg.status = worst.unwrap_or(Status::Draft);
Ok(agg)
}
}