refine-rails 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +413 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/refine_rails_manifest.js +0 -0
  5. data/app/assets/javascripts/refine-stimulus.esm.js +2 -0
  6. data/app/assets/javascripts/refine-stimulus.esm.js.map +1 -0
  7. data/app/assets/javascripts/refine-stimulus.js +2 -0
  8. data/app/assets/javascripts/refine-stimulus.js.map +1 -0
  9. data/app/assets/javascripts/refine-stimulus.modern.js +2 -0
  10. data/app/assets/javascripts/refine-stimulus.modern.js.map +1 -0
  11. data/app/assets/javascripts/refine-stimulus.umd.js +2 -0
  12. data/app/assets/javascripts/refine-stimulus.umd.js.map +1 -0
  13. data/app/assets/stylesheets/index.css +1873 -0
  14. data/app/assets/stylesheets/index.tailwind.css +1035 -0
  15. data/app/controllers/refine/blueprints_controller.rb +80 -0
  16. data/app/controllers/refine/filter_application_controller.rb +29 -0
  17. data/app/controllers/refine/inline/criteria_controller.rb +161 -0
  18. data/app/controllers/refine/inline/stored_filters_controller.rb +84 -0
  19. data/app/controllers/refine/stored_filters_controller.rb +69 -0
  20. data/app/javascript/controllers/index.js +66 -0
  21. data/app/javascript/controllers/refine/add-controller.js +42 -0
  22. data/app/javascript/controllers/refine/criterion-form-controller.js +31 -0
  23. data/app/javascript/controllers/refine/date-controller.js +113 -0
  24. data/app/javascript/controllers/refine/defaults-controller.js +32 -0
  25. data/app/javascript/controllers/refine/delete-controller.js +13 -0
  26. data/app/javascript/controllers/refine/filter-pills-controller.js +63 -0
  27. data/app/javascript/controllers/refine/form-controller.js +51 -0
  28. data/app/javascript/controllers/refine/inline-conditions-controller.js +33 -0
  29. data/app/javascript/controllers/refine/popup-controller.js +46 -0
  30. data/app/javascript/controllers/refine/search-filter-controller.js +50 -0
  31. data/app/javascript/controllers/refine/server-refresh-controller.js +43 -0
  32. data/app/javascript/controllers/refine/state-controller.js +220 -0
  33. data/app/javascript/controllers/refine/stored-filter-controller.js +23 -0
  34. data/app/javascript/controllers/refine/submit-form-controller.js +8 -0
  35. data/app/javascript/controllers/refine/toggle-controller.js +12 -0
  36. data/app/javascript/controllers/refine/turbo-stream-form-controller.js +24 -0
  37. data/app/javascript/controllers/refine/turbo-stream-link-controller.js +24 -0
  38. data/app/javascript/controllers/refine/update-controller.js +86 -0
  39. data/app/javascript/index.js +1 -0
  40. data/app/javascript/refine/helpers/index.js +77 -0
  41. data/app/models/refine/blueprints/blueprint.rb +58 -0
  42. data/app/models/refine/blueprints/blueprint_example.json +25 -0
  43. data/app/models/refine/conditions/boolean_condition.rb +112 -0
  44. data/app/models/refine/conditions/clause.rb +38 -0
  45. data/app/models/refine/conditions/clauses.rb +38 -0
  46. data/app/models/refine/conditions/condition.rb +285 -0
  47. data/app/models/refine/conditions/condition_error.rb +1 -0
  48. data/app/models/refine/conditions/date_condition.rb +464 -0
  49. data/app/models/refine/conditions/date_with_time_condition.rb +8 -0
  50. data/app/models/refine/conditions/errors/condition_clause_error.rb +7 -0
  51. data/app/models/refine/conditions/errors/criteria_limit_exceeded_error.rb +2 -0
  52. data/app/models/refine/conditions/errors/option_error.rb +2 -0
  53. data/app/models/refine/conditions/errors/relationship_error.rb +1 -0
  54. data/app/models/refine/conditions/filter_condition.rb +93 -0
  55. data/app/models/refine/conditions/has_clauses.rb +117 -0
  56. data/app/models/refine/conditions/has_meta.rb +10 -0
  57. data/app/models/refine/conditions/has_refinements.rb +156 -0
  58. data/app/models/refine/conditions/numeric_condition.rb +224 -0
  59. data/app/models/refine/conditions/option_condition.rb +260 -0
  60. data/app/models/refine/conditions/text_condition.rb +152 -0
  61. data/app/models/refine/conditions/uses_attributes.rb +168 -0
  62. data/app/models/refine/filter.rb +302 -0
  63. data/app/models/refine/filters/blueprint_editor.rb +102 -0
  64. data/app/models/refine/filters/builder.rb +59 -0
  65. data/app/models/refine/filters/criterion.rb +87 -0
  66. data/app/models/refine/filters/query.rb +82 -0
  67. data/app/models/refine/inline/criteria/input.rb +50 -0
  68. data/app/models/refine/inline/criteria/numeric_refinement.rb +13 -0
  69. data/app/models/refine/inline/criteria/option.rb +2 -0
  70. data/app/models/refine/inline/criterion.rb +141 -0
  71. data/app/models/refine/invalid_filter_error.rb +8 -0
  72. data/app/models/refine/stabilize.rb +29 -0
  73. data/app/models/refine/stabilizers/database_stabilizer.rb +21 -0
  74. data/app/models/refine/stabilizers/errors/url_stabilizer_error.rb +2 -0
  75. data/app/models/refine/stabilizers/url_encoded_stabilizer.rb +21 -0
  76. data/app/models/refine/stored_filter.rb +14 -0
  77. data/app/models/refine/tracks_pending_relationship_subqueries.rb +196 -0
  78. data/app/views/_filter_builder_dropdown.html.erb +63 -0
  79. data/app/views/_filter_pills.html.erb +40 -0
  80. data/app/views/_loading.html.erb +32 -0
  81. data/app/views/refine/blueprints/_add_and.html.erb +25 -0
  82. data/app/views/refine/blueprints/_add_group.html.erb +24 -0
  83. data/app/views/refine/blueprints/_clause_select.html.erb +24 -0
  84. data/app/views/refine/blueprints/_condition_select.html.erb +53 -0
  85. data/app/views/refine/blueprints/_criterion.html.erb +41 -0
  86. data/app/views/refine/blueprints/_criterion_errors.html.erb +7 -0
  87. data/app/views/refine/blueprints/_delete_criterion.html.erb +11 -0
  88. data/app/views/refine/blueprints/_group.html.erb +13 -0
  89. data/app/views/refine/blueprints/_query.html.erb +34 -0
  90. data/app/views/refine/blueprints/_stored_filters.html.erb +23 -0
  91. data/app/views/refine/blueprints/clauses/_date_condition.html.erb +80 -0
  92. data/app/views/refine/blueprints/clauses/_date_picker.html.erb +26 -0
  93. data/app/views/refine/blueprints/clauses/_filter_condition.html.erb +36 -0
  94. data/app/views/refine/blueprints/clauses/_numeric_condition.html.erb +35 -0
  95. data/app/views/refine/blueprints/clauses/_option_condition.html.erb +37 -0
  96. data/app/views/refine/blueprints/clauses/_text_condition.html.erb +13 -0
  97. data/app/views/refine/blueprints/create.turbo_stream.erb +22 -0
  98. data/app/views/refine/blueprints/new.html.erb +7 -0
  99. data/app/views/refine/blueprints/show.html.erb +4 -0
  100. data/app/views/refine/blueprints/show.turbo_stream.erb +22 -0
  101. data/app/views/refine/inline/criteria/_form_fields.html.erb +62 -0
  102. data/app/views/refine/inline/criteria/create.turbo_stream.erb +19 -0
  103. data/app/views/refine/inline/criteria/edit.turbo_stream.erb +26 -0
  104. data/app/views/refine/inline/criteria/index.html.erb +64 -0
  105. data/app/views/refine/inline/criteria/new.turbo_stream.erb +24 -0
  106. data/app/views/refine/inline/filters/_add_first_condition_button.html.erb +19 -0
  107. data/app/views/refine/inline/filters/_and_button.html.erb +26 -0
  108. data/app/views/refine/inline/filters/_criterion.html.erb +23 -0
  109. data/app/views/refine/inline/filters/_group.html.erb +13 -0
  110. data/app/views/refine/inline/filters/_load_button.html.erb +15 -0
  111. data/app/views/refine/inline/filters/_or_button.html.erb +26 -0
  112. data/app/views/refine/inline/filters/_popup.html.erb +26 -0
  113. data/app/views/refine/inline/filters/_save_button.html.erb +15 -0
  114. data/app/views/refine/inline/filters/_show.html.erb +40 -0
  115. data/app/views/refine/inline/inputs/_date_condition.html.erb +7 -0
  116. data/app/views/refine/inline/inputs/_date_condition_days.html.erb +18 -0
  117. data/app/views/refine/inline/inputs/_date_condition_range.html.erb +22 -0
  118. data/app/views/refine/inline/inputs/_date_condition_single.html.erb +9 -0
  119. data/app/views/refine/inline/inputs/_date_picker.html.erb +20 -0
  120. data/app/views/refine/inline/inputs/_numeric_condition.html.erb +23 -0
  121. data/app/views/refine/inline/inputs/_option_condition.html.erb +14 -0
  122. data/app/views/refine/inline/inputs/_text_condition.html.erb +8 -0
  123. data/app/views/refine/inline/stored_filters/find.turbo_stream.erb +19 -0
  124. data/app/views/refine/inline/stored_filters/index.html.erb +28 -0
  125. data/app/views/refine/inline/stored_filters/new.turbo_stream.erb +47 -0
  126. data/app/views/refine/stored_filters/create.turbo_stream.erb +2 -0
  127. data/app/views/refine/stored_filters/find.turbo_stream.erb +5 -0
  128. data/app/views/refine/stored_filters/index.html.erb +39 -0
  129. data/app/views/refine/stored_filters/new.html.erb +29 -0
  130. data/app/views/refine/stored_filters/show.html.erb +1 -0
  131. data/config/locales/en/dates.en.yml +29 -0
  132. data/config/locales/en/en.yml +20 -0
  133. data/config/locales/en/refine.en.yml +187 -0
  134. data/config/routes.rb +17 -0
  135. data/lib/generators/filter/filter_generator.rb +27 -0
  136. data/lib/generators/filter/templates/filter.rb.erb +20 -0
  137. data/lib/refine/rails/engine.rb +15 -0
  138. data/lib/refine/rails/version.rb +5 -0
  139. data/lib/refine/rails.rb +38 -0
  140. data/lib/tasks/refine/rails_tasks.rake +13 -0
  141. metadata +202 -0
