diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb index 976c6c635..6b3e21bb5 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb @@ -40,6 +40,8 @@ def self.parse_value(collection, leaf) return values.map { |item| !%w[false 0 no].include?(item) } if schema.column_type == 'Boolean' return values.map(&:to_f).select { |item| item.is_a? Numeric } if schema.column_type == 'Number' + + return values end leaf['value'] diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/frontend_filterable.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/frontend_filterable.rb index 5ee1d0b28..6e5ac7864 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/frontend_filterable.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/frontend_filterable.rb @@ -63,7 +63,7 @@ def self.filterable?(type, supported_operators = []) def self.get_required_operators(type) return OPERATOR_BY_TYPE[type] if type.is_a?(String) && OPERATOR_BY_TYPE.key?(type) - return ['Includes_All'] if type.is_a? Array + return [Operators::INCLUDES_ALL] if type.is_a? Array [] end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/charts/charts_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/charts/charts_spec.rb index c62c1f6ae..f009a79a2 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/charts/charts_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/charts/charts_spec.rb @@ -35,7 +35,7 @@ module Charts review_class = Struct.new(:id, :book_id, :author) stub_const('Review', review_class) - @datasource = Datasource.new + datasource = Datasource.new collection_book = instance_double( Collection, name: 'book', @@ -97,12 +97,14 @@ module Charts } ) allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil) - @datasource.add_collection(collection_book) - @datasource.add_collection(collection_review) - @datasource.add_collection(collection_book_review) - ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(@datasource) + datasource.add_collection(collection_book) + datasource.add_collection(collection_review) + datasource.add_collection(collection_book_review) + ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build + @datasource = ForestAdminAgent::Facades::Container.datasource + allow(ForestAdminAgent::Services::Permissions).to receive(:new).and_return(permissions) allow(permissions).to receive_messages( can_chart?: true, @@ -171,6 +173,7 @@ module Charts type: 'Value', timezone: 'Europe/Paris' }) + @datasource.get_collection('book') allow(@datasource.get_collection('book')).to receive(:aggregate).and_return( [{ value: 10, group: [] }], # first call [{ value: 5, group: [] }] # second call diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/count_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/count_spec.rb index 7fe5cd66d..6ed1a8b47 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/count_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/count_spec.rb @@ -24,8 +24,8 @@ module Resources let(:permissions) { instance_double(ForestAdminAgent::Services::Permissions) } before do - @datasource = Datasource.new - collection = Collection.new(@datasource, 'user') + datasource = Datasource.new + collection = Collection.new(datasource, 'user') allow(collection).to receive(:aggregate).and_return( [ { value: 1, group: [] } @@ -33,9 +33,10 @@ module Resources ) allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil) - @datasource.add_collection(collection) - ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(@datasource) + datasource.add_collection(collection) + ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build + @datasource = ForestAdminAgent::Facades::Container.datasource allow(ForestAdminAgent::Services::Permissions).to receive(:new).and_return(permissions) allow(permissions).to receive_messages(can?: true, get_scope: nil) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb index 044619133..915b148a7 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb @@ -43,7 +43,7 @@ def respond_to?(arg) end stub_const('User', user_class) - @datasource = Datasource.new + datasource = Datasource.new collection = instance_double( Collection, @@ -63,11 +63,12 @@ def respond_to?(arg) ) allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil) - @datasource.add_collection(collection) - ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(@datasource) + datasource.add_collection(collection) + ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build - @datasource + @datasource = ForestAdminAgent::Facades::Container.datasource + allow(@datasource.get_collection('user')).to receive(:delete) end context 'with simple request' do diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb index 86c1663c6..3cde79035 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb @@ -28,7 +28,7 @@ module Resources user_class = Struct.new(:id, :first_name, :last_name) stub_const('User', user_class) - @datasource = Datasource.new + datasource = Datasource.new collection = instance_double( Collection, name: 'user', @@ -42,9 +42,11 @@ module Resources list: [User.new(1, 'foo', 'foo')] ) allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil) - @datasource.add_collection(collection) - ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(@datasource) + datasource.add_collection(collection) + ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build + @datasource = ForestAdminAgent::Facades::Container.datasource + allow(@datasource.get_collection('user')).to receive(:list).and_return([User.new(1, 'foo', 'foo')]) allow(ForestAdminAgent::Services::Permissions).to receive(:new).and_return(permissions) allow(permissions).to receive_messages(can?: true, get_scope: nil) @@ -109,8 +111,8 @@ module Resources { aggregator: 'And', conditions: [ - { field: 'id', operator: 'Greater_Than', value: 7 }, - { field: 'first_name', operator: 'Contains', value: 'foo' } + { field: 'id', operator: 'greater_than', value: 7 }, + { field: 'first_name', operator: 'contains', value: 'foo' } ] } ) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/associate_related_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/associate_related_spec.rb index cc816ce4a..92c24df5a 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/associate_related_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/associate_related_spec.rb @@ -31,7 +31,7 @@ module Related address_class = Struct.new(:id, :location) stub_const('Address', address_class) - @datasource = Datasource.new + datasource = Datasource.new collection_user = instance_double( Collection, name: 'user', @@ -88,12 +88,14 @@ module Related ) allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil) - @datasource.add_collection(collection_user) - @datasource.add_collection(collection_address_user) - @datasource.add_collection(collection_address) - ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(@datasource) + datasource.add_collection(collection_user) + datasource.add_collection(collection_address_user) + datasource.add_collection(collection_address) + ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build + @datasource = ForestAdminAgent::Facades::Container.datasource + allow(ForestAdminAgent::Services::Permissions).to receive(:new).and_return(permissions) allow(permissions).to receive_messages(can?: true, get_scope: nil) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb index b4e03d828..a236b314e 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb @@ -12,10 +12,10 @@ module Related include_context 'with caller' subject(:dissociate) { described_class.new } - let(:datasource) { Datasource.new } let(:permissions) { instance_double(ForestAdminAgent::Services::Permissions) } before do + datasource = Datasource.new collection_user = Collection.new(datasource, 'user') collection_user.add_fields( { @@ -72,6 +72,8 @@ module Related ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build + @datasource = ForestAdminAgent::Facades::Container.datasource + allow(ForestAdminAgent::Services::Permissions).to receive(:new).and_return(permissions) allow(permissions).to receive_messages(can?: true, get_scope: nil) end @@ -103,7 +105,7 @@ module Related end it 'call dissociate_or_delete_one_to_many without deletion' do - allow(datasource.get_collection('address_user')).to receive(:update).and_return(true) + allow(@datasource.get_collection('address_user')).to receive(:update).and_return(true) args[:params]['relation_name'] = 'address_users' args[:params]['data'] = [{ 'id' => 1 }] @@ -111,7 +113,7 @@ module Related result = dissociate.handle_request(args) - expect(datasource.get_collection('address_user')).to have_received(:update) do |caller, filter, data| + expect(@datasource.get_collection('address_user')).to have_received(:update) do |caller, filter, data| expect(caller).to be_instance_of(Components::Caller) expect(filter).to have_attributes( condition_tree: have_attributes( @@ -134,8 +136,8 @@ module Related end it 'call dissociate_or_delete_one_to_many with deletion' do - allow(datasource.get_collection('address_user')).to receive(:delete).and_return(true) - allow(datasource.get_collection('address')).to receive(:delete).and_return(true) + allow(@datasource.get_collection('address_user')).to receive(:delete).and_return(true) + allow(@datasource.get_collection('address')).to receive(:delete).and_return(true) args[:params][:delete] = true args[:params]['relation_name'] = 'address_users' @@ -144,7 +146,7 @@ module Related result = dissociate.handle_request(args) - expect(datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| + expect(@datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| expect(caller).to be_instance_of(Components::Caller) expect(filter).to have_attributes( condition_tree: have_attributes( @@ -166,8 +168,8 @@ module Related end it 'call dissociate_or_delete_one_to_many with deletion on multiple records' do - allow(datasource.get_collection('address_user')).to receive(:delete).and_return(true) - allow(datasource.get_collection('address')).to receive(:delete).and_return(true) + allow(@datasource.get_collection('address_user')).to receive(:delete).and_return(true) + allow(@datasource.get_collection('address')).to receive(:delete).and_return(true) args[:params][:delete] = true args[:params]['relation_name'] = 'address_users' @@ -181,7 +183,7 @@ module Related result = dissociate.handle_request(args) - expect(datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| + expect(@datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| expect(caller).to be_instance_of(Components::Caller) expect(filter).to have_attributes( condition_tree: have_attributes( @@ -217,7 +219,7 @@ module Related end it 'call dissociate_or_delete_many_to_many without deletion' do - allow(datasource.get_collection('address_user')) + allow(@datasource.get_collection('address_user')) .to receive_messages(list: [AddressUser.new(1, 1, 1)], delete: true) args[:params]['relation_name'] = 'addresses' @@ -226,7 +228,7 @@ module Related result = dissociate.handle_request(args) - expect(datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| + expect(@datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| expect(caller).to be_instance_of(Components::Caller) expect(filter).to have_attributes( condition_tree: have_attributes( @@ -248,9 +250,9 @@ module Related end it 'call dissociate_or_delete_many_to_many with deletion' do - allow(datasource.get_collection('address_user')).to receive_messages(list: [AddressUser.new(1, 1, 1)], - delete: true) - allow(datasource.get_collection('address')).to receive(:delete).and_return(true) + allow(@datasource.get_collection('address_user')).to receive_messages(list: [AddressUser.new(1, 1, 1)], + delete: true) + allow(@datasource.get_collection('address')).to receive(:delete).and_return(true) args[:params][:delete] = true args[:params]['relation_name'] = 'addresses' @@ -259,7 +261,7 @@ module Related result = dissociate.handle_request(args) - expect(datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| + expect(@datasource.get_collection('address_user')).to have_received(:delete) do |caller, filter| expect(caller).to be_instance_of(Components::Caller) expect(filter).to have_attributes( condition_tree: have_attributes( @@ -277,7 +279,7 @@ module Related ) end - expect(datasource.get_collection('address')).to have_received(:delete) do |caller, filter| + expect(@datasource.get_collection('address')).to have_received(:delete) do |caller, filter| expect(caller).to be_instance_of(Components::Caller) expect(filter).to have_attributes( condition_tree: have_attributes( diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/list_related_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/list_related_spec.rb index f024bd425..babdcf976 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/list_related_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/list_related_spec.rb @@ -29,7 +29,7 @@ module Related user_class = Struct.new(:id, :first_name, :last_name) stub_const('User', user_class) - @datasource = Datasource.new + datasource = Datasource.new collection_user = instance_double( Collection, name: 'user', @@ -58,11 +58,13 @@ module Related } ) allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil) - @datasource.add_collection(collection_user) - @datasource.add_collection(collection_category) - ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(@datasource) + datasource.add_collection(collection_user) + datasource.add_collection(collection_category) + ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build + @datasource = ForestAdminAgent::Facades::Container.datasource + allow(ForestAdminAgent::Services::Permissions).to receive(:new).and_return(permissions) allow(permissions).to receive_messages(can?: true, get_scope: Nodes::ConditionTreeBranch.new('Or', [])) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/update_related_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/update_related_spec.rb index 21972d699..068a1b56f 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/update_related_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/update_related_spec.rb @@ -12,10 +12,10 @@ module Related include_context 'with caller' subject(:update) { described_class.new } - let(:datasource) { Datasource.new } let(:permissions) { instance_double(ForestAdminAgent::Services::Permissions) } before do + datasource = Datasource.new collection_user = Collection.new(datasource, 'user') collection_user.add_fields( { @@ -48,6 +48,7 @@ module Related datasource.add_collection(collection_book) ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build + @datasource = ForestAdminAgent::Facades::Container.datasource allow(ForestAdminAgent::Services::Permissions).to receive(:new).and_return(permissions) allow(permissions).to receive_messages(can?: true, get_scope: nil) @@ -77,7 +78,7 @@ module Related end it 'call handle_request on a many_to_one relation' do - allow(datasource.get_collection('book')).to receive(:update).and_return(true) + allow(@datasource.get_collection('book')).to receive(:update).and_return(true) args[:params]['collection_name'] = 'book' args[:params]['relation_name'] = 'author' @@ -86,7 +87,7 @@ module Related result = update.handle_request(args) - expect(datasource.get_collection('book')).to have_received(:update) do |caller, filter, data| + expect(@datasource.get_collection('book')).to have_received(:update) do |caller, filter, data| expect(caller).to be_instance_of(Components::Caller) expect(filter).to have_attributes( condition_tree: have_attributes(field: 'id', operator: Operators::EQUAL, value: 1), @@ -102,7 +103,7 @@ module Related end it 'call handle_request on a one_to_one relation' do - allow(datasource.get_collection('book')).to receive_messages(aggregate: [{ value: 1 }], update: true) + allow(@datasource.get_collection('book')).to receive_messages(aggregate: [{ value: 1 }], update: true) args[:params]['collection_name'] = 'user' args[:params]['relation_name'] = 'book' @@ -144,7 +145,7 @@ module Related ] ] - expect(datasource.get_collection('book')).to have_received(:update) + expect(@datasource.get_collection('book')).to have_received(:update) .exactly(2).times do |caller, filter, data| parameter = parameters.shift expect(caller).to be_instance_of(parameter[0]) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/show_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/show_spec.rb index 0e9d22351..5b21eb75f 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/show_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/show_spec.rb @@ -45,7 +45,7 @@ def respond_to?(arg) end stub_const('User', user_class) - @datasource = Datasource.new + datasource = Datasource.new collection = instance_double( Collection, @@ -67,11 +67,11 @@ def respond_to?(arg) ) allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil) - @datasource.add_collection(collection) - ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(@datasource) + datasource.add_collection(collection) + ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource) ForestAdminAgent::Builder::AgentFactory.instance.build - - @datasource + @datasource = ForestAdminAgent::Facades::Container.datasource + allow(@datasource.get_collection('user')).to receive(:list).and_return([User.new(1, 'foo', 'foo')]) end it 'return an serialized content' do diff --git a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb index 4223f3390..7862916a5 100644 --- a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb +++ b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb @@ -62,8 +62,8 @@ def compute_main_operator(condition_tree, aggregator) @query = @query.send(aggregator, @query.where(@arel_table[field.to_sym].lt(value))) when Operators::NOT_CONTAINS @query = @query.send(aggregator, @query.where.not(@arel_table[field.to_sym].matches("%#{value}%"))) - when Operators::CONTAINS - @query = @query.send(aggregator, @query.where(@arel_table[field.to_sym].matches("%#{value}%"))) + when Operators::LIKE + @query = @query.send(aggregator, @query.where(@arel_table[field.to_sym].matches(value))) when Operators::INCLUDES_ALL # TODO: to implement end 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 7daea56db..7a602b457 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 @@ -3,11 +3,12 @@ module Decorators class DecoratorsStack include ForestAdminDatasourceToolkit::Decorators - attr_reader :datasource, :empty, :schema + attr_reader :datasource, :schema def initialize(datasource) last = datasource - last = @empty = DatasourceDecorator.new(last, Empty::EmptyCollectionDecorator) + last = DatasourceDecorator.new(last, Empty::EmptyCollectionDecorator) + last = DatasourceDecorator.new(last, OperatorsEquivalence::OperatorsEquivalenceCollectionDecorator) last = @schema = DatasourceDecorator.new(last, Schema::SchemaCollectionDecorator) @datasource = last end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb new file mode 100644 index 000000000..fc273b32f --- /dev/null +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator.rb @@ -0,0 +1,46 @@ +module ForestAdminDatasourceCustomizer + module Decorators + module OperatorsEquivalence + class OperatorsEquivalenceCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator + include ForestAdminDatasourceToolkit::Decorators + include ForestAdminDatasourceToolkit::Components::Query::ConditionTree + + protected + + def refine_schema(sub_schema) + sub_schema[:fields].map do |_name, schema| + if schema.type == 'Column' + new_operators = Operators.all.select do |operator| + ConditionTreeEquivalent.equivalent_tree?(operator, schema.filter_operators, schema.column_type) + end + + schema.filter_operators = new_operators + else + schema + end + end + + sub_schema + end + + def refine_filter(caller, filter = nil) + filter&.override( + condition_tree: filter.condition_tree&.replace_leafs do |leaf| + schema = ForestAdminDatasourceToolkit::Utils::Collection.get_field_schema( + @child_collection, + leaf.field + ) + + ConditionTreeEquivalent.get_equivalent_tree( + leaf, + schema.filter_operators, + schema.column_type, + caller.timezone + ) + end + ) + end + end + end + end +end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator_spec.rb new file mode 100644 index 000000000..4c5f04d4a --- /dev/null +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/operators_equivalence/operators_equivalence_collection_decorator_spec.rb @@ -0,0 +1,119 @@ +require 'spec_helper' +require 'shared/caller' + +module ForestAdminDatasourceCustomizer + module Decorators + module OperatorsEquivalence + include ForestAdminDatasourceToolkit + include ForestAdminDatasourceToolkit::Components::Query + include ForestAdminDatasourceToolkit::Components::Query::ConditionTree + include ForestAdminDatasourceToolkit::Decorators + include ForestAdminDatasourceToolkit::Schema + + describe OperatorsEquivalenceCollectionDecorator do + include_context 'with caller' + subject(:operators_equivalence_collection_decorator) { described_class } + + let(:decorated_book) { @datasource_decorator.get_collection('book') } + let(:aggregation) { instance_double(ForestAdminDatasourceToolkit::Components::Query::Aggregation) } + + before do + datasource = Datasource.new + @child_collection_book = instance_double( + Collection, + name: 'book', + schema: { + fields: { + 'col' => ColumnSchema.new( + column_type: 'Date', + filter_operators: [Operators::LESS_THAN, Operators::EQUAL, Operators::GREATER_THAN] + ), + 'rel' => Relations::ManyToOneSchema.new( + foreign_collection: 'author', + foreign_key: 'col', + foreign_key_target: 'id' + ) + } + } + ) + datasource.add_collection(@child_collection_book) + + @datasource_decorator = DatasourceDecorator.new(datasource, operators_equivalence_collection_decorator) + end + + context 'with a date field which support only "<", "==" and ">"' do + it 'schema should support more operators' do + schema = @datasource_decorator.get_collection('book').schema[:fields]['col'] + + expect(schema.filter_operators.size).to be > 20 + end + + it 'schema should not have dropped relations' do + schema = @datasource_decorator.get_collection('book').schema + + expect(schema[:fields].keys).to eq(%w[col rel]) + end + + it 'list() should work with a null condition tree' do + allow(@child_collection_book).to receive(:list).and_return([]) + decorated_book.list( + caller, + Filter.new, + Projection.new + ) + + expect(@child_collection_book).to have_received(:list) + end + + it 'list() should not modify supported operators' do + tree = Nodes::ConditionTreeLeaf.new('col', Operators::EQUAL, 'someData') + allow(@child_collection_book).to receive(:list).and_return([]) + decorated_book.list( + caller, + Filter.new(condition_tree: tree), + Projection.new(['col']) + ) + + expect(@child_collection_book).to have_received(:list) do |context_caller, filter, projection| + expect(context_caller).to eq caller + expect(filter.condition_tree).to eq tree + expect(projection).to eq Projection.new(['col']) + end + end + + it 'list() should transform "In -> Equal"' do + tree = Nodes::ConditionTreeLeaf.new('col', Operators::IN, ['someData']) + allow(@child_collection_book).to receive(:list).and_return([]) + decorated_book.list( + caller, + Filter.new(condition_tree: tree), + Projection.new(['col']) + ) + + expect(@child_collection_book).to have_received(:list) do |context_caller, filter, projection| + expect(context_caller).to eq caller + expect(filter.condition_tree.to_h).to include(field: 'col', operator: Operators::EQUAL, value: 'someData') + expect(projection).to eq Projection.new(['col']) + end + end + + it 'list() should transform "Blank -> In -> Equal"' do + tree = Nodes::ConditionTreeLeaf.new('col', Operators::BLANK) + allow(@child_collection_book).to receive(:list).and_return([]) + decorated_book.list( + caller, + Filter.new(condition_tree: tree), + Projection.new(['col']) + ) + + expect(@child_collection_book).to have_received(:list) do |context_caller, filter, projection| + expect(context_caller).to eq caller + expect(filter.condition_tree.to_h).to include(field: 'col', operator: Operators::EQUAL, value: nil) + expect(projection).to eq Projection.new(['col']) + end + end + end + end + end + end +end diff --git a/packages/forest_admin_datasource_customizer/spec/shared/caller.rb b/packages/forest_admin_datasource_customizer/spec/shared/caller.rb new file mode 100644 index 000000000..d0deca570 --- /dev/null +++ b/packages/forest_admin_datasource_customizer/spec/shared/caller.rb @@ -0,0 +1,16 @@ +RSpec.shared_context 'with caller' do + let(:caller) do + ForestAdminDatasourceToolkit::Components::Caller.new( + id: 1, + email: 'sarah.connor@skynet.com', + first_name: 'sarah', + last_name: 'connor', + team: 'survivor', + rendering_id: 1, + tags: [], + timezone: 'Europe/Paris', + permission_level: 'admin', + role: 'dev' + ) + end +end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb index e4936854d..7e454f341 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb @@ -49,7 +49,7 @@ def get_replacer(operator, filter_operators, column_type, visited = []) if depends_replacers.all? { |r| !r.nil? } return lambda { |leaf, timezone| - replacer.call(leaf).replace_leafs do |sub_leaf| + replacer.call(leaf, timezone).replace_leafs do |sub_leaf| depends_replacers[depends_on.index(sub_leaf.operator)].call(sub_leaf, timezone) end } diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb index c3e5a3273..59b347d33 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb @@ -1,3 +1,6 @@ +require 'active_support' +require 'active_support/core_ext/string' + module ForestAdminDatasourceToolkit module Components module Query @@ -11,7 +14,7 @@ class ConditionTreeLeaf < ConditionTree def initialize(field, operator, value = nil) @field = field - @operator = operator + @operator = operator.underscore @value = value valid_operator(@operator) if @operator super() @@ -32,14 +35,14 @@ def valid_operator(value) end def inverse - return override(operator: "Not_#{@operator}") if Operators.exist?("Not_#{@operator}") - return override(operator: @operator[4..]) if @operator.start_with?('Not') + return override(operator: "not_#{@operator}") if Operators.exist?("not_#{@operator}") + return override(operator: @operator[4..]) if @operator.start_with?('not') case @operator - when 'Blank' - override(operator: 'Present') - when 'Present' - override(operator: 'Blank') + when Operators::BLANK + override(operator: Operators::BLANK) + when Operators::PRESENT + override(operator: Operators::PRESENT) else raise ForestException, "Operator: #{@operator} cannot be inverted." end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/operators.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/operators.rb index e818de3d1..c8fc379f3 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/operators.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/operators.rb @@ -3,64 +3,53 @@ module Components module Query module ConditionTree class Operators - EQUAL = 'Equal'.freeze - NOT_EQUAL = 'Not_Equal'.freeze - LESS_THAN = 'Less_Than'.freeze - GREATER_THAN = 'Greater_Than'.freeze - MATCH = 'Match'.freeze - LIKE = 'Like'.freeze - I_LIKE = 'ILike'.freeze - NOT_CONTAINS = 'Not_Contains'.freeze - CONTAINS = 'Contains'.freeze - I_CONTAINS = 'IContains'.freeze - LONGER_THAN = 'Longer_Than'.freeze - SHORTER_THAN = 'Shorter_Than'.freeze - INCLUDES_ALL = 'Includes_All'.freeze - PRESENT = 'Present'.freeze - BLANK = 'Blank'.freeze - IN = 'In'.freeze - NOT_IN = 'Not_In'.freeze - STARTS_WITH = 'Starts_With'.freeze - I_STARTS_WITH = 'IStarts_With'.freeze - ENDS_WITH = 'Ends_With'.freeze - I_ENDS_WITH = 'IEnds_With'.freeze - MISSING = 'Missing'.freeze - BEFORE = 'Before'.freeze - AFTER = 'After'.freeze - AFTER_X_HOURS_AGO = 'After_X_Hours_Ago'.freeze - BEFORE_X_HOURS_AGO = 'Before_X_Hours_Ago'.freeze - FUTURE = 'Future'.freeze - PAST = 'Past'.freeze - TODAY = 'Today'.freeze - YESTERDAY = 'Yesterday'.freeze - PREVIOUS_WEEK = 'Previous_Week'.freeze - PREVIOUS_MONTH = 'Previous_Month'.freeze - PREVIOUS_QUARTER = 'Previous_Quarter'.freeze - PREVIOUS_YEAR = 'Previous_Year'.freeze - PREVIOUS_WEEK_TO_DATE = 'Previous_Week_To_Date'.freeze - PREVIOUS_MONTH_TO_DATE = 'Previous_Month_To_Date'.freeze - PREVIOUS_QUARTER_TO_DATE = 'Previous_Quarter_To_Date'.freeze - PREVIOUS_YEAR_TO_DATE = 'Previous_Year_To_Date'.freeze - PREVIOUS_X_DAYS = 'Previous_X_Days'.freeze - PREVIOUS_X_DAYS_TO_DATE = 'Previous_X_Days_To_Date'.freeze + EQUAL = 'equal'.freeze + NOT_EQUAL = 'not_equal'.freeze + LESS_THAN = 'less_than'.freeze + GREATER_THAN = 'greater_than'.freeze + MATCH = 'match'.freeze + LIKE = 'like'.freeze + I_LIKE = 'i_like'.freeze + NOT_CONTAINS = 'not_contains'.freeze + CONTAINS = 'contains'.freeze + I_CONTAINS = 'i_contains'.freeze + LONGER_THAN = 'longer_than'.freeze + SHORTER_THAN = 'shorter_than'.freeze + INCLUDES_ALL = 'includes_all'.freeze + PRESENT = 'present'.freeze + BLANK = 'blank'.freeze + IN = 'in'.freeze + NOT_IN = 'not_in'.freeze + STARTS_WITH = 'starts_with'.freeze + I_STARTS_WITH = 'i_starts_with'.freeze + ENDS_WITH = 'ends_with'.freeze + I_ENDS_WITH = 'i_ends_with'.freeze + MISSING = 'missing'.freeze + BEFORE = 'before'.freeze + AFTER = 'after'.freeze + AFTER_X_HOURS_AGO = 'after_x_hours_ago'.freeze + BEFORE_X_HOURS_AGO = 'before_x_hours_ago'.freeze + FUTURE = 'future'.freeze + PAST = 'past'.freeze + TODAY = 'today'.freeze + YESTERDAY = 'yesterday'.freeze + PREVIOUS_WEEK = 'previous_week'.freeze + PREVIOUS_MONTH = 'previous_month'.freeze + PREVIOUS_QUARTER = 'previous_quarter'.freeze + PREVIOUS_YEAR = 'previous_year'.freeze + PREVIOUS_WEEK_TO_DATE = 'previous_week_to_date'.freeze + PREVIOUS_MONTH_TO_DATE = 'previous_month_to_date'.freeze + PREVIOUS_QUARTER_TO_DATE = 'previous_quarter_to_date'.freeze + PREVIOUS_YEAR_TO_DATE = 'previous_year_to_date'.freeze + PREVIOUS_X_DAYS = 'previous_x_days'.freeze + PREVIOUS_X_DAYS_TO_DATE = 'previous_x_days_to_date'.freeze def self.all - constants + constants.map { |constant| const_get(constant) } end def self.exist?(operator_value) - case operator_value - when 'ILike' - operator_value = 'I_LIKE' - when 'IContains' - operator_value = 'I_CONTAINS' - when 'IStarts_With' - operator_value = 'I_STARTS_WITH' - when 'IEnds_With' - operator_value = 'I_ENDS_WITH' - end - - all.include?(operator_value.upcase.to_sym) + all.include?(operator_value) end def self.interval_operators diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb index 8f1b98008..a09c0c302 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb @@ -12,41 +12,41 @@ def self.transforms { depends_on: [Operators::IN], for_types: ['String'], - replacer: ->(leaf) { leaf.override({ operator: Operators::IN, value: [nil, ''] }) } + replacer: proc { |leaf| leaf.override({ operator: Operators::IN, value: [nil, ''] }) } }, { depends_on: [Operators::MISSING], - replacer: ->(leaf) { leaf.override({ operator: Operators::MISSING }) } + replacer: proc { |leaf| leaf.override({ operator: Operators::MISSING }) } } ], Operators::MISSING => [ { depends_on: [Operators::EQUAL], - replacer: ->(leaf) { leaf.override({ operator: Operators::EQUAL, value: nil }) } + replacer: proc { |leaf| leaf.override({ operator: Operators::EQUAL, value: nil }) } } ], Operators::PRESENT => [ { depends_on: [Operators::NOT_IN], for_types: ['String'], - replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_IN, value: [nil, ''] }) } + replacer: proc { |leaf| leaf.override({ operator: Operators::NOT_IN, value: [nil, ''] }) } }, { depends_on: [Operators::NOT_EQUAL], - replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_EQUAL, value: nil }) } + replacer: proc { |leaf| leaf.override({ operator: Operators::NOT_EQUAL, value: nil }) } } ], Operators::EQUAL => [ { depends_on: [Operators::IN], - replacer: ->(leaf) { leaf.override({ operator: Operators::IN, value: [leaf.value] }) } + replacer: proc { |leaf| leaf.override({ operator: Operators::IN, value: [leaf.value] }) } } ], Operators::IN => [ { depends_on: [Operators::EQUAL, Operators::MATCH], for_types: ['String'], - replacer: lambda { |leaf| + replacer: proc { |leaf| values = leaf.value conditions = [] @@ -68,7 +68,7 @@ def self.transforms }, { depends_on: [Operators::EQUAL], - replacer: lambda { |leaf| + replacer: proc { |leaf| ConditionTreeFactory.union( leaf.value.map { |item| leaf.override({ operator: Operators::EQUAL, value: item }) } ) @@ -78,14 +78,14 @@ def self.transforms Operators::NOT_EQUAL => [ { depends_on: [Operators::NOT_IN], - replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_IN, value: [leaf.value] }) } + replacer: proc { |leaf| leaf.override({ operator: Operators::NOT_IN, value: [leaf.value] }) } } ], Operators::NOT_IN => [ { depends_on: [Operators::NOT_EQUAL, Operators::MATCH], for_types: ['String'], - replacer: lambda { |leaf| + replacer: proc { |leaf| values = leaf.value conditions = [] @@ -106,7 +106,7 @@ def self.transforms }, { depends_on: [Operators::NOT_EQUAL], - replacer: lambda { |leaf| + replacer: proc { |leaf| ConditionTreeFactory.intersect( leaf.value.map { |item| leaf.override({ operator: Operators::NOT_EQUAL, value: item }) } ) diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb index f05a2735d..f389eb1fc 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb @@ -8,17 +8,17 @@ def self.likes(get_pattern, case_sensitive) operator = case_sensitive ? Operators::LIKE : Operators::I_LIKE { - dependsOn: [operator], - forTypes: ['String'], - replacer: ->(leaf) { leaf.override(operator: operator, value: get_pattern.call(leaf.value)) } + depends_on: [operator], + for_types: ['String'], + replacer: proc { |leaf| leaf.override(operator: operator, value: get_pattern.call(leaf.value)) } } end def self.match(case_sensitive) { - dependsOn: [Operators::MATCH], - forTypes: ['String'], - replacer: lambda { |leaf| + depends_on: [Operators::MATCH], + for_types: ['String'], + replacer: proc { |leaf| regex = leaf.value.gsub(/([\.\\\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:\-])/, '\\\\\1') regex.gsub!('%', '.*') regex.tr!('_', '.') @@ -30,12 +30,12 @@ def self.match(case_sensitive) def self.transforms { - Operators::CONTAINS => [likes(->(value) { "%#{value}%" }, true)], - Operators::STARTS_WITH => [likes(->(value) { "#{value}%" }, true)], - Operators::ENDS_WITH => [likes(->(value) { "%#{value}" }, true)], - Operators::I_CONTAINS => [likes(->(value) { "%#{value}%" }, false)], - Operators::I_STARTS_WITH => [likes(->(value) { "#{value}%" }, false)], - Operators::I_ENDS_WITH => [likes(->(value) { "%#{value}" }, false)], + Operators::CONTAINS => [likes(proc { |value| "%#{value}%" }, true)], + Operators::STARTS_WITH => [likes(proc { |value| "#{value}%" }, true)], + Operators::ENDS_WITH => [likes(proc { |value| "%#{value}" }, true)], + Operators::I_CONTAINS => [likes(proc { |value| "%#{value}%" }, false)], + Operators::I_STARTS_WITH => [likes(proc { |value| "#{value}%" }, false)], + Operators::I_ENDS_WITH => [likes(proc { |value| "%#{value}" }, false)], Operators::I_LIKE => [match(false)], Operators::LIKE => [match(true)] } diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb index 7c95fb5aa..49018fcd4 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb @@ -13,9 +13,9 @@ def self.format(value) def self.compare(operator) { - dependsOn: [operator], - forTypes: ['Date', 'Dateonly'], - replacer: lambda { |leaf, tz| + depends_on: [operator], + for_types: ['Date', 'Dateonly'], + replacer: proc { |leaf, tz| leaf.override(operator: operator, value: format(yield(Time.now.in_time_zone(tz), leaf.value))) } } @@ -23,9 +23,9 @@ def self.compare(operator) def self.interval(start_fn, end_fn) { - dependsOn: [Operators::LESS_THAN, Operators::GREATER_THAN], - forTypes: ['Date', 'Dateonly'], - replacer: lambda do |leaf, tz| + depends_on: [Operators::LESS_THAN, Operators::GREATER_THAN], + for_types: ['Date', 'Dateonly'], + replacer: proc do |leaf, tz| value_greater_than = if leaf.value.nil? format(start_fn.call(Time.now.in_time_zone(tz))) else @@ -53,17 +53,21 @@ def self.interval(start_fn, end_fn) def self.previous_interval(duration) interval( - lambda { |now| - duration == 'quarter' ? now.prev_quarter : (now - 1.send(duration)).send(:"beginning_of_#{duration}") + proc { |now| + if duration == 'quarter' + now.prev_quarter.send(:"beginning_of_#{duration}") + else + (now - 1.send(duration)).send(:"beginning_of_#{duration}") + end }, - ->(now) { now.send(:"beginning_of_#{duration}") } + proc { |now| now.send(:"beginning_of_#{duration}") } ) end def self.previous_interval_to_date(duration) interval( - ->(now) { now.send(:"beginning_of_#{duration}") }, - ->(now) { now } + proc { |now| now.send(:"beginning_of_#{duration}") }, + proc { |now| now } ) end @@ -86,20 +90,20 @@ def self.transforms Operators::PREVIOUS_YEAR => [previous_interval('year')], Operators::PREVIOUS_X_DAYS_TO_DATE => [ interval( - ->(now, value) { (now - value.days).beginning_of_day }, - ->(now, _value) { now } + proc { |now, value| (now - value.days).beginning_of_day }, + proc { |now, _value| now } ) ], Operators::PREVIOUS_X_DAYS => [ interval( - ->(now, value) { (now - value.days).beginning_of_day }, - ->(now, _value) { now.beginning_of_day } + proc { |now, value| (now - value.days).beginning_of_day }, + proc { |now, _value| now.beginning_of_day } ) ], Operators::TODAY => [ interval( - ->(now) { now.beginning_of_day }, - ->(now) { (now + 1.day).beginning_of_day } + proc { |now| now.beginning_of_day }, + proc { |now| (now + 1.day).beginning_of_day } ) ] } diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb index 6959808e1..9324e858a 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb @@ -41,7 +41,7 @@ module Nodes condition_tree_leaf = ConditionTreeLeaf.new('column1', Operators::TODAY) expect do condition_tree_leaf.inverse - end.to raise_error(ForestException, '🌳🌳🌳 Operator: Today cannot be inverted.') + end.to raise_error(ForestException, '🌳🌳🌳 Operator: today cannot be inverted.') end end