diff --git a/Gemfile b/Gemfile index 9e2e33cbc1..aa7c02c01b 100644 --- a/Gemfile +++ b/Gemfile @@ -167,3 +167,5 @@ gem "image_processing", "~> 1.12" gem "prefixed_ids" gem "mapkick-rb", "~> 0.1.4" + +gem "turbo_power", "~> 0.5.0" diff --git a/Gemfile.lock b/Gemfile.lock index cb68a8882d..e61dec1e54 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,6 +13,7 @@ PATH meta-tags pagy turbo-rails + turbo_power (~> 0.5.0) view_component (>= 2.54.0) zeitwerk (>= 2.6.2) @@ -419,6 +420,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.5.0) + turbo-rails (~> 1.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.4.0) @@ -518,6 +521,7 @@ DEPENDENCIES sprockets-rails standard test-prof + turbo_power (~> 0.5.0) tzinfo-data web-console (>= 3.3.0) webdrivers (>= 5.3.0) diff --git a/app/controllers/avo/actions_controller.rb b/app/controllers/avo/actions_controller.rb index fb481662ad..a758f6ae49 100644 --- a/app/controllers/avo/actions_controller.rb +++ b/app/controllers/avo/actions_controller.rb @@ -79,19 +79,18 @@ def respond(response) end respond_to do |format| - format.html do + format.turbo_stream do # Flash the messages collected from the action flash_messages messages if response[:type] == :redirect - path = response[:path] - - if path.respond_to? :call - path = instance_eval(&path) - end - - redirect_to path, **{allow_other_host: response[:allow_other_host], status: response[:status]}.compact - elsif response[:type] == :reload + render turbo_stream: turbo_stream.redirect_to( + Avo::ExecutionContext.new(target: response[:path]).handle, + nil, + response[:redirect_args][:turbo_frame], + **response[:redirect_args].except(:turbo_frame) + ) + else redirect_back fallback_location: resources_path(resource: @resource) end end diff --git a/app/javascript/js/application.js b/app/javascript/js/application.js index e9d80b4043..7d0e360541 100644 --- a/app/javascript/js/application.js +++ b/app/javascript/js/application.js @@ -2,6 +2,9 @@ import 'mapkick/bundle' import { Alert, Popover } from 'tailwindcss-stimulus-components' import { Application } from '@hotwired/stimulus' +import TurboPower from 'turbo_power' + +TurboPower.initialize(Turbo.StreamActions) const application = Application.start() diff --git a/avo.gemspec b/avo.gemspec index 0e43163d4a..0efe1b2aab 100644 --- a/avo.gemspec +++ b/avo.gemspec @@ -40,6 +40,7 @@ Gem::Specification.new do |spec| spec.add_dependency "active_link_to" spec.add_dependency "view_component", ">= 2.54.0" spec.add_dependency "turbo-rails" + spec.add_dependency "turbo_power", "~> 0.5.0" spec.add_dependency "addressable" spec.add_dependency "meta-tags" spec.add_dependency "dry-initializer" diff --git a/gemfiles/rails_6.0_ruby_3.0.3.gemfile b/gemfiles/rails_6.0_ruby_3.0.3.gemfile index d3b968e464..4f38e34c8c 100644 --- a/gemfiles/rails_6.0_ruby_3.0.3.gemfile +++ b/gemfiles/rails_6.0_ruby_3.0.3.gemfile @@ -49,6 +49,7 @@ gem "sprockets-rails" gem "image_processing", "~> 1.12" gem "prefixed_ids" gem "mapkick-rb", "~> 0.1.4" +gem "turbo_power", "~> 0.5.0" group :development do gem "standard" diff --git a/gemfiles/rails_6.0_ruby_3.0.3.gemfile.lock b/gemfiles/rails_6.0_ruby_3.0.3.gemfile.lock index 96b4a31efb..08c572cb30 100644 --- a/gemfiles/rails_6.0_ruby_3.0.3.gemfile.lock +++ b/gemfiles/rails_6.0_ruby_3.0.3.gemfile.lock @@ -13,6 +13,7 @@ PATH meta-tags pagy turbo-rails + turbo_power (~> 0.5.0) view_component (>= 2.54.0) zeitwerk (>= 2.6.2) @@ -413,6 +414,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.5.0) + turbo-rails (~> 1.3) tzinfo (1.2.11) thread_safe (~> 0.1) unaccent (0.4.0) @@ -514,6 +517,7 @@ DEPENDENCIES sprockets-rails standard test-prof + turbo_power (~> 0.5.0) tzinfo-data web-console (>= 3.3.0) webdrivers (>= 5.3.0) diff --git a/gemfiles/rails_6.0_ruby_3.2.2.gemfile b/gemfiles/rails_6.0_ruby_3.2.2.gemfile index d3b968e464..4f38e34c8c 100644 --- a/gemfiles/rails_6.0_ruby_3.2.2.gemfile +++ b/gemfiles/rails_6.0_ruby_3.2.2.gemfile @@ -49,6 +49,7 @@ gem "sprockets-rails" gem "image_processing", "~> 1.12" gem "prefixed_ids" gem "mapkick-rb", "~> 0.1.4" +gem "turbo_power", "~> 0.5.0" group :development do gem "standard" diff --git a/gemfiles/rails_6.0_ruby_3.2.2.gemfile.lock b/gemfiles/rails_6.0_ruby_3.2.2.gemfile.lock index 96b4a31efb..08c572cb30 100644 --- a/gemfiles/rails_6.0_ruby_3.2.2.gemfile.lock +++ b/gemfiles/rails_6.0_ruby_3.2.2.gemfile.lock @@ -13,6 +13,7 @@ PATH meta-tags pagy turbo-rails + turbo_power (~> 0.5.0) view_component (>= 2.54.0) zeitwerk (>= 2.6.2) @@ -413,6 +414,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.5.0) + turbo-rails (~> 1.3) tzinfo (1.2.11) thread_safe (~> 0.1) unaccent (0.4.0) @@ -514,6 +517,7 @@ DEPENDENCIES sprockets-rails standard test-prof + turbo_power (~> 0.5.0) tzinfo-data web-console (>= 3.3.0) webdrivers (>= 5.3.0) diff --git a/gemfiles/rails_6.1_ruby_3.0.3.gemfile b/gemfiles/rails_6.1_ruby_3.0.3.gemfile index 9637e791de..7ca92b9847 100644 --- a/gemfiles/rails_6.1_ruby_3.0.3.gemfile +++ b/gemfiles/rails_6.1_ruby_3.0.3.gemfile @@ -49,6 +49,7 @@ gem "sprockets-rails" gem "image_processing", "~> 1.12" gem "prefixed_ids" gem "mapkick-rb", "~> 0.1.4" +gem "turbo_power", "~> 0.5.0" group :development do gem "standard" diff --git a/gemfiles/rails_6.1_ruby_3.0.3.gemfile.lock b/gemfiles/rails_6.1_ruby_3.0.3.gemfile.lock index dd9dddf7f9..fc98bc57fe 100644 --- a/gemfiles/rails_6.1_ruby_3.0.3.gemfile.lock +++ b/gemfiles/rails_6.1_ruby_3.0.3.gemfile.lock @@ -13,6 +13,7 @@ PATH meta-tags pagy turbo-rails + turbo_power (~> 0.5.0) view_component (>= 2.54.0) zeitwerk (>= 2.6.2) @@ -416,6 +417,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.5.0) + turbo-rails (~> 1.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unaccent (0.4.0) @@ -517,6 +520,7 @@ DEPENDENCIES sprockets-rails standard test-prof + turbo_power (~> 0.5.0) tzinfo-data web-console (>= 3.3.0) webdrivers (>= 5.3.0) diff --git a/gemfiles/rails_6.1_ruby_3.2.2.gemfile b/gemfiles/rails_6.1_ruby_3.2.2.gemfile index 9637e791de..7ca92b9847 100644 --- a/gemfiles/rails_6.1_ruby_3.2.2.gemfile +++ b/gemfiles/rails_6.1_ruby_3.2.2.gemfile @@ -49,6 +49,7 @@ gem "sprockets-rails" gem "image_processing", "~> 1.12" gem "prefixed_ids" gem "mapkick-rb", "~> 0.1.4" +gem "turbo_power", "~> 0.5.0" group :development do gem "standard" diff --git a/gemfiles/rails_6.1_ruby_3.2.2.gemfile.lock b/gemfiles/rails_6.1_ruby_3.2.2.gemfile.lock index dd9dddf7f9..fc98bc57fe 100644 --- a/gemfiles/rails_6.1_ruby_3.2.2.gemfile.lock +++ b/gemfiles/rails_6.1_ruby_3.2.2.gemfile.lock @@ -13,6 +13,7 @@ PATH meta-tags pagy turbo-rails + turbo_power (~> 0.5.0) view_component (>= 2.54.0) zeitwerk (>= 2.6.2) @@ -416,6 +417,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.5.0) + turbo-rails (~> 1.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unaccent (0.4.0) @@ -517,6 +520,7 @@ DEPENDENCIES sprockets-rails standard test-prof + turbo_power (~> 0.5.0) tzinfo-data web-console (>= 3.3.0) webdrivers (>= 5.3.0) diff --git a/gemfiles/rails_7.0_ruby_3.0.3.gemfile b/gemfiles/rails_7.0_ruby_3.0.3.gemfile index b9bbc43044..4bbf8cba44 100644 --- a/gemfiles/rails_7.0_ruby_3.0.3.gemfile +++ b/gemfiles/rails_7.0_ruby_3.0.3.gemfile @@ -49,6 +49,7 @@ gem "sprockets-rails" gem "image_processing", "~> 1.12" gem "prefixed_ids" gem "mapkick-rb", "~> 0.1.4" +gem "turbo_power", "~> 0.5.0" group :development do gem "standard" diff --git a/gemfiles/rails_7.0_ruby_3.0.3.gemfile.lock b/gemfiles/rails_7.0_ruby_3.0.3.gemfile.lock index 4223f4174d..fff634f615 100644 --- a/gemfiles/rails_7.0_ruby_3.0.3.gemfile.lock +++ b/gemfiles/rails_7.0_ruby_3.0.3.gemfile.lock @@ -13,6 +13,7 @@ PATH meta-tags pagy turbo-rails + turbo_power (~> 0.5.0) view_component (>= 2.54.0) zeitwerk (>= 2.6.2) @@ -422,6 +423,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.5.0) + turbo-rails (~> 1.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unaccent (0.4.0) @@ -523,6 +526,7 @@ DEPENDENCIES sprockets-rails standard test-prof + turbo_power (~> 0.5.0) tzinfo-data web-console (>= 3.3.0) webdrivers (>= 5.3.0) diff --git a/gemfiles/rails_7.0_ruby_3.2.2.gemfile b/gemfiles/rails_7.0_ruby_3.2.2.gemfile index b9bbc43044..4bbf8cba44 100644 --- a/gemfiles/rails_7.0_ruby_3.2.2.gemfile +++ b/gemfiles/rails_7.0_ruby_3.2.2.gemfile @@ -49,6 +49,7 @@ gem "sprockets-rails" gem "image_processing", "~> 1.12" gem "prefixed_ids" gem "mapkick-rb", "~> 0.1.4" +gem "turbo_power", "~> 0.5.0" group :development do gem "standard" diff --git a/gemfiles/rails_7.0_ruby_3.2.2.gemfile.lock b/gemfiles/rails_7.0_ruby_3.2.2.gemfile.lock index 4223f4174d..fff634f615 100644 --- a/gemfiles/rails_7.0_ruby_3.2.2.gemfile.lock +++ b/gemfiles/rails_7.0_ruby_3.2.2.gemfile.lock @@ -13,6 +13,7 @@ PATH meta-tags pagy turbo-rails + turbo_power (~> 0.5.0) view_component (>= 2.54.0) zeitwerk (>= 2.6.2) @@ -422,6 +423,8 @@ GEM actionpack (>= 6.0.0) activejob (>= 6.0.0) railties (>= 6.0.0) + turbo_power (0.5.0) + turbo-rails (~> 1.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unaccent (0.4.0) @@ -523,6 +526,7 @@ DEPENDENCIES sprockets-rails standard test-prof + turbo_power (~> 0.5.0) tzinfo-data web-console (>= 3.3.0) webdrivers (>= 5.3.0) diff --git a/lib/avo/base_action.rb b/lib/avo/base_action.rb index f85c659a23..e170796943 100644 --- a/lib/avo/base_action.rb +++ b/lib/avo/base_action.rb @@ -197,10 +197,9 @@ def silent self end - def redirect_to(path = nil, allow_other_host: nil, status: nil, &block) + def redirect_to(path = nil, **args, &block) response[:type] = :redirect - response[:allow_other_host] = allow_other_host - response[:status] = status + response[:redirect_args] = args response[:path] = if block.present? block else diff --git a/package.json b/package.json index eb6ae4d8d0..ba26d4d68c 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "tailwindcss-stimulus-components": "^3.0.3", "tippy.js": "^6.3.0", "trix": "^2.0.5", + "turbo_power": "^0.5.0", "urijs": "^1.19.11" }, "devDependencies": { diff --git a/spec/dummy/app/avo/actions/pre_update.rb b/spec/dummy/app/avo/actions/pre_update.rb new file mode 100644 index 0000000000..058f467ea5 --- /dev/null +++ b/spec/dummy/app/avo/actions/pre_update.rb @@ -0,0 +1,27 @@ +class PreUpdate < Avo::BaseAction + self.name = "Update" + self.message = "Set the fields you want to update." + + with_options as: :boolean do + field :first_name + field :last_name + field :user_email + field :active + field :admin + end + + def handle(**args) + arguments = Base64.encode64 Avo::Services::EncryptionService.encrypt( + message: { + render_first_name: args[:fields][:first_name], + render_last_name: args[:fields][:last_name], + render_user_email: args[:fields][:user_email], + render_active: args[:fields][:active], + render_admin: args[:fields][:admin] + }, + purpose: :action_arguments + ) + + redirect_to "/admin/resources/users/actions?action_id=Update&arguments=#{arguments}", turbo_frame: "actions_show" + end +end diff --git a/spec/dummy/app/avo/actions/update.rb b/spec/dummy/app/avo/actions/update.rb new file mode 100644 index 0000000000..299d26589d --- /dev/null +++ b/spec/dummy/app/avo/actions/update.rb @@ -0,0 +1,34 @@ +class Update < Avo::BaseAction + self.name = "Update" + self.message = "" + self.visible = -> do + false + end + + { + first_name: :text, + last_name: :text, + user_email: :text, + active: :boolean, + admin: :boolean + }.each do |field_name, field_type| + field field_name.to_sym, as: field_type, visible: -> (resource:) { + Avo::Services::EncryptionService.decrypt( + message: Base64.decode64(resource.params[:arguments]), + purpose: :action_arguments + ).dig("render_#{field_name}".to_sym) + } + end + + def handle(models:, fields:, **args) + non_roles_fields = fields.slice!(:admin) + + models.each { |model| model.update!(non_roles_fields) } + + fields.each do |field_name, field_value| + models.each { |model| model.update! roles: model.roles.merge!({"#{field_name}": field_value}) } + end + + succeed "User(s) updated!" + end +end diff --git a/spec/dummy/app/avo/resources/user_resource.rb b/spec/dummy/app/avo/resources/user_resource.rb index ed93c55f97..a4e21b5f9c 100644 --- a/spec/dummy/app/avo/resources/user_resource.rb +++ b/spec/dummy/app/avo/resources/user_resource.rb @@ -144,6 +144,8 @@ class UserResource < Avo::BaseResource action ToggleAdmin action Sub::DummyAction action DownloadFile + action PreUpdate + action Update filter UserNamesFilter filter IsAdmin diff --git a/spec/features/avo/action_filter_spec.rb b/spec/system/avo/action_filter_spec.rb similarity index 96% rename from spec/features/avo/action_filter_spec.rb rename to spec/system/avo/action_filter_spec.rb index 26e595098a..69d126e2e0 100644 --- a/spec/features/avo/action_filter_spec.rb +++ b/spec/system/avo/action_filter_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.feature "Actions", type: :feature do +RSpec.feature "Actions", type: :system do it "shows the different types of alerts" do visit "/admin/resources/users" diff --git a/spec/system/avo/multiple_actions_flux_spec.rb b/spec/system/avo/multiple_actions_flux_spec.rb new file mode 100644 index 0000000000..c3c4da71c6 --- /dev/null +++ b/spec/system/avo/multiple_actions_flux_spec.rb @@ -0,0 +1,62 @@ +require "rails_helper" + +RSpec.describe "Multiple Actions Flux", type: :system do + let!(:user) { create :user, first_name: "Spec", last_name: "User", active: false } + + describe "multiple actions flux" do + context "index" do + it "present the first action and render the second one with data from arguments" do + visit "/admin/resources/users" + + within("tr[data-resource-name=\"users\"][data-resource-id=\"#{user.id}\"][data-controller=\"item-selector\"]") do + find(:css, 'input[type="checkbox"][data-action="input->item-selector#toggle input->item-select-all#selectRow"]', match: :first).set(true) + end + + click_on "Actions" + within("[data-toggle-panel-target='panel']") do + click_on "Update" + end + + within(find("[role='dialog']")) do + expect(page).to have_text "FIRST NAME" + expect(page).to have_text "LAST NAME" + expect(page).to have_text "USER EMAIL" + expect(page).to have_text "ACTIVE" + expect(page).to have_text "ADMIN" + end + + check("First name") + check("Last name") + check("Admin") + + within(find("[role='dialog']")) do + click_on "Run" + expect(page).to have_text "FIRST NAME" + expect(page).to have_text "LAST NAME" + expect(page).not_to have_text "USER EMAIL" + expect(page).not_to have_text "ACTIVE" + expect(page).to have_text "ADMIN" + end + + expect(page).to have_text "FIRST NAME" + expect(page).to have_text "LAST NAME" + expect(page).to have_text "USER EMAIL" + expect(page).to have_text "ACTIVE" + expect(page).to have_text "ADMIN" + + fill_in "fields_first_name", with: "1" + fill_in "fields_last_name", with: "2" + check "Admin" + + click_on "Run" + + sleep 0.1 + + expect(page).to have_text "User(s) updated!" + expect(user.reload.first_name).to eq "1" + expect(user.last_name).to eq "2" + expect(user.is_admin?).to eq true + end + end + end +end diff --git a/yarn.lock b/yarn.lock index 6cc506083d..fa1f954378 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3275,6 +3275,11 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +turbo_power@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/turbo_power/-/turbo_power-0.5.0.tgz#3c9e68f49a8b701be195893b7d335633087287c5" + integrity sha512-IzCjy+pL5ohwJf26d4XTxlOp2gHLxhSyifRsysZeare7qjxnlHTuBUoeAx98+PlyKBMDYGQeitu+EFDwqxXVfw== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"