bcms_google_mini_search 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -9,9 +9,12 @@ It consists of the following two portlets.
9
9
 
10
10
  Features:
11
11
  1. Allows for formatted results of GSA search results.
12
- 2. Will display Key Matches (as of 1.1)
13
- 3. Will display Synonyms/Related Queries (as of 1.1)
14
- 4. Allows conditional overriding of collections (i.e. by default it will search one collection, but a 'site=OTHER_COLLECTION' param can be passed to override that)
12
+ 2. Will display Key Matches
13
+ 3. Will display Synonyms/Related Queries
14
+ 4. Allows conditional overriding of collections (i.e. by default it will search one default collection, but a 'site=OTHER_COLLECTION' param can be passed to override that)
15
+ 5. Sort by Date/Relevance - Users can now toggle the results display between date and relevance.
16
+ 6. Cached Links - Each result now correctly generate a link to the cached version of the document as stored by the GSA.
17
+ 7. [GSA Only] Narrow Your Search - Shows users a set of other suggested queries to allow them to 'Narrow Your Search', based on the current query and what results are stored in GSA. Only available for GSA instances, as Google Mini does not support Dynamic Result Clustering.
15
18
 
16
19
  Note: This module assume the BrowserCMS web site owner has access to their own GSA/Google Mini server, either hosted by
17
20
  themselves or a third party service.
@@ -1,11 +1,18 @@
1
1
  class SearchResult
2
2
 
3
- attr_accessor :number, :title, :url, :description, :size
3
+ # Creates a new GSA::Appliance from a GoogleMiniSearchPortlet.
4
+ #
5
+ def self.new_gsa(portlet)
6
+ options = {:portlet=>portlet}
7
+ normalize_query_options(options)
8
+ GSA::Appliance.new(options)
9
+ end
4
10
 
5
11
  #
6
12
  # Queries google mini by a specific URL to find all the results. Converts XML results to
7
13
  # a paging results of Search Results.
8
14
  #
15
+ # See SearchResult#query_url for acceptable _options_ params
9
16
  def self.find(query, options={})
10
17
  return QueryResult.new unless query
11
18
  xml_doc = fetch_xml_doc(query, options)
@@ -16,6 +23,14 @@ class SearchResult
16
23
  results
17
24
  end
18
25
 
26
+ def self.create_query(query, options={})
27
+ opts = options.clone
28
+ normalize_query_options(opts)
29
+ opts[:query] = query
30
+ opts[:engine] = GSA::Engine.new({:host => opts[:host]})
31
+ GSA::Query.new(opts)
32
+ end
33
+
19
34
  def self.parse_results_count(xml_doc)
20
35
  root = xml_doc.root
21
36
  count = root.elements["RES/M"]
@@ -26,16 +41,7 @@ class SearchResult
26
41
  root = xml_doc.root
27
42
  results = []
28
43
  xml_doc.elements.each('GSP/RES/R') do |ele|
29
- result = SearchResult.new
30
- result.number = ele.attributes["N"]
31
- result.title = ele.elements["T"].text
32
- result.url = ele.elements["U"].text
33
- result.description = ele.elements["S"].text
34
-
35
- doc_size_ele = ele.elements["HAS/C"]
36
- result.size = doc_size_ele ? doc_size_ele.attributes["SZ"] : ""
37
-
38
- results << result
44
+ results << GSA::Result.new(ele)
39
45
  end
40
46
  results
41
47
  end
@@ -59,39 +65,76 @@ class SearchResult
59
65
  num_pages
60
66
  end
61
67
 
68
+ # Construct a query url for the GSA.
69
+ #
70
+ # @param [String] query
71
+ # @param [Hash] options
72
+ # @option :host
73
+ # @option :start
74
+ # @option :front_end
75
+ # @option :collection
76
+ # @option :sort
77
+ # @option :as_xml [Boolean] Determines if the results are returned as xml or html. Default to false.
78
+ def self.query_url(query, options)
79
+ options[:as_xml] = true if options[:as_xml].nil?
62
80
 
63
- def self.build_mini_url(options, query)
64
- portlet = find_search_engine_portlet(options)
65
81
  encoded_query = CGI::escape(query)
66
82
 
67
- site = portlet.collection_name
68
- if options[:site]
69
- site = options[:site]
70
- end
71
- # encoded_query = query
72
- url = "#{portlet.service_url}/search?q=#{encoded_query}&output=xml_no_dtd&client=#{portlet.front_end_name}&site=#{site}&filter=0"
83
+ # Turns off automatic results filter (filter=0), which when set to 1, allows mini to reduces the # of similar/duplicate results,
84
+ # but makes it hard to determine the total # of results.
85
+ url = "#{options[:host]}/search?q=#{encoded_query}&output=xml_no_dtd&client=#{options[:front_end]}&site=#{options[:collection]}&filter=0"
73
86
  if options[:start]
74
87
  url = url + "&start=#{options[:start]}"
75
88
  end
76
- return url
89
+
90
+ if options[:sort]
91
+ url += "&sort=#{CGI::escape(options[:sort])}"
92
+ end
93
+
94
+ unless options[:as_xml]
95
+ url += "&proxystylesheet=#{options[:front_end]}"
96
+ end
97
+
98
+ # Ensure both results (oe) and query/input values (ie) are interpreted as UTF-8.
99
+ # See http://code.google.com/apis/searchappliance/documentation/46/xml_reference.html#request_i18n
100
+ url += "&oe=UTF-8&ie=UTF-8"
101
+ return url
102
+ end
103
+
104
+ def self.create_url_for_query(options, query)
105
+ normalize_query_options(options)
106
+ return query_url(query, options)
107
+ end
108
+
109
+ def self.normalize_query_options(options)
110
+ portlet = find_search_engine_portlet(options)
111
+ options[:front_end] = portlet.front_end_name
112
+ options[:collection] = portlet.collection_name
113
+ options[:host] = portlet.service_url
114
+
115
+ options[:collection] = options.delete(:site) if options[:site]
77
116
  end
