Merge pull request #329 from basecamp/bucket-level-workflows

Set buckets at the workflow level
This commit is contained in:
Jason Zimdars
2025-03-28 16:39:06 -05:00
committed by GitHub
13 changed files with 80 additions and 44 deletions
+2 -1
View File
@@ -33,7 +33,8 @@ class BucketsController < ApplicationController
end
def bucket_params
params.expect(bucket: [ :name, :all_access ]).with_defaults(all_access: false)
params.expect(bucket: [ :name, :all_access, :workflow_id ]).with_defaults(all_access: false)
params.require(:bucket).permit(:name, :all_access, :workflow_id)
end
def grantees
+1 -1
View File
@@ -12,7 +12,7 @@ class WorkflowsController < ApplicationController
def create
@workflow = Current.account.workflows.create! workflow_params
# FIXME: this should definitely change.
[ "Maybe?", "Not now", "Done" ].each { |name| @workflow.stages.create! name: name }
[ "Triage", "In progress", "On Hold", "Review" ].each { |name| @workflow.stages.create! name: name }
redirect_to workflows_path
end
+1 -1
View File
@@ -56,7 +56,7 @@ class Bubble < ApplicationRecord
end
def assign_initial_stage
if workflow_stage = bucket.account.workflows.first&.stages&.first
if workflow_stage = bucket.workflow&.stages&.first
self.stage = workflow_stage
save! touch: false
track_event :staged, stage_id: workflow_stage.id, stage_name: workflow_stage.name
+4 -6
View File
@@ -3,21 +3,19 @@ class Bucket < ApplicationRecord
belongs_to :account
belongs_to :creator, class_name: "User", default: -> { Current.user }
belongs_to :workflow, optional: true
has_many :bubbles, dependent: :destroy
has_many :tags, -> { distinct }, through: :bubbles
validates_presence_of :name
after_create :ensure_workflow_exists
after_save :update_bubbles_workflow, if: :saved_change_to_workflow_id?
scope :alphabetically, -> { order(name: :asc) }
private
def ensure_workflow_exists
unless account.workflows.exists?
workflow = account.workflows.create!(name: "Default Workflow")
[ "Maybe?", "Not now", "Done" ].each { |name| workflow.stages.create!(name: name) }
end
def update_bubbles_workflow
bubbles.update_all(stage_id: workflow&.stages&.first&.id)
end
end
+10 -1
View File
@@ -67,7 +67,7 @@
<% end %>
</section>
<button type="submit" id="log_in" class="btn btn--reversed center">
<button type="submit" id="log_in" class="btn btn--reversed center txt-medium">
<%= image_tag "check.svg", aria: { hidden: true }, size: 24 %>
<span class="for-screen-reader">Save</span>
</button>
@@ -75,3 +75,12 @@
<%= link_to "Cancel and go back", bubbles_path(bucket_ids: [ @bucket ]), data: { form_target: "cancel", turbo_frame: "_top" }, hidden: true %>
<% end %>
</div>
<div class="panel shadow center flex flex-column gap-half margin-block-double" style="--panel-size: 55ch;">
<strong class="txt-large">Choose a workflow</strong>
<p class="margin-none">Use a workflow to track progress in this collection.</p>
<%= form_with model: @bucket, local: true, data: { controller: "form" } do |form| %>
<%= form.select :workflow_id, Current.account.workflows.map { |w| [w.name, w.id] }, { include_blank: "Choose…" }, class: "input input--select full-width", data: { action: "change->form#submit" } %>
<% end %>
<p><%= link_to "Manage workflows", workflows_path, class: "btn btn--plain txt-link" %></p>
</div>
@@ -0,0 +1,5 @@
class AddWorkflowToBuckets < ActiveRecord::Migration[8.1]
def change
add_reference :buckets, :workflow, null: true, foreign_key: true
end
end
Generated
+4 -2
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_03_18_005138) do
ActiveRecord::Schema[8.1].define(version: 2025_03_27_191456) do
create_table "accesses", force: :cascade do |t|
t.integer "bucket_id", null: false
t.datetime "created_at", null: false
@@ -123,8 +123,10 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_18_005138) do
t.integer "creator_id", null: false
t.string "name", null: false
t.datetime "updated_at", null: false
t.integer "workflow_id"
t.index ["account_id"], name: "index_buckets_on_account_id"
t.index ["creator_id"], name: "index_buckets_on_creator_id"
t.index ["workflow_id"], name: "index_buckets_on_workflow_id"
end
create_table "buckets_filters", id: false, force: :cascade do |t|
@@ -220,7 +222,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_18_005138) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.index ["bubble_id", "user_id"], name: "index_pins_on_bubble_id_and_user_id", unique: true
t.index ["bubble_id"], name: "index_pins_on_bubble_id"
t.index ["user_id"], name: "index_pins_on_user_id"
end
@@ -324,6 +325,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_03_18_005138) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "bubbles", "workflow_stages", column: "stage_id"
add_foreign_key "buckets", "workflows"
add_foreign_key "events", "bubbles"
add_foreign_key "events", "event_summaries", column: "summary_id"
add_foreign_key "messages", "bubbles"
+27 -18
View File
@@ -519,6 +519,16 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: workflow_id
cast_type: *1
sql_type_metadata: *2
'null': true
default:
default_function:
collation:
comment:
buckets_filters:
- *18
- *21
@@ -1355,6 +1365,22 @@ indexes:
comment:
valid: true
buckets:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: buckets
name: index_buckets_on_workflow_id
unique: false
columns:
- workflow_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: buckets
name: index_buckets_on_creator_id
@@ -1710,23 +1736,6 @@ indexes:
comment:
valid: true
pins:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: pins
name: index_pins_on_bubble_id_and_user_id
unique: true
columns:
- bubble_id
- user_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: pins
name: index_pins_on_user_id
@@ -2062,4 +2071,4 @@ indexes:
nulls_not_distinct:
comment:
valid: true
version: 20250318005138
version: 20250327191456
+8 -2
View File
@@ -27,11 +27,17 @@ class BucketsControllerTest < ActionDispatch::IntegrationTest
end
test "update" do
patch bucket_url(buckets(:writebook)), params: { bucket: { name: "Writebook bugs" }, user_ids: users(:david, :jz).pluck(:id) }
patch bucket_url(buckets(:writebook)), params: {
bucket: {
name: "Writebook bugs",
all_access: false
},
user_ids: users(:david, :jz).pluck(:id)
}
assert_redirected_to bubbles_path(bucket_ids: [ buckets(:writebook) ])
assert_equal "Writebook bugs", buckets(:writebook).reload.name
assert_equal users(:david, :jz), buckets(:writebook).users
assert_equal users(:david, :jz).sort, buckets(:writebook).users.sort
assert_not buckets(:writebook).all_access?
end
+1
View File
@@ -3,3 +3,4 @@ writebook:
account: 37s
creator: david
all_access: true
workflow: qa
+10 -6
View File
@@ -1,11 +1,15 @@
qa_maybe:
name: Maybe?
qa_triage:
name: Triage
workflow: qa
qa_not_now:
name: Not now
qa_in_progress:
name: In progress
workflow: qa
qa_done:
name: Done
qa_on_hold:
name: On Hold
workflow: qa
qa_review:
name: Review
workflow: qa
+2 -2
View File
@@ -34,14 +34,14 @@ class BubbleMessagesTest < ActionDispatch::IntegrationTest
assert_equal "assigned", bubble.messages.last.messageable.events.last.action
# Stage it
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_maybe).id }
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_triage).id }
assert_equal 3, bubble.messages.count
assert_predicate bubble.messages.last, :event_summary?
assert_equal 2, bubble.messages.last.event_summary.events.count
assert_equal "staged", bubble.messages.last.messageable.events.last.action
# Unstage it
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_maybe).id }
post bucket_bubble_stagings_url(buckets(:writebook), bubble), params: { stage_id: workflow_stages(:qa_triage).id }
assert_equal 3, bubble.messages.count
assert_predicate bubble.messages.last, :event_summary?
assert_equal 3, bubble.messages.last.event_summary.events.count
+5 -4
View File
@@ -5,10 +5,11 @@ class FilterTest < ActiveSupport::TestCase
Current.set session: sessions(:david) do
@new_bucket = accounts("37s").buckets.create! name: "Inaccessible Bucket"
@new_bubble = @new_bucket.bubbles.create!
@new_bubble.update!(stage: workflow_stages(:qa_triage))
bubbles(:layout).capture Comment.new(body: "I hate haggis")
bubbles(:logo).capture Comment.new(body: "I love haggis")
bubbles(:logo).update(stage: workflow_stages(:qa_maybe))
bubbles(:logo).update(stage: workflow_stages(:qa_triage))
end
assert_not_includes users(:kevin).filters.new.bubbles, @new_bubble
@@ -19,7 +20,7 @@ class FilterTest < ActiveSupport::TestCase
filter = users(:david).filters.new creator_ids: [ users(:david).id ], tag_ids: [ tags(:mobile).id ]
assert_equal [ bubbles(:layout) ], filter.bubbles
filter = users(:david).filters.new stage_ids: [ workflow_stages(:qa_maybe).id ]
filter = users(:david).filters.new stage_ids: [ workflow_stages(:qa_triage).id ]
assert_equal [ bubbles(:logo), @new_bubble ], filter.bubbles
filter = users(:david).filters.new assignment_status: "unassigned", bucket_ids: [ @new_bucket.id ]
@@ -105,8 +106,8 @@ class FilterTest < ActiveSupport::TestCase
test "summary" do
assert_equal "Most discussed, tagged #Mobile, and assigned to JZ ", filters(:jz_assignments).summary
filters(:jz_assignments).update!(stages: workflow_stages(:qa_maybe, :qa_not_now))
assert_equal "Most discussed, tagged #Mobile, assigned to JZ, and staged in Maybe? or Not now ", filters(:jz_assignments).summary
filters(:jz_assignments).update!(stages: workflow_stages(:qa_triage, :qa_in_progress))
assert_equal "Most discussed, tagged #Mobile, assigned to JZ, and staged in Triage or In progress ", filters(:jz_assignments).summary
filters(:jz_assignments).update!(stages: [], assignees: [], tags: [], buckets: [ buckets(:writebook) ])
assert_equal "Most discussed in Writebook", filters(:jz_assignments).summary