Skip to content

Commit

Permalink
feat: add publication decorator support (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthv authored Mar 19, 2024
1 parent 451553a commit 7550e10
Show file tree
Hide file tree
Showing 17 changed files with 550 additions and 21 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ Metrics/MethodLength:
- '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/publication/publication_collection_decorator.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'
Expand Down Expand Up @@ -264,6 +265,7 @@ Layout/LineLength:
- 'packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module Builder
collection_book = ForestAdminDatasourceToolkit::Collection.new(datasource, 'Book')
datasource.add_collection(collection_book)
described_class.instance.add_datasource(datasource)
described_class.instance.customizer.datasource({})

expect(described_class.instance.customizer.collections.size).to eq(1)
expect(described_class.instance.customizer.get_collection('Book').name).to eq('Book')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ module Charts
schema: {
fields: {
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'book_id' => ColumnSchema.new(column_type: 'Number'),
'review_id' => ColumnSchema.new(column_type: 'Number'),
'book' => Relations::ManyToOneSchema.new(
foreign_key: 'book_id',
foreign_collection: 'book',
Expand All @@ -88,6 +90,7 @@ module Charts
fields: {
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'author' => ColumnSchema.new(column_type: 'String'),
'book_id' => ColumnSchema.new(column_type: 'Number'),
'book' => Relations::ManyToOneSchema.new(
foreign_key: 'book_id',
foreign_collection: 'book',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ module Related
schema: {
fields: {
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'address_id' => ColumnSchema.new(column_type: 'Number'),
'user_id' => ColumnSchema.new(column_type: 'Number'),
'address' => Relations::ManyToOneSchema.new(
foreign_key: 'address_id',
foreign_collection: 'address',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module Related
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'first_name' => ColumnSchema.new(column_type: 'String'),
'last_name' => ColumnSchema.new(column_type: 'String'),
'category_id' => ColumnSchema.new(column_type: 'Number'),
'category' => Relations::ManyToOneSchema.new(
foreign_key: 'category_id',
foreign_collection: 'category',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ module Related
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true,
filter_operators: [Operators::IN, Operators::EQUAL]),
'address_id' => Relations::ManyToOneSchema.new(
'address_id' => ColumnSchema.new(column_type: 'Number'),
'user_id' => ColumnSchema.new(column_type: 'Number'),
'address' => Relations::ManyToOneSchema.new(
foreign_key: 'address_id',
foreign_collection: 'address',
foreign_key_target: 'id'
),
'user_id' => Relations::ManyToOneSchema.new(
'user' => Relations::ManyToOneSchema.new(
foreign_key: 'user_id',
foreign_collection: 'user',
foreign_key_target: 'id'
Expand Down Expand Up @@ -236,7 +238,7 @@ module Related
aggregator: 'And',
conditions: [
have_attributes(field: 'user_id', operator: Operators::EQUAL, value: 1),
have_attributes(field: 'address_id:id', operator: Operators::EQUAL, value: 1)
have_attributes(field: 'address:id', operator: Operators::EQUAL, value: 1)
]
),
page: nil,
Expand Down Expand Up @@ -269,7 +271,7 @@ module Related
aggregator: 'And',
conditions: [
have_attributes(field: 'user_id', operator: Operators::EQUAL, value: 1),
have_attributes(field: 'address_id:id', operator: Operators::EQUAL, value: 1)
have_attributes(field: 'address:id', operator: Operators::EQUAL, value: 1)
]
),
page: nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module Related
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'first_name' => ColumnSchema.new(column_type: 'String'),
'last_name' => ColumnSchema.new(column_type: 'String'),
'category_id' => ColumnSchema.new(column_type: 'Number'),
'category' => Relations::ManyToOneSchema.new(
foreign_key: 'category_id',
foreign_collection: 'category',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module Related
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true,
filter_operators: [Operators::IN, Operators::EQUAL]),
'author_id' => ColumnSchema.new(column_type: 'Number'),
'author' => Relations::ManyToOneSchema.new(
foreign_key: 'author_id',
foreign_key_target: 'id',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,17 @@ def replace_field_sorting(name, equivalent_sort)
push_customization { @stack.sort.get_collection(@name).replace_field_sorting(name, equivalent_sort) }
end

# Remove fields from the exported schema (they will still be usable within the agent).
# @param names the names of the field or the relation
# @example
# .remove_field('fieldNameToRemove', 'relationNameToRemove')
def remove_field(*names)
push_customization do
collection = @stack.publication.get_collection(@name)
names.each { |name| collection.change_field_visibility(name, false) }
end
end

private

def push_customization(&customization)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,24 @@ def datasource(logger)
@stack.datasource
end

def add_datasource(datasource, _options)
# TODO: add include/exclude behavior
# TODO: add rename behavior
def add_datasource(datasource, options)
@stack.queue_customization(lambda {
# TODO: add call logger

datasource.collections.each_value { |collection| @composite_datasource.add_collection(collection) }
if options[:include] || options[:exclude]
publication_decorator = Decorators::Publication::PublicationDatasourceDecorator.new(datasource)
publication_decorator.keep_collections_matching(options[:include], options[:exclude])
datasource = publication_decorator
end

# TODO: add rename behavior

datasource.collections.each_value do |collection|
@composite_datasource.add_collection(collection)
end
})

self
end

def add_chart(name, definition)
Expand All @@ -44,8 +57,10 @@ def customize_collection(name, handle)
handle.call(get_collection(name))
end

def remove_collection(names)
# TODO: to implement
def remove_collection(*names)
@stack.queue_customization(-> { @stack.publication.keep_collections_matching(nil, names) })

self
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -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
:early_op_emulate, :validation, :sort, :rename_field, :publication

def initialize(datasource)
@customizations = []
Expand All @@ -27,6 +27,7 @@ def initialize(datasource)
last = @schema = DatasourceDecorator.new(last, Schema::SchemaCollectionDecorator)
last = @validation = DatasourceDecorator.new(last, Validation::ValidationCollectionDecorator)

last = @publication = Publication::PublicationDatasourceDecorator.new(last)
last = @rename_field = DatasourceDecorator.new(last, RenameField::RenameFieldCollectionDecorator)
@datasource = last
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module ForestAdminDatasourceCustomizer
module Decorators
module Publication
class PublicationCollectionDecorator < ForestAdminDatasourceToolkit::Decorators::CollectionDecorator
include ForestAdminDatasourceToolkit::Exceptions
attr_reader :blacklist

def initialize(child_collection, datasource)
super
@blacklist = []
end

def change_field_visibility(name, visible)
field = child_collection.schema[:fields][name]
raise ForestException, "No such field '#{name}'" unless field
raise ForestException, 'Cannot hide primary key' if ForestAdminDatasourceToolkit::Utils::Schema.primary_key?(
child_collection, name
)

if visible
@blacklist.delete(name)
else
@blacklist << name
end

mark_schema_as_dirty
end

def create(caller, data)
record = {}
child_collection.create(caller, data).each do |key, value|
record[key] = value unless @blacklist.include?(key)
end

record
end

def refine_schema(child_schema)
fields = {}

child_schema[:fields].each do |name, field|
fields[name] = field if published?(name)
end

child_schema[:fields] = fields

child_schema
end

def published?(name)
# Explicitly hidden
return false if @blacklist.include?(name)

# Implicitly hidden
field = child_collection.schema[:fields][name]

if field.type == 'ManyToOne'
return (
datasource.published?(field.foreign_collection) &&
published?(field.foreign_key) &&
datasource.get_collection(field.foreign_collection).published?(field.foreign_key_target)
)
end

if field.type == 'OneToOne' || field.type == 'OneToMany'
return (
datasource.published?(field.foreign_collection) &&
datasource.get_collection(field.foreign_collection).published?(field.origin_key) &&
published?(field.origin_key_target)
)
end

if field.type == 'ManyToMany'
return (
datasource.published?(field.through_collection) &&
datasource.published?(field.foreign_collection) &&
datasource.get_collection(field.through_collection).published?(field.foreign_key) &&
datasource.get_collection(field.through_collection).published?(field.origin_key) &&
published?(field.origin_key_target) &&
datasource.get_collection(field.foreign_collection).published?(field.foreign_key_target)
)
end

true
end

# rubocop:disable Lint/UselessMethodDefinition
def mark_schema_as_dirty
super
end
# rubocop:enable Lint/UselessMethodDefinition
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module ForestAdminDatasourceCustomizer
module Decorators
module Publication
class PublicationDatasourceDecorator < ForestAdminDatasourceToolkit::Decorators::DatasourceDecorator
include ForestAdminDatasourceToolkit::Exceptions
attr_reader :blacklist

def initialize(child_datasource)
super(child_datasource, PublicationCollectionDecorator)
@blacklist = []
end

def collections
@child_datasource.collections.except(*@blacklist).to_h do |name, _collection|
[name, get_collection(name)]
end
end

def get_collection(name)
raise ForestException, "Collection '#{name}' was removed." if @blacklist.include?(name)

super(name)
end

def keep_collections_matching(include = [], exclude = [])
validate_collection_names(include.to_a + exclude.to_a)

# List collection we're keeping from the white/black list.
@child_datasource.collections.each_key do |collection|
remove_collection(collection) if (include && !include.include?(collection)) || exclude&.include?(collection)
end
end

def remove_collection(collection_name)
validate_collection_names([collection_name])

# Delete the collection
@blacklist << collection_name

# Tell all collections that their schema is dirty: if we removed a collection, all
# relations to this collection are now invalid and should be unpublished.
collections.each_value(&:mark_schema_as_dirty)
end

def published?(collection_name)
!@blacklist.include?(collection_name)
end

private

def validate_collection_names(names)
names.each { |name| get_collection(name) }
end
end
end
end
end
Loading

0 comments on commit 7550e10

Please sign in to comment.