78
117
 
79
118
  def self.find_search_engine_portlet(options)
80
119
  portlet = GoogleMiniSearchEnginePortlet.new
81
120
  if options[:portlet]
82
- portlet = options[:portlet]
121
+ portlet = options.delete(:portlet)
83
122
  end
84
123
  portlet
85
124
  end
86
125
 
126
+ # Given a URL, GET it and return the contents
127
+ # @param [String] url A URL formatted string
128
+ def self.fetch_document(url)
129
+ Rails.logger.debug {"GSA: Fetching '#{url}'"}
130
+ Net::HTTP.get(URI.parse(url))
131
+ end
132
+
87
133
  # Fetches the xml response from the google mini server.
88
134
  def self.fetch_xml_doc(query, options={})
89
- # Turns off automatic results filter (filter=0), which when set to 1, allows mini to reduces the # of similar/duplicate results,
90
- # but makes it hard to determine the total # of results.
91
- url = build_mini_url(options, query)
92
- response = Net::HTTP.get(URI.parse(url))
93
- xml_doc = REXML::Document.new(response)
94
- return xml_doc
135
+ url = create_url_for_query(options, query)
136
+ response = fetch_document(url)
137
+ REXML::Document.new(response)
95
138
  end
96
139
 
97
140
  def self.parse_key_matches(xml_doc)
@@ -122,6 +165,10 @@ class SearchResult
122
165
  attr_accessor :results_count, :num_pages, :current_page, :start, :query, :pages, :key_matches, :synonyms
123
166
  attr_writer :path
124
167
 
168
+ # For what these codes mean, see http://code.google.com/apis/searchappliance/documentation/46/xml_reference.html#request_sort
169
+ SORT_BY_DATE_PARAM = "date:D:S:d1"
170
+ SORT_BY_RELEVANCE_PARAM = "date:D:L:d1"
171
+
125
172
  def initialize(array=[])
126
173
  # Need to set defaults so an empty result set works.
127
174
  self.start = 0
@@ -146,11 +193,15 @@ class SearchResult
146
193
  previous_start >= 0 && num_pages > 1
147
194
  end
148
195
 
196
+ # Returns a range of pages that should appear in the pager control. This is design to mimic GSA's pager control,
197
+ # which will show up to 20 pages at a time, based on the 'range' of pages around the current page.
198
+ #
199
+ # i.e. on page 12: < 2 3 4 5 6 7 8 9 10 11 _12_ 13 14 15 16 17 18 19 20 21 22 >
149
200
  def pages
150
- if num_pages > 1
151
- return (1..num_pages)
152
- end
153
- []
201
+ return [] if num_pages <= 1
202
+ first_page = current_page - 10 > 1 ? current_page - 10 : 1
203
+ last_page = current_page + 9 > num_pages ? num_pages : current_page + 9
204
+ (first_page..last_page)
154
205
  end
155
206
 
156
207
  def next_start
@@ -170,6 +221,28 @@ class SearchResult
170
221
  1
171
222
  end
172
223
 
224
+ # Determines the current Query is sorting by date.
225
+ #
226
+ # @param [Hash] params The query parameter from the search page. (same as Rails params)
227
+ def sorting_by_date?(params)
228
+ params[:sort] == SearchResult::QueryResult::SORT_BY_DATE_PARAM
229
+ end
230
+
231
+ def path_for(new_query)
232
+ "#{path}?query=#{new_query}"
233
+ end
234
+ # Return the path to sort the current search results by date.
235
+ #
236
+ # Based on http://code.google.com/apis/searchappliance/documentation/46/xml_reference.html#request_sort
237
+ def sort_by_date_path
238
+ "#{path}?query=#{query}&sort=#{SORT_BY_DATE_PARAM}"
239
+ end
240
+
241
+ # Returns the path to sort the current results by relevance (inverse of sort_by_date_path).
242
+ def sort_by_relevance_path
243
+ "#{path}?query=#{query}&sort=#{SORT_BY_RELEVANCE_PARAM}"
244
+ end
245
+
173
246
  def next_page_path
174
247
  "#{path}?query=#{query}&start=#{next_start}"
175
248
  end
@@ -1,12 +1,28 @@
1
1
  class GoogleMiniSearchEnginePortlet < Portlet
2
2
 
3
- enable_template_editor true
4
-
3
+ enable_template_editor false
4
+
5
5
  def render
6
- @query = params[:query]
7
6
  @site = params[:site]
8
7
  @start = params[:start] ? params[:start].to_i : 0
9
- @results = SearchResult.find(@query, {:start => @start, :portlet => @portlet, :site=>@site})
8
+ options = {:start => @start, :portlet => self, :site=>@site, :sort=>params[:sort]}
9
+ query_string = params[:query]
10
+
11
+ @results = SearchResult.find(query_string, options.clone) # Need to clone, so that :portlet isn't removed for the 2nd call.
12
+
13
+ # This is temporary, while the API is being reworked. Ideally, the search results would contain a reference
14
+ # to the query, so that two X calls isn't needed.
15
+ @query = SearchResult.create_query(query_string, options.clone)
16
+
17
+ if narrow_your_search?
18
+ @appliance = SearchResult.new_gsa(self)
19
+ @suggested_queries = @appliance.find_narrow_search_results(query_string)
20
+ end
10
21
  end
