Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e4d5c1c
feat: add route for live query chart
nicolasalexandre9 Oct 31, 2024
4d0f53e
feat: add support of live query segment
nicolasalexandre9 Nov 4, 2024
7b3e4bd
feat: add configuration & name to datasource
matthv Nov 7, 2024
510dfa3
chore: start implementing datasources to capabilities
matthv Nov 7, 2024
b0fc6d8
feat: add native query route
nicolasalexandre9 Nov 18, 2024
8f822f4
feat: add support of live query segment
nicolasalexandre9 Nov 19, 2024
ade1bdf
feat: add query-validator utils
matthv Nov 19, 2024
bf98ed0
feat: generate conditions tree from live query segment
nicolasalexandre9 Nov 19, 2024
ed2428c
feat: update module query validator checks
matthv Nov 19, 2024
5b9b65d
feat: validate query before use it
nicolasalexandre9 Nov 20, 2024
1a4c16c
fix(queryValidator): add injection patternn & select upcase
matthv Nov 20, 2024
967c1f7
chore: add tests on queryValidator
matthv Nov 20, 2024
33cbdc7
test: add tests on query string parser
nicolasalexandre9 Nov 20, 2024
0bc58f4
feat: use connection name instead of datasource name
nicolasalexandre9 Nov 20, 2024
24e354d
feat: bind context variables to sql query
nicolasalexandre9 Nov 21, 2024
1116612
feat: update native query route
nicolasalexandre9 Nov 22, 2024
7dd541c
feat: inject context variable in live query segment
nicolasalexandre9 Nov 25, 2024
e4306cd
test: add tests on native query route
nicolasalexandre9 Nov 27, 2024
161f386
test: update tests on list route
nicolasalexandre9 Nov 27, 2024
c531097
test: update tests on query string parser
nicolasalexandre9 Nov 27, 2024
ac8a0b2
feat(capabilities): update collections route and add tests
nicolasalexandre9 Nov 28, 2024
6ed9690
feat: force format result of execute_native_query funciton
nicolasalexandre9 Dec 2, 2024
ac501e4
feat: apply live query segment on list, export and count routes
nicolasalexandre9 Dec 3, 2024
a9b0fa4
feat: update permissions and add live query segment permissions
nicolasalexandre9 Dec 4, 2024
0e00833
feat: remove useless method parse_query_segment and add test on query…
nicolasalexandre9 Dec 4, 2024
8b14d1c
feat: raise error when connection is unknown
nicolasalexandre9 Dec 4, 2024
52e9575
test: add tests on queryHandler
nicolasalexandre9 Dec 4, 2024
e0c052e
feat: raise error when connection name is unknown or missing
nicolasalexandre9 Dec 4, 2024
3131522
chore: update error messages
nicolasalexandre9 Dec 10, 2024
6e1c52e
chore: lint rubocop
nicolasalexandre9 Dec 10, 2024
91dec54
test: update existing tests after linting rubocop
nicolasalexandre9 Dec 10, 2024
1427324
test: update existing tests after linting rubocop
nicolasalexandre9 Dec 11, 2024
ff6fad0
test: fix broken test on times comparaisons
nicolasalexandre9 Dec 11, 2024
43f6b3d
test: add tests on permissions
nicolasalexandre9 Dec 11, 2024
5e49b7e
test: add tests on datasource and datasource_customizer
nicolasalexandre9 Dec 11, 2024
445d825
fix: binding symbol in query
nicolasalexandre9 Dec 13, 2024
ff0e8aa
feat: rename method build symbol and fix test
nicolasalexandre9 Dec 16, 2024
d7d210d
test: add test on toolkit datasource
nicolasalexandre9 Dec 16, 2024
6ff8ea0
test: fix test on native_query_spec
nicolasalexandre9 Dec 16, 2024
6718800
feat: remove useless attribute name from datasource
nicolasalexandre9 Dec 16, 2024
7911755
chore(test_toolkit): update gemspec config
nicolasalexandre9 Dec 19, 2024
092e93c
chore: remove useless code and refactoring
nicolasalexandre9 Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .rubocop.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@ Gemspec/OrderedDependencies:
- 'packages/forest_admin_agent/forest_admin_agent.gemspec'
- 'packages/forest_admin_rails/forest_admin_rails.gemspec'
- 'packages/forest_admin_datasource_toolkit/forest_admin_datasource_toolkit.gemspec'
- 'packages/forest_admin_test_toolkit/forest_admin_test_toolkit.gemspec'
- 'packages/forest_admin_datasource_customizer/forest_admin_datasource_customizer.gemspec'
- 'packages/forest_admin_datasource_active_record/forest_admin_datasource_active_record.gemspec'

