Skip to content

Commit

Permalink
feat(form): add separator layout element (#69)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt <matthv@gmail.com>
  • Loading branch information
nicolasalexandre9 and matthv authored Sep 19, 2024
1 parent 693fa13 commit 5bfbd7a
Show file tree
Hide file tree
Showing 18 changed files with 940 additions and 89 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ Metrics/MethodLength:
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb'
- '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'

Metrics/BlockLength:
Exclude:
Expand Down Expand Up @@ -258,6 +259,7 @@ Metrics/ClassLength:
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/rules.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action_field_widget.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action.rb'

Style/OpenStructUse:
Exclude:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "agent-php",
"name": "agent-ruby",
"version": "1.0.0-beta.66",
"description": "The official agent Ruby for Forest.",
"private": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ def handle_request(args = {})
)

# Now that we have the field list, we can parse the data again.
data = Schema::ForestValueConverter.make_form_data(@datasource, raw_data, fields)
data = Schema::ForestValueConverter.make_form_data(
@datasource,
raw_data,
fields.reject { |field| field.type == 'Layout' }
)

{ content: @collection.execute(@caller, @action_name, data, filter_for_caller) }
end
Expand All @@ -82,7 +86,7 @@ def handle_hook_request(args = {})
search_values = {}
forest_fields&.each { |field| search_values[field['field']] = field['searchValue'] }

fields = @collection.get_form(
form = @collection.get_form(
@caller,
@action_name,
data,
Expand All @@ -94,10 +98,12 @@ def handle_hook_request(args = {})
includeHiddenFields: false
}
)
form_elements = Schema::GeneratorAction.extract_fields_and_layout(form)

{
content: {
fields: fields&.map { |field| Schema::GeneratorAction.build_field_schema(@datasource, field) } || {}
fields: Schema::GeneratorAction.build_fields(@collection, form_elements[:fields]),
layout: Schema::GeneratorAction.build_layout(form_elements[:layout])
}
}
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module ForestAdminAgent
module Utils
module Schema
class GeneratorAction
include ForestAdminDatasourceToolkit::Components