11
-
22
+
23
+ # Handles the fact that all portlet attributes, including checkboxes like enable_your_search are stored as strings.
24
+ def narrow_your_search?
25
+ self.enable_narrow_your_search == "1"
26
+ end
27
+
12
28
  end
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
5
+ <title><%= page_title %></title>
6
+ <%= yield :html_head %>
7
+ </head>
8
+ <body style="margin: 0; padding: 0; text-align: center;">
9
+ <%= cms_toolbar %>
10
+ <div id="wrapper" style="width: 700px; margin: 0 auto; text-align: left; padding: 30px">
11
+ Breadcrumbs: <%= render_breadcrumbs %>
12
+ Main Menu: <%= render_menu %>
13
+ <h1><%= page_title %></h1>
14
+ <%= container :main %>
15
+ </div>
16
+ </body>
17
+ </html>
@@ -3,4 +3,6 @@
3
3
  <%= f.cms_text_field :service_url, :default_value=> "http://", :instructions => "Full Domain name where the Google Mini server is hosted, i.e. 'http://mini.somedomain.com'." %>
4
4
  <%= f.cms_text_field :collection_name, :instructions => "Copy the name of the Collection from Google Mini here. (Can pass 'site' param to override as well).", :default_value=>"default_collection" %>
5
5
  <%= f.cms_text_field :front_end_name, :instructions => "Copy the name of the Collection front end from Google Mini here.", :default_value=>"default_frontend" %>
6
+ <%= f.cms_check_box :enable_narrow_your_search, :instructions => "The 'Narrow Your Search' feature is a only available for Google Search Appliance, not with Google Mini.",
7
+ :label=>"Enable Narrowing?" %>
6
8
  <%= f.cms_template_editor :template %>
@@ -1,33 +1,55 @@
1
+
1
2
  <h2>Search Results</h2>
2
- For '<%= @results.query %>', found <%= @results.results_count %> results. <br />
3
- <% if @results.previous_page? %><%= link_to h("< Back"), @results.previous_page_path %><% end %>
4
- <% if @results.next_page? %><%= link_to h("Next >"), @results.next_page_path %><% end %>
3
+ For '<%= @results.query %>', found <%= @results.results_count %> results. <br/>
4
+ <%= link_to_unless @results.sorting_by_date?(params), "Sort by Date", @results.sort_by_date_path %> /
5
+ <%= link_to_if @results.sorting_by_date?(params), "Sort by Relevance", @results.sort_by_relevance_path %> <br/>
6
+ <% if @results.previous_page? %><%= link_to h("< Back"), @results.previous_page_path %>
7
+ <% end %>
8
+ <% if @results.next_page? %><%= link_to h("Next >"), @results.next_page_path %>
9
+ <% end %>
5
10
  <% if @results.key_matches? %>
6
11
  <ul class="key_matches">
7
- <% @results.key_matches.each do |match| %>
8
- <li><%= link_to match.title, match.url %></li>
9
- <% end %>
12
+ <% @results.key_matches.each do |match| %>
13
+ <li><%= link_to match.title, match.url %></li>
14
+ <% end %>
10
15
  </ul>
11
16
  <% end %>
12
17
  <% if @results.synonyms? %>
13
18
  <span class="synonyms">You could also try:
14
- <% @results.synonyms.each do |synonym| %>
19
+ <% @results.synonyms.each do |synonym| %>
15
20
  <%= link_to synonym.label, synonym.url %>
16
21
  <% end %>
17
22
  </span>
18
23
  <% end %>
19
24
  <ul class="search_results">
20
- <% @results.each do |result|%>
21
- <li>
22
- <%= result.number %> <%= link_to result.title, result.url, :class=>"search_result_title" %><br />
23
- <span class="search_result_description"><%= result.description %></span> <br />
24
- <span class="search_result_url"><%= result.url %></span> -
25
+ <% @results.each do |result| %>
26
+ <li>
27
+ <%= result.number %> <%= link_to result.title, result.url, :class=>"search_result_title" %>
28
+ <br/>
29
+ <span class="search_result_description"><%= result.description %></span> <br/>
30
+ <span class="search_result_url"><%= result.url %></span> -
25
31
  <span class="search_result_size"><%= result.size %></span>
26
- </li>
32
+ <span class="search_result_cached"><%= link_to "Cached", result.cached_document_url(@query) %></span>
33
+
34
+ </li>
27
35
  <% end %>
28
36
  </ul>
29
- <% if @results.previous_page? %><%= link_to h("< Back"), @results.previous_page_path %><% end %>
30
- <% @results.pages.each do |p| %>
31
- <%= link_to_unless @results.current_page?(p), p, @results.page_path(p) %>
37
+
38
+ <% if @portlet.narrow_your_search? %>
39
+ <div id="clustering">
40
+ <h3>Narrow your search</h3>
41
+ <ul>
42
+ <% @suggested_queries.each_with_index do |suggestion, i| %>
43
+ <li id="cluster_label<%= i %>"><%= link_to suggestion.query, @results.path_for(suggestion.query) %></li>
44
+ <% end %>
45
+ </ul>
46
+ </div>
47
+ <% end %>
48
+
49
+ <% if @results.previous_page? %><%= link_to h("< Back"), @results.previous_page_path %>
50
+ <% end %>
51
+ <% @results.pages.each do |p| %>
52
+ <%= link_to_unless @results.current_page?(p), p, @results.page_path(p) %>
53
+ <% end %>
54
+ <% if @results.next_page? %><%= link_to h("Next >"), @results.next_page_path %>
32
55
  <% end %>
33
- <% if @results.next_page? %><%= link_to h("Next >"), @results.next_page_path %><% end %>
@@ -1 +1,2 @@
1
1
  require 'bcms_google_mini_search/routes'
