forest_admin_datasource_toolkit 1.0.0.pre.beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/README.md +31 -0
- data/Rakefile +12 -0
- data/forest_admin_datasource_toolkit.gemspec +37 -0
- data/lib/forest_admin_datasource_toolkit/collection.rb +50 -0
- data/lib/forest_admin_datasource_toolkit/components/caller.rb +31 -0
- data/lib/forest_admin_datasource_toolkit/components/contracts/collection_contract.rb +51 -0
- data/lib/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rb +27 -0
- data/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb +23 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_equivalent.rb +66 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb +123 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb +70 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_branch.rb +68 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb +144 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/operators.rb +100 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb +123 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb +48 -0
- data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/times.rb +112 -0
- data/lib/forest_admin_datasource_toolkit/components/query/filter.rb +45 -0
- data/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb +158 -0
- data/lib/forest_admin_datasource_toolkit/components/query/page.rb +14 -0
- data/lib/forest_admin_datasource_toolkit/components/query/projection.rb +42 -0
- data/lib/forest_admin_datasource_toolkit/components/query/projection_factory.rb +30 -0
- data/lib/forest_admin_datasource_toolkit/datasource.rb +29 -0
- data/lib/forest_admin_datasource_toolkit/exceptions/forest_exception.rb +10 -0
- data/lib/forest_admin_datasource_toolkit/schema/column_schema.rb +34 -0
- data/lib/forest_admin_datasource_toolkit/schema/primitive_type.rb +31 -0
- data/lib/forest_admin_datasource_toolkit/schema/relation_schema.rb +13 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rb +26 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/many_to_one_schema.rb +16 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/one_to_many_schema.rb +16 -0
- data/lib/forest_admin_datasource_toolkit/schema/relations/one_to_one_schema.rb +16 -0
- data/lib/forest_admin_datasource_toolkit/utils/collection.rb +162 -0
- data/lib/forest_admin_datasource_toolkit/utils/record.rb +20 -0
- data/lib/forest_admin_datasource_toolkit/utils/schema.rb +42 -0
- data/lib/forest_admin_datasource_toolkit/version.rb +3 -0
- data/lib/forest_admin_datasource_toolkit.rb +10 -0
- data/sig/forest_admin_datasource_toolkit/collection.rbs +26 -0
- data/sig/forest_admin_datasource_toolkit/components/contracts/collection_contract.rbs +29 -0
- data/sig/forest_admin_datasource_toolkit/components/contracts/datasource_contract.rbs +17 -0
- data/sig/forest_admin_datasource_toolkit/datasource.rbs +12 -0
- data/sig/forest_admin_datasource_toolkit/schema/column_schema.rbs +15 -0
- data/sig/forest_admin_datasource_toolkit/schema/relation_schema.rbs +8 -0
- data/sig/forest_admin_datasource_toolkit/schema/relations/many_relation_schema.rbs +10 -0
- data/sig/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rbs +11 -0
- data/sig/forest_admin_datasource_toolkit/schema/relations/single_relation_schema.rbs +10 -0
- data/sig/forest_admin_datasource_toolkit.rbs +4 -0
- metadata +126 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
|
2
|
+
module Components
|
|
3
|
+
module Query
|
|
4
|
+
module ConditionTree
|
|
5
|
+
module Nodes
|
|
6
|
+
class ConditionTreeLeaf < ConditionTree
|
|
7
|
+
include ForestAdminDatasourceToolkit::Utils
|
|
8
|
+
include ForestAdminDatasourceToolkit::Exceptions
|
|
9
|
+
|
|
10
|
+
attr_reader :field, :operator, :value
|
|
11
|
+
|
|
12
|
+
def initialize(field, operator, value = nil)
|
|
13
|
+
@field = field
|
|
14
|
+
@operator = operator
|
|
15
|
+
@value = value
|
|
16
|
+
valid_operator(@operator) if @operator
|
|
17
|
+
super()
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_h
|
|
21
|
+
{
|
|
22
|
+
field: @field,
|
|
23
|
+
operator: @operator,
|
|
24
|
+
value: @value
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def valid_operator(value)
|
|
29
|
+
return if Operators.exist?(value)
|
|
30
|
+
|
|
31
|
+
raise ForestException, "Invalid operators, the #{value} operator does not exist."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def inverse
|
|
35
|
+
return override(operator: "Not_#{@operator}") if Operators.exist?("Not_#{@operator}")
|
|
36
|
+
return override(operator: @operator[4..]) if @operator.start_with?('Not')
|
|
37
|
+
|
|
38
|
+
case @operator
|
|
39
|
+
when 'Blank'
|
|
40
|
+
override(operator: 'Present')
|
|
41
|
+
when 'Present'
|
|
42
|
+
override(operator: 'Blank')
|
|
43
|
+
else
|
|
44
|
+
raise ForestException, "Operator: #{@operator} cannot be inverted."
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def replace_leafs
|
|
49
|
+
result = yield(self)
|
|
50
|
+
if result.nil?
|
|
51
|
+
nil
|
|
52
|
+
elsif result.is_a?(ConditionTree)
|
|
53
|
+
result
|
|
54
|
+
else
|
|
55
|
+
ConditionTreeFactory.from_array(result)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def match(record, collection, timezone)
|
|
60
|
+
field_value = Record.field_value(record, @field)
|
|
61
|
+
column_type = Utils::Collection.get_field_schema(collection, @field).column_type
|
|
62
|
+
|
|
63
|
+
supported = [
|
|
64
|
+
Operators::IN,
|
|
65
|
+
Operators::EQUAL,
|
|
66
|
+
Operators::LESS_THAN,
|
|
67
|
+
Operators::GREATER_THAN,
|
|
68
|
+
Operators::MATCH,
|
|
69
|
+
Operators::STARTS_WITH,
|
|
70
|
+
Operators::ENDS_WITH,
|
|
71
|
+
Operators::LONGER_THAN,
|
|
72
|
+
Operators::SHORTER_THAN,
|
|
73
|
+
Operators::INCLUDES_ALL,
|
|
74
|
+
Operators::NOT_IN,
|
|
75
|
+
Operators::NOT_EQUAL,
|
|
76
|
+
Operators::NOT_CONTAINS
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
case @operator
|
|
80
|
+
when Operators::IN
|
|
81
|
+
Array(@value).include?(field_value)
|
|
82
|
+
when Operators::EQUAL
|
|
83
|
+
field_value == @value
|
|
84
|
+
when Operators::LESS_THAN
|
|
85
|
+
field_value < @value
|
|
86
|
+
when Operators::GREATER_THAN
|
|
87
|
+
field_value > @value
|
|
88
|
+
when Operators::MATCH
|
|
89
|
+
field_value.is_a?(String) && @value.match(field_value)
|
|
90
|
+
when Operators::STARTS_WITH
|
|
91
|
+
field_value.is_a?(String) && field_value.start_with?(@value)
|
|
92
|
+
when Operators::ENDS_WITH
|
|
93
|
+
field_value.is_a?(String) && field_value.end_with?(@value)
|
|
94
|
+
when Operators::LONGER_THAN
|
|
95
|
+
field_value.is_a?(String) && field_value.length > @value
|
|
96
|
+
when Operators::SHORTER_THAN
|
|
97
|
+
field_value.is_a?(String) && field_value.length < @value
|
|
98
|
+
when Operators::INCLUDES_ALL
|
|
99
|
+
Array(@value).all? { |v| field_value.include?(v) }
|
|
100
|
+
when Operators::NOT_IN, Operators::NOT_EQUAL, Operators::NOT_CONTAINS
|
|
101
|
+
!inverse.match(record, collection, timezone)
|
|
102
|
+
else
|
|
103
|
+
ConditionTreeEquivalent.get_equivalent_tree(
|
|
104
|
+
self,
|
|
105
|
+
supported,
|
|
106
|
+
column_type,
|
|
107
|
+
timezone
|
|
108
|
+
)&.match(record, collection, timezone)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def for_each_leaf
|
|
113
|
+
yield(self)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def every_leaf
|
|
117
|
+
yield(self)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def some_leaf
|
|
121
|
+
yield(self)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def projection
|
|
125
|
+
Projection.new([@field])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def override(args)
|
|
129
|
+
ConditionTreeLeaf.new(
|
|
130
|
+
args[:field] || @field,
|
|
131
|
+
args[:operator] || @operator,
|
|
132
|
+
args[:value] || @value
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def use_interval_operator
|
|
137
|
+
Operators.interval_operators.include?(@operator)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
|
2
|
+
module Components
|
|
3
|
+
module Query
|
|
4
|
+
module ConditionTree
|
|
5
|
+
class Operators
|
|
6
|
+
EQUAL = 'Equal'.freeze
|
|
7
|
+
NOT_EQUAL = 'Not_Equal'.freeze
|
|
8
|
+
LESS_THAN = 'Less_Than'.freeze
|
|
9
|
+
GREATER_THAN = 'Greater_Than'.freeze
|
|
10
|
+
MATCH = 'Match'.freeze
|
|
11
|
+
LIKE = 'Like'.freeze
|
|
12
|
+
I_LIKE = 'ILike'.freeze
|
|
13
|
+
NOT_CONTAINS = 'Not_Contains'.freeze
|
|
14
|
+
CONTAINS = 'Contains'.freeze
|
|
15
|
+
I_CONTAINS = 'IContains'.freeze
|
|
16
|
+
LONGER_THAN = 'Longer_Than'.freeze
|
|
17
|
+
SHORTER_THAN = 'Shorter_Than'.freeze
|
|
18
|
+
INCLUDES_ALL = 'Includes_All'.freeze
|
|
19
|
+
PRESENT = 'Present'.freeze
|
|
20
|
+
BLANK = 'Blank'.freeze
|
|
21
|
+
IN = 'In'.freeze
|
|
22
|
+
NOT_IN = 'Not_In'.freeze
|
|
23
|
+
STARTS_WITH = 'Starts_With'.freeze
|
|
24
|
+
I_STARTS_WITH = 'IStarts_With'.freeze
|
|
25
|
+
ENDS_WITH = 'Ends_With'.freeze
|
|
26
|
+
I_ENDS_WITH = 'IEnds_With'.freeze
|
|
27
|
+
MISSING = 'Missing'.freeze
|
|
28
|
+
BEFORE = 'Before'.freeze
|
|
29
|
+
AFTER = 'After'.freeze
|
|
30
|
+
AFTER_X_HOURS_AGO = 'After_X_Hours_Ago'.freeze
|
|
31
|
+
BEFORE_X_HOURS_AGO = 'Before_X_Hours_Ago'.freeze
|
|
32
|
+
FUTURE = 'Future'.freeze
|
|
33
|
+
PAST = 'Past'.freeze
|
|
34
|
+
TODAY = 'Today'.freeze
|
|
35
|
+
YESTERDAY = 'Yesterday'.freeze
|
|
36
|
+
PREVIOUS_WEEK = 'Previous_Week'.freeze
|
|
37
|
+
PREVIOUS_MONTH = 'Previous_Month'.freeze
|
|
38
|
+
PREVIOUS_QUARTER = 'Previous_Quarter'.freeze
|
|
39
|
+
PREVIOUS_YEAR = 'Previous_Year'.freeze
|
|
40
|
+
PREVIOUS_WEEK_TO_DATE = 'Previous_Week_To_Date'.freeze
|
|
41
|
+
PREVIOUS_MONTH_TO_DATE = 'Previous_Month_To_Date'.freeze
|
|
42
|
+
PREVIOUS_QUARTER_TO_DATE = 'Previous_Quarter_To_Date'.freeze
|
|
43
|
+
PREVIOUS_YEAR_TO_DATE = 'Previous_Year_To_Date'.freeze
|
|
44
|
+
PREVIOUS_X_DAYS = 'Previous_X_Days'.freeze
|
|
45
|
+
PREVIOUS_X_DAYS_TO_DATE = 'Previous_X_Days_To_Date'.freeze
|
|
46
|
+
|
|
47
|
+
def self.all
|
|
48
|
+
constants
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.exist?(operator_value)
|
|
52
|
+
case operator_value
|
|
53
|
+
when 'ILike'
|
|
54
|
+
operator_value = 'I_LIKE'
|
|
55
|
+
when 'IContains'
|
|
56
|
+
operator_value = 'I_CONTAINS'
|
|
57
|
+
when 'IStarts_With'
|
|
58
|
+
operator_value = 'I_STARTS_WITH'
|
|
59
|
+
when 'IEnds_With'
|
|
60
|
+
operator_value = 'I_ENDS_WITH'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
all.include?(operator_value.upcase.to_sym)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.interval_operators
|
|
67
|
+
[
|
|
68
|
+
self::TODAY,
|
|
69
|
+
self::YESTERDAY,
|
|
70
|
+
self::PREVIOUS_MONTH,
|
|
71
|
+
self::PREVIOUS_QUARTER,
|
|
72
|
+
self::PREVIOUS_WEEK,
|
|
73
|
+
self::PREVIOUS_YEAR,
|
|
74
|
+
self::PREVIOUS_MONTH_TO_DATE,
|
|
75
|
+
self::PREVIOUS_QUARTER_TO_DATE,
|
|
76
|
+
self::PREVIOUS_WEEK_TO_DATE,
|
|
77
|
+
self::PREVIOUS_X_DAYS_TO_DATE,
|
|
78
|
+
self::PREVIOUS_X_DAYS,
|
|
79
|
+
self::PREVIOUS_YEAR_TO_DATE
|
|
80
|
+
]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.unique_operators
|
|
84
|
+
[
|
|
85
|
+
self::EQUAL,
|
|
86
|
+
self::NOT_EQUAL,
|
|
87
|
+
self::LESS_THAN,
|
|
88
|
+
self::GREATER_THAN,
|
|
89
|
+
self::MATCH,
|
|
90
|
+
self::NOT_CONTAINS,
|
|
91
|
+
self::LONGER_THAN,
|
|
92
|
+
self::SHORTER_THAN,
|
|
93
|
+
self::INCLUDES_ALL
|
|
94
|
+
]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
|
2
|
+
module Components
|
|
3
|
+
module Query
|
|
4
|
+
module ConditionTree
|
|
5
|
+
module Transforms
|
|
6
|
+
class Comparisons
|
|
7
|
+
include ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Nodes
|
|
8
|
+
|
|
9
|
+
def self.transforms
|
|
10
|
+
{
|
|
11
|
+
Operators::BLANK => [
|
|
12
|
+
{
|
|
13
|
+
depends_on: [Operators::IN],
|
|
14
|
+
for_types: ['String'],
|
|
15
|
+
replacer: ->(leaf) { leaf.override({ operator: Operators::IN, value: [nil, ''] }) }
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
depends_on: [Operators::MISSING],
|
|
19
|
+
replacer: ->(leaf) { leaf.override({ operator: Operators::MISSING }) }
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
Operators::MISSING => [
|
|
23
|
+
{
|
|
24
|
+
depends_on: [Operators::EQUAL],
|
|
25
|
+
replacer: ->(leaf) { leaf.override({ operator: Operators::EQUAL, value: nil }) }
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
Operators::PRESENT => [
|
|
29
|
+
{
|
|
30
|
+
depends_on: [Operators::NOT_IN],
|
|
31
|
+
for_types: ['String'],
|
|
32
|
+
replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_IN, value: [nil, ''] }) }
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
depends_on: [Operators::NOT_EQUAL],
|
|
36
|
+
replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_EQUAL, value: nil }) }
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
Operators::EQUAL => [
|
|
40
|
+
{
|
|
41
|
+
depends_on: [Operators::IN],
|
|
42
|
+
replacer: ->(leaf) { leaf.override({ operator: Operators::IN, value: [leaf.value] }) }
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
Operators::IN => [
|
|
46
|
+
{
|
|
47
|
+
depends_on: [Operators::EQUAL, Operators::MATCH],
|
|
48
|
+
for_types: ['String'],
|
|
49
|
+
replacer: lambda { |leaf|
|
|
50
|
+
values = leaf.value
|
|
51
|
+
conditions = []
|
|
52
|
+
|
|
53
|
+
[nil, ''].each do |value|
|
|
54
|
+
if values.include?(value)
|
|
55
|
+
conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::EQUAL, value))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if values.any? { |value| !value.nil? && value != '' }
|
|
60
|
+
escaped = values.filter { |value| !value.nil? && value != '' }
|
|
61
|
+
|
|
62
|
+
conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::MATCH,
|
|
63
|
+
"/(#{escaped.join("|")})/g"))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
ConditionTreeFactory.union(conditions)
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
depends_on: [Operators::EQUAL],
|
|
71
|
+
replacer: lambda { |leaf|
|
|
72
|
+
ConditionTreeFactory.union(
|
|
73
|
+
leaf.value.map { |item| leaf.override({ operator: Operators::EQUAL, value: item }) }
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
Operators::NOT_EQUAL => [
|
|
79
|
+
{
|
|
80
|
+
depends_on: [Operators::NOT_IN],
|
|
81
|
+
replacer: ->(leaf) { leaf.override({ operator: Operators::NOT_IN, value: [leaf.value] }) }
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
Operators::NOT_IN => [
|
|
85
|
+
{
|
|
86
|
+
depends_on: [Operators::NOT_EQUAL, Operators::MATCH],
|
|
87
|
+
for_types: ['String'],
|
|
88
|
+
replacer: lambda { |leaf|
|
|
89
|
+
values = leaf.value
|
|
90
|
+
conditions = []
|
|
91
|
+
|
|
92
|
+
[nil, ''].each do |value|
|
|
93
|
+
if values.include?(value)
|
|
94
|
+
conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::NOT_EQUAL, value))
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if values.any? { |value| !value.nil? && value != '' }
|
|
99
|
+
escaped = values.filter { |value| !value.nil? && value != '' }
|
|
100
|
+
conditions.push(ConditionTreeLeaf.new(leaf.field, Operators::MATCH,
|
|
101
|
+
"/(?!(#{escaped.join("|")}))/g"))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
ConditionTreeFactory.intersect(conditions)
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
depends_on: [Operators::NOT_EQUAL],
|
|
109
|
+
replacer: lambda { |leaf|
|
|
110
|
+
ConditionTreeFactory.intersect(
|
|
111
|
+
leaf.value.map { |item| leaf.override({ operator: Operators::NOT_EQUAL, value: item }) }
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/pattern.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
|
2
|
+
module Components
|
|
3
|
+
module Query
|
|
4
|
+
module ConditionTree
|
|
5
|
+
module Transforms
|
|
6
|
+
class Pattern
|
|
7
|
+
def self.likes(get_pattern, case_sensitive)
|
|
8
|
+
operator = case_sensitive ? Operators::LIKE : Operators::I_LIKE
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
dependsOn: [operator],
|
|
12
|
+
forTypes: ['String'],
|
|
13
|
+
replacer: ->(leaf) { leaf.override(operator: operator, value: get_pattern.call(leaf.value)) }
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.match(case_sensitive)
|
|
18
|
+
{
|
|
19
|
+
dependsOn: [Operators::MATCH],
|
|
20
|
+
forTypes: ['String'],
|
|
21
|
+
replacer: lambda { |leaf|
|
|
22
|
+
regex = leaf.value.gsub(/([\.\\\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:\-])/, '\\\\\1')
|
|
23
|
+
regex.gsub!('%', '.*')
|
|
24
|
+
regex.tr!('_', '.')
|
|
25
|
+
|
|
26
|
+
leaf.override(operator: Operators::MATCH, value: "/^#{regex}$/#{case_sensitive ? "" : "i"}")
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.transforms
|
|
32
|
+
{
|
|
33
|
+
Operators::CONTAINS => [likes(->(value) { "%#{value}%" }, true)],
|
|
34
|
+
Operators::STARTS_WITH => [likes(->(value) { "#{value}%" }, true)],
|
|
35
|
+
Operators::ENDS_WITH => [likes(->(value) { "%#{value}" }, true)],
|
|
36
|
+
Operators::I_CONTAINS => [likes(->(value) { "%#{value}%" }, false)],
|
|
37
|
+
Operators::I_STARTS_WITH => [likes(->(value) { "#{value}%" }, false)],
|
|
38
|
+
Operators::I_ENDS_WITH => [likes(->(value) { "%#{value}" }, false)],
|
|
39
|
+
Operators::I_LIKE => [match(false)],
|
|
40
|
+
Operators::LIKE => [match(true)]
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require 'active_support/all'
|
|
2
|
+
require 'active_support/core_ext/numeric/time'
|
|
3
|
+
|
|
4
|
+
module ForestAdminDatasourceToolkit
|
|
5
|
+
module Components
|
|
6
|
+
module Query
|
|
7
|
+
module ConditionTree
|
|
8
|
+
module Transforms
|
|
9
|
+
class Times
|
|
10
|
+
def self.format(value)
|
|
11
|
+
value.in_time_zone('UTC').iso8601
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.compare(operator)
|
|
15
|
+
{
|
|
16
|
+
dependsOn: [operator],
|
|
17
|
+
forTypes: ['Date', 'Dateonly'],
|
|
18
|
+
replacer: lambda { |leaf, tz|
|
|
19
|
+
leaf.override(operator: operator, value: format(yield(Time.now.in_time_zone(tz), leaf.value)))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.interval(start_fn, end_fn)
|
|
25
|
+
{
|
|
26
|
+
dependsOn: [Operators::LESS_THAN, Operators::GREATER_THAN],
|
|
27
|
+
forTypes: ['Date', 'Dateonly'],
|
|
28
|
+
replacer: lambda do |leaf, tz|
|
|
29
|
+
value_greater_than = if leaf.value.nil?
|
|
30
|
+
format(start_fn.call(Time.now.in_time_zone(tz)))
|
|
31
|
+
else
|
|
32
|
+
format(start_fn.call(
|
|
33
|
+
Time.now.in_time_zone(tz), leaf.value
|
|
34
|
+
))
|
|
35
|
+
end
|
|
36
|
+
value_less_than = if leaf.value.nil?
|
|
37
|
+
format(end_fn.call(Time.now.in_time_zone(tz)))
|
|
38
|
+
else
|
|
39
|
+
format(end_fn.call(
|
|
40
|
+
Time.now.in_time_zone(tz), leaf.value
|
|
41
|
+
))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
ConditionTreeFactory.intersect(
|
|
45
|
+
[
|
|
46
|
+
leaf.override(operator: Operators::GREATER_THAN, value: value_greater_than),
|
|
47
|
+
leaf.override(operator: Operators::LESS_THAN, value: value_less_than)
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.previous_interval(duration)
|
|
55
|
+
interval(
|
|
56
|
+
lambda { |now|
|
|
57
|
+
duration == 'quarter' ? now.prev_quarter : (now - 1.send(duration)).send("beginning_of_#{duration}")
|
|
58
|
+
},
|
|
59
|
+
->(now) { now.send("beginning_of_#{duration}") }
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.previous_interval_to_date(duration)
|
|
64
|
+
interval(
|
|
65
|
+
->(now) { now.send("beginning_of_#{duration}") },
|
|
66
|
+
->(now) { now }
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.transforms
|
|
71
|
+
{
|
|
72
|
+
Operators::BEFORE => [compare(Operators::LESS_THAN) { |_now, value| Time.parse(value.to_s) }],
|
|
73
|
+
Operators::AFTER => [compare(Operators::GREATER_THAN) { |_now, value| Time.parse(value.to_s) }],
|
|
74
|
+
Operators::PAST => [compare(Operators::LESS_THAN) { |now| now }],
|
|
75
|
+
Operators::FUTURE => [compare(Operators::GREATER_THAN) { |now| now }],
|
|
76
|
+
Operators::BEFORE_X_HOURS_AGO => [compare(Operators::LESS_THAN) { |now, value| now - value.hours }],
|
|
77
|
+
Operators::AFTER_X_HOURS_AGO => [compare(Operators::GREATER_THAN) { |now, value| now - value.hours }],
|
|
78
|
+
Operators::PREVIOUS_WEEK_TO_DATE => [previous_interval_to_date('week')],
|
|
79
|
+
Operators::PREVIOUS_MONTH_TO_DATE => [previous_interval_to_date('month')],
|
|
80
|
+
Operators::PREVIOUS_QUARTER_TO_DATE => [previous_interval_to_date('quarter')],
|
|
81
|
+
Operators::PREVIOUS_YEAR_TO_DATE => [previous_interval_to_date('year')],
|
|
82
|
+
Operators::YESTERDAY => [previous_interval('day')],
|
|
83
|
+
Operators::PREVIOUS_WEEK => [previous_interval('week')],
|
|
84
|
+
Operators::PREVIOUS_MONTH => [previous_interval('month')],
|
|
85
|
+
Operators::PREVIOUS_QUARTER => [previous_interval('quarter')],
|
|
86
|
+
Operators::PREVIOUS_YEAR => [previous_interval('year')],
|
|
87
|
+
Operators::PREVIOUS_X_DAYS_TO_DATE => [
|
|
88
|
+
interval(
|
|
89
|
+
->(now, value) { (now - value.days).beginning_of_day },
|
|
90
|
+
->(now, _value) { now }
|
|
91
|
+
)
|
|
92
|
+
],
|
|
93
|
+
Operators::PREVIOUS_X_DAYS => [
|
|
94
|
+
interval(
|
|
95
|
+
->(now, value) { (now - value.days).beginning_of_day },
|
|
96
|
+
->(now, _value) { now.beginning_of_day }
|
|
97
|
+
)
|
|
98
|
+
],
|
|
99
|
+
Operators::TODAY => [
|
|
100
|
+
interval(
|
|
101
|
+
->(now) { now.beginning_of_day },
|
|
102
|
+
->(now) { (now + 1.day).beginning_of_day }
|
|
103
|
+
)
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module ForestAdminDatasourceToolkit
|
|
2
|
+
module Components
|
|
3
|
+
module Query
|
|
4
|
+
class Filter
|
|
5
|
+
attr_reader :condition_tree, :segment, :sort, :search, :search_extended, :page
|
|
6
|
+
|
|
7
|
+
def initialize(condition_tree: nil, search: nil, search_extended: nil, segment: nil, sort: nil, page: nil)
|
|
8
|
+
@condition_tree = condition_tree
|
|
9
|
+
@search = search
|
|
10
|
+
@search_extended = search_extended
|
|
11
|
+
@segment = segment
|
|
12
|
+
@sort = sort
|
|
13
|
+
@page = page
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
{
|
|
18
|
+
condition_tree: @condition_tree,
|
|
19
|
+
search: @search,
|
|
20
|
+
search_extended: @search_extended,
|
|
21
|
+
segment: @segment,
|
|
22
|
+
sort: @sort,
|
|
23
|
+
page: @page
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def nestable?
|
|
28
|
+
!@search && !@segment
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def override(args)
|
|
32
|
+
args = to_h.merge(args)
|
|
33
|
+
|
|
34
|
+
Filter.new(**args)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def nest(prefix)
|
|
38
|
+
raise ForestException, "Filter can't be nested" unless nestable?
|
|
39
|
+
|
|
40
|
+
override(condition_tree: @condition_tree&.nest(prefix))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|