• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / orangelight / e640e05f-5733-4293-9051-5f2a121346ba

30 Aug 2024 06:56PM UTC coverage: 95.737% (-0.02%) from 95.758%
e640e05f-5733-4293-9051-5f2a121346ba

Pull #4168

circleci

maxkadel
Fix left anchored search for new json query dsl

Co-authored-by: Christina Chortaria <christinach@users.noreply.github.com>
Co-authored-by: Jane Sandberg <sandbergja@users.noreply.github.com>
Pull Request #4168: Left anchor search

23 of 23 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

5839 of 6099 relevant lines covered (95.74%)

1401.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.25
/app/helpers/advanced_helper.rb
1
# frozen_string_literal: true
2

3
# Helper methods for the advanced search form
4
module AdvancedHelper
3✔
5
  include BlacklightAdvancedSearch::AdvancedHelperBehavior
3✔
6

7
  # Fill in default from existing search, if present
8
  # -- if you are using same search fields for basic
9
  # search and advanced, will even fill in properly if existing
10
  # search used basic search on same field present in advanced.
11
  def label_tag_default_for(key)
3✔
12
    if params[key].present?
142✔
13
      params[key]
×
14
    elsif params['search_field'] == key || guided_context(key)
142✔
15
      params['q']
5✔
16
    else
17
      param_for_field key
137✔
18
    end
19
  end
20

21
  def advanced_key_value
3✔
22
    key_value = []
140✔
23
    advanced_search_fields.each do |field|
140✔
24
      key_value << [field[1][:label], field[0]]
1,391✔
25
    end
26
    key_value
140✔
27
  end
28

29
  # carries over original search field and original guided search fields if user switches to guided search from regular search
30
  def guided_field(field_num, default_val)
3✔
31
    return advanced_search_fields[params[:search_field]].key || default_val if first_search_field_selector?(field_num) && no_advanced_search_fields_specified? && params[:search_field] && advanced_search_fields[params[:search_field]]
140✔
32
    params[field_num] || param_for_field(field_num) || default_val
137✔
33
  end
34

35
  # carries over original search query if user switches to guided search from regular search
36
  def guided_context(key)
3✔
37
    first_search_field?(key) &&
142✔
38
      params[:f1].nil? && params[:f2].nil? && params[:f3].nil? &&
39
      params[:search_field] && advanced_search_fields[params[:search_field]]
40
  end
41

42
  # carries over guided search operations if user switches back to guided search from regular search
43
  def guided_radio(op_num, op)
3✔
44
    if params[op_num]
264✔
45
      params[op_num] == op
12✔
46
    else
47
      op == 'AND'
252✔
48
    end
49
  end
50

51
  def generate_solr_fq
3✔
52
    filters.map do |solr_field, value_list|
38✔
53
      value_list = value_list.values if value_list.is_a?(Hash)
15✔
54

55
      "#{solr_field}:(" +
15✔
56
        Array(value_list).collect { |v| '"' + v.gsub('"', '\"') + '"' }.join(' OR  ') +
15✔
57
        ')'
58
    end
59
  end
60

61
  private
3✔
62

63
    def advanced_search_fields
3✔
64
      blacklight_config.search_fields.select { |_k, v| v.include_in_advanced_search || v.include_in_advanced_search.nil? }
2,670✔
65
    end
66

67
    def first_search_field_selector?(key)
3✔
68
      [:f1, :clause_0_field].include? key
140✔
69
    end
70

71
    def no_advanced_search_fields_specified?
3✔
72
      [:f1, :f2, :f3, :clause_0_field, :clause_1_field, :clause_2_field].none? do |key|
52✔
73
        params[key].present?
292✔
74
      end
75
    end
76

77
    def param_for_field(field_identifier)
3✔
78
      if field_identifier.to_s.start_with? 'clause'
266✔
79
        components = field_identifier.to_s.split('_')
128✔
80
        params.dig(*components)
128✔
81
      end
82
    end
83

84
    def first_search_field?(key)
3✔
85
      [:q1, :clause_0_query].include? key
142✔
86
    end
87
end
88

89
module BlacklightAdvancedSearch
3✔
90
  class QueryParser
3✔
91
    include AdvancedHelper
3✔
92
    def keyword_op
3✔
93
      # for guided search add the operations if there are queries to join
94
      # NOTs get added to the query. Only AND/OR are operations
95
      @keyword_op = []
38✔
96
      unless @params[:q1].blank? || @params[:q2].blank? || @params[:op2] == 'NOT'
38✔
97
        @keyword_op << @params[:op2] if @params[:f1] != @params[:f2]
3✔
98
      end
99
      unless @params[:q3].blank? || @params[:op3] == 'NOT' || (@params[:q1].blank? && @params[:q2].blank?)
38✔
100
        @keyword_op << @params[:op3] unless [@params[:f1], @params[:f2]].include?(@params[:f3]) && ((@params[:f1] == @params[:f3] && @params[:q1].present?) || (@params[:f2] == @params[:f3] && @params[:q2].present?))
