FIX: Discard empty bundles for reviewables (#30121)

Followup c7e471d35a

It is currently possible to add a bundle (which is a collection
of actions used for a dropdown on the client) for a reviewable
via actions.add_bundle and then never add any actions to it.

This causes the client to explode, as seen in the referenced
commit, because of the way our store expects to resolve objects
referenced by ID that are passed down by the serializer, which
then causes Ember to have an unrecoverable render error.

Fixing this on the serializer level is not really possible because
of all the ActiveModel::Serializer magic that serializes
objects by ID reference when doing things like has_many.
`Reviewable#actions_for` is a better place to do this anyway,
because this is the main location where the bundles and actions
are built for every action via the serializer.
This commit is contained in:
Martin Brennan 2024-12-05 15:41:13 +10:00 committed by GitHub
parent dd0b4e26a7
commit 8a89a77248
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 1 deletions

View File

@ -270,8 +270,15 @@ class Reviewable < ActiveRecord::Base
def actions_for(guardian, args = nil)
args ||= {}
built_actions =
Actions.new(self, guardian).tap { |actions| build_actions(actions, guardian, args) }
# Empty bundles can cause big issues on the client side, so we remove them
# here. It's not valid anyway to have a bundle with no actions, but you can
# add a bundle via actions.add_bundle and then not add any actions to it.
built_actions.bundles.reject!(&:empty?)
built_actions
end
def editable_for(guardian, args = nil)

View File

@ -30,6 +30,10 @@ class Reviewable < ActiveRecord::Base
@label = label
@actions = []
end
def empty?
@actions.empty?
end
end
class Action < Item

View File

@ -621,6 +621,45 @@ RSpec.describe Reviewable, type: :model do
end
end
describe "#actions_for" do
fab!(:reviewable) { Fabricate(:reviewable_queued_post) }
fab!(:user)
it "gets the bundles and actions for a reviewable" do
actions = reviewable.actions_for(user.guardian)
expect(actions.bundles.map(&:id)).to eq(%w[approve_post reject_post revise_and_reject_post])
expect(actions.bundles.find { |b| b.id == "approve_post" }.actions.map(&:id)).to eq(
["approve_post"],
)
expect(actions.bundles.find { |b| b.id == "reject_post" }.actions.map(&:id)).to eq(
["reject_post"],
)
expect(actions.bundles.find { |b| b.id == "revise_and_reject_post" }.actions.map(&:id)).to eq(
["revise_and_reject_post"],
)
end
describe "handling empty bundles" do
class ReviewableTestRecord < Reviewable
def build_actions(actions, guardian, args)
actions.add(:approve_post) do |action|
action.icon = "check"
action.label = "reviewables.actions.approve_post.title"
end
actions.add_bundle("empty_bundle", icon: "xmark", label: "Empty Bundle")
end
end
it "does not return empty bundles with no actions" do
actions = ReviewableTestRecord.new.actions_for(user.guardian)
expect(actions.bundles.map(&:id)).to eq(%w[approve_post])
expect(actions.bundles.find { |b| b.id == "approve_post" }.actions.map(&:id)).to eq(
["approve_post"],
)
end
end
end
describe "default actions" do
let(:reviewable) { Reviewable.new }
let(:actions) { Reviewable::Actions.new(reviewable, Guardian.new) }