fix: add an action field to timeline events
This commit is contained in:
parent
144257c2c8
commit
f750107bb1
8 changed files with 22 additions and 20 deletions
1
migrations/0005_add_action_to_timeline.sql
Normal file
1
migrations/0005_add_action_to_timeline.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE timeline_events ADD COLUMN action TEXT NOT NULL DEFAULT 'added';
|
||||||
|
|
@ -139,6 +139,7 @@ pub(crate) async fn create_bag(
|
||||||
let event = NewTimelineEvent {
|
let event = NewTimelineEvent {
|
||||||
entity_type: "bag".to_string(),
|
entity_type: "bag".to_string(),
|
||||||
entity_id: bag.id.into_inner(),
|
entity_id: bag.id.into_inner(),
|
||||||
|
action: "added".to_string(),
|
||||||
occurred_at: chrono::Utc::now(),
|
occurred_at: chrono::Utc::now(),
|
||||||
title: roast.name.to_string(),
|
title: roast.name.to_string(),
|
||||||
details: vec![
|
details: vec![
|
||||||
|
|
@ -235,6 +236,7 @@ pub(crate) async fn update_bag(
|
||||||
let event = NewTimelineEvent {
|
let event = NewTimelineEvent {
|
||||||
entity_type: "bag".to_string(),
|
entity_type: "bag".to_string(),
|
||||||
entity_id: bag.id.into_inner(),
|
entity_id: bag.id.into_inner(),
|
||||||
|
action: "finished".to_string(),
|
||||||
occurred_at: chrono::Utc::now(),
|
occurred_at: chrono::Utc::now(),
|
||||||
title: format!("{}", roast.name),
|
title: format!("{}", roast.name),
|
||||||
details: vec![TimelineEventDetail {
|
details: vec![TimelineEventDetail {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ pub struct TimelineEvent {
|
||||||
pub id: TimelineEventId,
|
pub id: TimelineEventId,
|
||||||
pub entity_type: String,
|
pub entity_type: String,
|
||||||
pub entity_id: i64,
|
pub entity_id: i64,
|
||||||
|
pub action: String,
|
||||||
pub occurred_at: DateTime<Utc>,
|
pub occurred_at: DateTime<Utc>,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub details: Vec<TimelineEventDetail>,
|
pub details: Vec<TimelineEventDetail>,
|
||||||
|
|
@ -27,6 +28,7 @@ pub struct TimelineEvent {
|
||||||
pub struct NewTimelineEvent {
|
pub struct NewTimelineEvent {
|
||||||
pub entity_type: String,
|
pub entity_type: String,
|
||||||
pub entity_id: i64,
|
pub entity_id: i64,
|
||||||
|
pub action: String,
|
||||||
pub occurred_at: DateTime<Utc>,
|
pub occurred_at: DateTime<Utc>,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub details: Vec<TimelineEventDetail>,
|
pub details: Vec<TimelineEventDetail>,
|
||||||
|
|
|
||||||
|
|
@ -130,10 +130,11 @@ impl RoasterRepository for SqlRoasterRepository {
|
||||||
let details_json = Self::details_for_roaster(&roaster)?;
|
let details_json = Self::details_for_roaster(&roaster)?;
|
||||||
|
|
||||||
query(
|
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("roaster")
|
||||||
.bind(i64::from(roaster.id))
|
.bind(i64::from(roaster.id))
|
||||||
|
.bind("added")
|
||||||
.bind(roaster.created_at)
|
.bind(roaster.created_at)
|
||||||
.bind(&roaster.name)
|
.bind(&roaster.name)
|
||||||
.bind(details_json)
|
.bind(details_json)
|
||||||
|
|
|
||||||
|
|
@ -166,10 +166,11 @@ impl RoastRepository for SqlRoastRepository {
|
||||||
};
|
};
|
||||||
|
|
||||||
query(
|
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("roast")
|
||||||
.bind(i64::from(roast.id))
|
.bind(i64::from(roast.id))
|
||||||
|
.bind("added")
|
||||||
.bind(roast.created_at)
|
.bind(roast.created_at)
|
||||||
.bind(&roast.name)
|
.bind(&roast.name)
|
||||||
.bind(details_json)
|
.bind(details_json)
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ impl SqlTimelineEventRepository {
|
||||||
impl TimelineEventRepository for SqlTimelineEventRepository {
|
impl TimelineEventRepository for SqlTimelineEventRepository {
|
||||||
async fn insert(&self, event: NewTimelineEvent) -> Result<TimelineEvent, RepositoryError> {
|
async fn insert(&self, event: NewTimelineEvent) -> Result<TimelineEvent, RepositoryError> {
|
||||||
let query = r#"
|
let query = r#"
|
||||||
INSERT INTO timeline_events (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 (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
RETURNING id, entity_type, entity_id, occurred_at, title, details_json, tasting_notes_json
|
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| {
|
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)
|
let record = sqlx::query_as::<_, TimelineEventRecord>(query)
|
||||||
.bind(event.entity_type)
|
.bind(event.entity_type)
|
||||||
.bind(event.entity_id)
|
.bind(event.entity_id)
|
||||||
|
.bind(event.action)
|
||||||
.bind(event.occurred_at)
|
.bind(event.occurred_at)
|
||||||
.bind(event.title)
|
.bind(event.title)
|
||||||
.bind(details_json)
|
.bind(details_json)
|
||||||
|
|
@ -65,7 +66,7 @@ impl TimelineEventRepository for SqlTimelineEventRepository {
|
||||||
|
|
||||||
let order_clause = format!("t.occurred_at {direction_sql}, t.id DESC");
|
let order_clause = format!("t.occurred_at {direction_sql}, t.id DESC");
|
||||||
let base_query = "SELECT
|
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
|
CASE
|
||||||
WHEN t.entity_type = 'roaster' THEN r.slug
|
WHEN t.entity_type = 'roaster' THEN r.slug
|
||||||
WHEN t.entity_type = 'roast' THEN rst.slug
|
WHEN t.entity_type = 'roast' THEN rst.slug
|
||||||
|
|
@ -103,6 +104,7 @@ struct TimelineEventRecord {
|
||||||
id: i64,
|
id: i64,
|
||||||
entity_type: String,
|
entity_type: String,
|
||||||
entity_id: i64,
|
entity_id: i64,
|
||||||
|
action: String,
|
||||||
occurred_at: DateTime<Utc>,
|
occurred_at: DateTime<Utc>,
|
||||||
title: String,
|
title: String,
|
||||||
details_json: Option<String>,
|
details_json: Option<String>,
|
||||||
|
|
@ -137,6 +139,7 @@ impl TimelineEventRecord {
|
||||||
id: TimelineEventId::from(self.id),
|
id: TimelineEventId::from(self.id),
|
||||||
entity_type: self.entity_type,
|
entity_type: self.entity_type,
|
||||||
entity_id: self.entity_id,
|
entity_id: self.entity_id,
|
||||||
|
action: self.action,
|
||||||
occurred_at: self.occurred_at,
|
occurred_at: self.occurred_at,
|
||||||
title: self.title,
|
title: self.title,
|
||||||
details,
|
details,
|
||||||
|
|
|
||||||
|
|
@ -440,6 +440,7 @@ impl TimelineEventView {
|
||||||
id,
|
id,
|
||||||
entity_type,
|
entity_type,
|
||||||
entity_id,
|
entity_id,
|
||||||
|
action,
|
||||||
occurred_at,
|
occurred_at,
|
||||||
title,
|
title,
|
||||||
details,
|
details,
|
||||||
|
|
@ -448,16 +449,11 @@ impl TimelineEventView {
|
||||||
roaster_slug,
|
roaster_slug,
|
||||||
} = event;
|
} = event;
|
||||||
|
|
||||||
let kind_label = match entity_type.as_str() {
|
let kind_label = match (entity_type.as_str(), action.as_str()) {
|
||||||
"roaster" => "Roaster Added",
|
("roaster", "added") => "Roaster Added",
|
||||||
"roast" => "Roast Added",
|
("roast", "added") => "Roast Added",
|
||||||
"bag" => {
|
("bag", "added") => "Bag Added",
|
||||||
if title.starts_with("Finished") {
|
("bag", "finished") => "Bag Finished",
|
||||||
"Bag Finished"
|
|
||||||
} else {
|
|
||||||
"Bag Added"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => "Event",
|
_ => "Event",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -410,8 +410,4 @@ async fn closing_a_bag_surfaces_on_the_timeline() {
|
||||||
body.contains("Bag Finished"),
|
body.contains("Bag Finished"),
|
||||||
"Expected 'Bag Finished' badge in timeline HTML, got: {body}"
|
"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}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue