refine-rails 2.9.0

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.
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 %>