From b798ecc038d6c20c7e35e9b5cd87db2dce21f69d Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 23 Sep 2024 14:56:14 +0200 Subject: [PATCH 1/8] feat(toolkit): add row element --- .../components/actions/action_field_factory.rb | 4 ++++ .../components/actions/action_layout_element.rb | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb index 1c91d60fc..5b3ea67a4 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb @@ -18,6 +18,10 @@ def self.build_layout_element(field) ActionLayoutElement::SeparatorElement.new(**field) when 'HtmlBlock' ActionLayoutElement::HtmlBlockElement.new(**field) + when 'Row' + ActionLayoutElement::RowElement.new(**field) unless field[:fields].empty? + + nil end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb index 35fd24c13..3ab0569b3 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_layout_element.rb @@ -43,6 +43,15 @@ def initialize(**options) super(component: 'Separator', **options) end end + + class RowElement < BaseLayoutElement + attr_accessor :fields + + def initialize(fields:, **options) + super(component: 'Row', **options) + @fields = fields + end + end end end end From 3223a37bf032d6c96bcdbee81bf8171c64bb8dd9 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 23 Sep 2024 14:59:15 +0200 Subject: [PATCH 2/8] feat(form): add new RowElement to LayoutElement --- .../decorators/action/form_layout_element.rb | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb index 69331c78f..b6f7be10a 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb @@ -3,13 +3,13 @@ module Decorators module Action module FormLayoutElement include Types + include ForestAdminDatasourceToolkit::Exceptions class LayoutElement < BaseFormElement attr_accessor :if_condition, :component def initialize(component:, if_condition: nil, **kwargs) super(type: 'Layout', **kwargs) - @component = component @if_condition = if_condition end @@ -29,6 +29,38 @@ def initialize(content:, **options) @content = content end end + + class RowElement < LayoutElement + attr_accessor :fields + + def initialize(options) + super(component: 'Row', **options) + validate_fields_presence!(options) + validate_no_layout_subfields!(options[:fields]) + @fields = instantiate_subfields(options[:fields] || []) + end + + private + + def validate_fields_presence!(options) + raise ForestException, "Using 'fields' in a 'Row' configuration is mandatory" unless options.key?(:fields) + end + + def validate_no_layout_subfields!(fields) + fields.each do |field| + if (field.is_a?(DynamicField) && field.type == 'Layout') || + (field.is_a?(Hash) && field[:type] == 'Layout') + raise ForestException, "A 'Row' form element doesn't allow layout elements as subfields" + end + end + end + + def instantiate_subfields(fields) + fields.map do |field| + ForestAdminDatasourceToolkit::Components::Actions::ActionFieldFactory.build(field.to_h) + end + end + end end end end From 50ecd5ff94da79397c8cfc1dca9d5c3dbaafc9a4 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 23 Sep 2024 14:59:49 +0200 Subject: [PATCH 3/8] feat(form): generate extract_field for row layout --- .../utils/schema/generator_action.rb | 16 ++++++++++------ .../action/action_collection_decorator.rb | 2 +- .../decorators/action/base_action.rb | 2 ++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb index 6ade32f08..0d7f4c6d7 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb @@ -112,7 +112,7 @@ def self.build_fields(collection, fields) end def self.build_layout(elements) - if elements + if elements.any? { |element| element[:component] != 'Input' } return elements.map do |element| build_layout_schema(element) end @@ -124,12 +124,18 @@ def self.build_layout(elements) def self.extract_fields_and_layout(form) fields = [] layout = [] - has_real_layout = false form&.each do |element| if element.type == Actions::FieldType::LAYOUT - layout << element - has_real_layout = true + if element.component == 'Row' + extract = extract_fields_and_layout(element.fields) + element.fields = extract[:layout] + layout << element + fields.concat(extract[:fields]) + else + layout << element + end + else fields << element # frontend rule @@ -137,8 +143,6 @@ def self.extract_fields_and_layout(form) end end - layout = [] unless has_real_layout - { fields: fields, layout: layout } end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb index 35b445961..f16cdc4c1 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb @@ -50,7 +50,7 @@ def get_form(caller, name, data = nil, filter = nil, metas = {}) fields = drop_deferred(context, metas[:search_values], dynamic_fields) - fields.each do |field| + fields.compact!.each do |field| next if field.type == 'Layout' if field.value.nil? diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb index 96c49dbd5..b5d6570d5 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb @@ -72,6 +72,8 @@ def build_layout_element(field) FormLayoutElement::SeparatorElement.new(**field) when 'HtmlBlock' FormLayoutElement::HtmlBlockElement.new(**field) + when 'Row' + FormLayoutElement::RowElement.new(**field) else raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unknow component type: #{field[:component]}" From c69cd769bdb55a26ee54c33feab7009c04f64fcc Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 23 Sep 2024 15:25:50 +0200 Subject: [PATCH 4/8] fix(action): if no field and build row --- .../utils/schema/generator_action.rb | 10 +++++++++- .../decorators/action/action_collection_decorator.rb | 4 ++-- .../components/actions/action_field_factory.rb | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb index 0d7f4c6d7..5f3ab4864 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb @@ -64,6 +64,14 @@ def self.build_schema(collection, name) end def self.build_layout_schema(field) + if field.component == 'Row' + return { + **field.to_h, + component: field.component.camelize(:lower), + fields: field.fields.map { |f| build_layout_schema(f) } + } + end + { **field.to_h, component: field.component.camelize(:lower) } end @@ -112,7 +120,7 @@ def self.build_fields(collection, fields) end def self.build_layout(elements) - if elements.any? { |element| element[:component] != 'Input' } + if elements.any? { |element| element.component != 'Input' } return elements.map do |element| build_layout_schema(element) end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb index f16cdc4c1..d70206579 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb @@ -48,9 +48,9 @@ def get_form(caller, name, data = nil, filter = nil, metas = {}) dynamic_fields = drop_defaults(context, dynamic_fields, form_values) dynamic_fields = drop_ifs(context, dynamic_fields) unless metas[:include_hidden_fields] - fields = drop_deferred(context, metas[:search_values], dynamic_fields) + fields = drop_deferred(context, metas[:search_values], dynamic_fields).compact - fields.compact!.each do |field| + fields.each do |field| next if field.type == 'Layout' if field.value.nil? diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb index 5b3ea67a4..0a7fd1183 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb @@ -19,7 +19,7 @@ def self.build_layout_element(field) when 'HtmlBlock' ActionLayoutElement::HtmlBlockElement.new(**field) when 'Row' - ActionLayoutElement::RowElement.new(**field) unless field[:fields].empty? + return ActionLayoutElement::RowElement.new(**field) unless field[:fields].empty? nil end From ddaeb21975f72c6b313fc0a86221b598f13663e5 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 23 Sep 2024 16:38:29 +0200 Subject: [PATCH 5/8] fix: tests & base_action --- .../utils/schema/generator_action.rb | 5 +- .../utils/schema/generator_action_spec.rb | 48 ++++--------------- .../decorators/action/base_action.rb | 2 +- 3 files changed, 11 insertions(+), 44 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb index 5f3ab4864..d9045fdde 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb @@ -30,8 +30,7 @@ def self.build_schema(collection, name) slug = get_action_slug(name) form_elements = extract_fields_and_layout(collection.get_form(nil, name)) - if action.static_form? && form_elements[:layout].empty? - # if action.static_form? + if action.static_form? fields = build_fields(collection, form_elements[:fields]) layout = form_elements[:layout] else @@ -58,7 +57,7 @@ def self.build_schema(collection, name) return schema unless layout && !layout.empty? - schema[:layout] = build_layout(layout) + # schema[:layout] = build_layout(layout) schema end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb index 5e0d1a32f..39866192d 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb @@ -234,31 +234,15 @@ module Schema download: false, fields: [ { - defaultValue: 'Form is loading', - description: '', - enums: nil, - field: 'Loading...', - hook: nil, - isReadOnly: true, + default_value: nil, + description: nil, + field: 'label', + isReadOnly: false, isRequired: false, - reference: nil, type: 'String', - value: nil, widgetEdit: nil } ], - # uncomment when back validations will be done ... - # fields: [ - # { - # default_value: nil, - # description: nil, - # field: 'label', - # isReadOnly: false, - # isRequired: false, - # type: 'String', - # widgetEdit: nil - # } - # ], # layout: [ # { component: 'input', fieldId: 'label', type: 'Layout' }, # { component: 'separator', type: 'Layout' } @@ -301,31 +285,15 @@ module Schema download: false, fields: [ { - defaultValue: 'Form is loading', - description: '', - enums: nil, - field: 'Loading...', - hook: nil, - isReadOnly: true, + default_value: nil, + description: nil, + field: 'label', + isReadOnly: false, isRequired: false, - reference: nil, type: 'String', - value: nil, widgetEdit: nil } ], - # uncomment when back validations will be done ... - # fields: [ - # { - # default_value: nil, - # description: nil, - # field: 'label', - # isReadOnly: false, - # isRequired: false, - # type: 'String', - # widgetEdit: nil - # } - # ], # layout: [ # { component: 'input', fieldId: 'label', type: 'Layout' }, # { component: 'htmlBlock', type: 'Layout', content: '