DEFAULT_FIELDS = [
{
field: 'Loading...',
Expand All @@ -23,27 +25,46 @@ def self.get_action_slug(name)
end

def self.build_schema(collection, name)
schema = collection.schema[:actions][name]
action = collection.schema[:actions][name]
action_index = collection.schema[:actions].keys.index(name)
slug = get_action_slug(name)
fields = build_fields(collection, name, schema)

{
form_elements = extract_fields_and_layout(collection.get_form(nil, name))
if action.static_form? && form_elements[:layout].empty?
# if action.static_form?
fields = build_fields(collection, form_elements[:fields])
layout = form_elements[:layout]
else
fields = DEFAULT_FIELDS
layout = nil
end

schema = {
id: "#{collection.name}-#{action_index}-#{slug}",
name: name,
type: schema.scope.downcase,
type: action.scope.downcase,
baseUrl: nil,
endpoint: "/forest/_actions/#{collection.name}/#{action_index}/#{slug}",
httpMethod: 'POST',
redirect: nil, # frontend ignores this attribute
download: schema.is_generate_file,
download: action.is_generate_file,
fields: fields,
hooks: {
load: !schema.static_form?,
load: !action.static_form?,
# Always registering the change hook has no consequences, even if we don't use it.
change: ['changeHook']
}
}

return schema unless layout && !layout.empty?

schema[:layout] = build_layout(layout)

schema
end

def self.build_layout_schema(field)
{ **field.to_h, component: field.component.downcase }
end

def self.build_field_schema(datasource, field)
Expand Down Expand Up @@ -76,26 +97,49 @@ def self.build_field_schema(datasource, field)
output
end

class << self
private

def build_fields(collection, name, action)
return DEFAULT_FIELDS unless action.static_form?
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.delete(:value)

fields = collection.get_form(nil, name)
new_field
end
end

if fields
return fields.map do |field|
new_field = build_field_schema(collection.datasource, field)
new_field[:default_value] = new_field[:value]
new_field.delete(:value)
[]
end

new_field
end
def self.build_layout(elements)
if elements
return elements.map do |element|
build_layout_schema(element)
end
end

[]
end

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
else
fields << element
# frontend rule
layout << Actions::ActionLayoutElement::InputElement.new(component: 'Input', field_id: element.label)
end
end

layout = [] unless has_real_layout

{ fields: fields, layout: layout }
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def serialize(schema)
schema[:collections].each do |collection|
collection_actions = collection[:actions]
collection_segments = collection[:segments]

collection.delete(:actions)
collection.delete(:segments)

Expand Down Expand Up @@ -94,6 +95,7 @@ def serialize(schema)

def get_smart_features_by_collection(type, data, with_attributes: false)
smart_features = []

data.each do |value|
smart_feature = { id: value[:id], type: type }
smart_feature[:attributes] = value if with_attributes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ module Action

describe 'handle_hook' do
it 'generate a clean form if called without params' do
allow(@action_collection).to receive(:get_form)
allow(@action_collection).to receive(:get_form).and_return([])
action.handle_hook_request(args)
expect(@action_collection).to have_received(:get_form) do |caller, action, data, filter, meta|
expect(caller).to be_instance_of(Components::Caller)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,86 @@ module Schema
)
end
end

describe 'with layout element' do
before do
@collection = collection_build(
schema: {
actions: {
'Send email' => BaseAction.new(
scope: Types::ActionScope::SINGLE
)
}
},
get_form: [
ActionField.new(label: 'label', type: 'String'),
ActionLayoutElement::SeparatorElement.new
]
)
end

describe 'build_schema' do
it 'generate schema correctly' do
schema = described_class.build_schema(@collection, 'Send email')

expect(schema).to eq(
{
id: 'collection-0-send-email',
name: 'Send email',
type: 'single',
baseUrl: nil,
endpoint: '/forest/_actions/collection/0/send-email',
httpMethod: 'POST',
redirect: nil,
download: false,
fields: [
{
defaultValue: 'Form is loading',
description: '',
enums: nil,
field: 'Loading...',
hook: nil,
isReadOnly: true,
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' }
# ],
hooks: { load: false, change: ['changeHook'] }
}
)
end
end

describe 'extract_fields_and_layout' do
it 'return fields and layout separately from form' do
result = described_class.extract_fields_and_layout(@collection.get_form(nil, 'Send email'))

expect(result).to have_key(:fields)
expect(result).to have_key(:layout)
expect(result[:fields]).to all(be_a(ActionField))
expect(result[:layout]).to all(be_a(ActionLayoutElement::BaseLayoutElement))
end
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def initialize(child_collection, datasource)
end

def add_action(name, action)
action.build_fields
action.build_elements
@actions[name] = action

mark_schema_as_dirty
Expand Down Expand Up @@ -51,6 +51,8 @@ def get_form(caller, name, data = nil, filter = nil, metas = {})
fields = drop_deferred(context, metas[:search_values], dynamic_fields)

fields.each do |field|
next if field.type == 'Layout'

if field.value.nil?
# customer did not define a handler to rewrite the previous value => reuse current one.
field.value = form_values[field.label]
Expand All @@ -72,13 +74,20 @@ def refine_schema(sub_schema)
private

def drop_defaults(context, fields, data)
unvalued_fields = fields.reject { |field| data.key?(field.label) }
defaults = unvalued_fields.map { |field| evaluate(context, field.default_value) }
unvalued_fields.each_with_index { |field, index| data[field.label] = defaults[index] }
fields.map do |field|
if field.type == 'Layout'
field
else
drop_default(context, field, data)
end
end
end

fields.each { |field| field.default_value = nil }
def drop_default(context, field, data)
data[field.label] = evaluate(context, field.default_value) unless data.key?(field.label)
field.default_value = nil

fields
field
end

def drop_ifs(context, fields)
Expand All @@ -105,7 +114,8 @@ def drop_deferred(context, search_values, fields)
value = field.send(key)
key = key.to_s.concat('=').to_sym

field.send(key, evaluate(context, value, search_values&.dig(field.label)))
search_value = field.type == 'Layout' ? nil : search_values&.dig(field.label)
field.send(key, evaluate(context, value, search_value))
end

new_fields << Actions::ActionFieldFactory.build(field.to_h)
Expand Down
Loading

0 comments on commit 5bfbd7a

Please sign in to comment.