@@ -443,6 +443,10 @@
|
||||
border: var(--bubble-border-width) solid var(--bubble-color);
|
||||
}
|
||||
|
||||
.bubble.drafted & {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.bubble:has(.bubble__link) & {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
class Bubbles::PublishesController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def create
|
||||
@bubble.publish
|
||||
redirect_to @bubble
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@ class BubblesController < ApplicationController
|
||||
before_action :set_bubble, only: %i[ show edit update destroy ]
|
||||
|
||||
def index
|
||||
@bubbles = @filter.bubbles
|
||||
@bubbles = @filter.bubbles.published_or_drafted_by(Current.user)
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
@@ -12,10 +12,10 @@ module NotificationsHelper
|
||||
def notification_body(notification)
|
||||
name = notification.creator.name
|
||||
|
||||
case notification.event.action
|
||||
case notification_event_action(notification)
|
||||
when "assigned" then "#{name} assigned to you"
|
||||
when "created" then "Added by #{name}"
|
||||
when "popped" then "Popped by by #{name}"
|
||||
when "published" then "Added by #{name}"
|
||||
else name
|
||||
end
|
||||
end
|
||||
@@ -24,4 +24,17 @@ module NotificationsHelper
|
||||
link_to notification.resource, id: dom_id(notification), class: "notification border-radius",
|
||||
data: { turbo_frame: "_top" }, &
|
||||
end
|
||||
|
||||
private
|
||||
def notification_event_action(notification)
|
||||
if notification_is_for_initial_assignement?(notification)
|
||||
"assigned"
|
||||
else
|
||||
notification.event.action
|
||||
end
|
||||
end
|
||||
|
||||
def notification_is_for_initial_assignement?(notification)
|
||||
notification.event.action == "published" && notification.bubble.assigned_to?(notification.user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Bubble < ApplicationRecord
|
||||
include Assignable, Boostable, Colored, Commentable, Eventable, Messages, Notifiable, Poppable, Searchable, Staged, Taggable
|
||||
include Assignable, Boostable, Colored, Commentable, Draftable,
|
||||
Eventable, Messages, Notifiable, Poppable, Searchable, Staged, Taggable
|
||||
|
||||
belongs_to :bucket, touch: true
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
module Bubble::Draftable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
enum :status, %w[ drafted published ].index_by(&:itself)
|
||||
|
||||
scope :published_or_drafted_by, ->(user) { where(status: :published).or(where(creator: user)) }
|
||||
end
|
||||
|
||||
def publish
|
||||
transaction do
|
||||
published!
|
||||
track_event :published
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,6 @@
|
||||
module Bubble::Eventable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
after_create -> { track_event :created, creator: creator }
|
||||
end
|
||||
|
||||
private
|
||||
def track_event(action, creator: Current.user, **particulars)
|
||||
event = find_or_capture_event_summary.events.create! action: action, creator: creator, particulars: particulars
|
||||
|
||||
@@ -17,7 +17,7 @@ class EventSummary < ApplicationRecord
|
||||
|
||||
def summarize(event)
|
||||
case event.action
|
||||
when "created"
|
||||
when "published"
|
||||
"Added by #{event.creator.name} #{time_ago_in_words(event.created_at)} ago."
|
||||
when "assigned"
|
||||
"Assigned to #{event.assignees.pluck(:name).to_sentence} #{time_ago_in_words(event.created_at)} ago."
|
||||
|
||||
@@ -10,8 +10,10 @@ class Notifier
|
||||
end
|
||||
|
||||
def generate
|
||||
recipients.map do |recipient|
|
||||
Notification.create! user: recipient, event: event, bubble: bubble, resource: resource
|
||||
if should_notify?
|
||||
recipients.map do |recipient|
|
||||
Notification.create! user: recipient, event: event, bubble: bubble, resource: resource
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,6 +22,10 @@ class Notifier
|
||||
@event = event
|
||||
end
|
||||
|
||||
def should_notify?
|
||||
bubble.published?
|
||||
end
|
||||
|
||||
def recipients
|
||||
bubble.bucket.users.without(creator)
|
||||
end
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class Notifier::Created < Notifier
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
class Notifier::Published < Notifier
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
<% cache bubble do %>
|
||||
<div class="bubble"
|
||||
<div class="<%= class_names("bubble", drafted: bubble.drafted?) %>"
|
||||
style="view-transition-name: bubble-<%= bubble.id -%>; --bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>"
|
||||
data-controller="animation upload-preview"
|
||||
data-animation-play-class="bubble--wobble"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<%= button_to bucket_bubble_publish_path(bubble.bucket, bubble), class: "btn btn--positive full-width justify-start borderless" do %>
|
||||
<%= image_tag "alert.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>Publish</span>
|
||||
<% end %>
|
||||
@@ -56,12 +56,16 @@
|
||||
<% end %>
|
||||
|
||||
<hr class="separator--horizontal full-width" style="--border-color: var(--color-subtle);" />
|
||||
<%= render "bubbles/pop_toggle", bubble: @bubble %>
|
||||
<% if @bubble.drafted? %>
|
||||
<%= render "bubbles/publish", bubble: @bubble %>
|
||||
<% else %>
|
||||
<%= render "bubbles/pop_toggle", bubble: @bubble %>
|
||||
<button class="btn full-width justify-start borderless">
|
||||
<%= image_tag "picture-double.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>I've seen this</span>
|
||||
</button>
|
||||
<% end %>
|
||||
|
||||
<button class="btn full-width justify-start borderless">
|
||||
<%= image_tag "picture-double.svg", aria: { hidden: true }, size: 24 %>
|
||||
<span>I've seen this</span>
|
||||
</button>
|
||||
|
||||
<%= button_to bucket_bubble_path(@bubble.bucket, @bubble),
|
||||
method: :delete,
|
||||
|
||||
@@ -29,6 +29,7 @@ Rails.application.routes.draw do
|
||||
scope module: :bubbles do
|
||||
resource :image
|
||||
resource :pop
|
||||
resource :publish
|
||||
resource :stage_picker
|
||||
resources :stagings
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddStatusToBubbles < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_column :bubbles, :status, :text, default: :drafted, null: false
|
||||
end
|
||||
end
|
||||
Generated
+1
@@ -106,6 +106,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_01_09_153649) do
|
||||
t.integer "stage_id"
|
||||
t.integer "comments_count", default: 0, null: false
|
||||
t.integer "activity_score", default: 0, null: false
|
||||
t.text "status", default: "drafted", null: false
|
||||
t.index ["bucket_id"], name: "index_bubbles_on_bucket_id"
|
||||
t.index ["stage_id"], name: "index_bubbles_on_stage_id"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
require "test_helper"
|
||||
|
||||
class Bubbles::PublishesControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "create" do
|
||||
bubble = bubbles(:logo)
|
||||
bubble.drafted!
|
||||
|
||||
assert_changes -> { bubble.reload.published? }, from: false, to: true do
|
||||
post bucket_bubble_publish_url(bubble.bucket, bubble)
|
||||
end
|
||||
|
||||
assert_redirected_to bubble
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,7 @@ class ReadingsControllerTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
|
||||
test "index" do
|
||||
assert_changes -> { notifications(:logo_created_kevin).reload.read? }, from: false, to: true do
|
||||
assert_changes -> { notifications(:logo_published_kevin).reload.read? }, from: false, to: true do
|
||||
post bucket_bubble_readings_url(bubbles(:logo).bucket, bubbles(:logo)), as: :turbo_stream
|
||||
end
|
||||
|
||||
|
||||
Vendored
+4
@@ -8,6 +8,7 @@ logo:
|
||||
boosts_count: 5
|
||||
comments_count: 2
|
||||
activity_score: 7
|
||||
status: published
|
||||
|
||||
layout:
|
||||
bucket: writebook
|
||||
@@ -17,6 +18,7 @@ layout:
|
||||
created_at: <%= 1.week.ago %>
|
||||
comments_count: 1
|
||||
activity_score: 1
|
||||
status: published
|
||||
|
||||
text:
|
||||
bucket: writebook
|
||||
@@ -24,6 +26,7 @@ text:
|
||||
title: The text is too small
|
||||
color: "#3B4B59"
|
||||
created_at: <%= 1.week.ago %>
|
||||
status: published
|
||||
|
||||
shipping:
|
||||
bucket: writebook
|
||||
@@ -31,3 +34,4 @@ shipping:
|
||||
title: We need to ship the app
|
||||
color: "#ED8008"
|
||||
created_at: <%= 1.week.ago %>
|
||||
status: published
|
||||
|
||||
Vendored
+8
-8
@@ -1,6 +1,6 @@
|
||||
logo_created:
|
||||
logo_published:
|
||||
creator: david
|
||||
action: created
|
||||
action: published
|
||||
summary: logo_initial_activity
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
@@ -48,9 +48,9 @@ logo_boost_jz2:
|
||||
summary: logo_third_activity
|
||||
created_at: <%= 1.hour.ago %>
|
||||
|
||||
layout_created:
|
||||
layout_published:
|
||||
creator: david
|
||||
action: created
|
||||
action: published
|
||||
summary: layout_initial_activity
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
@@ -68,15 +68,15 @@ layout_assignment_jz:
|
||||
particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }.to_json %>
|
||||
created_at: <%= 1.hour.ago %>
|
||||
|
||||
text_created:
|
||||
text_published:
|
||||
creator: kevin
|
||||
action: created
|
||||
action: published
|
||||
summary: text_initial_activity
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
shipping_created:
|
||||
shipping_published:
|
||||
creator: kevin
|
||||
action: created
|
||||
action: published
|
||||
summary: shipping_initial_activity
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,6 +1,6 @@
|
||||
logo_created_kevin:
|
||||
logo_published_kevin:
|
||||
user: kevin
|
||||
event: logo_created
|
||||
event: logo_published
|
||||
bubble: logo
|
||||
resource: logo (Bubble)
|
||||
read: false
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
require "test_helper"
|
||||
|
||||
class Bubble::DraftableTest < ActiveSupport::TestCase
|
||||
test "bubbles are only visible to the creator by default" do
|
||||
bubble = buckets(:writebook).bubbles.create! creator: users(:kevin), title: "Drafted Bubble"
|
||||
|
||||
assert bubble.drafted?
|
||||
assert_includes Bubble.published_or_drafted_by(users(:kevin)), bubble
|
||||
assert_not_includes Bubble.published_or_drafted_by(users(:jz)), bubble
|
||||
end
|
||||
|
||||
test "bubbles are visible to everyone when published" do
|
||||
bubble = buckets(:writebook).bubbles.create! creator: users(:kevin), title: "Drafted Bubble"
|
||||
bubble.published!
|
||||
|
||||
assert bubble.published?
|
||||
assert_includes Bubble.published_or_drafted_by(users(:kevin)), bubble
|
||||
assert_includes Bubble.published_or_drafted_by(users(:jz)), bubble
|
||||
end
|
||||
end
|
||||
@@ -1,14 +1,14 @@
|
||||
require "test_helper"
|
||||
|
||||
class Notifier::CreatedTest < ActiveSupport::TestCase
|
||||
class Notifier::PublishedTest < ActiveSupport::TestCase
|
||||
test "creates a notification for each recipient" do
|
||||
notifications = Notifier.for(events(:logo_created)).generate
|
||||
notifications = Notifier.for(events(:logo_published)).generate
|
||||
|
||||
assert_equal users(:kevin, :jz).sort, notifications.map(&:user).sort
|
||||
end
|
||||
|
||||
test "links to the bubble" do
|
||||
Notifier.for(events(:logo_created)).generate
|
||||
Notifier.for(events(:logo_published)).generate
|
||||
|
||||
assert_equal bubbles(:logo), Notification.last.resource
|
||||
end
|
||||
@@ -2,7 +2,7 @@ require "test_helper"
|
||||
|
||||
class NotifierTest < ActiveSupport::TestCase
|
||||
test "for returns the matching notifier class for the event" do
|
||||
assert_kind_of Notifier::Created, Notifier.for(events(:logo_created))
|
||||
assert_kind_of Notifier::Published, Notifier.for(events(:logo_published))
|
||||
end
|
||||
|
||||
test "for does not raise an error when the event is not notifiable" do
|
||||
@@ -12,4 +12,18 @@ class NotifierTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "generate creates notifications for the event recipients" do
|
||||
assert_difference -> { Notification.count }, +2 do
|
||||
Notifier.for(events(:logo_published)).generate
|
||||
end
|
||||
end
|
||||
|
||||
test "generate does not create notifications if the bubble is not published" do
|
||||
bubbles(:logo).drafted!
|
||||
|
||||
assert_no_difference -> { Notification.count } do
|
||||
Notifier.for(events(:logo_published)).generate
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user