foo

' } diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb index b5d6570d5..cc5771161 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb @@ -81,7 +81,7 @@ def build_layout_element(field) end def static_form? - return form&.all?(&:static?) if form + return form&.all?(&:static?) && form&.none? { |field| field.type == 'Layout' } if form true end From 0bb9bff43d2aa7eda4fb14052118f32c36e1bedc Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 23 Sep 2024 17:35:53 +0200 Subject: [PATCH 6/8] chore: add tests --- .../utils/schema/generator_action.rb | 2 +- .../utils/schema/generator_action_spec.rb | 69 ++++++++++++++++++- .../decorators/action/base_action_spec.rb | 11 +++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb index d9045fdde..0011798a5 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb @@ -108,7 +108,7 @@ def self.build_fields(collection, fields) if fields return fields.map do |field| new_field = build_field_schema(collection.datasource, field) - new_field[:default_value] = new_field[:value] + new_field[:defaultValue] = new_field[:value] new_field.delete(:value) new_field diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb index 39866192d..d49794688 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/schema/generator_action_spec.rb @@ -74,7 +74,7 @@ module Schema httpMethod: 'POST', redirect: nil, download: false, - fields: [{ description: 'email', isRequired: true, isReadOnly: false, field: 'label', widgetEdit: nil, type: 'String', default_value: '' }], + fields: [{ description: 'email', isRequired: true, isReadOnly: false, field: 'label', widgetEdit: nil, type: 'String', defaultValue: '' }], hooks: { load: false, change: ['changeHook'] } } ) @@ -234,7 +234,7 @@ module Schema download: false, fields: [ { - default_value: nil, + defaultValue: nil, description: nil, field: 'label', isReadOnly: false, @@ -285,7 +285,7 @@ module Schema download: false, fields: [ { - default_value: nil, + defaultValue: nil, description: nil, field: 'label', isReadOnly: false, @@ -304,6 +304,69 @@ module Schema end end + context 'with row element' do + before do + @collection = collection_build( + schema: { + actions: { + 'Charge credit card' => BaseAction.new( + scope: Types::ActionScope::SINGLE + ) + } + }, + get_form: [ + ActionLayoutElement::RowElement.new( + fields: [ + ActionField.new(label: 'label', type: 'String'), + ActionField.new(label: 'amount', type: 'String') + ] + ) + ] + ) + end + + it 'generate schema correctly' do + schema = described_class.build_schema(@collection, 'Charge credit card') + + expect(schema).to eq( + { + id: 'collection-0-charge-credit-card', + name: 'Charge credit card', + type: 'single', + baseUrl: nil, + endpoint: '/forest/_actions/collection/0/charge-credit-card', + httpMethod: 'POST', + redirect: nil, + download: false, + fields: [ + { + field: 'label', + type: 'String', + description: nil, + isRequired: false, + isReadOnly: false, + widgetEdit: nil, + defaultValue: nil + }, + { + field: 'amount', + type: 'String', + description: nil, + isRequired: false, + isReadOnly: false, + widgetEdit: nil, + defaultValue: nil + } + ], + # layout: [ + # { component: 'row', type: 'Layout', fields: ['label'] } + # ], + hooks: { load: false, change: ['changeHook'] } + } + ) + end + end + describe 'extract_fields_and_layout' do before do @collection = collection_build( diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb index e9fc6aacd..d2900c587 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb @@ -225,6 +225,17 @@ module Action expect(result.content).to eq('