Expand All@@ -25,6 +26,7 @@ Gemspec/RequireMFA:
- 'packages/forest_admin_agent/forest_admin_agent.gemspec'
- 'packages/forest_admin_rails/forest_admin_rails.gemspec'
- 'packages/forest_admin_datasource_toolkit/forest_admin_datasource_toolkit.gemspec'
- 'packages/forest_admin_test_toolkit/forest_admin_test_toolkit.gemspec'
- 'packages/forest_admin_datasource_customizer/forest_admin_datasource_customizer.gemspec'
- 'packages/forest_admin_datasource_active_record/forest_admin_datasource_active_record.gemspec'

Expand DownExpand Up@@ -125,6 +127,13 @@ Style/StringLiterals:
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/version.rb'
- 'packages/forest_admin_datasource_toolkit/spec/spec_helper.rb'
- 'packages/forest_admin_datasource_toolkit/forest_admin_test_toolkit.gemspec'
- 'packages/forest_admin_test_toolkit/Gemfile'
- 'packages/forest_admin_test_toolkit/Rakefile'
- 'packages/forest_admin_test_toolkit/bin/console'
- 'packages/forest_admin_test_toolkit/lib/forest_admin_test_toolkit.rb'
- 'packages/forest_admin_test_toolkit/lib/forest_admin_test_toolkit/version.rb'
- 'packages/forest_admin_test_toolkit/spec/spec_helper.rb'
- 'packages/forest_admin_datasource_customizer/forest_admin_datasource_customizer.gemspec'
- 'packages/forest_admin_datasource_customizer/Gemfile'
- 'packages/forest_admin_datasource_customizer/Rakefile'
Expand DownExpand Up@@ -248,6 +257,7 @@ Metrics/ClassLength:
- 'packages/forest_admin_agent/lib/forest_admin_agent/utils/query_string_parser.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/datasource.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/collection_customizer.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb'
Expand Down
6 changes: 3 additions & 3 deletions Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,9 +5,9 @@ gemspec
group :development, :tests do
gem "overcommit", "~> 0.60"
gem "rspec", "~> 3.0"
gem "rubocop", "~> 1.33"
gem "rubocop-performance", "~> 1.19"
gem "rubocop-rspec", "~> 2.23"
gem "rubocop"
gem "rubocop-performance"
gem "rubocop-rspec"
gem 'simplecov', "~> 0.22", require: false
gem 'simplecov_json_formatter', "~> 0.1.4"
end
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ def self.routes
Security::ScopeInvalidation.new.routes,
Charts::Charts.new.routes,
Capabilities::Collections.new.routes,
Resources::NativeQuery.new.routes,
Resources::Count.new.routes,
Resources::Delete.new.routes,
Resources::Csv.new.routes,
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,8 @@ def initialize

def build(args)
@datasource = ForestAdminAgent::Facades::Container.datasource
return unless args[:params]['collection_name']

@collection = @datasource.get_collection(args[:params]['collection_name'])
end

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,13 @@ def handle_request(args = {})
@datasource = ForestAdminAgent::Facades::Container.datasource
collections = args[:params]['collectionNames'] || []

connections = []
ForestAdminAgent::Builder::AgentFactory.instance.customizer.datasources.map do |root_datasource|
connections = connections.union(
root_datasource.live_query_connections.keys.map { |connection_name| { name: connection_name } }
)
end

