Skip to content

Commit

Permalink
feat(decorator): add compute support (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthv authored Jan 24, 2024
1 parent d8266d5 commit 3345fb4
Show file tree
Hide file tree
Showing 25 changed files with 1,549 additions and 73 deletions.
6 changes: 0 additions & 6 deletions .overcommit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,3 @@ PreCommit:

BundleAudit:
enabled: true

PrePush:
RSpec:
enabled: true
command: ["run_rspec"]
quiet: false
19 changes: 9 additions & 10 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,14 @@ Metrics/MethodLength:
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/parser/validation.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb'
- 'packages/forest_admin_datasource_active_record/spec/dummy/**/*'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/computed/utils/flattener.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/field_validator.rb'

Metrics/BlockLength:
Exclude:
Expand All @@ -227,6 +229,7 @@ Metrics/ClassLength:
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/rules.rb'

Style/OpenStructUse:
Exclude:
Expand All @@ -243,22 +246,18 @@ RSpec/MultipleExpectations:
Layout/LineLength:
Max: 120
Exclude:
- 'packages/forest_admin_agent/spec/shared/caller.rb'
- 'packages/forest_admin_agent/spec/**/*'
- 'packages/forest_admin_datasource_active_record/spec/**/*'
- 'packages/forest_admin_datasource_toolkit/spec/**/*'
- 'packages/forest_admin_datasource_customizer/spec/**/*'
- 'packages/forest_admin_rails/spec/**/*'
- 'packages/forest_admin_agent/lib/forest_admin_agent/http/forest_admin_api_requester.rb'
- 'packages/forest_admin_agent/spec/lib/forest_admin_agent/http/forest_admin_api_requester_spec.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_agent/spec/lib/forest_admin_agent/routes/resources/charts/charts_spec.rb'
- 'packages/forest_admin_agent/spec/lib/forest_admin_agent/services/smart_action_checker_spec.rb'
- 'packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/context_variables_injector_spec.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb'
- 'packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb'
- 'packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons_spec.rb'
- 'packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern_spec.rb'
- 'packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times_spec.rb'
- 'packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/field_validator.rb'

RSpec/MultipleMemoizedHelpers:
Max: 10
Expand Down
22 changes: 0 additions & 22 deletions bin/run_rspec

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ def add_datasource(datasource, options = {})
self
end

def customize_collection(name, handle)
def customize_collection(name, &handle)
@customizer.customize_collection(name, handle)
end

def build
@container.register(:datasource, @customizer.datasource)
@container.register(:datasource, @customizer.datasource(@logger))
send_schema
end

def send_schema(force: false)
return unless @has_env_secret

schema = ForestAdminAgent::Utils::Schema::SchemaEmitter.get_serialized_schema(@customizer.datasource)
schema = ForestAdminAgent::Utils::Schema::SchemaEmitter.get_serialized_schema(@container.resolve(:datasource))
schema_is_know = @container.key?(:schema_file_hash) &&
@container.resolve(:schema_file_hash).get('value') == schema[:meta][:schemaFileHash]

Expand Down Expand Up @@ -72,8 +72,8 @@ def build_cache
end

def build_logger
logger = Services::LoggerService.new(@options[:loggerLevel], @options[:logger])
@container.register(:logger, logger)
@logger = Services::LoggerService.new(@options[:loggerLevel], @options[:logger])
@container.register(:logger, @logger)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ module Builder
described_class.instance.build

expect(described_class.instance.container.resolve(:datasource))
.to eq(described_class.instance.customizer.datasource)
.to eq(described_class.instance.customizer.datasource({}))
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def collection

def use(plugin, options = [])
push_customization(
-> { plugin.run(@datasource_customizer, self, options) }
proc { plugin.run(@datasource_customizer, self, options) }
)
end

Expand All @@ -30,8 +30,32 @@ def disable_count

def replace_search(definition)
push_customization(
-> { @stack.search.get_collection(@name).replace_search(definition) }
proc { @stack.search.get_collection(@name).replace_search(definition) }
)
end

def add_field(name, definition)
push_customization(
proc {
collection_before_relations = @stack.early_computed.get_collection(@name)
collection_after_relations = @stack.late_computed.get_collection(@name)
can_be_computed_before_relations = definition.dependencies.all? do |field|
!ForestAdminDatasourceToolkit::Utils::Collection.get_field_schema(collection_before_relations, field).nil?
rescue StandardError
false
end

collection = can_be_computed_before_relations ? collection_before_relations : collection_after_relations

collection.register_computed(name, definition)
}
)
end

private

def push_customization(customization)
@stack.queue_customization(customization)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ def collections
@stack.datasource.collections.transform_values { |collection| get_collection(collection.name) }
end

def datasource
# TODO: call @stack.apply_queued_customizations(logger);
def datasource(logger)
@stack.apply_queued_customizations(logger)

@stack.datasource
end
Expand All @@ -41,7 +41,7 @@ def use(plugin, options)
end

def customize_collection(name, handle)
# TODO: to implement
handle.call(get_collection(name))
end

def remove_collection(names)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
module ForestAdminDatasourceCustomizer
module Decorators
module Computed
class ComputeCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
include ForestAdminDatasourceToolkit::Components::Query
include ForestAdminDatasourceToolkit::Validations
include ForestAdminDatasourceCustomizer::Decorators::Computed::Utils
include ForestAdminDatasourceToolkit::Exceptions

def initialize(child_collection, datasource)
super
@computeds = {}
end

def get_computed(path)
index = path.index(':')
return @computeds[path] if index.nil?

foreign_collection = schema[:fields][path[0, index]].foreign_collection
association = @datasource.get_collection(foreign_collection)

association.get_computed(path[index + 1, path.length - index - 1])
end

def register_computed(name, computed)
FieldValidator.validate_name(@name, name)

# Check that all dependencies exist and are columns
computed.dependencies.each do |field|
FieldValidator.validate(self, field)
end

if computed.dependencies.length <= 0
raise ForestException,
"Computed field '#{name}' must have at least one dependency."
end

@computeds[name] = computed
mark_schema_as_dirty
end

def list(caller, filter, projection)
child_projection = projection.replace { |path| rewrite_field(self, path) }
records = @child_collection.list(caller, filter, child_projection)
return records if child_projection.equals(projection)

context = ForestAdminDatasourceCustomizer::Context::CollectionCustomizationContext.new(self, caller)

ComputedField.compute_from_records(context, self, child_projection, projection, records)
end

def aggregate(caller, filter, aggregation, limit = nil)
# No computed are used in the aggregation => just delegate to the underlying collection.
unless aggregation.projection.any? do |field|
get_computed(field)
end
return @child_collection.aggregate(caller, filter, aggregation,
limit)
end

# Fallback to full emulation.
aggregation.apply(
list(caller, filter, aggregation.projection),
caller.timezone,
limit
)
end

def refine_schema(child_schema)
schema = child_schema.clone
schema[:fields] = child_schema[:fields].clone

@computeds.each do |name, computed|
schema[:fields][name] = ForestAdminDatasourceToolkit::Schema::ColumnSchema.new(
column_type: computed.column_type,
default_value: computed.default_value,
enum_values: computed.enum_values || [],
filter_operators: [],
is_primary_key: false,
is_read_only: true,
is_sortable: false
)
end

schema
end

def rewrite_field(collection, path)
# Projection is targeting a field on another collection => recurse.
if path.include?(':')
prefix = path.split(':')[0]
schema = collection.schema[:fields][prefix]
association = collection.datasource.get_collection(schema.foreign_collection)

return Projection.new([path])
.unnest
.replace { |sub_path| rewrite_field(association, sub_path) }
.nest(prefix: prefix)
end

# Computed field that we own: recursively replace by dependencies
computed = collection.get_computed(path)

if computed
Projection.new(computed.dependencies.flatten).replace do |dep_path|
rewrite_field(collection, dep_path)
end
else
Projection.new([path])
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module ForestAdminDatasourceCustomizer
module Decorators
module Computed
class ComputedDefinition
attr_reader :column_type, :dependencies, :default_value, :enum_values

def initialize(column_type:, dependencies:, values:, default_value: nil, enum_values: nil)
@column_type = column_type
@dependencies = dependencies
@values = values
@default_value = default_value
@enum_values = enum_values
end

def get_values(*args)
@values.call(*args)
end
end
end
end
end
Loading

0 comments on commit 3345fb4

Please sign in to comment.