card-mod-search 0.11.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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/lib/card/filter_query.rb +74 -0
  3. data/set/abstract/00_filter_helper.rb +46 -0
  4. data/set/abstract/02_search_params.rb +52 -0
  5. data/set/abstract/03_filter.rb +29 -0
  6. data/set/abstract/03_filter/_filter_input.haml +8 -0
  7. data/set/abstract/03_filter/filter_form.haml +56 -0
  8. data/set/abstract/03_filter/filter_form.rb +101 -0
  9. data/set/abstract/03_filter/filtered_content.haml +5 -0
  10. data/set/abstract/03_filter/form_helper.rb +105 -0
  11. data/set/abstract/03_filter/query_construction.rb +38 -0
  12. data/set/abstract/03_filter/quick_filters.haml +11 -0
  13. data/set/abstract/03_filter/selectable_filtered_content.haml +2 -0
  14. data/set/abstract/04_right_filter_form.rb +26 -0
  15. data/set/abstract/05_search.rb +80 -0
  16. data/set/abstract/05_search/checkbox_item.haml +5 -0
  17. data/set/abstract/05_search/select_item.haml +15 -0
  18. data/set/abstract/05_search/views.rb +167 -0
  19. data/set/abstract/06_cql_search.rb +91 -0
  20. data/set/abstract/filterable.rb +13 -0
  21. data/set/abstract/filterable_bar.rb +11 -0
  22. data/set/right/children.rb +4 -0
  23. data/set/right/created.rb +3 -0
  24. data/set/right/edited.rb +3 -0
  25. data/set/right/editors.rb +3 -0
  26. data/set/right/follow.rb +3 -0
  27. data/set/right/linked_to_by.rb +3 -0
  28. data/set/right/links_to.rb +5 -0
  29. data/set/right/mates.rb +3 -0
  30. data/set/right/nested_by.rb +3 -0
  31. data/set/right/nests.rb +3 -0
  32. data/set/right/referred_to_by.rb +3 -0
  33. data/set/right/refers_to.rb +3 -0
  34. data/set/self/recent.rb +35 -0
  35. data/set/self/search.rb +93 -0
  36. data/set/type/cardtype.rb +5 -0
  37. data/set/type/search_type.rb +80 -0
  38. metadata +110 -0
