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..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 @@ -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,12 +57,20 @@ def self.build_schema(collection, name) return schema unless layout && !layout.empty? - schema[:layout] = build_layout(layout) + # schema[:layout] = build_layout(layout) schema 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 @@ -101,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 @@ -112,7 +119,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 +131,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 +150,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_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..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,31 +234,15 @@ module Schema download: false, fields: [ { - defaultValue: 'Form is loading', - description: '', - enums: nil, - field: 'Loading...', - hook: nil, - isReadOnly: true, + defaultValue: 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, + defaultValue: 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
' } @@ -336,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/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..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,7 +48,7 @@ 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.each do |field| next if field.type == 'Layout' 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..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 @@ -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]}" @@ -79,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 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..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 @@ -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,39 @@ def initialize(content:, **options) @content = content end end + + class RowElement < LayoutElement + include ForestAdminDatasourceToolkit::Exceptions + 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 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..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 @@ -225,6 +225,27 @@ 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 + + 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 describe 'when check form is static' do @@ -237,7 +258,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 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..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 @@ -18,6 +18,10 @@ def self.build_layout_element(field) ActionLayoutElement::SeparatorElement.new(**field) when 'HtmlBlock' ActionLayoutElement::HtmlBlockElement.new(**field) + when 'Row' + return 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