card-mod-search 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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