@@ -0,0 +1,196 @@
1
+ module Refine
2
+ module TracksPendingRelationshipSubqueries
3
+ def pending_relationship_subquery_depth
4
+ @pending_relationship_subquery_depth ||= []
5
+ end
6
+
7
+ def pending_relationship_subqueries
8
+ # The Hash.new block will dynamically created multi-level nested keys
9
+ # Note: Will create a value of {} when accessed with `dig`
10
+ @pending_relationship_subqueries ||= Hash.new { |h, k| h[k] = h.dup.clear }
11
+ end
12
+
13
+ # Allow filter refinements to be collapsable in order to slot them in a the appropriate depth
14
+ def allow_pending_relationship_to_collapse
15
+ pending_relationship_subqueries.dig(*get_current_relationship)[:collapsible] = true
16
+ end
17
+
18
+ def set_pending_relationship(relation, instance)
19
+ pending_relationship_subquery_depth << relation.to_sym
20
+ # this populates modelA[:children][:modelB]
21
+ pending_relationship_subqueries.dig(*get_current_relationship)[:instance] = instance
22
+ end
23
+
24
+ def get_current_relationship
25
+ pending_relationship_subquery_depth.join(".children.").split(".").map(&:to_sym)
26
+ end
27
+
28
+ def add_pending_joins_relationship_subquery(subquery:)
29
+ add_pending_relationship_subquery(subquery: subquery, primary_key: JOINS)
30
+ end
31
+
32
+ def add_pending_relationship_subquery(subquery:, primary_key:, secondary_key: nil, inverse_clause: false)
33
+ # Add key, query, and secondary keys at the correct depth
34
+ pending_relationship_subqueries.dig(*get_current_relationship)[:key] = primary_key
35
+ pending_relationship_subqueries.dig(*get_current_relationship)[:query] = subquery
36
+ pending_relationship_subqueries.dig(*get_current_relationship)[:secondary] = secondary_key
37
+ pending_relationship_subqueries.dig(*get_current_relationship)[:inverse_clause] = inverse_clause
38
+ end
39
+
40
+ def get_pending_relationship_instance
41
+ get_pending_relationship_item("instance")
42
+ end
43
+
44
+ def get_pending_relationship_item(key)
45
+ # Digging for a particular key will create a {} value, must check presence.
46
+ if pending_relationship_subqueries.dig(*get_current_relationship, key.to_sym).present?
47
+ pending_relationship_subqueries.dig(*get_current_relationship, key.to_sym)
48
+ else
49
+ nil
50
+ end
51
+ end
52
+
53
+ def set_pending_relationship_subquery_wrapper(callback)
54
+ pending_relationship_subqueries.dig(*get_current_relationship)[:wrapper] = callback
55
+ end
56
+
57
+ def relationship_supports_collapsing(instance)
58
+ if get_current_relationship.present?
59
+ return true if pending_relationship_subqueries.dig(*get_current_relationship)[:collapsible] == true
60
+ end
61
+
62
+ (instance.is_a? ActiveRecord::Reflection::BelongsToReflection) || (instance.is_a? ActiveRecord::Reflection::HasOneReflection)
63
+ end
64
+
65
+ # This method can be challenging to understand. Walk through the values with the `can handle nested relationships` test.
66
+ # The query will eventually return the following SQL:
67
+ # SELECT "btt_phones".* FROM "btt_phones"
68
+ # WHERE ("btt_phones"."btt_user_id" IN
69
+ # (SELECT "btt_users"."id" FROM "btt_users" WHERE "btt_users"."id" IN
70
+ # (SELECT "btt_notes"."btt_user_id" FROM "btt_notes" WHERE ("btt_notes"."body" LIKE \'%foo%\'))))
71
+
72
+ def release_pending_relationship
73
+ # instance is the ActiveRecord::Reflection::HasManyReflection between btt_notes and BttUser
74
+ instance = get_pending_relationship_instance
75
+ # Pop off the last key (last relationship)
76
+ # popped = :btt_notes
77
+ popped = pending_relationship_subquery_depth.pop.to_sym
78
+ return if relationship_supports_collapsing(instance)
79
+ # current = [:btt_user]
80
+ current = get_current_relationship
81
+ if current.blank?
82
+ @immediately_commit_pending_relationship_subqueries = true
83
+ return
84
+ end
85
+ # Grab the query one level higher than the current stack (removed during pop)
86
+ # query is the pending_relationship_subqueries[:btt_user][:query] = "SELECT `btt_users`.`id` FROM `btt_users`"
87
+ query = pending_relationship_subqueries.dig(*current)[:query]
88
+ # Build hash to send to commit_subset with popped -> value at popped
89
+ subset = {}
90
+ # subset is a hash with key [:btt_notes] (the popped relationship) = value at popped.
91
+ # subset[:btt_notes].keys = [:instance, :query, :key, :secondary, :inverse_clause]
92
+ # subset[:btt_notes][:query].to_sql
93
+ # "SELECT `btt_notes`.`btt_user_id` FROM `btt_notes` WHERE (`btt_notes`.`body` LIKE '%foo%')"
94
+
95
+ subset[popped] = pending_relationship_subqueries.dig(*current)[:children][popped]
96
+ # Remove popped from pending relationships subqueries -> handled in commit_subset
97
+ # We are removing the "child" relationship we just set to subset (in this case :btt_notes)
98
+ # pending_relationship_subqueries[:btt_user][:children] = {}
99
+ # And assigning the value at [:btt_user][:query] to what is returned in commit_subset
100
+ pending_relationship_subqueries.dig(*current)[:children].except!(popped)
101
+ # query is the high level linking query = "SELECT `btt_users`.`id` FROM `btt_users`"
102
+ # and subset is the hash of the child relationship we just removed from pending_relationship_subqueries
103
+ # subset[:btt_notes].keys = [:instance, :query, :key, :secondary, :inverse_clause]
104
+ pending_relationship_subqueries.dig(*current)[:query] = commit_subset(subset: subset, query: query)
105
+ end
106
+
107
+ def commit_pending_relationship_subqueries
108
+ applied_query = commit_subset(subset: pending_relationship_subqueries)
109
+ @pending_relationship_subqueries = Hash.new { |h, k| h[k] = h.dup.clear }
110
+ applied_query
111
+ end
112
+
113
+ def commit_subset(subset:, query: nil)
114
+ # Turn pending relationship subqueries into nodes to apply
115
+ # Subquery below is the hash value which has a query key.
116
+ subset.each do |relation, subquery|
117
+ # relation = table
118
+ # subquery.keys = [:instance, :query, :key, :secondary] possibly (:children)
119
+ if subquery.dig(:children).present?
120
+ # Send in the values at children as the subset hash and remove from existing subquery
121
+ child_nodes = subquery.delete(:children)
122
+ # If there are children, we recursively call this method again
123
+ # to build up the inner (child) queries first. This allows us
124
+ # to intelligently nest multiple levels of relationships.
125
+ commit_subset(query: subquery[:query], subset: child_nodes)
126
+ end
127
+ # If the subquery has a wrapper proc we are dealing with a compare to 0 situation
128
+ if subquery.dig(:wrapper).respond_to? :call
129
+ subquery[:query] = subquery[:wrapper].call(subquery[:query], subquery[:key], subquery[:secondary])
130
+ end
131
+
132
+ parent_table = subquery[:instance].active_record.arel_table
133
+ linking_key = subquery[:key]
134
+ inner_query = subquery[:query]
135
+
136
+ # Compare database connections for inner and outer query. Refer to ActiveRecord::Reflection
137
+ # Example:
138
+ # class Company < ActiveRecord::Base
139
+ # has_many :clients
140
+ # end
141
+
142
+ # Company.reflect_on_association(:clients).klass
143
+ # # => Client
144
+
145
+ # If the query needs to be flipped because it's a negative do that here
146
+ connecting_method = subquery[:inverse_clause] ? :not_in : :in
147
+
148
+ current_model = subquery[:instance]&.klass
149
+ parent_model = subquery[:instance]&.active_record
150
+ use_multiple_databases = (inner_query.is_a? Arel::SelectManager) && use_multiple_databases?(current_model, parent_model)
151
+ if query.present?
152
+ # If query exists and is a Select Manager we are deeply nested and need to build the query
153
+ # with a WHERE statement
154
+ if query.is_a? Arel::SelectManager
155
+ if use_multiple_databases
156
+ array_of_ids = current_model.connection.exec_query(inner_query.to_sql).rows.flatten
157
+ query.where(parent_table[linking_key.to_s].in(array_of_ids))
158
+ else
159
+ # Same DB, don’t decompose. Note: Where’s called on AREL select managers modify the object in place
160
+ query.where(parent_table[linking_key.to_s].in(inner_query))
161
+ end
162
+ else
163
+ # Otherwise we are joining nodes, which requires an AND statement (ORs are immediately commited)
164
+ # query can be a `Arel::Nodes::In` class (See HMMT test for example)
165
+ # The group() in front of query is required for nested relationship attributes.
166
+ if use_multiple_databases
167
+ array_of_ids = current_model.connection.exec_query(inner_query.to_sql).rows.flatten
168
+ query = group(query).and(group(parent_table[linking_key.to_s].in(array_of_ids)))
169
+ else
170
+ query = group(query).and(group(parent_table[linking_key.to_s].in(inner_query)))
171
+ end
172
+ # query = group(query).and(group(parent_table[linking_key.to_s].in(inner_query)))
173
+ end
174
+ else
175
+ # No existing query, top level of stack
176
+ if use_multiple_databases
177
+ array_of_ids = current_model.connection.exec_query(inner_query.to_sql).rows.flatten
178
+ query = parent_table[linking_key.to_s].send(connecting_method, array_of_ids.uniq)
179
+ else
180
+ query = parent_table[linking_key.to_s].send(connecting_method, inner_query)
181
+ end
182
+ end
183
+ end
184
+ query
185
+ end
186
+
187
+ def use_multiple_databases?(current_model, parent_model)
188
+ # Are the queries on different databases?
189
+ parent_model.connection_db_config.configuration_hash != current_model.connection_db_config.configuration_hash
190
+ end
191
+
192
+ def get_pending_relationship_subquery
193
+ get_pending_relationship_item("query")
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,63 @@
1
+ <%
2
+ unless defined? stored_filters
3
+ stored_filters = false
4
+ end
5
+ %>
6
+ <div
7
+ data-controller="refine--search-filter"
8
+ data-refine--search-filter-search-frame-id-value="index_search"
9
+ data-refine--search-filter-submit-url-value="<%= refine_blueprint_path %>"
10
+ data-action="filter-submit-success@document->refine--search-filter#loadResults"
11
+ >
12
+
13
+ <%= tag.div class: "refine-filter-builder-dropdown-container", data: {
14
+ controller: "refine--toggle"
15
+ } do %>
16
+ <button
17
+ class="refine-filter-condition-toggle-button"
18
+ data-action="refine--toggle#toggle"
19
+ type="button"
20
+ >
21
+ <%= t('global.buttons.filter') %>
22
+ </button>
23
+ <div
24
+ hidden
25
+ data-refine--toggle-target="content"
26
+ data-reveal
27
+ data-transition
28
+ data-transition-enter="transition ease-out duration-100"
29
+ data-transition-enter-start="transform opacity-0 scale-95"
30
+ data-transition-enter-end="transform opacity-100 scale-100"
31
+ data-transition-leave="transition ease-in duration-75"
32
+ data-transition-leave-start="transform opacity-100 scale-100"
33
+ data-transition-leave-end="transform opacity-0 scale-95"
34
+ data-controller="filter-link"
35
+
36
+ class="refine-filter-builder-dropdown-search-box"
37
+ style="min-width: 50vw; max-height: 66vh; overflow: auto;"
38
+ >
39
+ <%= turbo_frame_tag :refine_filter_modal, loading: :lazy,
40
+ src: new_refine_blueprint_path(refine_filters_builder: {stable_id: @stable_id, filter_class: @refine_filter.class.name}, stored_filters: stored_filters.presence) do %>
41
+
42
+ <% end %>
43
+ <div class="refine-filter-builder-dropdown-button-container">
44
+ <button
45
+ class="refine-filter-builder-dropdown-cancel-button"
46
+ data-action="click->refine--toggle#toggle"
47
+ type="button"
48
+ >
49
+ <%= t('global.buttons.cancel') %>
50
+ </button>
51
+ <button
52
+ class="refine-filter-builder-dropdown-apply-button"
53
+ data-action="click->refine--search-filter#search"
54
+ data-filter-link-target="link"
55
+ type="button"
56
+ >
57
+ <%= t('global.buttons.apply') %>
58
+ </button>
59
+ </div>
60
+ </div>
61
+ <% end %>
62
+
63
+ </div>
@@ -0,0 +1,40 @@
1
+ <% if @refine_filter %>
2
+ <%= tag.div id: dom_id(@refine_filter_builder, "query"),
3
+ data: {
4
+ controller: "refine--filter-pills refine--state turbo-visit",
5
+ refine__state_conditions_value: @refine_filter_query.available_conditions_attributes.to_json,
6
+ refine__state_blueprint_value: @refine_filter_builder.blueprint.to_json,
7
+ refine__state_class_name_value: @refine_filter_query.configuration[:class_name],
8
+ refine__state_refresh_url_value: refine_blueprint_path,
9
+ refine__state_client_id_value: @refine_filter_builder.client_id,
10
+ refine__filter_pills_submit_url_value: refine_blueprint_path,
11
+ action: "filter-submit-success->turbo-visit#visit"
12
+ } do
13
+ %>
14
+
15
+ <% if @refine_filter.human_readable_criterions.any? %>
16
+ <div class="text-blue-600 pb-3 font-medium text-sm rounded w-full">
17
+ <div class="w-full flex pb-1 refine-filter-condition-pills-wrapper">
18
+ <div class="refine-filter-conditions-wrapper-icon"></div>
19
+ <% @refine_filter.human_readable_criterions.each_with_index do |condition, index| %>
20
+
21
+ <% if ["or", "and"].include?(condition) %>
22
+ <span class="refine-filter-condition-pill-and"><%= t(".pill_and.#{condition}") %></span>
23
+ <% else %>
24
+ <% data_action = "click->refine--filter-pills#delete" %>
25
+ <div class="refine-filter-condition-pill">
26
+ <%= condition %>
27
+ <button
28
+ class="refine-filter-remove-condition-pill"
29
+ data-action="<%= data_action %>"
30
+ data-criterion-id="<%= index %>"
31
+ >
32
+ </button>
33
+ </div>
34
+ <% end %>
35
+ <% end %>
36
+ </div>
37
+ </div>
38
+ <% end %>
39
+ <% end %>
40
+ <% end %>
@@ -0,0 +1,32 @@
1
+ <%
2
+ style ||= ""
3
+
4
+ case style
5
+ when "tr"
6
+ classes = "rounded-sm p-1 w-full mx-auto mt-0"
7
+ spacing = "2"
8
+ rows = 1
9
+ when "large"
10
+ classes = "rounded-md shadow p-12 w-full mx-auto mt-6"
11
+ spacing = "6"
12
+ rows = 5
13
+ else
14
+ classes = "rounded-sm shadow-sm p-4 w-full mx-auto mt-4"
15
+ spacing = "4"
16
+ rows = 3
17
+ end
18
+ %>
19
+ <div class="<%= classes %>">
20
+ <div class="animate-pulse flex space-x-<%= spacing %>">
21
+ <div class="rounded-full bg-gray-400 h-12 w-12"></div>
22
+ <div class="flex-1 space-y-<%= spacing %> py-1">
23
+ <div class="h-4 bg-gray-400 rounded w-3/4"></div>
24
+ <div class="space-y-<%= spacing.to_i / 2 %>">
25
+ <% rows.times do %>
26
+ <div class="h-4 bg-gray-400 rounded"></div>
27
+ <% end %>
28
+ <div class="h-4 bg-gray-400 rounded w-5/6"></div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
@@ -0,0 +1,25 @@
1
+ <%
2
+ is_disabled = @refine_filter_builder.refine_filter.criteria_limit_reached?
3
+ criteria_limit = @refine_filter_builder.refine_filter.criteria_limit
4
+ %>
5
+
6
+ <%= tag.div(
7
+ class: "refine-add-container",
8
+ data: {
9
+ controller: 'refine--add',
10
+ refine__add_previous_criterion_id_value: (criteria.last.position unless is_disabled)
11
+ }) do %>
12
+
13
+ <%= tag.div(
14
+ class: class_names(
15
+ "refine-add-and" => true,
16
+ "refine-add-disabled" => is_disabled
17
+ ),
18
+ style: "transition: all .15s ease",
19
+ data: {action: ("click->refine--add#criterion" unless is_disabled)},
20
+ title: (t("refine.inline.filters.criteria_limit", criteria_limit: criteria_limit) if is_disabled)
21
+
22
+ ) do %>
23
+ <span class="refine-add-button-text"><%= t("refine.refine_blueprints.add_and.add_and") %></span><i class="refine-add-icon fal fa-plus"></i>
24
+ <% end %>
25
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <%
2
+ is_disabled = @refine_filter_builder.refine_filter.criteria_limit_reached?
3
+ criteria_limit = @refine_filter_builder.refine_filter.criteria_limit
4
+ %>
5
+
6
+ <%= tag.div(
7
+ class: "refine-add-container",
8
+ data: {controller: 'refine--add'}) do %>
9
+
10
+ <%= tag.div(
11
+ class: class_names(
12
+ "refine-add-group" => true,
13
+ "refine-add-disabled" => is_disabled
14
+ ),
15
+ data: {action: ("click->refine--add#group" unless is_disabled)},
16
+ title: (t("refine.inline.filters.criteria_limit", criteria_limit: criteria_limit) if is_disabled)
17
+ ) do %>
18
+ <% if @refine_filter_query.grouped_criteria.any? %>
19
+ <%= t("refine.refine_blueprints.add_group.add_or") %>
20
+ <% else %>
21
+ <%= t("refine.refine_blueprints.add_group.create_filter") %>
22
+ <% end %>
23
+ <% end %>
24
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <% prefixedClass = 'refine-clause-select-prefixed' if meta[:prefix] %>
2
+ <%= tag.div class: prefixedClass, data: {
3
+ action: "$change->refine--update#clause",
4
+ controller: 'fields--super-select',
5
+ fields__super_select_enable_search_value: 'true',
6
+ fields__super_select_container_width_value: 'resolve',
7
+ } do %>
8
+ <% if meta[:prefix] %>
9
+ <span class="refine-clause-select-prefix"><%= meta[:prefix] %></span>
10
+ <% end %>
11
+ <select
12
+ data-fields--super-select-target="select"
13
+ data-input-id='<%= input_id if defined? input_id %>'
14
+ name="clauses"
15
+ class="refine-clause-select"
16
+ >
17
+ <% meta[:clauses].each do |clause| %>
18
+ <option
19
+ value="<%= clause[:id] %>"
20
+ <% if selected_clause == clause[:id] %>selected<% end %>
21
+ ><%= clause[:display] %></option>
22
+ <% end %>
23
+ </select>
24
+ <% end %>
@@ -0,0 +1,53 @@
1
+ <%
2
+ conditions = @refine_filter_query.available_conditions_attributes
3
+
4
+ conditions_for_category = -> (category) do
5
+ conditions.filter { |c| c[:meta][:category] == category}
6
+ end
7
+
8
+ categories = conditions.map { |c| c[:meta][:category] }.uniq.compact
9
+
10
+ # Note that the stimulus controllers set default condition id for new conditions
11
+ # so this is only for rare cases where it gets unset
12
+ selected_condition_id ||= @refine_filter.default_condition_id
13
+ selected_condition_id ||= categories
14
+ .first
15
+ &.conditions_for_category.call(categories.first)
16
+ [:id]
17
+
18
+ uncategorized_conditions = conditions.filter { |c| c[:meta][:category].nil? }
19
+
20
+ %>
21
+
22
+ <%= tag.div data: {
23
+ action: "$change->refine--update#condition",
24
+ controller: 'fields--super-select',
25
+ fields__super_select_enable_search_value: 'true',
26
+ fields__super_select_container_width_value: 'resolve',
27
+ } do %>
28
+ <select
29
+ data-fields--super-select-target="select"
30
+ data-condition-id="<%= selected_condition_id %>"
31
+ name="conditions"
32
+ class="refine-condition-select"
33
+ >
34
+ <optgroup >
35
+ <% uncategorized_conditions.each do |condition_option| %>
36
+ <option
37
+ value="<%= condition_option[:id] %>"
38
+ <% if selected_condition_id == condition_option[:id] %>selected<% end %>
39
+ ><%= condition_option[:display] %></option>
40
+ <% end %>
41
+ </optgroup>
42
+ <% categories.each do |category| %>
43
+ <optgroup class="divider" label="<%= category %>">
44
+ <% conditions_for_category.call(category).each do |condition_option| %>
45
+ <option
46
+ value="<%= condition_option[:id] %>"
47
+ <% if selected_condition_id == condition_option[:id] %>selected<% end %>
48
+ ><%= condition_option[:display] %></option>
49
+ <% end %>
50
+ </optgroup>
51
+ <% end %>
52
+ </select>
53
+ <% end %>
@@ -0,0 +1,41 @@
1
+ <% criterion_id = criterion.position %>
2
+
3
+ <%= tag.div(class: 'refine-criterion-container', data: {
4
+ controller: 'refine--update',
5
+ refine__update_criterion_id_value: criterion_id,
6
+ position: criterion.position}) do %>
7
+
8
+ <!-- Select Condition -->
9
+ <div class="refine-criterion-condition-container">
10
+ <%= render partial: 'refine/blueprints/condition_select', locals: {
11
+ selected_condition_id: criterion.condition_id } %>
12
+ </div>
13
+
14
+ <!-- Select Clause -->
15
+ <div class="refine-criterion-clause-container">
16
+ <%= render partial: 'refine/blueprints/clause_select', locals: {
17
+ meta: criterion.meta, selected_clause: criterion.input[:clause]} %>
18
+ </div>
19
+
20
+ <!-- Render correct type of condition -->
21
+ <%= render partial: "refine/blueprints/clauses/#{criterion.component}", locals: {
22
+ criterion: criterion,
23
+ condition: criterion.condition, input: criterion.input, criterion_id: criterion_id, meta: criterion.meta, meta_clause: criterion.selected_clause_meta, input_id: nil } %>
24
+
25
+ <!-- Refinements -->
26
+ <% criterion.refinements.each do |refinement|%>
27
+
28
+ <div class="refine-criterion-refinement-container">
29
+ <%= render partial: 'refine/blueprints/clause_select', locals: {
30
+ meta: refinement[:meta], input_id: "input, #{refinement[:id]}", selected_clause: criterion.input.dig(:input, refinement[:id].to_sym, :clause) || {} } %>
31
+ </div>
32
+
33
+ <%= render partial: "refine/blueprints/clauses/#{refinement[:component].underscore}", locals: {
34
+ condition: refinement, input: criterion.input[refinement[:id].to_sym] || {}, criterion_id: criterion_id, meta: refinement[:meta], input_id: "input, #{refinement[:id]}", meta_clause: criterion.meta_for_refinement_clause(refinement), criterion: criterion} %>
35
+ <% end %>
36
+ <!-- End Refinements -->
37
+
38
+ <% end %>
39
+
40
+ <%= render 'refine/blueprints/delete_criterion', criterion_id: criterion_id %>
41
+
@@ -0,0 +1,7 @@
1
+ <% if criterion.errors.any? %>
2
+ <% criterion.errors.each do |error| %>
3
+ <p class="refine-criterion-error">
4
+ <%= error.full_message %>
5
+ </p>
6
+ <% end %>
7
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <%= tag.div class: 'refine-delete-container', data: {
2
+ controller: 'refine--delete',
3
+ refine__delete_criterion_id_value: criterion_id,
4
+ } do %>
5
+ <%= button_tag type: 'button', class: 'refine-delete-button', data: {
6
+ action: "click->refine--delete#criterion",
7
+ } do %>
8
+ <i class="refine-delete-criterion-icon ti ti-trash"></i>
9
+ <span class="refine-delete-criterion-label"><%= t("refine.refine_blueprints.query.buttons.remove_filter") %></span>
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%= tag.div id: dom_id(@refine_filter_builder, "group_#{group_id}") do %>
2
+ <div class="refine-group-container">
3
+ <% criteria&.each_with_index do |criterion, index| %>
4
+ <div class="refine-group-criterion-container">
5
+ <%= render "refine/blueprints/criterion", criterion: criterion %>
6
+ </div>
7
+ <% if criteria.count > 1 && index < criteria.count - 1 %>
8
+ <p class="refine-group-and"><%= t("refine.refine_blueprints.add_and.add_and") %></p>
9
+ <% end %>
10
+ <% end %>
11
+ <%= render 'refine/blueprints/add_and', criteria: criteria %>
12
+ </div>
13
+ <% end %>
@@ -0,0 +1,34 @@
1
+ <%= tag.div id: dom_id(@refine_filter_builder, "query"), class: 'refine-query-container', data: {
2
+ controller: "refine--state",
3
+ refine__state_conditions_value: @refine_filter_query.available_conditions_attributes.to_json,
4
+ refine__state_blueprint_value: @refine_filter_query.blueprint.to_json,
5
+ refine__state_class_name_value: @refine_filter_query.configuration[:class_name],
6
+ refine__state_refresh_url_value: refine_blueprint_path,
7
+ refine__state_client_id_value: @refine_filter_builder.client_id,
8
+ refine__state_default_condition_id_value: @refine_filter_query.filter.default_condition_id,
9
+ refine__state_validate_blueprint_url_value: validate_refine_blueprint_path } do %>
10
+ <%= content_tag :div, class: "refine-query-content-container" do %>
11
+ <div
12
+ class="refine-query-loading-container <%= "hidden" unless action_name == "create" %>"
13
+ id="refine-loader"
14
+ data-refine--state-target="loading">
15
+ <p class="refine-query-loading-text"><%= t("refine.refine_blueprints.query.loading") %></p>
16
+ </div>
17
+
18
+ <%= tag.div id: dom_id(@refine_filter_builder, "groups") do %>
19
+ <% @refine_filter_query.grouped_criteria.each_with_index do |criteria, index| %>
20
+ <% if index > 0 %>
21
+ <p class="refine-query-or"><%= t("refine.refine_blueprints.query.query_or") %></p>
22
+ <% end %>
23
+ <%= render partial: 'refine/blueprints/group', locals: {
24
+ criteria: criteria,
25
+ group_id: index }
26
+ %>
27
+ <% end %>
28
+
29
+ <% end %>
30
+
31
+ <%= render 'refine/blueprints/add_group' %>
32
+ <% end %>
33
+ <% end %>
34
+
@@ -0,0 +1,23 @@
1
+ <%= turbo_frame_tag "stored_filters" do %>
2
+ <%= content_tag :div, class: "refine-stored-filter-head-container" do %>
3
+ <h3
4
+ class="refine-stored-filter-header"
5
+ >
6
+ <%= @stored_filter&.name || t("global.buttons.filter") %>
7
+ </h3>
8
+ <a
9
+ href="<%= refine_stored_filters_path(@refine_filter_builder.to_params) %>"
10
+ class="refine-stored-filter-link"
11
+ >
12
+ <%= t('global.buttons.find_filter') %>
13
+ </a>
14
+ <span class="text-xs ml-4 mr-4 leading-6">
15
+ <a
16
+ class="text-blue-600 background-transparent font-bold underline outline-none focus:outline-none"
17
+ href="<%= new_refine_stored_filter_path(@refine_filter_builder.to_params) %>"
18
+ >
19
+ <%= t('global.buttons.save_filter') %>
20
+ </a>
21
+ </span>
22
+ <% end %>
23
+ <% end %>