result = collections.map do |collection_name|
collection = @datasource.get_collection(collection_name)
{
Expand All@@ -34,7 +41,8 @@ def handle_request(args = {})

{
content: {
collections: result
collections: result,
nativeQueryConnections: connections
},
status: 200
}
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
module ForestAdminAgent
module Routes
module QueryHandler
include ForestAdminAgent::Utils
include ForestAdminAgent::Builder
include ForestAdminDatasourceToolkit::Components::Query
include ForestAdminDatasourceToolkit::Validations

def inject_context_variables(connection_name, query, permissions, caller, context_variables)
user = permissions.get_user_data(caller.id)
team = permissions.get_team(caller.rendering_id)
context_variables = ContextVariables.new(team, user, context_variables)

ContextVariablesInjector.inject_context_in_native_query(connection_name, query, context_variables)
end

def execute_query(query, connection_name, permissions, caller, context_variables)
root_datasource = AgentFactory.instance.customizer.get_root_datasource_by_connection(connection_name)
query = query.strip
query, context_variables = inject_context_variables(connection_name, query, permissions, caller,
context_variables)

root_datasource.execute_native_query(
connection_name,
query,
context_variables.values
)
end

def parse_query_segment(collection, args, permissions, caller)
return unless args[:params][:segmentQuery]

unless args[:params][:connectionName]
raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Missing native query connection attribute'
end

QueryValidator.valid?(args[:params][:segmentQuery])

permissions.can_execute_query_segment?(collection, args[:params][:segmentQuery], args[:params][:connectionName])

ids = execute_query(
args[:params][:segmentQuery],
args[:params][:connectionName],
permissions,
caller,
args[:params][:contextVariables]
).map(&:values)

condition_tree_segment = ConditionTree::ConditionTreeFactory.match_ids(collection, ids)
ConditionTreeValidator.validate(condition_tree_segment, collection)

condition_tree_segment
end
end
end
end
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ module Resources
class Count < AbstractAuthenticatedRoute
include ForestAdminAgent::Builder
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
include ForestAdminAgent::Routes::QueryHandler

def setup_routes
add_route('forest_count', 'get', '/:collection_name/count', ->(args) { handle_request(args) })

Expand All@@ -18,7 +20,16 @@ def handle_request(args = {})

if @collection.is_countable?
filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
condition_tree: @permissions.get_scope(@collection)
condition_tree: ConditionTreeFactory.intersect(
[
@permissions.get_scope(@collection),
parse_query_segment(@collection, args, @permissions, @caller),
ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(@collection, args)
]
),
search: QueryStringParser.parse_search(@collection, args),
search_extended: QueryStringParser.parse_search_extended(args),
segment: QueryStringParser.parse_segment(@collection, args)
)
aggregation = ForestAdminDatasourceToolkit::Components::Query::Aggregation.new(operation: 'Count')
result = @collection.aggregate(@caller, filter, aggregation)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ module Resources
class Csv < AbstractAuthenticatedRoute
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
include ForestAdminAgent::Utils
include ForestAdminAgent::Routes::QueryHandler

