summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2014-01-18 22:12:55 +0000
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2014-01-18 22:12:55 +0000
commitd95e56f05e577a260444681e42a58109834d8284 (patch)
tree4cefb1fd0df80b5511f68edb2082e1cf6889c220
parent96808199b24c24923ccdb2e5299a5b2d73e6f85d (diff)
parent1016b547f51ade65fa67684d787b53a440a1e000 (diff)
downloadgitlab-ce-d95e56f05e577a260444681e42a58109834d8284.tar.gz
Merge branch 'improve/search_autocomplete' into 'master'
Improve: Search autocomplete * fetch options via ajax * only show options related to user input * add limit to amount of options
-rw-r--r--CHANGELOG1
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee8
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee9
-rw-r--r--app/controllers/search_controller.rb10
-rw-r--r--app/helpers/search_helper.rb55
-rw-r--r--app/models/project.rb4
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--config/routes.rb1
-rw-r--r--spec/helpers/search_helper_spec.rb27
9 files changed, 75 insertions, 42 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 89656265289..a7ab8f569b6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,7 @@ v 6.5.0
- Use jquery timeago plugin
- Fix 500 error for rdoc files
- Ability to customize merge commit message (sponsored by Say Media)
+ - Search autocomplete via ajax
v6.4.3
- Don't use unicorn worker killer if PhusionPassenger is defined
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index e264e281309..8ea302f256a 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -47,5 +47,9 @@ class Dispatcher
initSearch: ->
- autocomplete_json = $('.search-autocomplete-json').data('autocomplete-opts')
- new SearchAutocomplete(autocomplete_json)
+ opts = $('.search-autocomplete-opts')
+ path = opts.data('autocomplete-path')
+ project_id = opts.data('autocomplete-project-id')
+ project_ref = opts.data('autocomplete-project-ref')
+
+ new SearchAutocomplete(path, project_id, project_ref)
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index 3418690e109..e144dfa1d68 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -1,7 +1,12 @@
class SearchAutocomplete
- constructor: (json) ->
+ constructor: (search_autocomplete_path, project_id, project_ref) ->
+ project_id = '' unless project_id
+ project_ref = '' unless project_ref
+ query = "?project_id=" + project_id + "&project_ref=" + project_ref
+
$("#search").autocomplete
- source: json
+ source: search_autocomplete_path + query
+ minLength: 1
select: (event, ui) ->
location.href = ui.item.url
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index e853c22800a..0152d53f7fc 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,4 +1,6 @@
class SearchController < ApplicationController
+ include SearchHelper
+
def show
@project = Project.find_by_id(params[:project_id]) if params[:project_id].present?
@group = Group.find_by_id(params[:group_id]) if params[:group_id].present?
@@ -10,4 +12,12 @@ class SearchController < ApplicationController
@search_results = Search::GlobalService.new(current_user, params).execute
end
end
+
+ def autocomplete
+ term = params[:term]
+ @project = Project.find(params[:project_id]) if params[:project_id].present?
+ @ref = params[:project_ref] if params[:project_ref].present?
+
+ render json: search_autocomplete_opts(term).to_json
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index f24156e4d85..470a495f036 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -1,16 +1,22 @@
module SearchHelper
- def search_autocomplete_source
+ def search_autocomplete_opts(term)
return unless current_user
+
+ resources_results = [
+ groups_autocomplete(term),
+ projects_autocomplete(term),
+ public_projects_autocomplete(term),
+ ].flatten
+
+ generic_results = project_autocomplete + default_autocomplete + help_autocomplete
+ generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") }
+
[
- groups_autocomplete,
- projects_autocomplete,
- public_projects_autocomplete,
- default_autocomplete,
- project_autocomplete,
- help_autocomplete
+ resources_results,
+ generic_results
].flatten.uniq do |item|
item[:label]
- end.to_json
+ end
end
private
@@ -43,7 +49,7 @@ module SearchHelper
# Autocomplete results for the current project, if it's defined
def project_autocomplete
if @project && @project.repository.exists? && @project.repository.root_ref
- prefix = simple_sanitize(@project.name_with_namespace)
+ prefix = search_result_sanitize(@project.name_with_namespace)
ref = @ref || @project.repository.root_ref
[
@@ -65,23 +71,36 @@ module SearchHelper
end
# Autocomplete results for the current user's groups
- def groups_autocomplete
- current_user.authorized_groups.map do |group|
- { label: "group: #{simple_sanitize(group.name)}", url: group_path(group) }
+ def groups_autocomplete(term, limit = 5)
+ current_user.authorized_groups.search(term).limit(limit).map do |group|
+ {
+ label: "group: #{search_result_sanitize(group.name)}",
+ url: group_path(group)
+ }
end
end
# Autocomplete results for the current user's projects
- def projects_autocomplete
- current_user.authorized_projects.non_archived.map do |p|
- { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
+ def projects_autocomplete(term, limit = 5)
+ current_user.authorized_projects.search_by_title(term).non_archived.limit(limit).map do |p|
+ {
+ label: "project: #{search_result_sanitize(p.name_with_namespace)}",
+ url: project_path(p)
+ }
end
end
# Autocomplete results for the current user's projects
- def public_projects_autocomplete
- Project.public_or_internal_only(current_user).non_archived.map do |p|
- { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
+ def public_projects_autocomplete(term, limit = 5)
+ Project.public_or_internal_only(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
+ {
+ label: "project: #{search_result_sanitize(p.name_with_namespace)}",
+ url: project_path(p)
+ }
end
end
+
+ def search_result_sanitize(str)
+ Sanitize.clean(str)
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index f322b5a2e25..30faaf00fff 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -138,6 +138,10 @@ class Project < ActiveRecord::Base
joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%")
end
+ def search_by_title query
+ where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%")
+ end
+
def find_with_namespace(id)
if id.include?("/")
id = id.split("/")
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 9a0db99332a..a0e55b21c32 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -7,4 +7,4 @@
= hidden_field_tag :search_code, true
= hidden_field_tag :repository_ref, @ref
= submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
- .search-autocomplete-json.hide{:'data-autocomplete-opts' => search_autocomplete_source }
+ .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
diff --git a/config/routes.rb b/config/routes.rb
index 734421ede1d..315c339016b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,6 +6,7 @@ Gitlab::Application.routes.draw do
# Search
#
get 'search' => "search#show"
+ get 'search/autocomplete' => "search#autocomplete", as: :search_autocomplete
# API
API::API.logger Rails.logger
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 33ecb980202..d04945dfe35 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -13,52 +13,41 @@ describe SearchHelper do
end
it "it returns nil" do
- search_autocomplete_source.should be_nil
+ search_autocomplete_opts("q").should be_nil
end
end
context "with a user" do
let(:user) { create(:user) }
- let(:result) { JSON.parse(search_autocomplete_source) }
before do
allow(self).to receive(:current_user).and_return(user)
end
it "includes Help sections" do
- result.select { |h| h['label'] =~ /^help:/ }.length.should == 9
+ search_autocomplete_opts("hel").size.should == 9
end
it "includes default sections" do
- result.count { |h| h['label'] =~ /^(My|Admin)\s/ }.should == 4
+ search_autocomplete_opts("adm").size.should == 1
end
it "includes the user's groups" do
create(:group).add_owner(user)
- result.count { |h| h['label'] =~ /^group:/ }.should == 1
+ search_autocomplete_opts("gro").size.should == 1
end
it "includes the user's projects" do
- create(:project, namespace: create(:namespace, owner: user))
- result.count { |h| h['label'] =~ /^project:/ }.should == 1
+ project = create(:project, namespace: create(:namespace, owner: user))
+ search_autocomplete_opts(project.name).size.should == 1
end
context "with a current project" do
before { @project = create(:project_with_code) }
it "includes project-specific sections" do
- result.count { |h| h['label'] =~ /^#{@project.name_with_namespace} - / }.should == 11
- end
-
- it "uses @ref in urls if defined" do
- @ref = "foo_bar"
- result.count { |h| h['url'] == project_tree_path(@project, @ref) }.should == 1
- end
- end
-
- context "with no current project" do
- it "does not include project-specific sections" do
- result.count { |h| h['label'] =~ /Files$/ }.should == 0
+ search_autocomplete_opts("Files").size.should == 1
+ search_autocomplete_opts("Commits").size.should == 1
end
end
end