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,86 @@
1
+ import ServerRefreshController from './server-refresh-controller'
2
+ import { debounce } from 'lodash'
3
+
4
+ export default class extends ServerRefreshController {
5
+ static values = {
6
+ criterionId: Number,
7
+ }
8
+
9
+ initialize() {
10
+ this.updateBlueprint = debounce((event, value, inputKey) => {
11
+ this.value(event, value, inputKey)
12
+ }, 500)
13
+ }
14
+
15
+ refinedFilter(event) {
16
+ const { criterionIdValue, state } = this
17
+ const dataset = event.target.dataset
18
+ const inputId = dataset.inputId
19
+
20
+ state.updateInput(
21
+ criterionIdValue,
22
+ {
23
+ id: event.target.value,
24
+ },
25
+ inputId
26
+ )
27
+ this.refreshFromServer()
28
+ }
29
+
30
+ clause(event) {
31
+ const { criterionIdValue, state } = this
32
+ const dataset = event.target.dataset
33
+ const inputId = dataset.inputId
34
+ state.updateInput(
35
+ criterionIdValue,
36
+ {
37
+ clause: event.target.value,
38
+ },
39
+ inputId
40
+ )
41
+ this.refreshFromServer()
42
+ }
43
+
44
+ selected(event) {
45
+ const { target: select } = event
46
+ const options = Array.prototype.slice.call(select.options)
47
+ const selectedOptions = options.filter((option) => option.selected)
48
+ const selected = selectedOptions.map((option) => option.value)
49
+ this.value(event, selected, 'selected')
50
+ }
51
+
52
+ value(event, value, inputKey) {
53
+ const { criterionIdValue, state } = this
54
+ const dataset = event.target.dataset
55
+ const inputId = dataset.inputId
56
+ inputKey = inputKey || dataset.inputKey || 'value'
57
+ value = value || event.target.value
58
+ state.updateInput(
59
+ criterionIdValue,
60
+ {
61
+ [inputKey]: value,
62
+ },
63
+ inputId
64
+ )
65
+ }
66
+
67
+ condition(event) {
68
+ const { criterionIdValue, state } = this
69
+ const element = event.target
70
+ let newConditionId = element.value
71
+ if (!newConditionId) newConditionId = element.querySelector('select').value
72
+ const config = this.state.conditionConfigFor(newConditionId)
73
+ const updatePerformed = state.replaceCriterion(criterionIdValue, newConditionId, config)
74
+ if (updatePerformed) {
75
+ this.refreshFromServer()
76
+ }
77
+ }
78
+
79
+ // Prevent form submission when hitting enter in a text box
80
+ cancelEnter(event) {
81
+ if (event.code === "Enter") {
82
+ event.preventDefault()
83
+ event.stopPropagation()
84
+ }
85
+ }
86
+ }
@@ -0,0 +1 @@
1
+ export * from './controllers'
@@ -0,0 +1,77 @@
1
+ // Polyfill for custom events in IE9-11
2
+ // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#polyfill
3
+ ;(function () {
4
+ if (typeof window.CustomEvent === 'function') return false
5
+
6
+ function CustomEvent(event, params) {
7
+ params = params || { bubbles: false, cancelable: false, detail: undefined }
8
+ var evt = document.createEvent('CustomEvent')
9
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
10
+ return evt
11
+ }
12
+
13
+ CustomEvent.prototype = window.Event.prototype
14
+
15
+ window.CustomEvent = CustomEvent
16
+
17
+ // eslint expects a return here
18
+ return true
19
+ })()
20
+
21
+ export const filterStabilizedEvent = (element, stableId, filterName) => {
22
+ const event = new CustomEvent('filter-stabilized', {
23
+ bubbles: true,
24
+ cancelable: true,
25
+ detail: {
26
+ stableId,
27
+ filterName,
28
+ },
29
+ })
30
+ element.dispatchEvent(event)
31
+ }
32
+
33
+ export const filterUnstableEvent = (blueprint) => {
34
+ const event = new CustomEvent('filter-unstable', {
35
+ bubbles: true,
36
+ cancelable: true,
37
+ detail: {
38
+ blueprint,
39
+ },
40
+ })
41
+ window.dispatchEvent(event)
42
+ }
43
+
44
+ export const filterInvalidEvent = ({blueprint, errors}) => {
45
+ const event = new CustomEvent('filter-invalid', {
46
+ bubbles: true,
47
+ cancelable: true,
48
+ detail: {
49
+ blueprint,
50
+ errors,
51
+ },
52
+ })
53
+ window.dispatchEvent(event)
54
+ }
55
+
56
+ export const filterStoredEvent = (storedFilterId) => {
57
+ const event = new CustomEvent('filter-stored', {
58
+ bubbles: true,
59
+ cancelable: true,
60
+ detail: {
61
+ storedFilterId,
62
+ },
63
+ })
64
+ window.dispatchEvent(event)
65
+ }
66
+
67
+ export const blueprintUpdatedEvent = (element, {blueprint, formId}) => {
68
+ const event = new CustomEvent('blueprint-updated', {
69
+ bubbles: true,
70
+ cancelable: true,
71
+ detail: {
72
+ blueprint,
73
+ formId
74
+ },
75
+ })
76
+ element.dispatchEvent(event)
77
+ }
@@ -0,0 +1,58 @@
1
+ module Refine::Blueprints
2
+ class Blueprint
3
+ # DSL-based model for building filter blueprints
4
+ attr_reader :blueprint
5
+
6
+ def initialize
7
+ @blueprint = []
8
+ @depth = 0
9
+ end
10
+
11
+ def group(&block)
12
+ @depth = @depth += 1
13
+ instance_eval(&block)
14
+ @depth = @depth -= 1
15
+ self
16
+ end
17
+
18
+ def criterion(condition_id, input)
19
+ if !@blueprint.blank? && @blueprint.last[:type] == "criterion"
20
+ raise "Conjunction missing"
21
+ end
22
+
23
+ add({
24
+ depth: @depth,
25
+ type: "criterion",
26
+ condition_id: condition_id,
27
+ input: input,
28
+ })
29
+ self
30
+ end
31
+
32
+ def conjunction(word)
33
+ add({
34
+ depth: @depth,
35
+ type: "conjunction",
36
+ word: word
37
+ })
38
+ end
39
+
40
+ def and
41
+ conjunction("and")
42
+ self
43
+ end
44
+
45
+ def or
46
+ conjunction("or")
47
+ self
48
+ end
49
+
50
+ def add(item)
51
+ @blueprint.append(item)
52
+ end
53
+
54
+ def to_array
55
+ @blueprint
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ [
2
+ {
3
+ "depth": 0,
4
+ "type": "criterion",
5
+ "condition_id": "some_text_condition",
6
+ "input": {
7
+ "clause": "eq",
8
+ "value": "aaron"
9
+ }
10
+ },
11
+ {
12
+ "depth": 0,
13
+ "type": "conjunction",
14
+ "word": "and"
15
+ },
16
+ {
17
+ "depth": 0,
18
+ "type": "criterion",
19
+ "condition_id": "some_text_condition",
20
+ "input": {
21
+ "clause": "sw",
22
+ "value": "aa"
23
+ }
24
+ }
25
+ ]
@@ -0,0 +1,112 @@
1
+ module Refine::Conditions
2
+ class BooleanCondition < Condition
3
+ include HasClauses
4
+
5
+ CLAUSE_TRUE = Clauses::TRUE
6
+ CLAUSE_FALSE = Clauses::FALSE
7
+ CLAUSE_SET = Clauses::SET # non null
8
+ CLAUSE_NOT_SET = Clauses::NOT_SET # null
9
+
10
+ I18N_PREFIX = "refine.refine_blueprints.boolean_condition."
11
+
12
+ def component
13
+ "boolean-condition"
14
+ end
15
+
16
+ def human_readable(input)
17
+ current_clause = get_clause_by_id(input[:clause])
18
+ if input[:value]
19
+ "#{display} #{current_clause.display} #{input[:value]}"
20
+ else
21
+ "#{display} #{current_clause.display}"
22
+ end
23
+ end
24
+
25
+ def boot
26
+ @nulls_are = nil
27
+ hide_unknowns
28
+ end
29
+
30
+ def hide_unknowns
31
+ without_clauses([
32
+ CLAUSE_SET,
33
+ CLAUSE_NOT_SET,
34
+ ])
35
+ self
36
+ end
37
+
38
+ def nulls_are_true
39
+ @nulls_are = true
40
+ self
41
+ end
42
+
43
+ def nulls_are_false
44
+ @nulls_are = false
45
+ self
46
+ end
47
+
48
+ def nulls_are_unknown
49
+ @nulls_are = nil
50
+ self
51
+ end
52
+
53
+ def show_unknowns
54
+ with_clauses([
55
+ CLAUSE_SET,
56
+ CLAUSE_NOT_SET
57
+ ])
58
+ self
59
+ end
60
+
61
+ def clauses
62
+ [
63
+ Clause.new(CLAUSE_TRUE, I18n.t("#{I18N_PREFIX}is_true")),
64
+ Clause.new(CLAUSE_FALSE, I18n.t("#{I18N_PREFIX}is_false")),
65
+ Clause.new(CLAUSE_SET, I18n.t("#{I18N_PREFIX}is_set")),
66
+ Clause.new(CLAUSE_NOT_SET, I18n.t("#{I18N_PREFIX}is_not_set")),
67
+ ]
68
+ end
69
+
70
+ def apply_condition(_input, table, _inverse_clause)
71
+ case clause
72
+ when CLAUSE_SET
73
+ apply_clause_set(table)
74
+
75
+ when CLAUSE_NOT_SET
76
+ apply_clause_not_set(table)
77
+
78
+ when CLAUSE_TRUE
79
+ apply_clause_true(table)
80
+
81
+ when CLAUSE_FALSE
82
+ apply_clause_false(table)
83
+ end
84
+
85
+ # Apply a custom clause
86
+ end
87
+
88
+ def apply_clause_set(table)
89
+ table.grouping(table[:"#{attribute}"].not_eq(nil))
90
+ end
91
+
92
+ def apply_clause_not_set(table)
93
+ table.grouping(table[:"#{attribute}"].eq(nil))
94
+ end
95
+
96
+ def apply_clause_true(table)
97
+ apply_clause_bool(table, true)
98
+ end
99
+
100
+ def apply_clause_bool(table, bool)
101
+ if @nulls_are == bool
102
+ table.grouping(table[:"#{attribute}"].eq(bool).or(table[:"#{attribute}"].eq(nil)))
103
+ else
104
+ table.grouping(table[:"#{attribute}"].eq(bool))
105
+ end
106
+ end
107
+
108
+ def apply_clause_false(table)
109
+ apply_clause_bool(table, false)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,38 @@
1
+ module Refine::Conditions
2
+ class Clause
3
+ include HasMeta
4
+
5
+ attr_reader :id, :rules
6
+ attr_accessor :display
7
+
8
+ I18N_PREFIX = "refine.refine_blueprints.clause."
9
+
10
+ def initialize(id = nil, display = nil)
11
+ @id = id
12
+ @display = display || id.humanize(keep_id_suffix: true).titleize
13
+ @rules = {}
14
+ @messages
15
+ end
16
+
17
+ def with_rules(user_defined_hash)
18
+ @rules.merge!(user_defined_hash)
19
+ self
20
+ end
21
+
22
+ def requires_inputs(fields)
23
+ # Coerce field to an array
24
+ [*fields].each do |field|
25
+ @rules.merge!({"#{field}": I18n.t("#{I18N_PREFIX}required")})
26
+ end
27
+ self
28
+ end
29
+
30
+ def to_array
31
+ {
32
+ id: @id,
33
+ display: @display,
34
+ meta: meta
35
+ }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module Refine::Conditions
2
+ class Clauses
3
+ EQUALS = "eq"
4
+ DOESNT_EQUAL = "dne"
5
+
6
+ SET = "st"
7
+ NOT_SET = "nst"
8
+
9
+ TRUE = "true"
10
+ FALSE = "false"
11
+
12
+ LESS_THAN_OR_EQUAL = "lte"
13
+ LESS_THAN = "lt"
14
+ BETWEEN = "btwn"
15
+ NOT_BETWEEN = "nbtwn"
16
+ GREATER_THAN = "gt"
17
+ GREATER_THAN_OR_EQUAL = "gte"
18
+
19
+ EXACTLY = "exct"
20
+
21
+ EXISTS = "exst"
22
+ DOESNT_EXIST = "dexst"
23
+
24
+ IN = "in"
25
+ NOT_IN = "nin"
26
+
27
+ CONTAINS = "cont"
28
+ DOESNT_CONTAIN = "dcont"
29
+
30
+ STARTS_WITH = "sw"
31
+ ENDS_WITH = "ew"
32
+
33
+ DOESNT_START_WITH = "dsw"
34
+ DOESNT_END_WITH = "dew"
35
+
36
+ FLIPPABLE = [NOT_IN, DOESNT_EQUAL].freeze
37
+ end
38
+ end