From 3b706ce57a456af4d12248e4c11ad0f41caf30dd Mon Sep 17 00:00:00 2001 From: Paul Bob <69730720+Paul-Bob@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:35:19 +0300 Subject: [PATCH] feature: resource custom components (#3176) * step 1: use string as identifier for actual components * step 2: grid view * step3: tests * separate tests * resource table component * map component * add TableRowComponent * lint * lint * rename name * end rename --- .../index/resource_grid_component.html.erb | 2 +- .../avo/index/resource_map_component.html.erb | 2 +- .../avo/index/resource_table_component.rb | 2 +- .../avo/index/table_row_component.rb | 2 +- .../views/resource_index_component.html.erb | 4 +- app/controllers/avo/base_controller.rb | 16 ++- lib/avo/resources/base.rb | 13 +++ .../avo/index/field_wrapper_component_spec.rb | 12 --- .../avo/index/grid_item_component_spec.rb | 12 --- .../avo/index/resource_grid_component_spec.rb | 12 --- .../index/resource_table_component_spec.rb | 12 --- .../avo/index/table_row_component_spec.rb | 12 --- .../avo/for_test/grid_item_component.html.erb | 4 + .../avo/for_test/grid_item_component.rb | 7 ++ .../for_test/resource_edit_component.html.erb | 4 + .../avo/for_test/resource_edit_component.rb | 7 ++ .../resource_index_component.html.erb | 4 + .../avo/for_test/resource_index_component.rb | 7 ++ .../for_test/resource_map_component.html.erb | 4 + .../avo/for_test/resource_map_component.rb | 7 ++ .../for_test/resource_new_component.html.erb | 4 + .../avo/for_test/resource_new_component.rb | 7 ++ .../for_test/resource_show_component.html.erb | 4 + .../avo/for_test/resource_show_component.rb | 7 ++ .../resource_table_component.html.erb | 4 + .../avo/for_test/resource_table_component.rb | 7 ++ .../avo/for_test/table_row_component.html.erb | 4 + .../avo/for_test/table_row_component.rb | 12 +++ .../avo/resource_custom_components_spec.rb | 98 +++++++++++++++++++ 29 files changed, 216 insertions(+), 76 deletions(-) delete mode 100644 spec/components/avo/index/field_wrapper_component_spec.rb delete mode 100644 spec/components/avo/index/grid_item_component_spec.rb delete mode 100644 spec/components/avo/index/resource_grid_component_spec.rb delete mode 100644 spec/components/avo/index/resource_table_component_spec.rb delete mode 100644 spec/components/avo/index/table_row_component_spec.rb create mode 100644 spec/dummy/app/components/avo/for_test/grid_item_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/grid_item_component.rb create mode 100644 spec/dummy/app/components/avo/for_test/resource_edit_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/resource_edit_component.rb create mode 100644 spec/dummy/app/components/avo/for_test/resource_index_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/resource_index_component.rb create mode 100644 spec/dummy/app/components/avo/for_test/resource_map_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/resource_map_component.rb create mode 100644 spec/dummy/app/components/avo/for_test/resource_new_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/resource_new_component.rb create mode 100644 spec/dummy/app/components/avo/for_test/resource_show_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/resource_show_component.rb create mode 100644 spec/dummy/app/components/avo/for_test/resource_table_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/resource_table_component.rb create mode 100644 spec/dummy/app/components/avo/for_test/table_row_component.html.erb create mode 100644 spec/dummy/app/components/avo/for_test/table_row_component.rb create mode 100644 spec/features/avo/resource_custom_components_spec.rb diff --git a/app/components/avo/index/resource_grid_component.html.erb b/app/components/avo/index/resource_grid_component.html.erb index f808b51996..c0b7ce7a46 100644 --- a/app/components/avo/index/resource_grid_component.html.erb +++ b/app/components/avo/index/resource_grid_component.html.erb @@ -10,7 +10,7 @@ } do %> <% @resources.each_with_index do |resource, index| %> <% cache_if Avo.configuration.cache_resources_on_index_view, resource.cache_hash(@parent_record) do %> - <%= render(Avo::Index::GridItemComponent.new(resource: resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, actions: actions)) %> + <%= render(resource.resolve_component(Avo::Index::GridItemComponent).new(resource: resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, actions: actions)) %> <% end %> <% end %> <% end %> diff --git a/app/components/avo/index/resource_map_component.html.erb b/app/components/avo/index/resource_map_component.html.erb index 56b4cfedf9..70ef677653 100644 --- a/app/components/avo/index/resource_map_component.html.erb +++ b/app/components/avo/index/resource_map_component.html.erb @@ -5,7 +5,7 @@ <% if render_table? %>
- <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %> + <%= render(@resource.resolve_component(Avo::Index::ResourceTableComponent).new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
<% end %> diff --git a/app/components/avo/index/resource_table_component.rb b/app/components/avo/index/resource_table_component.rb index 1505378734..67ae873cf4 100644 --- a/app/components/avo/index/resource_table_component.rb +++ b/app/components/avo/index/resource_table_component.rb @@ -71,7 +71,7 @@ def generate_table_row_components header_fields.concat row_fields # Create a TableRowComponent instance for the resource and add it to @table_row_components - table_row_components << Avo::Index::TableRowComponent.new( + table_row_components << resource.resolve_component(Avo::Index::TableRowComponent).new( resource: resource, fields: row_fields, reflection: @reflection, diff --git a/app/components/avo/index/table_row_component.rb b/app/components/avo/index/table_row_component.rb index 4a07fd2285..efa9f85d26 100644 --- a/app/components/avo/index/table_row_component.rb +++ b/app/components/avo/index/table_row_component.rb @@ -11,7 +11,7 @@ class Avo::Index::TableRowComponent < Avo::BaseComponent prop :parent_resource, _Nilable(Avo::BaseResource) prop :actions, _Nilable(_Array(Avo::BaseAction)) prop :fields, _Nilable(_Array(Avo::Fields::BaseField)) - prop :header_fields, _Nilable(_Array(Avo::Fields::BaseField)) + prop :header_fields, _Nilable(_Array(String)) def resource_controls_component Avo::Index::ResourceControlsComponent.new( diff --git a/app/components/avo/views/resource_index_component.html.erb b/app/components/avo/views/resource_index_component.html.erb index 514eda9f14..612d8c54f6 100644 --- a/app/components/avo/views/resource_index_component.html.erb +++ b/app/components/avo/views/resource_index_component.html.erb @@ -59,7 +59,7 @@ <% if view_type.to_sym == :table %> <% if @resources.present? %>
- <%= render(Avo::Index::ResourceTableComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query, actions: @actions)) %> + <%= render(@resource.resolve_component(Avo::Index::ResourceTableComponent).new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query, actions: @actions)) %>
<% else %> <%= helpers.empty_state by_association: params[:related_name].present?, view_type: view_type, add_background: true %> @@ -70,7 +70,7 @@ <% if view_type.to_sym == :map %> <% if @resources.present? %>
- <%= render(Avo::Index::ResourceMapComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %> + <%= render(@resource.resolve_component(Avo::Index::ResourceMapComponent).new(resources: @resources, resource: @resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, pagy: @pagy, query: @query)) %>
<% else %> <%= helpers.empty_state by_association: params[:related_name].present?, view_type: view_type, add_background: true %> diff --git a/app/controllers/avo/base_controller.rb b/app/controllers/avo/base_controller.rb index 2a3fc63d36..3b8376b8aa 100644 --- a/app/controllers/avo/base_controller.rb +++ b/app/controllers/avo/base_controller.rb @@ -581,18 +581,14 @@ def pagy_query # Set the view component for the current view # It will try to use the custom component if it's set, otherwise it will use the default one def set_component_for(view, fallback_view: nil) - # Fetch the components from the resource - components = Avo::ExecutionContext.new( - target: @resource.components, - resource: @resource, - record: @record, - view: @view - ).handle + default_component = "Avo::Views::Resource#{(fallback_view || view).to_s.classify}Component" + + # Search for the custom component by key and by class name: + custom_component = @resource.custom_components.dig(:"resource_#{view}_component") || + @resource.custom_components.dig(default_component) # If the component is not set, use the default one - if (custom_component = components.dig(:"resource_#{view}_component")).nil? - return @component = "Avo::Views::Resource#{(fallback_view || view).to_s.classify}Component".constantize - end + return @component = default_component.constantize if custom_component.nil? # If the component is set, try to use it @component = custom_component.to_s.safe_constantize diff --git a/lib/avo/resources/base.rb b/lib/avo/resources/base.rb index cdb2a357e9..062b4dcade 100644 --- a/lib/avo/resources/base.rb +++ b/lib/avo/resources/base.rb @@ -619,6 +619,19 @@ def record_param @record_param ||= @record.persisted? ? @record.to_param : nil end + def custom_components + @custom_components ||= Avo::ExecutionContext.new( + target: components, + resource: self, + record: @record, + view: @view + ).handle.with_indifferent_access + end + + def resolve_component(original_component) + custom_components.dig(original_component.to_s)&.to_s&.safe_constantize || original_component + end + private def flatten_keys(array) diff --git a/spec/components/avo/index/field_wrapper_component_spec.rb b/spec/components/avo/index/field_wrapper_component_spec.rb deleted file mode 100644 index 0692d2ea27..0000000000 --- a/spec/components/avo/index/field_wrapper_component_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require "rails_helper" - -RSpec.describe Avo::Index::FieldWrapperComponent, type: :component do - - # it "renders something useful" do - # expect( - # render_inline(described_class.new(attr: "value")) { "Hello, components!" }.css("p").to_html - # ).to include( - # "Hello, components!" - # ) - # end -end diff --git a/spec/components/avo/index/grid_item_component_spec.rb b/spec/components/avo/index/grid_item_component_spec.rb deleted file mode 100644 index 204335c4b7..0000000000 --- a/spec/components/avo/index/grid_item_component_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require "rails_helper" - -RSpec.describe Avo::Index::GridItemComponent, type: :component do - - # it "renders something useful" do - # expect( - # render_inline(described_class.new(attr: "value")) { "Hello, components!" }.css("p").to_html - # ).to include( - # "Hello, components!" - # ) - # end -end diff --git a/spec/components/avo/index/resource_grid_component_spec.rb b/spec/components/avo/index/resource_grid_component_spec.rb deleted file mode 100644 index 601572d915..0000000000 --- a/spec/components/avo/index/resource_grid_component_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require "rails_helper" - -RSpec.describe Avo::Index::ResourceGridComponent, type: :component do - - # it "renders something useful" do - # expect( - # render_inline(described_class.new(attr: "value")) { "Hello, components!" }.css("p").to_html - # ).to include( - # "Hello, components!" - # ) - # end -end diff --git a/spec/components/avo/index/resource_table_component_spec.rb b/spec/components/avo/index/resource_table_component_spec.rb deleted file mode 100644 index 981b8a2df7..0000000000 --- a/spec/components/avo/index/resource_table_component_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require "rails_helper" - -RSpec.describe Avo::Index::ResourceTableComponent, type: :component do - - # it "renders something useful" do - # expect( - # render_inline(described_class.new(attr: "value")) { "Hello, components!" }.css("p").to_html - # ).to include( - # "Hello, components!" - # ) - # end -end diff --git a/spec/components/avo/index/table_row_component_spec.rb b/spec/components/avo/index/table_row_component_spec.rb deleted file mode 100644 index d41ad8e123..0000000000 --- a/spec/components/avo/index/table_row_component_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require "rails_helper" - -RSpec.describe Avo::Index::TableRowComponent, type: :component do - - # it "renders something useful" do - # expect( - # render_inline(described_class.new(attr: "value")) { "Hello, components!" }.css("p").to_html - # ).to include( - # "Hello, components!" - # ) - # end -end diff --git a/spec/dummy/app/components/avo/for_test/grid_item_component.html.erb b/spec/dummy/app/components/avo/for_test/grid_item_component.html.erb new file mode 100644 index 0000000000..b71565cb89 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/grid_item_component.html.erb @@ -0,0 +1,4 @@ +
+ Custom grid item component here! + <%= render(Avo::Index::GridItemComponent.new(**@args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/grid_item_component.rb b/spec/dummy/app/components/avo/for_test/grid_item_component.rb new file mode 100644 index 0000000000..e74a5c23bb --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/grid_item_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Avo::ForTest::GridItemComponent < Avo::BaseComponent + def initialize(**args) + @args = args + end +end diff --git a/spec/dummy/app/components/avo/for_test/resource_edit_component.html.erb b/spec/dummy/app/components/avo/for_test/resource_edit_component.html.erb new file mode 100644 index 0000000000..f9dcdf948c --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_edit_component.html.erb @@ -0,0 +1,4 @@ +
+ Custom edit component here! + <%= render(Avo::Views::ResourceEditComponent.new(**@args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/resource_edit_component.rb b/spec/dummy/app/components/avo/for_test/resource_edit_component.rb new file mode 100644 index 0000000000..35b0d590fd --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_edit_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Avo::ForTest::ResourceEditComponent < Avo::ResourceComponent + def initialize(**args) + @args = args + end +end diff --git a/spec/dummy/app/components/avo/for_test/resource_index_component.html.erb b/spec/dummy/app/components/avo/for_test/resource_index_component.html.erb new file mode 100644 index 0000000000..61f3334fa1 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_index_component.html.erb @@ -0,0 +1,4 @@ +
+ Custom index component here! + <%= render(Avo::Views::ResourceIndexComponent.new(**@args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/resource_index_component.rb b/spec/dummy/app/components/avo/for_test/resource_index_component.rb new file mode 100644 index 0000000000..ac7e256ea0 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_index_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Avo::ForTest::ResourceIndexComponent < Avo::ResourceComponent + def initialize(**args) + @args = args + end +end diff --git a/spec/dummy/app/components/avo/for_test/resource_map_component.html.erb b/spec/dummy/app/components/avo/for_test/resource_map_component.html.erb new file mode 100644 index 0000000000..aa97eceedf --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_map_component.html.erb @@ -0,0 +1,4 @@ +
+ Custom map component here! + <%= render(Avo::Index::ResourceMapComponent.new(**@args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/resource_map_component.rb b/spec/dummy/app/components/avo/for_test/resource_map_component.rb new file mode 100644 index 0000000000..41cc497828 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_map_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Avo::ForTest::ResourceMapComponent < Avo::BaseComponent + def initialize(**args) + @args = args + end +end diff --git a/spec/dummy/app/components/avo/for_test/resource_new_component.html.erb b/spec/dummy/app/components/avo/for_test/resource_new_component.html.erb new file mode 100644 index 0000000000..d007f01733 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_new_component.html.erb @@ -0,0 +1,4 @@ +
+ Custom new component here! + <%= render(Avo::Views::ResourceEditComponent.new(**@args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/resource_new_component.rb b/spec/dummy/app/components/avo/for_test/resource_new_component.rb new file mode 100644 index 0000000000..3fd97a9ce0 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_new_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Avo::ForTest::ResourceNewComponent < Avo::ResourceComponent + def initialize(**args) + @args = args + end +end diff --git a/spec/dummy/app/components/avo/for_test/resource_show_component.html.erb b/spec/dummy/app/components/avo/for_test/resource_show_component.html.erb new file mode 100644 index 0000000000..d19923702b --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_show_component.html.erb @@ -0,0 +1,4 @@ +
+ Custom show component here! + <%= render(Avo::Views::ResourceShowComponent.new(**@args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/resource_show_component.rb b/spec/dummy/app/components/avo/for_test/resource_show_component.rb new file mode 100644 index 0000000000..9b79a5dbb1 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_show_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Avo::ForTest::ResourceShowComponent < Avo::ResourceComponent + def initialize(**args) + @args = args + end +end diff --git a/spec/dummy/app/components/avo/for_test/resource_table_component.html.erb b/spec/dummy/app/components/avo/for_test/resource_table_component.html.erb new file mode 100644 index 0000000000..9cfd7ae14b --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_table_component.html.erb @@ -0,0 +1,4 @@ +
+ Resource table component here! + <%= render(Avo::Index::ResourceTableComponent.new(**@args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/resource_table_component.rb b/spec/dummy/app/components/avo/for_test/resource_table_component.rb new file mode 100644 index 0000000000..1430c74205 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/resource_table_component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Avo::ForTest::ResourceTableComponent < Avo::BaseComponent + def initialize(**args) + @args = args + end +end diff --git a/spec/dummy/app/components/avo/for_test/table_row_component.html.erb b/spec/dummy/app/components/avo/for_test/table_row_component.html.erb new file mode 100644 index 0000000000..431cbad743 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/table_row_component.html.erb @@ -0,0 +1,4 @@ +
+ Table row component here! + <%= render(Avo::Index::TableRowComponent.new(**args)) %> +
diff --git a/spec/dummy/app/components/avo/for_test/table_row_component.rb b/spec/dummy/app/components/avo/for_test/table_row_component.rb new file mode 100644 index 0000000000..136a63ab84 --- /dev/null +++ b/spec/dummy/app/components/avo/for_test/table_row_component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Avo::ForTest::TableRowComponent < Avo::Index::TableRowComponent + def initialize(**args) + @args = args + end + + def args + @args.merge(header_fields: @header_fields) + end +end +# TODO: document that need to inherit from < Avo::Index::TableRowComponent and need to pass @header_files... and explain why diff --git a/spec/features/avo/resource_custom_components_spec.rb b/spec/features/avo/resource_custom_components_spec.rb new file mode 100644 index 0000000000..3ba0ccdc22 --- /dev/null +++ b/spec/features/avo/resource_custom_components_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Custom components", type: :feature do + describe "symbol" do + before do + Avo::Resources::User.components = { + resource_index_component: Avo::ForTest::ResourceIndexComponent, + resource_show_component: "Avo::ForTest::ResourceShowComponent", + resource_edit_component: "Avo::ForTest::ResourceEditComponent", + resource_new_component: Avo::ForTest::ResourceNewComponent + } + end + + it "index" do + visit avo.resources_users_path + + expect(page).to have_text("Custom index component here!") + end + + it "show" do + visit avo.resources_user_path(admin) + + expect(page).to have_text("Custom show component here!") + end + + it "new" do + visit avo.new_resources_user_path + + expect(page).to have_text("Custom new component here!") + end + + it "edit" do + visit avo.edit_resources_user_path(admin) + + expect(page).to have_text("Custom edit component here!") + end + end + + describe "class" do + before do + Avo::Resources::User.components = { + "Avo::Views::ResourceIndexComponent": Avo::ForTest::ResourceIndexComponent, + "Avo::Views::ResourceShowComponent": "Avo::ForTest::ResourceShowComponent", + "Avo::Views::ResourceEditComponent": "Avo::ForTest::ResourceEditComponent", + "Avo::Index::GridItemComponent": "Avo::ForTest::GridItemComponent", + "Avo::Index::ResourceTableComponent": "Avo::ForTest::ResourceTableComponent", + "Avo::Index::TableRowComponent": "Avo::ForTest::TableRowComponent" + } + end + + it "index and table" do + visit avo.resources_users_path + + expect(page).to have_text("Custom index component here!") + expect(page).to have_text("Resource table component here!") + expect(page).to have_text("Table row component here!") + end + + it "grid item component" do + visit avo.resources_users_path(view_type: :grid) + + expect(page).to have_text("Custom grid item component here!") + end + + it "show" do + visit avo.resources_user_path(admin) + + expect(page).to have_text("Custom show component here!") + end + + # Impossible to specify for new, only for edit. + it "new" do + visit avo.new_resources_user_path + + expect(page).to have_text("Custom edit component here!") + end + + it "edit" do + visit avo.edit_resources_user_path(admin) + + expect(page).to have_text("Custom edit component here!") + end + + it "map" do + Avo::Resources::City.components = { + "Avo::Index::ResourceMapComponent": "Avo::ForTest::ResourceMapComponent" + } + + City.create! + + visit avo.resources_cities_path(view_type: :map) + + expect(page).to have_text("Custom map component here!") + end + end +end