Skip to content

Commit

Permalink
feat: add pages in action forms (#74)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicolas Alexandre <nicolasalexandre9@gmail.com>
  • Loading branch information
matthv and nicolasalexandre9 authored Oct 9, 2024
1 parent fdaf691 commit 2a8a7f4
Show file tree
Hide file tree
Showing 14 changed files with 732 additions and 396 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ Metrics/MethodLength:
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb'

Metrics/BlockLength:
Exclude:
Expand Down Expand Up @@ -285,6 +286,7 @@ Layout/LineLength:
- 'packages/forest_admin_agent/lib/forest_admin_agent/http/forest_admin_api_requester.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/list.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/override/context/create_override_customization_context.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/override/context/update_override_customization_context.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/override/context/delete_override_customization_context.rb'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ def self.build_schema(collection, name)
action = collection.schema[:actions][name]
action_index = collection.schema[:actions].keys.index(name)
slug = get_action_slug(name)

form_elements = extract_fields_and_layout(collection.get_form(nil, name))
if action.static_form?
fields = build_fields(collection, form_elements[:fields])
Expand Down Expand Up @@ -72,6 +71,14 @@ def self.build_layout_schema(field)
component: field.component.camelize(:lower),
fields: field.fields.map { |f| build_layout_schema(f) }
}
elsif field.component == 'Page'
return {
**field.to_h,
component: field.component.camelize(:lower),
elements: field.elements.map do |f|
build_layout_schema(f)
end
}
end

{ **field.to_h, component: field.component.camelize(:lower) }
Expand Down Expand Up @@ -135,18 +142,15 @@ def self.build_layout(elements)
def self.extract_fields_and_layout(form)
fields = []
layout = []

form&.each do |element|
if element.type == Actions::FieldType::LAYOUT
if element.component == 'Row'
extract = extract_fields_and_layout(element.fields)
element.fields = extract[:layout]
if %w[Page Row].include?(element.component)
extract = extract_fields_and_layout_for_component(element)
layout << element
fields.concat(extract[:fields])
else
layout << element
end

else
fields << element
# frontend rule
Expand All @@ -156,6 +160,13 @@ def self.extract_fields_and_layout(form)

{ fields: fields, layout: layout }
end

def self.extract_fields_and_layout_for_component(element)
key = element.component == 'Page' ? :elements : :fields
extract = extract_fields_and_layout(element.public_send(key))
element.public_send(:"#{key}=", extract[:layout])
extract
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,80 @@ module Schema
end
end

