Generalize event's cards as eventable,add collection

Lots to tidy up but this is working
This commit is contained in:
Jorge Manrubia
2025-04-24 07:44:16 +02:00
parent e8e3bd16a6
commit 54f4bd5144
15 changed files with 132 additions and 82 deletions
+1 -2
View File
@@ -29,12 +29,11 @@ module EventsTimeline
end
def user_events
Event.where(card: user_cards)
Event.where(collection: collection_filter).where(eventable: user_cards)
end
def user_cards
Current.user.accessible_cards
.published_or_drafted_by(Current.user)
.where(collection_id: collection_filter)
end
end
+1 -1
View File
@@ -1,6 +1,6 @@
class TerminalsController < ApplicationController
def show
@events = Event.where(card: Current.user.accessible_cards, creator: Current.user).chronologically.reverse_order.limit(20)
@events = Event.where(eventable: Current.user.accessible_cards, creator: Current.user).chronologically.reverse_order.limit(20)
end
def edit
+18 -15
View File
@@ -45,10 +45,13 @@ module EventsHelper
start_time = day.beginning_of_day
end_time = day.end_of_day
accessible_events = Event.joins(card: :collection)
.merge(Current.user.collections)
collections = Current.user.collections
collections = collections.where(id: params[:collection_ids]) if params[:collection_ids].present?
# TODO: this needs tidying up
accessible_events = Event.where(eventable_type: "Card")
.where(created_at: start_time..end_time)
.where(cards: { collection_id: params[:collection_ids].presence || Current.user.collection_ids })
.where(collection: collections)
headers = {
"Added" => accessible_events.where(action: "published").count,
@@ -66,30 +69,30 @@ module EventsHelper
case event.action
when "assigned"
if event.assignees.include?(Current.user)
"#{ event.creator == Current.user ? "You" : event.creator.name } will handle <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } will handle <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
else
"#{ event.creator == Current.user ? "You" : event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
end
when "unassigned"
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
when "commented"
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
when "published"
"#{ event.creator == Current.user ? "You" : event.creator.name } added <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } added <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
when "closed"
"#{ event.creator == Current.user ? "You" : event.creator.name } closed <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } closed <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
when "staged"
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.card.title }</span> to the #{event.stage_name} stage".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.eventable.title }</span> to the #{event.stage_name} stage".html_safe
when "unstaged"
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.card.title }</span> out ofthe #{event.stage_name} stage".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.eventable.title }</span> out ofthe #{event.stage_name} stage".html_safe
when "due_date_added"
"#{event.creator == Current.user ? "You" : event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
when "due_date_changed"
"#{event.creator == Current.user ? "You" : event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.eventable.title }</span>".html_safe
when "due_date_removed"
"#{event.creator == Current.user ? "You" : event.creator.name} removed the date on <span style='color: var(--card-color)'>#{ event.card.title }</span>"
"#{event.creator == Current.user ? "You" : event.creator.name} removed the date on <span style='color: var(--card-color)'>#{ event.eventable.title }</span>"
when "title_changed"
"#{event.creator == Current.user ? "You" : event.creator.name} renamed <span style='color: var(--card-color)'>#{ event.card.title }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} renamed <span style='color: var(--card-color)'>#{ event.eventable.title }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
end
end
+3 -3
View File
@@ -1,9 +1,9 @@
module NotificationsHelper
def event_notification_title(event)
case event_notification_action(event)
when "commented" then "RE: " + event.card.title
when "assigned" then "Assigned to you: " + event.card.title
else event.card.title
when "commented" then "RE: " + event.eventable.title
when "assigned" then "Assigned to you: " + event.eventable.title
else event.eventable.title
end
end
+3 -3
View File
@@ -2,7 +2,7 @@ module Card::Eventable
extend ActiveSupport::Concern
included do
has_many :events, dependent: :destroy
has_many :events, as: :eventable, dependent: :destroy
before_create { self.last_active_at = Time.current }
@@ -10,9 +10,9 @@ module Card::Eventable
after_save :track_title_change, if: :saved_change_to_title?
end
def track_event(action, creator: Current.user, **particulars)
def track_event(action, creator: Current.user, collection: self.collection, **particulars)
if published?
find_or_capture_event_summary.events.create! action: action, creator: creator, card: self, particulars: particulars
find_or_capture_event_summary.events.create! action:, creator:, collection:, eventable: self, particulars:
end
end
+8 -4
View File
@@ -1,12 +1,15 @@
class Event < ApplicationRecord
include Notifiable, Particulars
belongs_to :collection
belongs_to :creator, class_name: "User"
belongs_to :card
belongs_to :eventable, polymorphic: true
belongs_to :summary, touch: true, class_name: "EventSummary"
scope :chronologically, -> { order created_at: :asc, id: :desc }
after_create -> { card.touch(:last_active_at) }
# TODO: Remove dependency with last_active_at via hook
after_create -> { eventable.touch(:last_active_at) }
def action
super.inquiry
@@ -16,11 +19,12 @@ class Event < ApplicationRecord
if action.commented?
comment
else
card
eventable
end
end
# TODO: This doesn't belong here anymore
def initial_assignment?
action == "published" && card.assigned_to?(creator)
action == "published" && eventable.assigned_to?(creator)
end
end
+1 -1
View File
@@ -1,7 +1,7 @@
class EventSummary < ApplicationRecord
include Messageable
has_many :events, -> { chronologically }, dependent: :delete_all, foreign_key: :summary_id
has_many :events, -> { chronologically }, dependent: :delete_all, inverse_of: :summary
# FIXME: Consider persisting the body and compute at write time.
def body
+6 -2
View File
@@ -1,12 +1,12 @@
class Notifier::EventNotifier < Notifier
delegate :card, :creator, to: :source
delegate :creator, to: :source
delegate :watchers_and_subscribers, to: :card
private
def recipients
case source.action
when "assigned"
source.assignees.excluding(card.collection.access_only_users)
source.assignees.excluding(source.collection.access_only_users)
when "published"
watchers_and_subscribers(include_only_watching: true).without(creator, *card.mentionees)
when "commented"
@@ -15,4 +15,8 @@ class Notifier::EventNotifier < Notifier
watchers_and_subscribers.without(creator)
end
end
def card
source.eventable
end
end
+1 -1
View File
@@ -1,4 +1,4 @@
<% card = event.card %>
<% card = event.eventable %>
<% link = event.action == "commented" ? collection_card_path(card.collection, card, anchor: dom_id(event.comment)) : collection_card_path(card.collection, card) %>
<%= link_to link,
@@ -4,4 +4,4 @@
<div class="overflow-ellipsis txt-small txt-tight-lines">
<%= event_notification_body(event) %>
</div>
<div class="tray__item-meta overflow-ellipsis translucent"><%= notification.source.card.collection.name %> · <%= local_datetime_tag(notification.created_at, style: :ago) %></div>
<div class="tray__item-meta overflow-ellipsis translucent"><%= notification.source.collection.name %> · <%= local_datetime_tag(notification.created_at, style: :ago) %></div>
+3 -3
View File
@@ -1,5 +1,5 @@
<li class="min-width overflow-ellipsis">
<%= link_to event_action_sentence(event), collection_card_path(event.card.collection, event.card),
class: "terminal__link",
<%= link_to event_action_sentence(event), collection_card_path(event.collection, event.eventable),
class: "terminal__link",
data: { turbo_frame: "_top" } %>
</li>
</li>
Generated
+8 -5
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_04_23_114737) do
ActiveRecord::Schema[8.1].define(version: 2025_04_24_051059) do
create_table "accesses", force: :cascade do |t|
t.integer "collection_id", null: false
t.datetime "created_at", null: false
@@ -177,14 +177,17 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_23_114737) do
create_table "events", force: :cascade do |t|
t.string "action", null: false
t.integer "card_id", null: false
t.integer "collection_id", null: false
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.integer "eventable_id", null: false
t.string "eventable_type", null: false
t.json "particulars", default: {}
t.integer "summary_id", null: false
t.datetime "updated_at", null: false
t.index ["card_id"], name: "index_events_on_card_id"
t.index ["collection_id"], name: "index_events_on_collection_id"
t.index ["creator_id"], name: "index_events_on_creator_id"
t.index ["eventable_type", "eventable_id"], name: "index_events_on_eventable"
t.index ["summary_id", "action"], name: "index_events_on_summary_id_and_action"
end
@@ -237,7 +240,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_23_114737) do
t.datetime "created_at", null: false
t.integer "creator_id"
t.datetime "read_at"
t.integer "source_id"
t.integer "source_id", null: false
t.string "source_type", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
@@ -335,7 +338,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_23_114737) do
add_foreign_key "closures", "cards"
add_foreign_key "closures", "users"
add_foreign_key "collections", "workflows"
add_foreign_key "events", "cards"
add_foreign_key "events", "collections"
add_foreign_key "events", "event_summaries", column: "summary_id"
add_foreign_key "mentions", "users", column: "mentionee_id"
add_foreign_key "mentions", "users", column: "mentioner_id"
+59 -31
View File
@@ -80,7 +80,7 @@ columns:
default_function:
collation:
comment:
- &27 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &28 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: user_id
cast_type: *1
@@ -435,7 +435,7 @@ columns:
default_function:
collation:
comment:
- &29 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: title
cast_type: *7
@@ -489,11 +489,11 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: all_access
cast_type: &30 !ruby/object:ActiveModel::Type::Boolean
cast_type: &31 !ruby/object:ActiveModel::Type::Boolean
precision:
scale:
limit:
sql_type_metadata: &31 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type_metadata: &32 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: boolean
type: :boolean
limit:
@@ -545,9 +545,29 @@ columns:
default_function:
collation:
comment:
- *21
- *22
- *5
- *23
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: eventable_id
cast_type: *1
sql_type_metadata: *2
'null': false
default:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: eventable_type
cast_type: *7
sql_type_metadata: *8
'null': false
default:
default_function:
collation:
comment:
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -617,7 +637,7 @@ columns:
comment:
filters_tags:
- *18
- &28 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &29 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: tag_id
cast_type: *1
@@ -650,7 +670,7 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &26 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: source_id
cast_type: *1
@@ -660,7 +680,7 @@ columns:
default_function:
collation:
comment:
- &26 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &27 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: source_type
cast_type: *7
@@ -719,25 +739,16 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: source_id
cast_type: *1
sql_type_metadata: *2
'null': true
default:
default_function:
collation:
comment:
- *26
- *9
- *27
- *9
- *28
pins:
- *21
- *5
- *6
- *9
- *27
- *28
reactions:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -817,24 +828,24 @@ columns:
default_function:
collation:
comment:
- *27
- *28
taggings:
- *21
- *5
- *6
- *28
- *29
- *9
tags:
- *5
- *6
- *29
- *30
- *9
users:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: active
cast_type: *30
sql_type_metadata: *31
cast_type: *31
sql_type_metadata: *32
'null': false
default: true
default_function:
@@ -879,12 +890,12 @@ columns:
- *5
- *6
- *9
- *27
- *28
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: watching
cast_type: *30
sql_type_metadata: *31
cast_type: *31
sql_type_metadata: *32
'null': false
default: true
default_function:
@@ -1493,10 +1504,10 @@ indexes:
events:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: events
name: index_events_on_card_id
name: index_events_on_collection_id
unique: false
columns:
- card_id
- collection_id
lengths: {}
orders: {}
opclasses: {}
@@ -1523,6 +1534,23 @@ indexes:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: events
name: index_events_on_eventable
unique: false
columns:
- eventable_type
- eventable_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: events
name: index_events_on_summary_id_and_action
@@ -1998,4 +2026,4 @@ indexes:
comment:
valid: true
workflows: []
version: 20250423114737
version: 20250424051059
+18 -9
View File
@@ -1,13 +1,15 @@
logo_published:
creator: david
card: logo
collection: writebook
eventable: logo (Card)
action: published
summary: logo_initial_activity
created_at: <%= 1.week.ago %>
logo_assignment_jz:
creator: david
card: logo
collection: writebook
eventable: logo (Card)
action: assigned
summary: logo_initial_activity
particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }.to_json %>
@@ -15,7 +17,8 @@ logo_assignment_jz:
logo_assignment_km:
creator: david
card: logo
collection: writebook
eventable: logo (Card)
action: assigned
summary: logo_second_activity
particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:kevin) ] }.to_json %>
@@ -23,14 +26,16 @@ logo_assignment_km:
layout_published:
creator: david
card: layout
collection: writebook
eventable: layout (Card)
action: published
summary: layout_initial_activity
created_at: <%= 1.week.ago %>
layout_commented:
creator: david
card: layout
collection: writebook
eventable: layout (Card)
action: commented
summary: layout_initial_activity
particulars: <%= { comment_id: ActiveRecord::FixtureSet.identify(:layout_overflowing_david) }.to_json %>
@@ -38,7 +43,8 @@ layout_commented:
layout_assignment_jz:
creator: david
card: layout
collection: writebook
eventable: layout (Card)
action: assigned
summary: layout_initial_activity
particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }.to_json %>
@@ -46,21 +52,24 @@ layout_assignment_jz:
text_published:
creator: kevin
card: text
collection: writebook
eventable: text (Card)
action: published
summary: text_initial_activity
created_at: <%= 1.week.ago %>
shipping_published:
creator: kevin
card: shipping
collection: writebook
eventable: shipping (Card)
action: published
summary: shipping_initial_activity
created_at: <%= 1.week.ago %>
shipping_closed:
creator: kevin
card: shipping
collection: writebook
eventable: shipping (Card)
action: closed
summary: shipping_initial_activity
created_at: <%= 2.days.ago %>
+1 -1
View File
@@ -43,7 +43,7 @@ class Notifier::EventNotifierTest < ActiveSupport::TestCase
test "links to the card" do
Notifier.for(events(:logo_published)).notify
assert_equal cards(:logo), Notification.last.source.card
assert_equal cards(:logo), Notification.last.source.eventable
end
test "assignment events only create a notification for the assignee" do