2
+ require 'bcms_google_mini_search/gsa'
@@ -0,0 +1,144 @@
1
+ # A general purpose API for querying a Google Search Appliance for results.
2
+ module GSA
3
+
4
+ # Represents a single instance of a Google Mini
5
+ class Engine
6
+ attr_accessor :host, :port, :path, :default_collection, :default_front_end
7
+
8
+ def initialize(options = {})
9
+ self.port = 8080
10
+ self.path = "/search"
11
+ self.host = options[:host]
12
+ self.default_front_end = options[:front_end]
13
+ self.default_collection = options[:collection]
14
+ end
15
+
16
+ # Return a Hash suitable to be passed to SearchResult.find()
17
+ def options_for_query
18
+ {:host=>host, :front_end=>default_front_end, :collection=>default_collection}
19
+ end
20
+ end
21
+
22
+ # GSA support slightly different features than Google Mini.
23
+ class Appliance < Engine
24
+
25
+
26
+
27
+ # Fetch a set of Suggested queries, based on a given query.
28
+ #
29
+ # See http://code.google.com/apis/searchappliance/documentation/50/admin_searchexp/ce_understanding.html#h3drc for the spec.
30
+ # See http://groups.google.com/group/Google-Search-Appliance-Help/browse_thread/thread/8a821fc8475a5e24/34a5c3c8ab74ed35?hl=en&lnk=gst#34a5c3c8ab74ed35
31
+ # for details about how this is implemented.
32
+ #
33
+ # Clustering (aka Narrow your search) is only supported by GSA.
34
+ # @param [String] query A term to fetch 'suggested' queries for/
35
+ # @return [GSA::SuggestedQueries] A set of suggested queries
36
+ def find_narrow_search_results(query)
37
+ return [] unless query
38
+
39
+ url = narrow_search_results_url(query)
40
+ document = SearchResult.fetch_document(url)
41
+ SuggestedQueries.new(document)
42
+ end
43
+
44
+ private
45
+
46
+ # Returns the URL to GET a set of Dynamic Search Clusters for a particular query.
47
+ def narrow_search_results_url(query)
48
+ "#{host}/cluster?coutput=xml&q=#{CGI::escape(query)}&site=#{default_collection}&client=#{default_front_end}&output=xml_no_dtd&oe=UTF-8&ie=UTF-8"
49
+ end
50
+ end
51
+
52
+ # Represents a set of suggested search terms, based on results from a GSA.
53
+ # AKA DynamicResultClusters
54
+ class SuggestedQueries
55
+ def initialize(xml_as_string)
56
+ @clusters = []
57
+ doc = REXML::Document.new(xml_as_string)
58
+ doc.elements.each('toplevel/Response/cluster/gcluster') do |ele|
59
+ @clusters << Suggestion.new(ele.elements["label"].attributes["data"])
60
+ end
61
+
62
+ end
63
+
64
+ delegate :each, :each_with_index, :size, '[]', :to=>:clusters
65
+
66
+ private
67
+
68
+ def clusters
69
+ @clusters
70
+ end
71
+
72
+
73
+ # Since generating are handled in the view, this might no longer be necessary a separate class, and could probably be converted into a String.
74
+ class Suggestion
75
+ attr_accessor :query
76
+
77
+ def initialize(query)
78
+ self.query = query
79
+ end
80
+
81
+ end
82
+ end
83
+
84
+
85
+
86
+
87
+ class Query
88
+ attr_reader :engine, :query, :front_end, :collection
89
+
90
+ def initialize(options={})
91
+ @engine = options[:engine]
92
+ @query = options[:query]
93
+ @front_end = options[:front_end]
94
+ @collection = options[:collection]
95
+ end
96
+ end
97
+
98
+ # Represent a collection of results from a GSA search.
99
+ class Results
100
+
101
+ attr_accessor :query
102
+
103
+
104
+ end
105
+
106
+ # Represents a single result (aka Hit) from a GSA query.
107
+ # Defined by http://code.google.com/apis/searchappliance/documentation/46/xml_reference.html#results_xml_tag_r
108
+ class Result
109
+ attr_accessor :number, :title, :url, :description, :size, :cache_id, :results
110
+
111
+ # @param [RXEML::Element] xml_element The <R> result from GSA a search.
112
+ def initialize(xml_element = nil)
113
+ return if xml_element == nil
114
+ self.number = xml_element.attributes["N"]
115
+ self.title = xml_element.elements["T"].text
116
+ self.url = xml_element.elements["U"].text
117
+ self.description = xml_element.elements["S"].text
118
+
119
+ cache_element = xml_element.elements["HAS/C"]
120
+
121
+ if cache_element
122
+ self.size = cache_element.attributes["SZ"]
123
+ self.cache_id = cache_element.attributes["CID"]
124
+ else
125
+ self.size = ""
126
+ self.cache_id=""
127
+ end
128
+
129
+ end
130
+
131
+ # Returns the value for q if a user wants to request the cached version of this document.
132
+ def cached_document_param
133
+ param = "cache:#{cache_id}:#{url}"
134
+ if results
135
+ param += "+#{results.query}"
136
+ end
137
+ param
138
+ end
139
+
140
+ def cached_document_url(gsa_query)
141
+ SearchResult.query_url(cached_document_param, {:host=>gsa_query.engine.host, :collection=>gsa_query.collection, :front_end=>gsa_query.front_end, :as_xml=>false})
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,182 @@
1
+ require "test_helper"
2
+
3
+ class GSA::ApplianceTest < ActiveSupport::TestCase
4
+
5
+ test "Create Engine" do
6
+ app = GSA::Engine.new
7
+ app.host = "http://example.com"
8
+ assert_equal "http://example.com", app.host
9
+ assert_equal 8080, app.port
10
+ assert_equal "/search", app.path
11
+ end
12
+
13
+ test "Create from options" do
14
+ app = GSA::Engine.new({:host=>"http://example.com", :front_end=>"F", :collection=>"C"})
15
+ assert_equal "http://example.com", app.host
16
+ assert_equal "F", app.default_front_end
17
+ assert_equal "C", app.default_collection
18
+ end
19
+
20
+ test "options_for_query" do
21
+ options = {:host=>"http://example.com", :front_end=>"F", :collection=>"C"}
22
+ app = GSA::Engine.new(options)
23
+ assert_equal options, app.options_for_query
24
+ end
25
+ end
26
+
27
+ class SuggestedQueries < ActiveSupport::TestCase
28
+
29
+ def setup
30
+ @cluster_xml = <<XML
31
+ <toplevel>
32
+ <Response>
33
+ <algorithm data="Concepts"/>
34
+ <t_cluster int="95"/>
35
+ <cluster>
36
+ <gcluster>
37
+ <label data="label 0"/>
38
+ </gcluster>
39
+ <gcluster>
40
+ <label data="label 1"/>
41
+ </gcluster>
42
+ <gcluster>
43
+ <label data="label 2"/>
44
+ </gcluster>
45
+ <gcluster>
46
+ <label data="label 3"/>
47
+ </gcluster>
48
+ <gcluster>
49
+ <label data="label 4"/>
50
+ </gcluster>
51
+ <gcluster>
52
+ <label data="label 5"/>
53
+ </gcluster>
54
+ <gcluster>
55
+ <label data="label 6"/>
56
+ </gcluster>
57
+ <gcluster>
58
+ <label data="label 7"/>
59
+ </gcluster>
60
+ <gcluster>
61
+ <label data="label 8"/>
62
+ </gcluster>
63
+ <gcluster>
64
+ <label data="label 9"/>
65
+ </gcluster>
66
+ </cluster>
67
+ </Response>
68
+ </toplevel>
69
+ XML
70
+
71
+ @app = GSA::Appliance.new(:host=>"http://example.com", :collection=>"My_Collection", :front_end=>"My_Front")
72
+
73
+ end
74
+
75
+ test "parse results" do
76
+ suggested_queries = GSA::SuggestedQueries.new(@cluster_xml)
77
+ assert_equal 10, suggested_queries.size
78
+ assert_equal "label 0", suggested_queries[0].query
79
+ assert_equal "label 9", suggested_queries[9].query
80
+ end
81
+
82
+ test "Google Search Appliances should generate the URL for Dynamic Results Clustering" do
83
+ expected = "http://example.com/cluster?coutput=xml&q=TEST&site=My_Collection&client=My_Front&output=xml_no_dtd&oe=UTF-8&ie=UTF-8"
84
+ assert_equal expected, @app.send(:narrow_search_results_url, "TEST")
85
+ end
86
+
87
+ test "URLs will escape queries" do
88
+ expected = "http://example.com/cluster?coutput=xml&q=TWO+WORDS&site=My_Collection&client=My_Front&output=xml_no_dtd&oe=UTF-8&ie=UTF-8"
89
+ assert_equal expected, @app.send(:narrow_search_results_url, "TWO WORDS")
90
+ end
91
+
92
+ test "find narrowed search results" do
93
+ @app.expects(:narrow_search_results_url).with("TEST").returns("EXPECTED URL")
94
+ SearchResult.expects(:fetch_document).with("EXPECTED URL").returns("XML Content")
95
+ expected_suggestions = mock()
96
+ GSA::SuggestedQueries.expects(:new).with("XML Content").returns(expected_suggestions)
97
+
98
+ assert_equal expected_suggestions, @app.find_narrow_search_results("TEST")
99
+
100
+ end
101
+
102
+ test "each_with_index" do
103
+ suggestions = GSA::SuggestedQueries.new(@cluster_xml)
104
+ count = 0
105
+ suggestions.each_with_index do |s, i|
106
+ assert_not_nil s
107
+ assert_not_nil i
108
+ count += 1
109
+ end
110
+ assert_equal suggestions.size, count
111
+ end
112
+
113
+ test "each" do
114
+ suggestions = GSA::SuggestedQueries.new(@cluster_xml)
115
+ count = 0
116
+ suggestions.each do |s|
117
+ assert_not_nil s
118
+ count += 1
119
+ end
120
+ assert_equal suggestions.size, count
121
+ end
122
+
123
+ test "A nil query should return an empty set of Suggested Queries" do
124
+ r = @app.find_narrow_search_results(nil)
125
+ assert_equal 0, r.size
126
+ end
127
+ end
128
+
129
+ class ResultsTest < ActiveSupport::TestCase
130
+
131
+ def setup
132
+ @results = GSA::Results.new
133
+ @results.query = "QUERY"
134
+ @result = GSA::Result.new
135
+ @result.results = @results
136
+
137
+ @engine = GSA::Engine.new(:host=>"http://mini.someurl.com")
138
+ @query = GSA::Query.new(:engine=>@engine, :collection=>"COLLECT", :front_end=>"FRONT_END")
139
+ end
140
+
141
+ test "Create result from xml" do
142
+ xml = <<XML
143
+ <R N="1">
144
+ <U>http://someurl.com</U>
145
+ <T>TITLE</T>
146
+ <S>BLURB</S>
147
+ <HAS>
148
+ <C SZ="1k" CID="Ax1j5"/>
149
+ </HAS>
150
+ </R>
151
+ XML
152
+ xml_doc = REXML::Document.new(xml)
153
+ result = GSA::Result.new(xml_doc.elements.first)
154
+ assert_equal "http://someurl.com", result.url
155
+ assert_equal "TITLE", result.title
156
+ assert_equal "BLURB", result.description
157
+ assert_equal "1k", result.size
158
+ assert_equal "1", result.number
159
+ assert_equal "Ax1j5", result.cache_id
160
+ end
161
+
162
+ test "cached_document_param" do
163
+ @result.cache_id = "A2B"
164
+ @result.url = "http://example.com"
165
+
166
+ assert_equal "cache:A2B:http://example.com+QUERY", @result.cached_document_param
167
+ end
168
+
169
+ test "cached_document_param with no result attached" do
170
+ @result.results = nil
171
+ @result.cache_id = "A2B"
172
+ @result.url = "http://example.com"
173
+
174
+ assert_equal "cache:A2B:http://example.com", @result.cached_document_param
175
+ end
176
+
177
+ test "cached_document_url" do
178
+ @result.expects(:cached_document_param).returns("cache:something")
179
+ expected_url = "http://mini.someurl.com/search?q=cache%3Asomething&output=xml_no_dtd&client=FRONT_END&site=COLLECT&filter=0&proxystylesheet=FRONT_END&oe=UTF-8&ie=UTF-8"
180
+ assert_equal expected_url, @result.cached_document_url(@query)
181
+ end
182
+ end
@@ -2,14 +2,52 @@ require File.join(File.dirname(__FILE__), '/../../test_helper')
2
2
 
