card-mod-search 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/card/filter_query.rb +74 -0
- data/set/abstract/00_filter_helper.rb +46 -0
- data/set/abstract/02_search_params.rb +52 -0
- data/set/abstract/03_filter.rb +29 -0
- data/set/abstract/03_filter/_filter_input.haml +8 -0
- data/set/abstract/03_filter/filter_form.haml +56 -0
- data/set/abstract/03_filter/filter_form.rb +101 -0
- data/set/abstract/03_filter/filtered_content.haml +5 -0
- data/set/abstract/03_filter/form_helper.rb +105 -0
- data/set/abstract/03_filter/query_construction.rb +38 -0
- data/set/abstract/03_filter/quick_filters.haml +11 -0
- data/set/abstract/03_filter/selectable_filtered_content.haml +2 -0
- data/set/abstract/04_right_filter_form.rb +26 -0
- data/set/abstract/05_search.rb +80 -0
- data/set/abstract/05_search/checkbox_item.haml +5 -0
- data/set/abstract/05_search/select_item.haml +15 -0
- data/set/abstract/05_search/views.rb +167 -0
- data/set/abstract/06_cql_search.rb +91 -0
- data/set/abstract/filterable.rb +13 -0
- data/set/abstract/filterable_bar.rb +11 -0
- data/set/right/children.rb +4 -0
- data/set/right/created.rb +3 -0
- data/set/right/edited.rb +3 -0
- data/set/right/editors.rb +3 -0
- data/set/right/follow.rb +3 -0
- data/set/right/linked_to_by.rb +3 -0
- data/set/right/links_to.rb +5 -0
- data/set/right/mates.rb +3 -0
- data/set/right/nested_by.rb +3 -0
- data/set/right/nests.rb +3 -0
- data/set/right/referred_to_by.rb +3 -0
- data/set/right/refers_to.rb +3 -0
- data/set/self/recent.rb +35 -0
- data/set/self/search.rb +93 -0
- data/set/type/cardtype.rb +5 -0
- data/set/type/search_type.rb +80 -0
- 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,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,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
|