def setup_routes
add_route(
Expand All@@ -25,6 +26,7 @@ def handle_request(args = {})
condition_tree: ConditionTreeFactory.intersect(
[
@permissions.get_scope(@collection),
parse_query_segment(@collection, args, @permissions, @caller),
ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(
@collection, args
)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ module Routes
module Resources
class List < AbstractAuthenticatedRoute
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree
include ForestAdminAgent::Utils
include ForestAdminAgent::Routes::QueryHandler

def setup_routes
add_route('forest_list', 'get', '/:collection_name', ->(args) { handle_request(args) })
Expand All@@ -17,20 +19,21 @@ def handle_request(args = {})
@permissions.can?(:browse, @collection)

filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
condition_tree: ConditionTreeFactory.intersect([
@permissions.get_scope(@collection),
ForestAdminAgent::Utils::QueryStringParser.parse_condition_tree(
@collection, args
)
]),
page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args),
search: ForestAdminAgent::Utils::QueryStringParser.parse_search(@collection, args),
search_extended: ForestAdminAgent::Utils::QueryStringParser.parse_search_extended(args),
sort: ForestAdminAgent::Utils::QueryStringParser.parse_sort(@collection, args),
segment: ForestAdminAgent::Utils::QueryStringParser.parse_segment(@collection, args)
condition_tree: ConditionTreeFactory.intersect(
[
@permissions.get_scope(@collection),
QueryStringParser.parse_condition_tree(@collection, args),
parse_query_segment(@collection, args, @permissions, @caller)
]
),
page: QueryStringParser.parse_pagination(args),
search: QueryStringParser.parse_search(@collection, args),
search_extended: QueryStringParser.parse_search_extended(args),
sort: QueryStringParser.parse_sort(@collection, args),
segment: QueryStringParser.parse_segment(@collection, args)
)

projection = ForestAdminAgent::Utils::QueryStringParser.parse_projection_with_pks(@collection, args)
projection = QueryStringParser.parse_projection_with_pks(@collection, args)
records = @collection.list(@caller, filter, projection)

{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
require 'jsonapi-serializers'
require 'active_support/inflector'

module ForestAdminAgent
module Routes
module Resources
class NativeQuery < AbstractAuthenticatedRoute
include ForestAdminAgent::Builder
include ForestAdminAgent::Utils
include ForestAdminDatasourceToolkit::Exceptions
include ForestAdminDatasourceToolkit::Components::Charts
include ForestAdminAgent::Routes::QueryHandler

def setup_routes
add_route(
'forest_native_query',
'post',
'/_internal/native_query',
lambda { |args|
handle_request(args)
}
)

self
end

def handle_request(args = {})
build(args)
query = args[:params][:query].strip

QueryValidator.valid?(query)
unless args[:params][:connectionName]
raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Missing native query connection attribute'
end

@permissions.can_chart?(args[:params])

query.gsub!('?', args[:params][:record_id].to_s) if args[:params][:record_id]
self.type = args[:params][:type]
result = execute_query(
query,
args[:params][:connectionName],
@permissions,
@caller,
args[:params][:contextVariables]
)

{ content: Serializer::ForestChartSerializer.serialize(send(:"make_#{@type}", result)) }
end

private

def type=(type)
chart_types = %w[Value Objective Pie Line Leaderboard]
unless chart_types.include?(type)
raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Invalid Chart type #{type}"
end

@type = type.downcase
end

def raise_error(result, key_names)
raise ForestException,
"The result columns must be named #{key_names} instead of '#{result.keys.join("', '")}'"
end

def make_value(result)
return unless result.count

result = result.first

raise_error(result, "'value'") unless result.key?(:value)

ValueChart.new(result[:value] || 0, result[:previous] || nil).serialize
end

def make_objective(result)
return unless result.count

result = result.first

raise_error(result, "'value', 'objective'") unless result.key?(:value) || result.key?(:objective)

ObjectiveChart.new(result[:value] || 0, result[:objective]).serialize
end

def make_pie(result)
return unless result.count

raise_error(result[0], "'key', 'value'") if !result[0]&.key?(:value) || !result[0]&.key?(:key)

PieChart.new(result).serialize
end

def make_leaderboard(result)
return unless result.count

raise_error(result[0], "'key', 'value'") if !result[0]&.key?(:value) || !result[0]&.key?(:key)

LeaderboardChart.new(result).serialize
end

def make_line(result)
return unless result.count

result = result.map! do |result_line|
raise_error(result_line, "'key', 'value'") if !result_line.key?(:value) || !result_line.key?(:key)

{ label: result_line[:key], values: { value: result_line[:value] } }
end

LineChart.new(result).serialize
end
end
end
end
end
Original file line numberDiff line numberDiff line change
Expand Up@@ -21,8 +21,7 @@ def handle_request(args = {})
id = Utils::Id.unpack_id(@collection, args[:params]['id'], with_key: true)
condition_tree = ConditionTree::ConditionTreeFactory.match_records(@collection, [id])
filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
condition_tree: ConditionTree::ConditionTreeFactory.intersect([condition_tree, scope]),
page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
condition_tree: ConditionTree::ConditionTreeFactory.intersect([condition_tree, scope])
)
data = format_attributes(args)
@collection.update(@caller, filter, data)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ def setup_routes
def handle_request(args)
# Check if user is logged
Utils::QueryStringParser.parse_caller(args)
Permissions.invalidate_cache('forest.scopes')
Permissions.invalidate_cache('forest.rendering')

{ content: nil, status: 204 }
end
Expand Down
Loading