3
3
  class GoogleMiniSearchEngineTest < ActiveSupport::TestCase
4
4
 
5
+ def setup
6
+ @portlet = GoogleMiniSearchEnginePortlet.new(:name=>"Engine", :path => "/engine")
7
+ end
8
+
5
9
  test "Should be able to create new instance of a portlet" do
6
10
  assert GoogleMiniSearchEnginePortlet.create!(:name => "New Portlet")
7
11
  end
8
12
 
9
-
10
-
11
13
  test "Path attribute can be set in constructor" do
12
14
  portlet = GoogleMiniSearchEnginePortlet.create!(:name=>"Engine", :path => "/engine")
13
15
  assert_equal "/engine", portlet.path
14
16
  end
17
+
18
+ test "Determine if Narrow Your Search is enabled?" do
19
+ @portlet.enable_narrow_your_search = "1"
20
+ assert_equal true, @portlet.narrow_your_search?
21
+
22
+ @portlet.enable_narrow_your_search = "0"
23
+ assert_equal false, @portlet.narrow_your_search?
24
+
25
+ @portlet.enable_narrow_your_search = ""
26
+ assert_equal false, @portlet.narrow_your_search?
27
+
28
+ @portlet.enable_narrow_your_search = nil
29
+ assert_equal false, @portlet.narrow_your_search?
30
+ end
31
+
32
+
33
+ end
34
+
35
+ class RenderTest < ActiveSupport::TestCase
36
+ def setup
37
+ @portlet = GoogleMiniSearchEnginePortlet.new(:name=>"Engine", :path => "/engine")
38
+ @params = {:start => 10, :query => "X", :site=>'default_collection', :sort=>"date:D:S:d1"}
39
+ @portlet.expects('params').returns(@params).at_least_once
40
+ end
41
+
42
+ test "Sort params" do
43
+ SearchResult.expects(:find).with("X", {:start => 10, :portlet => @portlet, :site=>'default_collection', :sort=>"date:D:S:d1"})
44
+ @portlet.render
45
+ end
46
+
47
+ test "Find narrow queries only if enabled" do
48
+ @portlet.enable_narrow_your_search = "1"
49
+ GSA::Appliance.any_instance.expects(:find_narrow_search_results).with("X")
50
+ SearchResult.expects(:find).with("X", {:start => 10, :portlet => @portlet, :site=>'default_collection', :sort=>"date:D:S:d1"})
51
+ @portlet.render
52
+ end
15
53
  end
