git_bug/entities/issue/snapshot/
timeline.rs1use log::warn;
14
15use super::history_step::{
16 BodyHistoryStep, CommentHistoryStep, CommentItem, IssueHistoryStep, LabelHistoryStep,
17 NonEmptyVec, StatusHistoryStep, TitleHistoryStep,
18};
19use crate::{
20 entities::issue::{Issue, issue_operation::IssueOperationData},
21 replica::entity::{
22 Entity, id::entity_id::EntityId, operation::Operation, snapshot::timeline::Timeline,
23 },
24};
25
26#[derive(Debug, Clone)]
28pub struct IssueTimeline {
29 history: Vec<IssueHistoryStep>,
30}
31
32impl Timeline<Issue> for IssueTimeline {
33 fn new() -> Self {
34 Self {
35 history: Vec::new(),
36 }
37 }
38
39 fn from_root_operation(op: &Operation<Issue>) -> Self {
40 let mut me = Self::new();
41 let IssueOperationData::Create {
42 title,
43 message,
44 files,
45 } = &op.operation_data()
46 else {
47 unreachable!("We should have assured that this call is impossible.");
49 };
50
51 me.add_title_history(TitleHistoryStep {
52 author: op.author(),
53 title: title.to_owned(),
54 at: op.creation_time(),
55 });
56
57 me.add_body_history(BodyHistoryStep {
58 author: op.author(),
59 message: message.to_owned(),
60 files: files.to_owned(),
61 at: op.creation_time(),
62 });
63
64 me
65 }
66
67 fn add(&mut self, op: &Operation<Issue>) {
68 match &op.operation_data() {
69 IssueOperationData::AddComment { message, files } => {
70 self.add_comment_history(
74 CommentHistoryStep {
75 author: op.author(),
76 message: message.to_owned(),
77 files: files.to_owned(),
78 at: op.creation_time(),
79 },
80 op.id(),
81 );
82 }
83 IssueOperationData::Create { .. } => unreachable!("Already handled in constructor"),
84 IssueOperationData::EditComment {
85 target,
86 message,
87 files,
88 } => {
89 self.add_comment_history(
94 CommentHistoryStep {
95 author: op.author(),
96 message: message.to_owned(),
97 files: files.to_owned(),
98 at: op.creation_time(),
99 },
100 *target,
101 );
102 }
103 IssueOperationData::LabelChange { added, removed } => {
104 self.add_label_history(LabelHistoryStep {
106 author: op.author(),
107 added: added.to_owned(),
108 removed: removed.to_owned(),
109 at: op.creation_time(),
110 });
111 }
112 IssueOperationData::SetMetadata {
113 target,
114 new_metadata: _,
115 } => {
116 warn!("Skipping metadata op for target: {target}");
117 }
118 IssueOperationData::SetStatus { status } => {
119 self.add_status_history(StatusHistoryStep {
121 author: op.author(),
122 status: *status,
123 at: op.creation_time(),
124 });
125 }
126 IssueOperationData::SetTitle { title, was: _ } => {
127 self.add_title_history(TitleHistoryStep {
129 author: op.author(),
130 title: title.to_owned(),
131 at: op.creation_time(),
132 });
133 }
134 IssueOperationData::Noop {} => todo!(),
135 }
136 }
137
138 fn history(&self) -> &[<Issue as Entity>::HistoryStep] {
139 &self.history
140 }
141}
142
143macro_rules! filter_history {
144 ($history:expr, $type_name:ident) => {
145 filter_history!(@iter $history, $type_name)
146 };
147 (@$mode:ident $history:expr, $type_name:ident) => {
148 $history.$mode().filter_map(|h| {
149 if let IssueHistoryStep::$type_name(a) = h {
150 Some(a)
151 } else {
152 None
153 }
154 })
155 };
156}
157
158impl IssueTimeline {
159 pub fn body_history(&self) -> impl Iterator<Item = &BodyHistoryStep> {
161 filter_history!(self.history, Body)
162 }
163
164 pub fn comments(&self) -> impl Iterator<Item = &CommentItem> {
166 filter_history!(self.history, Comment)
167 }
168
169 pub fn labels_history(&self) -> impl Iterator<Item = &LabelHistoryStep> {
171 filter_history!(self.history, Label)
172 }
173
174 pub fn status_history(&self) -> impl Iterator<Item = &StatusHistoryStep> {
176 filter_history!(self.history, Status)
177 }
178
179 pub fn title_history(&self) -> impl Iterator<Item = &TitleHistoryStep> {
181 filter_history!(self.history, Title)
182 }
183
184 pub fn add_body_history(&mut self, item: BodyHistoryStep) {
188 self.history
189 .push(<Issue as Entity>::HistoryStep::Body(item));
190 }
191
192 pub fn add_title_history(&mut self, item: TitleHistoryStep) {
194 self.history
195 .push(<Issue as Entity>::HistoryStep::Title(item));
196 }
197
198 pub fn add_status_history(&mut self, item: StatusHistoryStep) {
200 self.history
201 .push(<Issue as Entity>::HistoryStep::Status(item));
202 }
203
204 pub fn add_label_history(&mut self, item: LabelHistoryStep) {
206 self.history
207 .push(<Issue as Entity>::HistoryStep::Label(item));
208 }
209
210 pub fn add_comment_history(&mut self, item: CommentHistoryStep, id: EntityId<Issue>) {
215 if let Some(comment) = filter_history!(@iter_mut self.history, Comment).find(|c| c.id == id)
216 {
217 comment.history.push(item);
218 } else {
219 self.history
220 .push(<Issue as Entity>::HistoryStep::Comment(CommentItem {
221 id,
222 history: NonEmptyVec::new(item),
223 }));
224 }
225 }
226}