3✔
101
      end
102
      @keyword_op
38✔
103
    end
104

105
    def keyword_queries
3✔
106
      unless @keyword_queries
80✔
107
        @keyword_queries = {}
40✔
108

109
        return @keyword_queries unless @params[:search_field] == ::AdvancedController.blacklight_config.advanced_search[:url_key]
40✔
110

111
        # Spaces need to be stripped from the query because they don't get properly stripped in Solr
112
        q1 = %w[left_anchor in_series].include?(@params[:f1]) ? prep_left_anchor_search(@params[:q1]) : odd_quotes(@params[:q1])
37✔
113
        q2 = @params[:f2] == 'left_anchor' ? prep_left_anchor_search(@params[:q2]) : odd_quotes(@params[:q2])
37✔
114
        q3 = @params[:f3] == 'left_anchor' ? prep_left_anchor_search(@params[:q3]) : odd_quotes(@params[:q3])
37✔
115

116
        @been_combined = false
37✔
117
        @keyword_queries[@params[:f1]] = q1 if @params[:q1].present?
37✔
118
        @keyword_queries[@params[:f2]] = prepare_q2(q2) if @params[:q2].present?
37✔
119
        @keyword_queries[@params[:f3]] = prepare_q3(q3) if @params[:q3].present?
37✔
120
      end
121
      @keyword_queries
77✔
122
    end
123

124
    private
3✔
125

126
      # Remove stray quotation mark if there are an odd number of them
127
      # @param query [String] the query
128
      # @return [String] the query with an even number of quotation marks
129
      def odd_quotes(query)
3✔
130
        if query&.count('"')&.odd?
104✔
131
          query.sub(/"/, '')
2✔
132
        else
133
          query
102✔
134
        end
135
      end
136

137
      # Escape spaces for left-anchor search fields and adds asterisk if not present
138
      # Removes quotation marks
139
      # @param query [String] the query within which whitespace is being escaped
140
      # @return [String] the escaped query
141
      def prep_left_anchor_search(query)
3✔
142
        if query
7✔
143
          cleaned_query = query.gsub(/(\s)/, '\\\\\\\\\1')
7✔
144
          cleaned_query = cleaned_query.delete('"')
7✔
145
          cleaned_query = cleaned_query.gsub(/(["\{\}\[\]\^\~\(\)])/, '\\\\\\\\\1')
7✔
146
          if cleaned_query.end_with?('*')
7✔
UNCOV
147
            cleaned_query
×
148
          else
149
            cleaned_query + '*'
7✔
150
          end
151
        end
152
      end
153

154
      def prepare_q2(q2)
3✔
155
        if @keyword_queries.key?(@params[:f2])
6✔
156
          @been_combined = true
3✔
157
          "(#{@keyword_queries[@params[:f2]]}) " + @params[:op2] + " (#{q2})"
3✔
158
        elsif @params[:op2] == 'NOT'
3✔
UNCOV
159
          'NOT ' + q2
×
160
        else
161
          q2
3✔
162
        end
163
      end
164

165
      def prepare_q3(q3)
3✔
166
        if @keyword_queries.key?(@params[:f3])
6✔
167
          kq3 = @keyword_queries[@params[:f3]]
1✔
168
          kq3 = "(#{kq3})" unless @been_combined
1✔
169
          "#{kq3} " + @params[:op3] + " (#{q3})"
1✔
170
        elsif @params[:op3] == 'NOT'
5✔
171
          'NOT ' + q3
1✔
172
        else
173
          q3
4✔
174
        end
175
      end
176
  end
177
end
178

179
module BlacklightAdvancedSearch
3✔
180
  module ParsingNestingParser
3✔
181
    # Iterates through the keyword queries and appends each operator the extracting queries
182
    # @param [ActiveSupport::HashWithIndifferentAccess] _params
183
    # @param [Blacklight::Configuration] config
184
    # @return [Array<String>]
185
    def process_query(_params, config)
3✔
186
      if config.advanced_search.nil?
41✔
187
        Blacklight.logger.error "Failed to parse the advanced search, config. settings are not accessible for: #{config}"
1✔
188
        return []
1✔
189
      end
190

191
      queries = []
40✔
192
      ops = keyword_op
40✔
193
      keyword_queries.each do |field, query|
40✔
194
        query_parser_config = config.advanced_search[:query_parser]
24✔
195
        begin
196
          parsed = ParsingNesting::Tree.parse(query, query_parser_config)
24✔
197
        rescue Parslet::ParseFailed => parse_failure
198
          Blacklight.logger.warn "Failed to parse the query: #{query}: #{parse_failure}"
1✔
199
          next
1✔
200
        end
201

202
        # Test if the field is valid
203
        next unless config.search_fields[field]
23✔
204
        local_param = local_param_hash(field, config)
22✔
205
        queries << parsed.to_query(local_param)
22✔
206
        queries << ops.shift
22✔
207
      end
208
      queries.join(' ')
40✔
209
    end
210
  end
211
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc