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