use log::warn;
use super::history_step::{
BodyHistoryStep, CommentHistoryStep, CommentItem, IssueHistoryStep, LabelHistoryStep,
NonEmptyVec, StatusHistoryStep, TitleHistoryStep,
};
use crate::{
entities::issue::{Issue, issue_operation::IssueOperationData},
replica::entity::{
Entity, id::entity_id::EntityId, operation::Operation, snapshot::timeline::Timeline,
},
};
#[derive(Debug, Clone)]
pub struct IssueTimeline {
history: Vec<IssueHistoryStep>,
}
impl Timeline<Issue> for IssueTimeline {
fn new() -> Self {
Self {
history: Vec::new(),
}
}
fn from_root_operation(op: &Operation<Issue>) -> Self {
let mut me = Self::new();
let IssueOperationData::Create {
title,
message,
files,
} = &op.operation_data()
else {
unreachable!("We should have assured that this call is impossible.");
};
me.add_title_history(TitleHistoryStep {
author: op.author(),
title: title.to_owned(),
at: op.creation_time(),
});
me.add_body_history(BodyHistoryStep {
author: op.author(),
message: message.to_owned(),
files: files.to_owned(),
at: op.creation_time(),
});
me
}
fn add(&mut self, op: &Operation<Issue>) {
match &op.operation_data() {
IssueOperationData::AddComment { message, files } => {
self.add_comment_history(
CommentHistoryStep {
author: op.author(),
message: message.to_owned(),
files: files.to_owned(),
at: op.creation_time(),
},
op.id(),
);
}
IssueOperationData::Create { .. } => unreachable!("Already handled in constructor"),
IssueOperationData::EditComment {
target,
message,
files,
} => {
self.add_comment_history(
CommentHistoryStep {
author: op.author(),
message: message.to_owned(),
files: files.to_owned(),
at: op.creation_time(),
},
*target,
);
}
IssueOperationData::LabelChange { added, removed } => {
self.add_label_history(LabelHistoryStep {
author: op.author(),
added: added.to_owned(),
removed: removed.to_owned(),
at: op.creation_time(),
});
}
IssueOperationData::SetMetadata {
target,
new_metadata: _,
} => {
warn!("Skipping metadata op for target: {target}");
}
IssueOperationData::SetStatus { status } => {
self.add_status_history(StatusHistoryStep {
author: op.author(),
status: *status,
at: op.creation_time(),
});
}
IssueOperationData::SetTitle { title, was: _ } => {
self.add_title_history(TitleHistoryStep {
author: op.author(),
title: title.to_owned(),
at: op.creation_time(),
});
}
IssueOperationData::Noop {} => todo!(),
}
}
fn history(&self) -> &[<Issue as Entity>::HistoryStep] {
&self.history
}
}
macro_rules! filter_history {
($history:expr, $type_name:ident) => {
filter_history!(@iter $history, $type_name)
};
(@$mode:ident $history:expr, $type_name:ident) => {
$history.$mode().filter_map(|h| {
if let IssueHistoryStep::$type_name(a) = h {
Some(a)
} else {
None
}
})
};
}
impl IssueTimeline {
pub fn body_history(&self) -> impl Iterator<Item = &BodyHistoryStep> {
filter_history!(self.history, Body)
}
pub fn comments(&self) -> impl Iterator<Item = &CommentItem> {
filter_history!(self.history, Comment)
}
pub fn labels_history(&self) -> impl Iterator<Item = &LabelHistoryStep> {
filter_history!(self.history, Label)
}
pub fn status_history(&self) -> impl Iterator<Item = &StatusHistoryStep> {
filter_history!(self.history, Status)
}
pub fn title_history(&self) -> impl Iterator<Item = &TitleHistoryStep> {
filter_history!(self.history, Title)
}
pub fn add_body_history(&mut self, item: BodyHistoryStep) {
self.history
.push(<Issue as Entity>::HistoryStep::Body(item));
}
pub fn add_title_history(&mut self, item: TitleHistoryStep) {
self.history
.push(<Issue as Entity>::HistoryStep::Title(item));
}
pub fn add_status_history(&mut self, item: StatusHistoryStep) {
self.history
.push(<Issue as Entity>::HistoryStep::Status(item));
}
pub fn add_label_history(&mut self, item: LabelHistoryStep) {
self.history
.push(<Issue as Entity>::HistoryStep::Label(item));
}
pub fn add_comment_history(&mut self, item: CommentHistoryStep, id: EntityId<Issue>) {
if let Some(comment) = filter_history!(@iter_mut self.history, Comment).find(|c| c.id == id)
{
comment.history.push(item);
} else {
self.history
.push(<Issue as Entity>::HistoryStep::Comment(CommentItem {
id,
history: NonEmptyVec::new(item),
}));
}
}
}