foo

') end end + + context 'when element is a Row' do + let(:element) { { type: 'Layout', component: 'Row', fields: [field_send_notification, field_message] } } + + it 'returns a row element' do + result = action.build_layout_element(element) + expect(result).to be_a(ForestAdminDatasourceCustomizer::Decorators::Action::FormLayoutElement::RowElement) + expect(result.fields[0].label).to eq('Send a notification') + expect(result.fields[1].label).to eq('Notification message') + end + end end describe 'when check form is static' do From 3e9c5681a9ace8f2c803349a349a0f3a776bab67 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 24 Sep 2024 09:47:33 +0200 Subject: [PATCH 7/8] fix: test --- .../decorators/action/base_action_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb index d2900c587..cf215b732 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb @@ -248,7 +248,7 @@ module Action end context 'when all fields are static' do - let(:form) { [instance_double(DynamicField, static?: true), instance_double(DynamicField, static?: true)] } + let(:form) { [instance_double(DynamicField, static?: true, type: 'String'), instance_double(DynamicField, static?: true, type: 'String')] } let(:action) { described_class.new(scope: :single, form: form) } it 'returns true' do From feedd1d36b0d521b453d2f502eb6eefd21f0051a Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 25 Sep 2024 15:02:51 +0200 Subject: [PATCH 8/8] chore: add tests --- .../decorators/action/form_layout_element.rb | 1 + .../decorators/action/base_action_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb index b6f7be10a..3fa62a401 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb @@ -31,6 +31,7 @@ def initialize(content:, **options) end class RowElement < LayoutElement + include ForestAdminDatasourceToolkit::Exceptions attr_accessor :fields def initialize(options) diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb index cf215b732..5e74903f7 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/base_action_spec.rb @@ -235,6 +235,16 @@ module Action expect(result.fields[0].label).to eq('Send a notification') expect(result.fields[1].label).to eq('Notification message') end + + it 'raises an exception when fields are missing' do + element.delete(:fields) + expect { action.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException) + end + + it 'raises an exception when fields contain a layout element' do + element[:fields] << { type: 'Layout' } + expect { action.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException) + end end end