@@ -1,5 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), '/../test_helper')
2
2
 
3
+
3
4
  class SearchResultTest < ActiveSupport::TestCase
4
5
 
5
6
  def setup
@@ -205,14 +206,7 @@ EOF
205
206
  assert_equal [], results.pages
206
207
  end
207
208
 
208
- test "Behavior of ranges" do
209
- c = 0
210
- (1..4).each_with_index do |i, count|
211
- assert_equal count + 1, i
212
- c = count
213
- end
214
- assert_equal 3, c
215
- end
209
+
216
210
 
217
211
  test "current_page should check to see if the current page matches" do
218
212
  results = SearchResult::QueryResult.new
@@ -225,45 +219,6 @@ EOF
225
219
 
226
220
  end
227
221
 
228
- test "Path to next page" do
229
- results = SearchResult::QueryResult.new
230
- results.start = 0
231
- results.path = "/search/search-results"
232
- results.query = "X"
233
-
234
- assert_equal "/search/search-results?query=X&start=10", results.next_page_path
235
- end
236
-
237
- test "Path to previous page" do
238
- results = SearchResult::QueryResult.new
239
- results.start = 20
240
- results.path = "/search/search-results"
241
- results.query = "X"
242
-
243
- assert_equal "/search/search-results?query=X&start=10", results.previous_page_path
244
- end
245
-
246
- test "Sets path to default search-results" do
247
- results = SearchResult::QueryResult.new
248
- assert_equal "/search/search-results", results.path
249
- end
250
-
251
- test "Setting path overrides the defaults" do
252
- results = SearchResult::QueryResult.new
253
- results.path = "/other"
254
- assert_equal "/other", results.path
255
- end
256
-
257
- test "page_path" do
258
- results = SearchResult::QueryResult.new
259
- results.query = "X"
260
-
261
- assert_equal "/search/search-results?query=X&start=0", results.page_path(1)
262
- assert_equal "/search/search-results?query=X&start=10", results.page_path(2)
263
- assert_equal "/search/search-results?query=X&start=20", results.page_path(3)
264
- assert_equal "/search/search-results?query=X&start=30", results.page_path(4)
265
-
266
- end
267
222
 
268
223
  test "Portlet attributes are used to look up path" do
269
224
  portlet = GoogleMiniSearchEnginePortlet.new(:name=>"Engine", :path => "/engine")
@@ -285,12 +240,48 @@ EOF
285
240
  :name=>"Engine", :path => "/engine", :service_url => "http://mini.someurl.com",
286
241
  :collection_name => "COLLECT", :front_end_name => "FRONT_END")
287
242
 
288
- url = SearchResult.build_mini_url({:portlet => portlet}, "STUFF")
289
- assert_equal "http://mini.someurl.com/search?q=STUFF&output=xml_no_dtd&client=FRONT_END&site=COLLECT&filter=0", url
243
+ url = SearchResult.create_url_for_query({:portlet => portlet}, "STUFF")
244
+ assert_equal "http://mini.someurl.com/search?q=STUFF&output=xml_no_dtd&client=FRONT_END&site=COLLECT&filter=0&oe=UTF-8&ie=UTF-8", url
245
+
246
+ url = SearchResult.create_url_for_query({:portlet => portlet, :start=>100}, "STUFF")
247
+ assert_equal "http://mini.someurl.com/search?q=STUFF&output=xml_no_dtd&client=FRONT_END&site=COLLECT&filter=0&start=100&oe=UTF-8&ie=UTF-8", url
290
248
 
