fix: add an action field to timeline events

This commit is contained in:
Jon Seager 2025-11-27 14:52:13 +00:00
parent 144257c2c8
commit f750107bb1
No known key found for this signature in database
8 changed files with 22 additions and 20 deletions

View file

@ -0,0 +1 @@
ALTER TABLE timeline_events ADD COLUMN action TEXT NOT NULL DEFAULT 'added';

View file

@ -139,6 +139,7 @@ pub(crate) async fn create_bag(
let event = NewTimelineEvent {
entity_type: "bag".to_string(),
entity_id: bag.id.into_inner(),
action: "added".to_string(),
occurred_at: chrono::Utc::now(),
title: roast.name.to_string(),
details: vec![
@ -235,6 +236,7 @@ pub(crate) async fn update_bag(
let event = NewTimelineEvent {
entity_type: "bag".to_string(),
entity_id: bag.id.into_inner(),
action: "finished".to_string(),
occurred_at: chrono::Utc::now(),
title: format!("{}", roast.name),
details: vec![TimelineEventDetail {

View file

@ -15,6 +15,7 @@ pub struct TimelineEvent {
pub id: TimelineEventId,
pub entity_type: String,
pub entity_id: i64,
pub action: String,
pub occurred_at: DateTime<Utc>,
pub title: String,
pub details: Vec<TimelineEventDetail>,
@ -27,6 +28,7 @@ pub struct TimelineEvent {
pub struct NewTimelineEvent {
pub entity_type: String,
pub entity_id: i64,
pub action: String,
pub occurred_at: DateTime<Utc>,
pub title: String,
pub details: Vec<TimelineEventDetail>,

View file

@ -130,10 +130,11 @@ impl RoasterRepository for SqlRoasterRepository {
let details_json = Self::details_for_roaster(&roaster)?;
query(
"INSERT INTO timeline_events (entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json) VALUES (?, ?, ?, ?, ?, ?)",
"INSERT INTO timeline_events (entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json) VALUES (?, ?, ?, ?, ?, ?, ?)",
)
.bind("roaster")
.bind(i64::from(roaster.id))
.bind("added")
.bind(roaster.created_at)
.bind(&roaster.name)
.bind(details_json)

View file

@ -166,10 +166,11 @@ impl RoastRepository for SqlRoastRepository {
};
query(
"INSERT INTO timeline_events (entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json) VALUES (?, ?, ?, ?, ?, ?)",
"INSERT INTO timeline_events (entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json) VALUES (?, ?, ?, ?, ?, ?, ?)",
)
.bind("roast")
.bind(i64::from(roast.id))
.bind("added")
.bind(roast.created_at)
.bind(&roast.name)
.bind(details_json)

View file

@ -25,9 +25,9 @@ impl SqlTimelineEventRepository {
impl TimelineEventRepository for SqlTimelineEventRepository {
async fn insert(&self, event: NewTimelineEvent) -> Result<TimelineEvent, RepositoryError> {
let query = r#"
INSERT INTO timeline_events (entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING id, entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json
INSERT INTO timeline_events (entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json)
VALUES (?, ?, ?, ?, ?, ?, ?)
RETURNING id, entity_type, entity_id, action, occurred_at, title, details_json, tasting_notes_json
"#;
let details_json = serde_json::to_string(&event.details).map_err(|err| {
@ -43,6 +43,7 @@ impl TimelineEventRepository for SqlTimelineEventRepository {
let record = sqlx::query_as::<_, TimelineEventRecord>(query)
.bind(event.entity_type)
.bind(event.entity_id)
.bind(event.action)
.bind(event.occurred_at)
.bind(event.title)
.bind(details_json)
@ -65,7 +66,7 @@ impl TimelineEventRepository for SqlTimelineEventRepository {
let order_clause = format!("t.occurred_at {direction_sql}, t.id DESC");
let base_query = "SELECT
t.id, t.entity_type, t.entity_id, t.occurred_at, t.title, t.details_json, t.tasting_notes_json,
t.id, t.entity_type, t.entity_id, t.action, t.occurred_at, t.title, t.details_json, t.tasting_notes_json,
CASE
WHEN t.entity_type = 'roaster' THEN r.slug
WHEN t.entity_type = 'roast' THEN rst.slug
@ -103,6 +104,7 @@ struct TimelineEventRecord {
id: i64,
entity_type: String,
entity_id: i64,
action: String,
occurred_at: DateTime<Utc>,
title: String,
details_json: Option<String>,
@ -137,6 +139,7 @@ impl TimelineEventRecord {
id: TimelineEventId::from(self.id),
entity_type: self.entity_type,
entity_id: self.entity_id,
action: self.action,
occurred_at: self.occurred_at,
title: self.title,
details,

View file

@ -440,6 +440,7 @@ impl TimelineEventView {
id,
entity_type,
entity_id,
action,
occurred_at,
title,
details,
@ -448,16 +449,11 @@ impl TimelineEventView {
roaster_slug,
} = event;
let kind_label = match entity_type.as_str() {
"roaster" => "Roaster Added",
"roast" => "Roast Added",
"bag" => {
if title.starts_with("Finished") {
"Bag Finished"
} else {
"Bag Added"
}
}
let kind_label = match (entity_type.as_str(), action.as_str()) {
("roaster", "added") => "Roaster Added",
("roast", "added") => "Roast Added",
("bag", "added") => "Bag Added",
("bag", "finished") => "Bag Finished",
_ => "Event",
};

View file

@ -410,8 +410,4 @@ async fn closing_a_bag_surfaces_on_the_timeline() {
body.contains("Bag Finished"),
"Expected 'Bag Finished' badge in timeline HTML, got: {body}"
);
assert!(
body.contains(&format!("Finished: {}", roast_name)),
"Expected bag finished title to appear in timeline HTML, got: {body}"
);
}