context 'with page element' do
before do
@collection = collection_build(
schema: {
actions: {
'Charge credit card' => BaseAction.new(
scope: Types::ActionScope::SINGLE
)
}
},
get_form: [
ActionLayoutElement::PageElement.new(
next_button_label: 'Next',
previous_button_label: 'Previous',
elements: [
ActionLayoutElement::HtmlBlockElement.new(content: '<h1>Charge the credit card of the customer</h1>'),
ActionLayoutElement::RowElement.new(
fields: [
ActionField.new(id: 'label', label: 'label', type: 'String'),
ActionField.new(id: 'amount', 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',
submitButtonLabel: nil,
description: nil,
type: 'single',
baseUrl: nil,
endpoint: '/forest/_actions/collection/0/charge-credit-card',
httpMethod: 'POST',
redirect: nil,
download: false,
fields: [
{
field: 'label',
label: 'label',
type: 'String',
description: nil,
isRequired: false,
isReadOnly: false,
widgetEdit: nil,
defaultValue: nil
},
{
field: 'amount',
label: 'amount',
type: 'String',
description: nil,
isRequired: false,
isReadOnly: false,
widgetEdit: nil,
defaultValue: nil
}
],
# layout: [
# { component: 'page', type: 'Layout', elements: ['htmlBlock', 'row'] }
# ],
hooks: { load: false, change: ['changeHook'] }
}
)
end
end

describe 'extract_fields_and_layout' do
before do
@collection = collection_build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def initialize(child_collection, datasource)
end

def add_action(name, action)
ensure_form_is_correct(action.form, name)
action.build_elements
action.validate_fields_ids
@actions[name] = action
Expand Down Expand Up @@ -41,15 +42,13 @@ def get_form(caller, name, data = nil, filter = nil, metas = {})
context = get_context(caller, action, form_values, filter, used, metas[:change_field])

dynamic_fields = action.form
if metas[:search_field]
# in the case of a search hook,
# we don't want to rebuild all the fields. only the one searched
dynamic_fields = dynamic_fields.select { |field| field.id == metas[:search_field] }
end
dynamic_fields = select_in_form_fields(dynamic_fields, metas[:search_field]) if metas[:search_field]
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).compact
fields = drop_deferred(context, metas[:search_values], dynamic_fields)

fields.compact

set_watch_changes_on_fields(form_values, used, fields)

Expand All @@ -64,6 +63,31 @@ def refine_schema(sub_schema)

private

def ensure_form_is_correct(form, action_name)
return if form.nil? || form.empty?

is_page_component = ->(element) { element[:type] == 'Layout' && element[:component] == 'Page' }
pages = is_page_component.call(form.first)

form.each do |element|
if pages != is_page_component.call(element)
raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
"You cannot mix pages and other form elements in smart action '#{action_name}' form"
end
end
end

def select_in_form_fields(fields, search_field)
fields.select do |field|
if nested_layout_element?(field)
key = field.component == 'Page' ? :elements : :fields
select_in_form_fields(field.public_send(:"#{key}"), search_field)
else
field.id == search_field
end
end
end

def set_watch_changes_on_fields(form_values, used, fields)
fields.each do |field|
if field.type != 'Layout'
Expand All @@ -77,14 +101,20 @@ def set_watch_changes_on_fields(form_values, used, fields)
field.watch_changes = used.include?(field.id)
elsif field.component == 'Row'
set_watch_changes_on_fields(form_values, used, field.fields)
elsif field.component == 'Page'
set_watch_changes_on_fields(form_values, used, field.elements)
end
end
end

def execute_on_sub_fields(field)
return unless field.type == 'Layout' && field.component == 'Row'
return unless nested_layout_element?(field)

field.fields = yield(field.fields)
if field.component == 'Page'
field.elements = yield(field.elements)
elsif field.component == 'Row'
field.fields = yield(field.fields)
end
end

def drop_defaults(context, fields, data)
Expand All @@ -110,10 +140,12 @@ def drop_ifs(context, fields)
if_values = fields.map do |field|
if evaluate(context, field.if_condition) == false
false
elsif field.type == 'Layout' && field.component == 'Row'
field.fields = drop_ifs(context, field.fields || [])
elsif nested_layout_element?(field)
key = field.component == 'Page' ? :elements : :fields
field.public_send(:"#{key}=", drop_ifs(context, field.public_send(:"#{key}") || []))

true unless field.public_send(:"#{key}").empty?

true unless field.fields.empty?
else
true
end
Expand Down Expand Up @@ -170,6 +202,10 @@ def get_context(caller, action, form_values = [], filter = nil, used = [], chang

Context::ActionContext.new(self, caller, form_values, filter, used, change_field)
end

def nested_layout_element?(field)
field.type == 'Layout' && %w[Page Row].include?(field.component)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,22 @@ def initialize(scope:, form: nil, is_generate_file: false, description: nil, sub
end

def build_elements
@form = @form&.map do |field|
if field.key? :widget
build_widget(field)
elsif field[:type] == 'Layout'
build_layout_element(field)
else
DynamicField.new(**field)
end
end
@form = FormFactory.build_elements(form)
end

def static_form?
return form&.all?(&:static?) && form&.none? { |field| field.type == 'Layout' } if form

true
end

def validate_fields_ids(form = @form, used = [])
form&.each do |element|
if element.type == 'Layout' && element.component == 'Row'
validate_fields_ids(element.fields, used)
if element.type == 'Layout'
if %w[Page Row].include?(element.component)
key = element.component == 'Page' ? :elements : :fields
validate_fields_ids(element.public_send(key), used)
end
else
if used.include?(element.id)
raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
Expand All @@ -38,69 +39,6 @@ def validate_fields_ids(form = @form, used = [])
end
end
end

def build_widget(field)
case field[:widget]
when 'AddressAutocomplete'
WidgetField::AddressAutocompleteField.new(**field)
when 'Checkbox'
WidgetField::CheckboxField.new(**field)
when 'CheckboxGroup'
WidgetField::CheckboxGroupField.new(**field)
when 'ColorPicker'
WidgetField::ColorPickerField.new(**field)
when 'CurrencyInput'
WidgetField::CurrencyInputField.new(**field)
when 'DatePicker'
WidgetField::DatePickerField.new(**field)
when 'Dropdown'
WidgetField::DropdownField.new(**field)
when 'FilePicker'
WidgetField::FilePickerField.new(**field)
when 'JsonEditor'
WidgetField::JsonEditorField.new(**field)
when 'NumberInput'
WidgetField::NumberInputField.new(**field)
when 'NumberInputList'
WidgetField::NumberInputListField.new(**field)
when 'RadioGroup'
WidgetField::RadioGroupField.new(**field)
when 'RichText'
WidgetField::RichTextField.new(**field)
when 'TextArea'
WidgetField::TextAreaField.new(**field)
when 'TextInput'
WidgetField::TextInputField.new(**field)
when 'TextInputList'
WidgetField::TextInputListField.new(**field)
when 'TimePicker'
WidgetField::TimePickerField.new(**field)
when 'UserDropdown'
WidgetField::UserDropdownField.new(**field)
else
raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unknow widget type: #{field[:widget]}"
end
end

def build_layout_element(field)
case field[:component]
when 'Separator'
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]}"
end
end

def static_form?
return form&.all?(&:static?) && form&.none? { |field| field.type == 'Layout' } if form

true
end
end
end
end
Expand Down
Loading

0 comments on commit 2a8a7f4

Please sign in to comment.