291
- url = SearchResult.build_mini_url({:portlet => portlet, :start=>100}, "STUFF")
292
- assert_equal "http://mini.someurl.com/search?q=STUFF&output=xml_no_dtd&client=FRONT_END&site=COLLECT&filter=0&start=100", url
249
+ end
250
+
251
+ test "Create Engine and Query from portlet attributes" do
252
+ portlet = GoogleMiniSearchEnginePortlet.new(
253
+ :name=>"Engine", :path => "/engine", :service_url => "http://mini.someurl.com",
254
+ :collection_name => "COLLECT", :front_end_name => "FRONT_END")
293
255
 
256
+ query = SearchResult.create_query("therapy", {:portlet=>portlet})
257
+ assert_equal "http://mini.someurl.com", query.engine.host
258
+ assert_equal portlet.front_end_name, query.front_end
259
+ assert_equal portlet.collection_name, query.collection
260
+ assert_equal "therapy", query.query
261
+ end
262
+
263
+ test "should look up options from portlet and add to hash" do
264
+ portlet = GoogleMiniSearchEnginePortlet.new(
265
+ :name=>"Engine", :path => "/engine", :service_url => "http://mini.someurl.com",
266
+ :collection_name => "COLLECT", :front_end_name => "FRONT_END")
267
+ options = {:portlet=>portlet}
268
+ SearchResult.normalize_query_options(options)
269
+
270
+ assert_equal "FRONT_END", options[:front_end]
271
+ assert_equal "COLLECT", options[:collection]
272
+ assert_equal "http://mini.someurl.com", options[:host]
273
+ assert_equal nil, options[:portlet]
274
+ end
275
+
276
+ test "Create an appliance from attributes in the portlet." do
277
+ portlet = GoogleMiniSearchEnginePortlet.new(
278
+ :name=>"Engine", :path => "/engine", :service_url => "http://mini.someurl.com",
279
+ :collection_name => "COLLECT", :front_end_name => "FRONT_END")
280
+
281
+ gsa = SearchResult.new_gsa(portlet)
282
+ assert_equal "http://mini.someurl.com", gsa.host
283
+ assert_equal "FRONT_END", gsa.default_front_end
284
+ assert_equal "COLLECT", gsa.default_collection
294
285
  end
295
286
 
296
287
  test "Explicitly passing a collection in will query with that rather than a default collection" do
@@ -298,13 +289,23 @@ EOF
298
289
  :name=>"Engine", :path => "/engine", :service_url => "http://mini.someurl.com",
299
290
  :collection_name => "COLLECT", :front_end_name => "FRONT_END")
300
291
 
301
- url = SearchResult.build_mini_url({:portlet => portlet, :site=>"ANOTHER_COLLECTION"}, "STUFF")
302
- assert_equal "http://mini.someurl.com/search?q=STUFF&output=xml_no_dtd&client=FRONT_END&site=ANOTHER_COLLECTION&filter=0", url
292
+ url = SearchResult.create_url_for_query({:portlet => portlet, :site=>"ANOTHER_COLLECTION"}, "STUFF")
293
+ assert_equal "http://mini.someurl.com/search?q=STUFF&output=xml_no_dtd&client=FRONT_END&site=ANOTHER_COLLECTION&filter=0&oe=UTF-8&ie=UTF-8", url
303
294
  end
304
295
 
305
296
  test "Handles multiword queries" do
306
- url = SearchResult.build_mini_url({}, "One Two")
307
- assert_equal "/search?q=One+Two&output=xml_no_dtd&client=&site=&filter=0", url
297
+ url = SearchResult.create_url_for_query({}, "One Two")
298
+ assert_equal "/search?q=One+Two&output=xml_no_dtd&client=&site=&filter=0&oe=UTF-8&ie=UTF-8", url
299
+ end
300
+
301
+ test "sort is added to google mini query" do
302
+ url = SearchResult.create_url_for_query({:sort=>"XYZ"}, "STUFF")
303
+ assert_equal "/search?q=STUFF&output=xml_no_dtd&client=&site=&filter=0&sort=XYZ&oe=UTF-8&ie=UTF-8", url
304
+ end
305
+
306
+ test "sort params are escaped" do
307
+ url = SearchResult.create_url_for_query({:sort=>"date:D:S:d1"}, "STUFF")
308
+ assert_equal "/search?q=STUFF&output=xml_no_dtd&client=&site=&filter=0&sort=date%3AD%3AS%3Ad1&oe=UTF-8&ie=UTF-8", url
308
309
  end
309
310
 
310
311
  test "Handles keymatches in results" do
@@ -464,5 +465,123 @@ EOF
464
465
  assert_equal "TITLE 2", results[1].title
465
466
  assert_equal "BLURB 2", results[1].description
466
467
  assert_equal "", results[1].size
