use crate::{Command, Operation};
use chrono::{DateTime, Utc};
use dpm_model::{DecisionId, WorkItemId};
use std::collections::BTreeMap;
pub(crate) struct HistoryIndex<'a> {
work: BTreeMap<WorkItemId, Vec<&'a Operation>>,
ordered: Vec<&'a Operation>,
opened: BTreeMap<DecisionId, &'a Operation>,
decided: BTreeMap<DecisionId, &'a Operation>,
}
impl<'a> HistoryIndex<'a> {
pub(crate) fn new(history: &'a [Operation]) -> Self {
let mut index = Self {
work: BTreeMap::new(),
ordered: Vec::new(),
opened: BTreeMap::new(),
decided: BTreeMap::new(),
};
for operation in history {
if let Some(work) = target(&operation.command) {
index.work.entry(work).or_default().push(operation);
index.ordered.push(operation);
}
match &operation.command {
Command::Decide { decision, .. } => {
index.decided.insert(*decision, operation);
}
Command::ApplyChange { changes, .. } | Command::RevertChange { changes, .. } => {
for added in changes.iter().filter(|c| {
c.collection == "decisions" && c.before.is_null() && !c.after.is_null()
}) {
let id = added.id.clone().map(serde_json::Value::String);
if let Some(decision) = id.and_then(|id| serde_json::from_value(id).ok()) {
index.opened.entry(decision).or_insert(operation);
}
}
}
_ => {}
}
}
index
}
pub(crate) fn of(&self, work: WorkItemId) -> &[&'a Operation] {
self.work.get(&work).map_or(&[], Vec::as_slice)
}
pub(crate) fn last(
&self,
work: WorkItemId,
matches: impl Fn(&Command) -> bool,
) -> Option<&'a Operation> {
self.of(work)
.iter()
.rev()
.find(|op| matches(&op.command))
.copied()
}
pub(crate) fn work_operations(&self) -> &[&'a Operation] {
&self.ordered
}
pub(crate) fn opened(&self, decision: DecisionId) -> Option<&'a Operation> {
self.opened.get(&decision).copied()
}
pub(crate) fn decided(&self, decision: DecisionId) -> Option<&'a Operation> {
self.decided.get(&decision).copied()
}
}
pub(crate) fn target(command: &Command) -> Option<WorkItemId> {
match command {
Command::Claim { work }
| Command::Release { work, .. }
| Command::Handoff { work, .. }
| Command::Start { work, .. }
| Command::Submit { work, .. }
| Command::Verify { work, .. }
| Command::Reject { work, .. }
| Command::Block { work, .. }
| Command::Unblock { work } => Some(*work),
_ => None,
}
}
pub(crate) fn occurred(operation: &Operation) -> DateTime<Utc> {
backfilled(&operation.command).unwrap_or(operation.timestamp)
}
pub(crate) fn backfilled(command: &Command) -> Option<DateTime<Utc>> {
match command {
Command::Start { occurred_at, .. }
| Command::Submit { occurred_at, .. }
| Command::Verify { occurred_at, .. } => *occurred_at,
_ => None,
}
}