From e55d55f6678d0c5e6d212ae4b0587a46b29ec06b Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Thu, 11 Apr 2024 13:14:07 +0200 Subject: [PATCH 1/5] feat(segment): add collection decorator --- .../segment/segment_collection_decorator.rb | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb new file mode 100644 index 000000000..4ab3dfbc8 --- /dev/null +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb @@ -0,0 +1,57 @@ +module ForestAdminDatasourceCustomizer + module Decorators + module Segment + class SegmentCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator + include ForestAdminDatasourceToolkit::Decorators + include ForestAdminDatasourceToolkit::Components::Query::ConditionTree + + def initialize(child_collection, datasource) + super + @segments = {} + end + + def add_segment(name, definition) + @segments[name] = definition + + mark_schema_as_dirty + end + + def refine_schema(sub_schema) + sub_schema[:segments] = sub_schema[:segments].merge(@segments) + + sub_schema + end + + def refine_filter(caller, filter = nil) + return nil unless filter + + condition_tree = filter.condition_tree + segment = filter.segment + + if segment && @segments.key?(segment) + definition = @segments[segment] + + result = if definition.respond_to? :call + definition.call(Context::CollectionCustomizationContext.new(self, caller)) + else + definition + end + + condition_tree_segment = if result.is_a? Nodes::ConditionTree + result + else + ConditionTreeFactory.from_plain_object(result) + end + + Validation::ConditionTreeValidator.validate(condition_tree_segment, self) + + condition_tree = ConditionTreeFactory.intersect([condition_tree_segment, filter.condition_tree]) + segment = nil + end + + filter.override(condition_tree: condition_tree, segment: segment) + end + end + end + end +end From 515c553e484b852b01a1a335743309b35c34003e Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Fri, 12 Apr 2024 12:25:12 +0200 Subject: [PATCH 2/5] feat(segment): add tests on segment collection decorator --- .../forest_admin_agent/spec/shared/factory.rb | 3 +- .../segment/segment_collection_decorator.rb | 3 +- .../segment_collection_decorator_spec.rb | 99 +++++++++++++++++++ .../spec/shared/factory.rb | 8 +- .../collection.rb | 6 +- .../spec/shared/factory.rb | 13 ++- 6 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb diff --git a/packages/forest_admin_agent/spec/shared/factory.rb b/packages/forest_admin_agent/spec/shared/factory.rb index 285ade318..96d58ba1a 100644 --- a/packages/forest_admin_agent/spec/shared/factory.rb +++ b/packages/forest_admin_agent/spec/shared/factory.rb @@ -33,7 +33,8 @@ def collection_build(args = {}) charts: [], fields: {}, countable: false, - searchable: false + searchable: false, + segments: {} }.merge(args[:schema]), execute: nil, get_form: nil, diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb index 4ab3dfbc8..69e85805c 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb @@ -3,6 +3,7 @@ module Decorators module Segment class SegmentCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator include ForestAdminDatasourceToolkit::Decorators + include ForestAdminDatasourceToolkit::Validations include ForestAdminDatasourceToolkit::Components::Query::ConditionTree def initialize(child_collection, datasource) @@ -43,7 +44,7 @@ def refine_filter(caller, filter = nil) ConditionTreeFactory.from_plain_object(result) end - Validation::ConditionTreeValidator.validate(condition_tree_segment, self) + ConditionTreeValidator.validate(condition_tree_segment, self) condition_tree = ConditionTreeFactory.intersect([condition_tree_segment, filter.condition_tree]) segment = nil diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb new file mode 100644 index 000000000..b5a3ca224 --- /dev/null +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +module ForestAdminDatasourceCustomizer + module Decorators + module Segment + include ForestAdminDatasourceToolkit + include ForestAdminDatasourceToolkit::Components::Query + include ForestAdminDatasourceToolkit::Components::Query::ConditionTree + include ForestAdminDatasourceToolkit::Decorators + include ForestAdminDatasourceToolkit::Schema + + describe SegmentCollectionDecorator do + before do + datasource = Datasource.new + @collection = collection_build( + name: 'book', + schema: { + fields: { + 'name' => column_build({ filter_operators: [Operators::EQUAL, Operators::IN] }) + } + } + ) + datasource.add_collection(@collection) + + @decorated_datasource = DatasourceDecorator.new(datasource, described_class) + @decorated_collection = @decorated_datasource.get_collection('book') + end + + context 'when there is no filter' do + describe 'refine_filter' do + it 'return nil' do + condition_tree_generator = instance_double(Proc, call: nil) + @decorated_collection.add_segment('segment_name', condition_tree_generator) + + filter = @decorated_collection.refine_filter(caller) + + expect(filter).to be_nil + expect(condition_tree_generator).not_to have_received(:call) + end + end + end + + context 'when there is a filter' do + context 'when the segment is not managed by this decorator' do + describe 'refine_filter' do + it 'return the given filter' do + condition_tree_generator = instance_double(Proc, call: nil) + @decorated_collection.add_segment('segment_name', condition_tree_generator) + + a_filter = Filter.new(segment: 'a_segment') + filter = @decorated_collection.refine_filter(caller, a_filter) + + expect(filter.to_h).to eq(a_filter.to_h) + expect(condition_tree_generator).not_to have_received(:call) + end + end + end + + context 'when the segment is managed by this decorator' do + describe 'refine_filter' do + it 'return the filter with the merged conditionTree' do + condition_tree_generator = Nodes::ConditionTreeLeaf.new('name', Operators::EQUAL, 'foo') + @decorated_collection.add_segment('segment_name', condition_tree_generator) + a_filter = Filter.new( + segment: 'segment_name', + condition_tree: Nodes::ConditionTreeLeaf.new('name', Operators::EQUAL, 'other_value') + ) + filter = @decorated_collection.refine_filter(caller, a_filter) + + expect(filter.segment).to be_nil + expect(filter.condition_tree.to_h).to eq( + { + aggregator: 'And', + conditions: [ + { field: 'name', operator: Operators::EQUAL, value: 'foo' }, + { field: 'name', operator: Operators::EQUAL, value: 'other_value' } + ] + } + ) + end + + it 'raise an error when a conditionTree is not valid' do + condition_tree_generator = instance_double( + Proc, + call: Nodes::ConditionTreeLeaf.new('do not exists', Operators::EQUAL, 'foo') + ) + @decorated_collection.add_segment('segment_name', condition_tree_generator) + + expect do + @decorated_collection.refine_filter(caller, Filter.new(segment: 'segment_name')) + end.to raise_error(Exceptions::ForestException, '🌳🌳🌳 Column not found book.do not exists') + end + end + end + end + end + end + end +end diff --git a/packages/forest_admin_datasource_customizer/spec/shared/factory.rb b/packages/forest_admin_datasource_customizer/spec/shared/factory.rb index 27bfed142..e7ca61483 100644 --- a/packages/forest_admin_datasource_customizer/spec/shared/factory.rb +++ b/packages/forest_admin_datasource_customizer/spec/shared/factory.rb @@ -30,10 +30,12 @@ def collection_build(args = {}) datasource: ForestAdminDatasourceToolkit::Datasource.new, name: 'collection', schema: { + charts: [], fields: {}, countable: false, - searchable: false - }, + searchable: false, + segments: {} + }.merge(args[:schema]), execute: nil, get_form: nil, render_chart: nil, @@ -42,7 +44,7 @@ def collection_build(args = {}) update: nil, delete: nil, aggregate: nil, - **args + **args.except(:schema) } ) end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb index 9c33ebc55..d4c9ab1bb 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb @@ -1,7 +1,5 @@ module ForestAdminDatasourceToolkit class Collection < Components::Contracts::CollectionContract - attr_accessor :segments - attr_reader :actions, :datasource, :name, @@ -17,10 +15,10 @@ def initialize(datasource, name, native_driver: nil) fields: {}, countable: false, searchable: false, - charts: [] + charts: [], + segments: {} } @actions = {} - @segments = {} end def enable_count diff --git a/packages/forest_admin_datasource_toolkit/spec/shared/factory.rb b/packages/forest_admin_datasource_toolkit/spec/shared/factory.rb index 5b3cba179..3e5b357fe 100644 --- a/packages/forest_admin_datasource_toolkit/spec/shared/factory.rb +++ b/packages/forest_admin_datasource_toolkit/spec/shared/factory.rb @@ -9,17 +9,19 @@ def datasource_with_collections_build(collections) datasource end - def collection_build(args) + def collection_build(args = {}) instance_double( ForestAdminDatasourceToolkit::Collection, { datasource: ForestAdminDatasourceToolkit::Datasource.new, name: 'collection', schema: { + charts: [], fields: {}, countable: false, - searchable: false - }, + searchable: false, + segments: {} + }.merge(args[:schema]), execute: nil, get_form: nil, render_chart: nil, @@ -27,8 +29,9 @@ def collection_build(args) list: nil, update: nil, delete: nil, - aggregate: nil - }.merge(args) + aggregate: nil, + **args.except!(:schema) + } ) end end From fb9e3d5aa98945013eb0ffc60d6f8e9ebfaf8572 Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Fri, 12 Apr 2024 14:59:06 +0200 Subject: [PATCH 3/5] feat(segment): add segment decorator to the stack --- .../collection_customizer.rb | 12 ++++++++++++ .../decorators/decorators_stack.rb | 3 ++- .../segment/segment_collection_decorator.rb | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/collection_customizer.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/collection_customizer.rb index 7fd0c02c9..ab9dfef0f 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/collection_customizer.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/collection_customizer.rb @@ -238,6 +238,18 @@ def add_hook(position, type, &handler) push_customization { @stack.hook.get_collection(@name).add_hook(position, type, handler) } end + # Add a new segment on the collection. + # @param name the name of the segment + # @param definition a function used to generate a condition tree or a condition tree + # @example + # .add_segment( + # 'Wrote more than 2 books', + # { field: 'booksCount', operator: 'GreaterThan', value: 2 } + # ); + def add_segment(name, definition) + push_customization { @stack.segment.get_collection(@name).add_segment(name, definition) } + end + private def push_customization(&customization) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb index a5fee5fe3..deb60a398 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/decorators_stack.rb @@ -4,7 +4,7 @@ class DecoratorsStack include ForestAdminDatasourceToolkit::Decorators attr_reader :datasource, :schema, :search, :early_computed, :late_computed, :action, :relation, :late_op_emulate, - :early_op_emulate, :validation, :sort, :rename_field, :publication, :write, :chart, :hook + :early_op_emulate, :validation, :sort, :rename_field, :publication, :write, :chart, :hook, :segment def initialize(datasource) @customizations = [] @@ -22,6 +22,7 @@ def initialize(datasource) last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator) last = @search = DatasourceDecorator.new(last, Search::SearchCollectionDecorator) + last = @segment = DatasourceDecorator.new(last, Segment::SegmentCollectionDecorator) last = @sort = DatasourceDecorator.new(last, Sort::SortCollectionDecorator) last = @chart = Chart::ChartDatasourceDecorator.new(last) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb index 69e85805c..9c04de3b4 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator.rb @@ -6,6 +6,8 @@ class SegmentCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::Col include ForestAdminDatasourceToolkit::Validations include ForestAdminDatasourceToolkit::Components::Query::ConditionTree + attr_reader :segments + def initialize(child_collection, datasource) super @segments = {} From 60ebac21d53ed557ab92c589c04859a36d3c2679 Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Fri, 12 Apr 2024 14:59:49 +0200 Subject: [PATCH 4/5] test(segment): add test on add_segment proxy function --- .../collection_customizer_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb index 7602c9398..5663b7203 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb @@ -486,5 +486,22 @@ module ForestAdminDatasourceCustomizer expect(hook_collection.hooks['list'].before.first).to eq(handler) end end + + context 'when using add_segment' do + it 'add a segment' do + stack = @datasource_customizer.stack + stack.apply_queued_customizations({}) + allow(stack.segment).to receive(:get_collection).with('book').and_return(@datasource_customizer.stack.segment.get_collection('book')) + + customizer = described_class.new(@datasource_customizer, @datasource_customizer.stack, 'book') + definition = proc { Nodes::ConditionTreeLeaf.new('title', Operators::EQUAL, 'foo') } + customizer.add_segment('foo_segment', definition) + stack.apply_queued_customizations({}) + segment_collection = @datasource_customizer.stack.segment.get_collection('book') + + expect(segment_collection.segments).to have_key('foo_segment') + expect(segment_collection.segments['foo_segment']).to eq(definition) + end + end end end From d0a5103d8b7781fb55369ef281fc5b4a65d5d772 Mon Sep 17 00:00:00 2001 From: Nicolas Alexandre Date: Fri, 12 Apr 2024 15:07:35 +0200 Subject: [PATCH 5/5] fix: tests --- .../collection_customizer_spec.rb | 12 ++++-------- .../spec/shared/factory.rb | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb index 5663b7203..d5b88b49a 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb @@ -12,8 +12,7 @@ module ForestAdminDatasourceCustomizer include_context 'with caller' before do datasource = Datasource.new - collection_book = instance_double( - Collection, + collection_book = collection_build( name: 'book', schema: { charts: [], @@ -40,8 +39,7 @@ module ForestAdminDatasourceCustomizer } ) - collection_book_person = instance_double( - Collection, + collection_book_person = collection_build( name: 'book_person', schema: { charts: [], @@ -63,8 +61,7 @@ module ForestAdminDatasourceCustomizer } ) - collection_person = instance_double( - Collection, + collection_person = collection_build( name: 'person', schema: { charts: [], @@ -89,8 +86,7 @@ module ForestAdminDatasourceCustomizer } ) - collection_category = instance_double( - Collection, + collection_category = collection_build( name: 'category', schema: { charts: [], diff --git a/packages/forest_admin_datasource_customizer/spec/shared/factory.rb b/packages/forest_admin_datasource_customizer/spec/shared/factory.rb index e7ca61483..53b453442 100644 --- a/packages/forest_admin_datasource_customizer/spec/shared/factory.rb +++ b/packages/forest_admin_datasource_customizer/spec/shared/factory.rb @@ -35,7 +35,7 @@ def collection_build(args = {}) countable: false, searchable: false, segments: {} - }.merge(args[:schema]), + }.merge(args[:schema] || {}), execute: nil, get_form: nil, render_chart: nil,