468
+ end
469
+
470
+ end
471
+
472
+ class SearchPathsTest < ActiveSupport::TestCase
473
+
474
+ def setup
475
+ @results = SearchResult::QueryResult.new
476
+ @results.start = 0
477
+ @results.path = "/search/search-results"
478
+ @results.query = "X"
479
+ end
480
+
481
+ test "path_for" do
482
+ assert_equal "/search/search-results?query=Y", @results.path_for("Y")
483
+ end
484
+
485
+ test "sort by date" do
486
+ assert_equal "#{@results.path}?query=#{@results.query}&sort=#{SearchResult::QueryResult::SORT_BY_DATE_PARAM}", @results.sort_by_date_path
487
+ end
488
+
489
+ test "sort by relevance" do
490
+ assert @results.sort_by_relevance_path != @results.sort_by_date_path, "Paths should not be the same."
491
+ assert_equal "#{@results.path}?query=#{@results.query}&sort=#{SearchResult::QueryResult::SORT_BY_RELEVANCE_PARAM}", @results.sort_by_relevance_path
492
+ end
493
+
494
+ test "Path to next page" do
495
+ assert_equal "/search/search-results?query=X&start=10", @results.next_page_path
496
+ end
497
+
498
+ test "Path to previous page" do
499
+ @results.start = 20
500
+ assert_equal "/search/search-results?query=X&start=10", @results.previous_page_path
501
+ end
502
+
503
+ test "Sets path to default search-results" do
504
+ assert_equal "/search/search-results", @results.path
505
+ end
506
+
507
+ test "Setting path overrides the defaults" do
508
+ @results.path = "/other"
509
+ assert_equal "/other", @results.path
510
+ end
511
+
512
+ test "page_path" do
513
+ assert_equal "/search/search-results?query=X&start=0", @results.page_path(1)
514
+ assert_equal "/search/search-results?query=X&start=10", @results.page_path(2)
515
+ assert_equal "/search/search-results?query=X&start=20", @results.page_path(3)
516
+ assert_equal "/search/search-results?query=X&start=30", @results.page_path(4)
517
+ end
518
+
519
+ test "sorting_by_date?" do
520
+ assert_equal true, @results.sorting_by_date?({:sort=>SearchResult::QueryResult::SORT_BY_DATE_PARAM})
521
+ assert_equal false, @results.sorting_by_date?({:sort=>SearchResult::QueryResult::SORT_BY_RELEVANCE_PARAM})
522
+ assert_equal false, @results.sorting_by_date?({})
523
+ end
524
+ end
525
+
526
+
527
+ class PagingTest < ActiveSupport::TestCase
528
+
529
+ def setup
530
+ @results = SearchResult::QueryResult.new
531
+ and_the_max_number_pages_is 100
532
+ end
533
+
534
+ test "Behavior of Ruby Ranges" do
535
+ c = 0
536
+ (1..4).each_with_index do |i, count|
537
+ assert_equal count + 1, i
538
+ c = count
539
+ end
540
+ assert_equal 3, c
467
541
  end
542
+
543
+
544
+ test "When on page 1, show links for pages 1 - 10" do
545
+ when_current_page_is(1)
546
+ assert_equal (1..10), @results.pages
547
+ end
548
+
549
+ test "When on page 11, show links for pages 1-20" do
550
+ when_current_page_is(11)
551
+ assert_equal (1..20), @results.pages
552
+ end
553
+
554
+ test "When on page 12, show links for pages 2-22" do
555
+ when_current_page_is 12
556
+ assert_equal (2..21), @results.pages
557
+ end
558
+
559
+ test "When less than 10 pages only show up to last page" do
560
+ when_current_page_is 1
561
+ and_the_max_number_pages_is 4
562
+
563
+ assert_equal (1..4), @results.pages
564
+ end
565
+
566
+ test "When no results, should be empty set of pages." do
567
+ when_current_page_is 1
568
+ and_the_max_number_pages_is 0
569
+ assert_equal [], @results.pages
570
+ end
571
+
572
+ test "With one page, return a single page." do
573
+ when_current_page_is 1
574
+ and_the_max_number_pages_is 1
575
+ assert_equal [], @results.pages, "A single page of results needs no pager control"
576
+ end
577
+
578
+ private
579
+
580
+ def and_the_max_number_pages_is(number)
581
+ @results.expects(:num_pages).returns(number).times(0..5)
582
+ end
583
+
584
+ def when_current_page_is(current_page)
585
+ @results.expects(:current_page).returns(current_page).times(0..5)
586
+ end
468
587
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bcms_google_mini_search
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 31
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
- - 1
9
- - 1
10
- version: 1.1.1
8
+ - 2
9
+ - 0
10
+ version: 1.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - BrowserMedia
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-09 00:00:00 -05:00
18
+ date: 2011-04-05 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -33,16 +33,19 @@ files:
33
33
  - app/models/search_result.rb
34
34
  - app/portlets/google_mini_search_engine_portlet.rb
35
35
  - app/portlets/search_box_portlet.rb
36
+ - app/views/layouts/templates/default.html.erb.html.erb
36
37
  - app/views/portlets/google_mini_search_engine/_form.html.erb
37
38
  - app/views/portlets/google_mini_search_engine/render.html.erb
38
39
  - app/views/portlets/search_box/_form.html.erb
39
40
  - app/views/portlets/search_box/render.html.erb
40
41
  - lib/bcms_google_mini_search.rb
42
+ - lib/bcms_google_mini_search/gsa.rb
41
43
  - lib/bcms_google_mini_search/routes.rb
42
44
  - rails/init.rb
43
45
  - README.markdown
44
46
  - test/performance/browsing_test.rb
45
47
  - test/test_helper.rb
48
+ - test/unit/gsa_test.rb
46
49
  - test/unit/helpers/search_engine_helper_test.rb
47
50
  - test/unit/portlets/google_mini_search_engine_portlet_test.rb
48
51
  - test/unit/portlets/search_box_portlet_test.rb
@@ -52,8 +55,8 @@ homepage: http://github.com/browsermedia/bcms_google_mini_search
52
55
  licenses: []
53
56
 
54
57
  post_install_message:
55
- rdoc_options:
56
- - --charset=UTF-8
58
+ rdoc_options: []
59
+
57
60
  require_paths:
58
61
  - lib
59
62
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -84,6 +87,7 @@ summary: A Google Mini Search Module for BrowserCMS
84
87
  test_files:
85
88
  - test/performance/browsing_test.rb
86
89
  - test/test_helper.rb
90
+ - test/unit/gsa_test.rb
87
91
  - test/unit/helpers/search_engine_helper_test.rb
88
92
  - test/unit/portlets/google_mini_search_engine_portlet_test.rb
89
93
  - test/unit/portlets/search_box_portlet_test.rb