From 10d2c622266f61b5da21ceabf999e44cdbcfb09e Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 15:54:44 +0200 Subject: [PATCH 01/13] Change flash on #update success Follow common pattern for flashes everywhere else in admin. --- admin/app/controllers/solidus_admin/products_controller.rb | 6 +----- admin/config/locales/products.en.yml | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/admin/app/controllers/solidus_admin/products_controller.rb b/admin/app/controllers/solidus_admin/products_controller.rb index 4475d11cb47..bb5822813f8 100644 --- a/admin/app/controllers/solidus_admin/products_controller.rb +++ b/admin/app/controllers/solidus_admin/products_controller.rb @@ -43,11 +43,7 @@ def update @product = Spree::Product.friendly.find(params[:id]) if @product.update(product_params) - flash[:notice] = t('spree.successfully_updated', resource: [ - Spree::Product.model_name.human, - @product.name.inspect, - ].join(' ')) - + flash[:success] = t('.success') redirect_to action: :show, status: :see_other else flash.now[:error] = @product.errors.full_messages.join(", ") diff --git a/admin/config/locales/products.en.yml b/admin/config/locales/products.en.yml index fb075351261..c69e3991b14 100644 --- a/admin/config/locales/products.en.yml +++ b/admin/config/locales/products.en.yml @@ -8,3 +8,5 @@ en: success: "Products were successfully discontinued." activate: success: "Products were successfully activated." + update: + success: "Product was successfully updated." From d5296caa6fa5bcd3d5519a662f4f2b78176a8677 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 18:10:36 +0200 Subject: [PATCH 02/13] Create product_option_types_controller.rb Responsible for sorting on backend --- .../solidus_admin/product_option_types_controller.rb | 5 +++++ admin/config/routes.rb | 1 + .../solidus_admin/product_option_types_spec.rb | 11 +++++++++++ 3 files changed, 17 insertions(+) create mode 100644 admin/app/controllers/solidus_admin/product_option_types_controller.rb create mode 100644 admin/spec/requests/solidus_admin/product_option_types_spec.rb diff --git a/admin/app/controllers/solidus_admin/product_option_types_controller.rb b/admin/app/controllers/solidus_admin/product_option_types_controller.rb new file mode 100644 index 00000000000..640ef324d5b --- /dev/null +++ b/admin/app/controllers/solidus_admin/product_option_types_controller.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class SolidusAdmin::ProductOptionTypesController < SolidusAdmin::BaseController + include SolidusAdmin::Moveable +end diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 57306812340..ffb8e94999a 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -87,4 +87,5 @@ admin_resources :roles, except: [:show] admin_resources :adjustment_reasons, except: [:show] admin_resources :store_credit_reasons, except: [:show] + admin_resources :product_option_types, only: [], sortable: true end diff --git a/admin/spec/requests/solidus_admin/product_option_types_spec.rb b/admin/spec/requests/solidus_admin/product_option_types_spec.rb new file mode 100644 index 00000000000..efbe1c8d338 --- /dev/null +++ b/admin/spec/requests/solidus_admin/product_option_types_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "spec_helper" +require "solidus_admin/testing_support/shared_examples/moveable" + +RSpec.describe "SolidusAdmin::ProductOptionTypesController", type: :request do + it_behaves_like "requests: moveable" do + let(:factory) { :product_option_type } + let(:request_path) { solidus_admin.move_product_option_type_path(record, format: :js) } + end +end From e22f34a0ce42412309b463cb50bfd39cfb0f2024 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 18:23:17 +0200 Subject: [PATCH 03/13] Reorder translations alphabetically --- .../solidus_admin/products/show/component.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/admin/app/components/solidus_admin/products/show/component.yml b/admin/app/components/solidus_admin/products/show/component.yml index 6d6d7cc1b9d..b7d0e5096ee 100644 --- a/admin/app/components/solidus_admin/products/show/component.yml +++ b/admin/app/components/solidus_admin/products/show/component.yml @@ -1,8 +1,14 @@ en: - duplicate: "Duplicate" - view: "View online" + back: "Back" delete: "Delete" delete_confirmation: "Are you sure you want to delete this product?" + duplicate: "Duplicate" + hints: + available_on_html: "Product availability starts from the set date.
Empty date indicates no availability." + discontinue_on_html: "Product availability ends from the set date.
Empty date indicates continuous availability." + promotionable_html: "Promotions can apply to this product" + shipping_category_html: "Manage Shipping in Settings" + tax_category_html: "Manage Taxes in Settings" manage_images: "Manage images" manage_properties: "Manage product specifications" manage_stock: "Manage stock" @@ -12,13 +18,9 @@ en: pricing: "Pricing" product_organization: "Product organization" publishing: "Publishing" + save: "Save" seo: "SEO" stock: "Stock" shipping: "Shipping" specifications: "Specifications" - hints: - available_on_html: "Product availability starts from the set date.
Empty date indicates no availability." - discontinue_on_html: "Product availability ends from the set date.
Empty date indicates continuous availability." - promotionable_html: "Promotions can apply to this product" - shipping_category_html: "Manage Shipping in Settings" - tax_category_html: "Manage Taxes in Settings" + view: "View online" From e29d90b8693836723c24b00dd7c38b4e909c70a3 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 18:23:33 +0200 Subject: [PATCH 04/13] Add correct label translation --- core/config/locales/en.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml index ad8164abb08..1d259f176be 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -182,6 +182,7 @@ en: meta_title: Meta Title name: Name on_hand: On Hand + option_type_ids: Option Types price: Master Price primary_taxon: Primary Taxon primary_taxon_id: Primary Taxon From efa1932803337262a28364731c55f145c7f321c7 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 18:25:18 +0200 Subject: [PATCH 05/13] Display list of product option types Add a section in Options panel to display all saved product option types along with associated option values. Make them sortable and show "Edit" button in front of each option that would direct to the edit page of respective option type. --- .../products/show/component.html.erb | 29 ++++++++++++++++++- .../solidus_admin/products/show/component.yml | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/products/show/component.html.erb b/admin/app/components/solidus_admin/products/show/component.html.erb index 36bb5bef790..9e33376be6c 100644 --- a/admin/app/components/solidus_admin/products/show/component.html.erb +++ b/admin/app/components/solidus_admin/products/show/component.html.erb @@ -76,7 +76,34 @@ ) %> <% end %> - <%= render component("ui/panel").new(title: t(".options")) do %> + <%= render component("ui/panel").new(title: t(".options")) do |panel| %> + <% if @product.product_option_types.present? %> + <% panel.with_section do %> +
+ <% @product.product_option_types.includes(option_type: :option_values).order(:position).each do |product_option| %> +
> +
+
+ <%= render component("ui/icon").new(name: "draggable", class: "w-6 h-6 cursor-grab handle fill-gray-500") %> +
+
+ <%= product_option.option_type.name %>:<%= product_option.option_type.presentation %> +
+ <% product_option.option_type.option_values.each do |value| %> + <%= render component("ui/badge").new(name: "#{value.name}:#{value.presentation}") %> + <% end %> +
+
+
+
+ <%= render component("ui/button").new(tag: :a, href: spree.edit_admin_option_type_path(product_option.option_type), scheme: :secondary, text: t(".edit")) %> +
+
+ <% end %> +
+ <% end %> + <% end %> + <%= f.select(:option_type_ids, option_type_options, multiple: true) %> <% end %> diff --git a/admin/app/components/solidus_admin/products/show/component.yml b/admin/app/components/solidus_admin/products/show/component.yml index b7d0e5096ee..efafd2b7f1e 100644 --- a/admin/app/components/solidus_admin/products/show/component.yml +++ b/admin/app/components/solidus_admin/products/show/component.yml @@ -3,6 +3,7 @@ en: delete: "Delete" delete_confirmation: "Are you sure you want to delete this product?" duplicate: "Duplicate" + edit: "Edit" hints: available_on_html: "Product availability starts from the set date.
Empty date indicates no availability." discontinue_on_html: "Product availability ends from the set date.
Empty date indicates continuous availability." From dcf7a887a972489c5002c9bf323fd5b1b2649587 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 18:27:46 +0200 Subject: [PATCH 06/13] Allow saving selected option types Adds a submit button to save selected options and a hidden field to allow removing all saved options. --- .../solidus_admin/products/show/component.html.erb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/products/show/component.html.erb b/admin/app/components/solidus_admin/products/show/component.html.erb index 9e33376be6c..879f6e14f69 100644 --- a/admin/app/components/solidus_admin/products/show/component.html.erb +++ b/admin/app/components/solidus_admin/products/show/component.html.erb @@ -104,7 +104,11 @@ <% end %> <% end %> - <%= f.select(:option_type_ids, option_type_options, multiple: true) %> +
+ <%= hidden_field_tag "#{f.object_name}[option_type_ids][]", nil %> + <%= f.select(:option_type_ids, option_type_options, multiple: true) %> + <%= render component("ui/button").new(type: :submit, text: t(".save")) %> +
<% end %> <%= render component("ui/panel").new(title: t(".specifications")) do |panel| %> From 1b8138cf835016424f491714815020444269bcfa Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 18:29:35 +0200 Subject: [PATCH 07/13] Change display text for option types in select field This way it mimics how they are displayed in the list. --- admin/app/components/solidus_admin/products/show/component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/products/show/component.rb b/admin/app/components/solidus_admin/products/show/component.rb index 66ae89250fd..9bef1292513 100644 --- a/admin/app/components/solidus_admin/products/show/component.rb +++ b/admin/app/components/solidus_admin/products/show/component.rb @@ -22,7 +22,7 @@ def taxon_options def option_type_options @option_type_options ||= Spree::OptionType.order(:presentation).pluck(:presentation, :name, :id).map do - ["#{_1} (#{_2})", _3] + ["#{_2}:#{_1}", _3] end end From 02795e3cc7f02da2557b625c8c6d90179d5c442b Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 20:29:52 +0200 Subject: [PATCH 08/13] Add panel action to options Link to option types page. --- .../solidus_admin/products/show/component.html.erb | 5 +++++ .../app/components/solidus_admin/products/show/component.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/admin/app/components/solidus_admin/products/show/component.html.erb b/admin/app/components/solidus_admin/products/show/component.html.erb index 879f6e14f69..d30146e7e5c 100644 --- a/admin/app/components/solidus_admin/products/show/component.html.erb +++ b/admin/app/components/solidus_admin/products/show/component.html.erb @@ -109,6 +109,11 @@ <%= f.select(:option_type_ids, option_type_options, multiple: true) %> <%= render component("ui/button").new(type: :submit, text: t(".save")) %> + + <% panel.with_action( + name: t(".manage_options"), + href: solidus_admin.option_types_path + ) %> <% end %> <%= render component("ui/panel").new(title: t(".specifications")) do |panel| %> diff --git a/admin/app/components/solidus_admin/products/show/component.yml b/admin/app/components/solidus_admin/products/show/component.yml index efafd2b7f1e..9d9e3855b6f 100644 --- a/admin/app/components/solidus_admin/products/show/component.yml +++ b/admin/app/components/solidus_admin/products/show/component.yml @@ -11,6 +11,7 @@ en: shipping_category_html: "Manage Shipping in Settings" tax_category_html: "Manage Taxes in Settings" manage_images: "Manage images" + manage_options: "Manage option types" manage_properties: "Manage product specifications" manage_stock: "Manage stock" media: "Media" From 1c4f59a30dd4542d5c8515bcec2640546697b8f3 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 21:45:59 +0200 Subject: [PATCH 09/13] Add feature helpers --- .../solidus_admin/testing_support/feature_helpers.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/admin/lib/solidus_admin/testing_support/feature_helpers.rb b/admin/lib/solidus_admin/testing_support/feature_helpers.rb index bf47f18d52c..f9828217100 100644 --- a/admin/lib/solidus_admin/testing_support/feature_helpers.rb +++ b/admin/lib/solidus_admin/testing_support/feature_helpers.rb @@ -33,6 +33,10 @@ def select_row(text) end end + def panel(title:) + find("section", text: title).find(:xpath, "..") + end + # Select options from a "solidus-select" field # # @param value [String, Array] which option(s) to select @@ -52,6 +56,14 @@ def solidus_select(value, from:) end end + def solidus_unselect(value, from:) + input = find_field(from, visible: :all) + Array.wrap(value).each do |val| + item = input.sibling("div", text: val) + item.find("a").click + end + end + def checkbox(locator) find(:checkbox, locator) end From 27da638d3698eb6b0f7522e8b1a4c3a0b511026b Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 22:02:04 +0200 Subject: [PATCH 10/13] Add feature specs --- admin/spec/features/product_spec.rb | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/admin/spec/features/product_spec.rb b/admin/spec/features/product_spec.rb index 396ab30a2cf..fb771bb0fec 100644 --- a/admin/spec/features/product_spec.rb +++ b/admin/spec/features/product_spec.rb @@ -52,4 +52,72 @@ expect(page).to have_content("Name can't be blank") expect(page).to be_axe_clean end + + describe "option types", :js do + before do + create(:option_type, name: "clothing-size", presentation: "Size").tap do |option_type| + option_type.option_values << [ + create(:option_value, name: "S", presentation: "Small"), + create(:option_value, name: "M", presentation: "Medium") + ] + end + + create(:option_type, name: "clothing-color", presentation: "Color").tap do |option_type| + option_type.option_values << [ + create(:option_value, name: "brown", presentation: "Brown"), + create(:option_value, name: "red", presentation: "Red") + ] + end + end + + let!(:product) { create(:product, name: "Just a product", slug: 'just-a-prod', price: 19.99) } + + it "updates option types" do + visit "/admin/products/just-a-prod" + solidus_select(%w[clothing-size:Size clothing-color:Color], from: "Option Types") + options_panel = panel(title: "Options") + # for some reason capybara on circle ci does not register a form submit when clicking "Save" within options panel, + # so we have to resort to Save button in the header + within("header") { click_on "Save" } + + expect(options_panel).to have_content("clothing-size:Size") + expect(options_panel).to have_content("S:Small") + expect(options_panel).to have_content("M:Medium") + expect(options_panel).to have_content("clothing-color:Color") + expect(options_panel).to have_content("brown:Brown") + expect(options_panel).to have_content("red:Red") + + solidus_unselect(%w[clothing-size:Size clothing-color:Color], from: "Option Types") + within(options_panel) { click_on "Save" } + + expect(options_panel).not_to have_content("clothing-size:Size") + expect(options_panel).not_to have_content("S:Small") + expect(options_panel).not_to have_content("M:Medium") + expect(options_panel).not_to have_content("clothing-color:Color") + expect(options_panel).not_to have_content("brown:Brown") + expect(options_panel).not_to have_content("red:Red") + end + + context "clicking on Edit" do + # skipping test until updated option types UI is merged + # https://github.com/solidusio/solidus/pull/6236 + xit "leads to option type edit page" do + option_type = create(:option_type) + product.option_types << option_type + visit "/admin/products/just-a-prod" + + within(panel(title: "Options")) { click_on "Edit" } + expect(page).to have_current_path("/admin/option_types/#{option_type.id}/edit") + end + end + + context "clicking on Manage option types" do + it "leads to option types index page" do + visit "/admin/products/just-a-prod" + + within(panel(title: "Options")) { click_on "Manage option types" } + expect(page).to have_current_path("/admin/option_types") + end + end + end end From 9e86efa1507c955f4d79d0719537bd6c297d66cc Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Mon, 9 Jun 2025 22:10:37 +0200 Subject: [PATCH 11/13] Fix bad copy/paste --- admin/spec/requests/solidus_admin/products_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/spec/requests/solidus_admin/products_spec.rb b/admin/spec/requests/solidus_admin/products_spec.rb index 0addacf2c72..b2d4c1fc795 100644 --- a/admin/spec/requests/solidus_admin/products_spec.rb +++ b/admin/spec/requests/solidus_admin/products_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe "SolidusAdmin::PropertiesController", type: :request do +RSpec.describe "SolidusAdmin::ProductsController", type: :request do let(:admin_user) { create(:admin_user) } before do From cc57a651c73f4a3c8fe66192151310cc00f30492 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 10 Jun 2025 15:49:19 +0200 Subject: [PATCH 12/13] Update shared examples for sorting If sortable elements have a handle we need to account for it, so that drag and drop can be performed correctly. --- .../testing_support/shared_examples/moveable.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/admin/lib/solidus_admin/testing_support/shared_examples/moveable.rb b/admin/lib/solidus_admin/testing_support/shared_examples/moveable.rb index a321ea5faa4..53fc48558c0 100644 --- a/admin/lib/solidus_admin/testing_support/shared_examples/moveable.rb +++ b/admin/lib/solidus_admin/testing_support/shared_examples/moveable.rb @@ -22,6 +22,7 @@ RSpec.shared_examples_for "features: sortable" do let(:factory_attrs) { {} } let(:scope) { "body" } + let(:handle) { nil } before do create(factory, displayed_attribute => "First", position: 1, **factory_attrs) @@ -35,7 +36,10 @@ expect(find("[data-controller='sortable']").all(:xpath, "./*").last).to have_text("Second") rows = find("[data-controller='sortable']").all(:xpath, "./*") - rows[1].drag_to rows[0] + target = rows[0] + source = rows[1] + source = source.find(handle) if handle + source.drag_to target expect(find("[data-controller='sortable']").all(:xpath, "./*").first).to have_text("Second") expect(find("[data-controller='sortable']").all(:xpath, "./*").last).to have_text("First") From bb3a05e1e444bee5de7f78b4bae2a3b75759577e Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 10 Jun 2025 15:49:46 +0200 Subject: [PATCH 13/13] Add feature tests for sorting --- admin/spec/features/product_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/admin/spec/features/product_spec.rb b/admin/spec/features/product_spec.rb index fb771bb0fec..0a2e81ad4db 100644 --- a/admin/spec/features/product_spec.rb +++ b/admin/spec/features/product_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spec_helper' +require "solidus_admin/testing_support/shared_examples/moveable" describe "Product", type: :feature do before do @@ -119,5 +120,14 @@ expect(page).to have_current_path("/admin/option_types") end end + + it_behaves_like "features: sortable" do + let(:product) { create(:product) } + let(:factory) { :option_type } + let(:factory_attrs) { { products: [product] } } + let(:displayed_attribute) { :name } + let(:handle) { ".handle" } + let(:path) { solidus_admin.product_path(product) } + end end end