@@ -0,0 +1,38 @@
1
+ format do
2
+ # all filter keys in the order they were selected
3
+ def all_filter_keys
4
+ @all_filter_keys ||= filter_keys_from_params | filter_keys
5
+ end
6
+
7
+ def filter_and_sort_cql
8
+ filter_cql.merge sort_cql
9
+ end
10
+
11
+ def filter_cql
12
+ return {} if filter_hash.empty?
13
+
14
+ filter_cql_from_params
15
+ end
16
+
17
+ # separate method is needed for tests
18
+ def filter_cql_from_params
19
+ filter_class.new(filter_keys_with_values, blocked_id_cql).to_cql
20
+ end
21
+
22
+ def sort_cql
23
+ { sort: current_sort }
24
+ end
25
+
26
+ def blocked_id_cql
27
+ not_ids = filter_param :not_ids
28
+ not_ids.present? ? { id: ["not in", not_ids.split(",")] } : {}
29
+ end
30
+
31
+ def current_sort
32
+ sort_param || default_sort_option
33
+ end
34
+
35
+ def default_sort_option
36
+ card.cql_content[:sort]
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ .quick-filter._quick-filter
2
+ %i.fa.fa-bolt.filter-section-icon
3
+ .quick-filter-links.d-flex.flex-wrap
4
+ - filter_list.each do |f|
5
+ %a{ "data-filter": f[:filter],
6
+ href: "#",
7
+ class: f[:class],
8
+ title: "Quick Filter: #{f[:text]}" }
9
+ = f[:icon]
10
+ = f[:text]
11
+ = custom_quick_filters
@@ -0,0 +1,2 @@
1
+ ._selectable-filtered-content{ "data-input-selector" => Env.params["input_selector"] }
2
+ = render_filtered_content
@@ -0,0 +1,26 @@
1
+ # To be included in a field card to get a filter for the parent.
2
+ # The core view renders a filter for the left card.
3
+
4
+ include_set Set::Abstract::Filter
5
+
6
+ def virtual?
7
+ new?
8
+ end
9
+
10
+ format :html do
11
+ def filter_action_path
12
+ path mark: card.name.left, view: filter_view
13
+ end
14
+
15
+ view :core, cache: :never do
16
+ filter_fields slot_selector: filter_selector
17
+ end
18
+
19
+ def filter_view
20
+ :filter_result
21
+ end
22
+
23
+ def filter_selector
24
+ ".#{filter_view}-view"
25
+ end
26
+ end
@@ -0,0 +1,80 @@
1
+ include_set Abstract::Paging
2
+ include_set Abstract::SearchParams
3
+ include_set Abstract::Filter
4
+
5
+ def search _args={}
6
+ raise Error, "search not overridden"
7
+ end
8
+
9
+ def cached_search args={}
10
+ @search_results ||= {}
11
+ @search_results[args.to_s] ||= search args
12
+ end
13
+
14
+ def returning item, args
15
+ args[:return] = item
16
+ yield
17
+ end
18
+
19
+ def item_cards args={}
20
+ args[:limit] ||= 0
21
+ returning(:card, args) { search args }
22
+ end
23
+
24
+ def item_names args={}
25
+ args[:limit] ||= 0
26
+ returning(:name, args) { search args }
27
+ end
28
+
29
+ def item_ids args={}
30
+ args[:limit] ||= 0
31
+ returning(:id, args) { search args }
32
+ end
33
+
34
+ def count args={}
35
+ args[:offset] = 0
36
+ args[:limit] = 0
37
+ returning(:count, args) { search args }
38
+ end
39
+
40
+ # for override
41
+ def item_type
42
+ nil
43
+ end
44
+
45
+ def each_item_name_with_options _content=nil
46
+ options = {}
47
+ item = fetch_query.statement[:view]
48
+ options[:view] = item if item
49
+ item_names.each do |name|
50
+ yield name, options
51
+ end
52
+ end
53
+
54
+ format do
55
+ def search_with_params
56
+ @search_with_params ||= search_with_rescue search_params
57
+ end
58
+
59
+ def count_with_params
60
+ @count_with_params ||= search_with_rescue search_params.merge(return: :count)
61
+ end
62
+
63
+ def search_with_rescue query_args
64
+ rescuing_bad_query query_args do
65
+ card.cached_search query_args
66
+ end
67
+ end
68
+
69
+ def rescuing_bad_query query_args
70
+ yield
71
+ rescue Error::BadQuery => e
72
+ Rails.logger.info "BadQuery: #{query_args}"
73
+ e
74
+ end
75
+
76
+ def implicit_item_view
77
+ view = voo_items_view || default_item_view
78
+ Card::View.normalize view
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ .checkbox-list-item.w-100.d-flex
2
+ .checkbox-side
3
+ = check_box_tag item_card.name, item_card.key, false, class: "_checkbox-list-checkbox"
4
+ .item-view-side{ style: "background: #fff" }
5
+ = nest_item item_card
@@ -0,0 +1,15 @@
1
+ .filter-header.card-header
2
+ %h5
3
+ Select Item
4
+ .badge.badge-secondary
5
+ = card.count
6
+ .filter-selector.p-2
7
+ - if card.count > 0
8
+ .filter-bulk-selector.pb-3
9
+ %input#select-all._select-all{name: "", type: "checkbox", value: ""}
10
+ %label{for: "select-all"}
11
+ select
12
+ %span._unselected-items
13
+ = search_with_params.size
14
+ following
15
+ = render :checkbox_list
@@ -0,0 +1,167 @@
1
+
2
+ format do
3
+ view :search_count, cache: :never do
4
+ search_with_params.to_s
5
+ end
6
+
7
+ view :search_error, cache: :never do
8
+ sr_class = search_with_params.class.to_s
9
+ %(#{sr_class} :: #{search_with_params.message} :: #{card.content})
10
+ end
11
+
12
+ view :card_list, cache: :never do
13
+ if search_with_params.empty?
14
+ "no results"
15
+ else
16
+ search_with_params.map do |item_card|
17
+ nest_item item_card
18
+ end.join "\n"
19
+ end
20
+ end
21
+
22
+ view :no_search_results do
23
+ "no results"
24
+ end
25
+
26
+ def with_results
27
+ search_with_params.empty? ? render_no_search_results : yield
28
+ end
29
+ end
30
+
31
+ format :json do
32
+ AUTOCOMPLETE_LIMIT = 8 # number of name suggestions for autocomplete text fields
33
+
34
+ def item_cards
35
+ search_with_params
36
+ end
37
+
38
+ # NOCACHE because paging_urls is uncacheable hash and thus not safe to merge
39
+ view :molecule, cache: :never do
40
+ molecule.merge render_paging_urls
41
+ end
42
+
43
+ # TODO: design better autocomplete API
44
+ view :name_complete, cache: :never do
45
+ complete_search limit: AUTOCOMPLETE_LIMIT
46
+ end
47
+
48
+ view :name_match, cache: :never do
49
+ complete_or_match_search limit: AUTOCOMPLETE_LIMIT
50
+ end
51
+
52
+ def complete_or_match_search limit: AUTOCOMPLETE_LIMIT, start_only: false
53
+ starts_with = complete_search limit: limit
54
+ return starts_with if start_only
55
+
56
+ remaining_slots = limit - starts_with.size
57
+ return starts_with if remaining_slots.zero?
58
+ starts_with + match_search(not_names: starts_with, limit: remaining_slots)
59
+ end
60
+
61
+ def complete_search limit: AUTOCOMPLETE_LIMIT
62
+ card.search name_cql(limit).merge(complete_cql)
63
+ end
64
+
65
+ def match_search limit: AUTOCOMPLETE_LIMIT, not_names: []
66
+ card.search name_cql(limit).merge(match_cql(not_names))
67
+ end
68
+
69
+ def name_cql limit
70
+ { limit: limit, sort: "name", return: "name" }
71
+ end
72
+
73
+ def complete_cql
74
+ { complete: term_param }
75
+ end
76
+
77
+ def match_cql not_names
78
+ cql = { name_match: term_param }
79
+ cql[:name] = ["not in"] + not_names if not_names.any?
80
+ cql
81
+ end
82
+
83
+ def term_param
84
+ params[:term]
85
+ end
86
+ end
87
+
88
+ format :data do
89
+ view :card_list do
90
+ search_with_params.map do |item_card|
91
+ nest_item item_card
92
+ end
93
+ end
94
+ end
95
+
96
+ format :csv do
97
+ view :core, :core, mod: All::Csv::CsvFormat
98
+
99
+ view :card_list do
100
+ items = super()
101
+ if depth.zero?
102
+ title_row + items
103
+ else
104
+ items
105
+ end
106
+ end
107
+ end
108
+
109
+ format :html do
110
+ view :card_list, cache: :never do
111
+ with_results do
112
+ search_result_list "search-result-list" do |item_card|
113
+ card_list_item item_card
114
+ end
115
+ end
116
+ end
117
+
118
+ view :select_item, cache: :never do
119
+ wrap do
120
+ haml :select_item
121
+ end
122
+ end
123
+
124
+ before :select_item do
125
+ class_up "card-slot", "_filter-result-slot"
126
+ end
127
+
128
+ view :checkbox_list, cache: :never do
129
+ with_results do
130
+ search_result_list "_search-checkbox-list pr-2" do |item_card|
131
+ checkbox_item item_card
132
+ end
133
+ end
134
+ end
135
+
136
+ view :no_search_results do
137
+ wrap_with :div, "", class: "search-no-results"
138
+ end
139
+
140
+ private
141
+
142
+ def card_list_item item_card
143
+ nest_item item_card, size: voo.size do |rendered, item_view|
144
+ %(<div class="search-result-item item-#{item_view}">#{rendered}</div>)
145
+ end
146
+ end
147
+
148
+ def search_result_list klass
149
+ with_paging do
150
+ wrap_with :div, class: klass do
151
+ search_with_params.map do |item_card|
152
+ yield item_card
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def checkbox_item item_card
159
+ subformat(item_card).wrap do
160
+ haml :checkbox_item, unique_id: unique_id, item_card: item_card
161
+ end
162
+ end
163
+
164
+ def closed_limit
165
+ [search_params[:limit].to_i, Card.config.closed_search_limit].min
166
+ end
167
+ end
@@ -0,0 +1,91 @@
1
+ include_set Abstract::Search
2
+
3
+ def cql_hash
4
+ cache_query? ? (@cql_hash ||= cql_content) : cql_content
5
+ end
6
+
7
+ # override this to define search
8
+ def cql_content
9
+ query = content
10
+ query = query.is_a?(Hash) ? query : parse_json_cql(query)
11
+ query.symbolize_keys
12
+ end
13
+
14
+ def item_type
15
+ type = cql_hash[:type]
16
+ return if type.is_a?(Array) || type.is_a?(Hash)
17
+ type
18
+ end
19
+
20
+ # for override, eg when required subqueries are known to be missing
21
+ def skip_search?
22
+ false
23
+ end
24
+
25
+ def cache_query?
26
+ true
27
+ end
28
+
29
+ def parse_json_cql query
30
+ empty_query_error! if query.empty?
31
+ JSON.parse query
32
+ rescue JSON::ParserError
33
+ raise Error::BadQuery, "Invalid JSON search query: #{query}"
34
+ end
35
+
36
+ def empty_query_error!
37
+ raise Error::BadQuery, "can't run search with empty content"
38
+ end
39
+
40
+ # These search methods are shared by card and format
41
+ def search args={}
42
+ with_skipping args do
43
+ query = fetch_query(args)
44
+ # forces explicit limiting
45
+ # can be 0 or less to force no limit
46
+ raise "OH NO.. no limit" unless query.mods[:limit]
47
+ query.run
48
+ end
49
+ end
50
+
51
+ def with_skipping args
52
+ return yield unless skip_search?
53
+
54
+ args[:return] == :count ? 0 : []
55
+ end
56
+
57
+ def fetch_query args={}
58
+ @query = nil unless cache_query?
59
+ @query ||= {}
60
+ @query[args.to_s] ||= query(args.clone) # cache query
61
+ end
62
+
63
+ def query args={}
64
+ Query.new standardized_query_args(args), name
65
+ end
66
+
67
+ def standardized_query_args args
68
+ args = cql_hash.merge args.symbolize_keys
69
+ args[:context] ||= name
70
+ args
71
+ end
72
+
73
+ format do
74
+ def search_params
75
+ super.merge filter_and_sort_cql
76
+ end
77
+
78
+ def default_limit
79
+ card_content_limit || super
80
+ end
81
+
82
+ def card_content_limit
83
+ card.cql_hash&.dig :limit
84
+ end
85
+ end
86
+
87
+ format :html do
88
+ def default_limit
89
+ card_content_limit || super
90
+ end
91
+ end
@@ -0,0 +1,13 @@
1
+ format :html do
2
+ def filterable filter_hash={}, html_opts={}
3
+ add_class html_opts, "_filterable _noFilterUrlUpdates"
4
+ html_opts[:data] ||= {}
5
+ html_opts[:data][:filter] = filter_hash
6
+ wrap_with :div, yield, html_opts
7
+ end
8
+
9
+ def filtering selector=nil
10
+ selector ||= "._filter-widget:visible"
11
+ wrap_with :div, yield, class: "_filtering", "data-filter-selector": selector
12
+ end
13
+ end