]> source.dussan.org Git - redmine.git/commitdiff
Added a quick search form in page header. Search functionality moved to a dedicated...
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Mon, 30 Apr 2007 08:52:39 +0000 (08:52 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Mon, 30 Apr 2007 08:52:39 +0000 (08:52 +0000)
When used:
* outside of a project: searches projects
* inside a project: searches issues, changesets, news, documents and wiki pages of the current project

If an issue number is given, user is redirected to the corresponding issue.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@489 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/controllers/projects_controller.rb
app/controllers/search_controller.rb [new file with mode: 0644]
app/helpers/projects_helper.rb
app/helpers/search_helper.rb [new file with mode: 0644]
app/views/layouts/base.rhtml
app/views/projects/search.rhtml [deleted file]
app/views/search/index.rhtml [new file with mode: 0644]
public/stylesheets/application.css
test/functional/projects_controller_test.rb
test/functional/search_controller_test.rb [new file with mode: 0644]

index a471260528bf0fb07d0bf389bc74a6ea89ceae6b..ffc71ce1f74d58a1b31d08facb6b8882890013a4 100644 (file)
@@ -612,33 +612,7 @@ class ProjectsController < ApplicationController
       render :template => "projects/gantt.rhtml"
     end
   end
-  
-  def search
-    @question = params[:q] || ""
-    @question.strip!
-    @all_words = params[:all_words] || (params[:submit] ? false : true)
-    @scope = params[:scope] || (params[:submit] ? [] : %w(issues changesets news documents wiki) )
-    # tokens must be at least 3 character long
-    @tokens = @question.split.uniq.select {|w| w.length > 2 }
-    if !@tokens.empty?
-      # no more than 5 tokens to search for
-      @tokens.slice! 5..-1 if @tokens.size > 5
-      # strings used in sql like statement
-      like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
-      operator = @all_words ? " AND " : " OR "
-      limit = 10
-      @results = []
-      @results += @project.issues.find(:all, :limit => limit, :include => :author, :conditions => [ (["(LOWER(subject) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'issues'
-      @results += @project.news.find(:all, :limit => limit, :conditions => [ (["(LOWER(title) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort], :include => :author ) if @scope.include? 'news'
-      @results += @project.documents.find(:all, :limit => limit, :conditions => [ (["(LOWER(title) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'documents'
-      @results += @project.wiki.pages.find(:all, :limit => limit, :include => :content, :conditions => [ (["(LOWER(title) like ? OR LOWER(text) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @project.wiki && @scope.include?('wiki')
-      @results += @project.repository.changesets.find(:all, :limit => limit, :conditions => [ (["(LOWER(comments) like ?)"] * like_tokens.size).join(operator), * (like_tokens).sort] ) if @project.repository && @scope.include?('changesets')
-      @question = @tokens.join(" ")
-    else
-      @question = ""
-    end
-  end
-  
+    
   def feeds
     @queries = @project.queries.find :all, :conditions => ["is_public=? or user_id=?", true, (logged_in_user ? logged_in_user.id : 0)]
     @key = logged_in_user.get_or_create_rss_key.value if logged_in_user
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
new file mode 100644 (file)
index 0000000..c463e4a
--- /dev/null
@@ -0,0 +1,75 @@
+# redMine - project management software
+# Copyright (C) 2006  Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+class SearchController < ApplicationController
+  layout 'base'
+
+  def index
+    @question = params[:q] || ""
+    @question.strip!
+    @all_words = params[:all_words] || (params[:submit] ? false : true)
+    @scope = params[:scope] || (params[:submit] ? [] : %w(projects issues changesets news documents wiki) )
+    
+    # quick jump to an issue
+    if @scope.include?('issues') && @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(logged_in_user))
+      redirect_to :controller => "issues", :action => "show", :id => $1
+      return
+    end
+    
+    if params[:id]
+      find_project
+      return unless check_project_privacy
+    end
+    
+    # tokens must be at least 3 character long
+    @tokens = @question.split.uniq.select {|w| w.length > 2 }
+    
+    if !@tokens.empty?
+      # no more than 5 tokens to search for
+      @tokens.slice! 5..-1 if @tokens.size > 5
+      # strings used in sql like statement
+      like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
+      operator = @all_words ? " AND " : " OR "
+      limit = 10
+      @results = []
+      if @project
+        @results += @project.issues.find(:all, :limit => limit, :include => :author, :conditions => [ (["(LOWER(subject) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'issues'
+        @results += @project.news.find(:all, :limit => limit, :conditions => [ (["(LOWER(title) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort], :include => :author ) if @scope.include? 'news'
+        @results += @project.documents.find(:all, :limit => limit, :conditions => [ (["(LOWER(title) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'documents'
+        @results += @project.wiki.pages.find(:all, :limit => limit, :include => :content, :conditions => [ (["(LOWER(title) like ? OR LOWER(text) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @project.wiki && @scope.include?('wiki')
+        @results += @project.repository.changesets.find(:all, :limit => limit, :conditions => [ (["(LOWER(comments) like ?)"] * like_tokens.size).join(operator), * (like_tokens).sort] ) if @project.repository && @scope.include?('changesets')
+      else
+        Project.with_scope(:find => {:conditions => Project.visible_by(logged_in_user)}) do
+          @results += Project.find(:all, :limit => limit, :conditions => [ (["(LOWER(name) like ? OR LOWER(description) like ?)"] * like_tokens.size).join(operator), * (like_tokens * 2).sort] ) if @scope.include? 'projects'
+        end
+        # if only one project is found, user is redirected to its overview
+        redirect_to :controller => 'projects', :action => 'show', :id => @results.first and return if @results.size == 1
+      end
+      @question = @tokens.join(" ")
+    else
+      @question = ""
+    end
+  end
+
+private  
+  def find_project
+    @project = Project.find(params[:id])
+    @html_title = @project.name
+  rescue ActiveRecord::RecordNotFound
+    render_404
+  end
+end
index 43febd4134ff55437c7da7dedad90c6fbfd524f2..c7e80b0a09315e2ce6d21942348bf45d0fcf74c0 100644 (file)
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 module ProjectsHelper
-
-  def highlight_tokens(text, tokens)
-    return text unless tokens && !tokens.empty?
-    regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE    
-    result = ''
-    text.split(regexp).each_with_index do |words, i|
-      result << (i.even? ? (words.length > 100 ? "#{words[0..44]} ... #{words[-45..-1]}" : words) : content_tag('span', words, :class => 'highlight'))
-    end
-    result
-  end
 end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
new file mode 100644 (file)
index 0000000..bc408a3
--- /dev/null
@@ -0,0 +1,28 @@
+# redMine - project management software
+# Copyright (C) 2006-2007  Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+module SearchHelper
+  def highlight_tokens(text, tokens)
+    return text unless tokens && !tokens.empty?
+    regexp = Regexp.new "(#{tokens.join('|')})", Regexp::IGNORECASE    
+    result = ''
+    text.split(regexp).each_with_index do |words, i|
+      result << (i.even? ? (words.length > 100 ? "#{words[0..44]} ... #{words[-45..-1]}" : words) : content_tag('span', words, :class => 'highlight'))
+    end
+    result
+  end
+end
index 3d6390b95cd1a1bb5ee0006a73f80ca7e99a0a48..945803b0a09469d0e71eb815190b68aa9ab4b506 100644 (file)
         <h2><%= Setting.app_subtitle %></h2>
     </div>
     <div style="float: right; padding-right: 1em; padding-top: 0.2em;">
-      <% if loggedin? %><small><%=l(:label_logged_as)%> <b><%= @logged_in_user.login %></b></small><% end %>
+      <% if loggedin? %><small><%=l(:label_logged_as)%> <strong><%= @logged_in_user.login %></strong> -</small><% end %>
+      <small><%= toggle_link 'Search', 'quick-search-form', :focus => 'quick-search-input' %></small>
+      <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get, :id => 'quick-search-form', :style => "display:none;" ) do %>
+        <%= text_field_tag 'q', @question, :size => 15, :class => 'small', :id => 'quick-search-input' %>
+      <% end %>
+    </div>
     </div>
-    </div> 
        
        <div id="navigation">
                <ul>
        <% else %>      
                <li class="right"><%= link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => "icon icon-user" %></li>
        <% end %>
+       
+       <% unless @project.nil? || @project.id.nil? %>
+      <li class="right" style="padding-right:0.8em;">
+      </li>
+    <% end %>
+       
                </ul>           
        </div>
 
@@ -76,7 +86,7 @@
         <%= link_to l(:label_document_plural), {:controller => 'projects', :action => 'list_documents', :id => @project }, :class => "menuItem" %>
         <%= link_to l(:label_wiki), {:controller => 'wiki', :id => @project, :page => nil }, :class => "menuItem" if @project.wiki and !@project.wiki.new_record? %>        
         <%= link_to l(:label_attachment_plural), {:controller => 'projects', :action => 'list_files', :id => @project }, :class => "menuItem" %>
-        <%= link_to l(:label_search), {:controller => 'projects', :action => 'search', :id => @project }, :class => "menuItem" %>
+        <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project }, :class => "menuItem" %>
         <%= link_to l(:label_repository), {:controller => 'repositories', :action => 'show', :id => @project}, :class => "menuItem" if @project.repository and !@project.repository.new_record? %>
         <%= link_to_if_authorized l(:label_settings), {:controller => 'projects', :action => 'settings', :id => @project }, :class => "menuItem" %>
     </div>
                                <li><%= link_to l(:label_document_plural), :controller => 'projects', :action => 'list_documents', :id => @project %></li>
                                <%= content_tag("li", link_to(l(:label_wiki), :controller => 'wiki', :id => @project, :page => nil)) if @project.wiki and !@project.wiki.new_record? %>
                                <li><%= link_to l(:label_attachment_plural), :controller => 'projects', :action => 'list_files', :id => @project %></li>
-                               <li><%= link_to l(:label_search), :controller => 'projects', :action => 'search', :id => @project %></li>
+                               <li><%= link_to l(:label_search), :controller => 'search', :action => 'index', :id => @project %></li>
                                <%= content_tag("li", link_to(l(:label_repository), :controller => 'repositories', :action => 'show', :id => @project)) if @project.repository and !@project.repository.new_record? %>
                                <li><%= link_to_if_authorized l(:label_settings), :controller => 'projects', :action => 'settings', :id => @project %></li>
                        </ul>
diff --git a/app/views/projects/search.rhtml b/app/views/projects/search.rhtml
deleted file mode 100644 (file)
index d13302b..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<h2><%= l(:label_search) %></h2>
-
-<div class="box">
-<% form_tag({:action => 'search', :id => @project}, :method => :get) do %>
-<p><%= text_field_tag 'q', @question, :size => 30 %>
-<%= check_box_tag 'scope[]', 'issues', (@scope.include? 'issues') %> <label><%= l(:label_issue_plural) %></label>
-<% if @project.repository %>
-<%= check_box_tag 'scope[]', 'changesets', (@scope.include? 'changesets') %> <label><%= l(:label_revision_plural) %></label>
-<% end %>
-<%= check_box_tag 'scope[]', 'news', (@scope.include? 'news') %> <label><%= l(:label_news_plural) %></label>
-<%= check_box_tag 'scope[]', 'documents', (@scope.include? 'documents') %> <label><%= l(:label_document_plural) %></label>
-<% if @project.wiki %>
-<%= check_box_tag 'scope[]', 'wiki', (@scope.include? 'wiki') %> <label><%= l(:label_wiki) %></label>
-<% end %>
-<br />
-<%= check_box_tag 'all_words', 1, @all_words %> <%= l(:label_all_words) %></p>
-<%= submit_tag l(:button_submit), :name => 'submit' %>
-<% end %>
-</div>
-
-<% if @results %>
-    <h3><%= lwr(:label_result, @results.length) %></h3>
-    <ul>
-      <% @results.each do |e| %>
-        <li><p>
-        <% if e.is_a? Issue %>
-            <%= link_to_issue e %>: <%= highlight_tokens(h(e.subject), @tokens) %><br />
-            <%= highlight_tokens(e.description, @tokens) %><br />
-            <i><%= e.author.name %>, <%= format_time(e.created_on) %></i>
-        <% elsif e.is_a? News %>
-            <%=l(:label_news)%>: <%= link_to highlight_tokens(h(e.title), @tokens), :controller => 'news', :action => 'show', :id => e %><br />
-            <%= highlight_tokens(e.description, @tokens) %><br />
-            <i><%= e.author.name %>, <%= format_time(e.created_on) %></i>
-        <% elsif e.is_a? Document %>
-            <%=l(:label_document)%>: <%= link_to highlight_tokens(h(e.title), @tokens), :controller => 'documents', :action => 'show', :id => e %><br />
-            <%= highlight_tokens(e.description, @tokens) %><br />
-            <i><%= format_time(e.created_on) %></i>
-        <% elsif e.is_a? WikiPage %>
-            <%=l(:label_wiki)%>: <%= link_to highlight_tokens(h(e.pretty_title), @tokens), :controller => 'wiki', :action => 'index', :id => @project, :page => e.title %><br />
-            <%= highlight_tokens(e.content.text, @tokens) %><br />
-            <i><%= e.content.author ? e.content.author.name : "Anonymous" %>, <%= format_time(e.content.updated_on) %></i>
-        <% elsif e.is_a? Changeset %>
-            <%=l(:label_revision)%> <%= link_to h(e.revision), :controller => 'repositories', :action => 'revision', :id => @project, :rev => e.revision %><br />
-            <%= highlight_tokens(e.comments, @tokens) %><br />
-            <em><%= e.committer.blank? ? e.committer : "Anonymous" %>, <%= format_time(e.committed_on) %></em>
-        <% end %>
-        </p></li>  
-      <% end %>
-    </ul>
-<% end %>
\ No newline at end of file
diff --git a/app/views/search/index.rhtml b/app/views/search/index.rhtml
new file mode 100644 (file)
index 0000000..110f814
--- /dev/null
@@ -0,0 +1,58 @@
+<h2><%= l(:label_search) %></h2>\r
+\r
+<div class="box">\r
+<% form_tag({}, :method => :get) do %>\r
+<p><%= text_field_tag 'q', @question, :size => 30 %>\r
+\r
+<% if @project %>\r
+  <%= check_box_tag 'scope[]', 'issues', (@scope.include? 'issues') %> <label><%= l(:label_issue_plural) %></label>\r
+  <% if @project.repository %>\r
+  <%= check_box_tag 'scope[]', 'changesets', (@scope.include? 'changesets') %> <label><%= l(:label_revision_plural) %></label>\r
+  <% end %>\r
+  <%= check_box_tag 'scope[]', 'news', (@scope.include? 'news') %> <label><%= l(:label_news_plural) %></label>\r
+  <%= check_box_tag 'scope[]', 'documents', (@scope.include? 'documents') %> <label><%= l(:label_document_plural) %></label>\r
+  <% if @project.wiki %>\r
+  <%= check_box_tag 'scope[]', 'wiki', (@scope.include? 'wiki') %> <label><%= l(:label_wiki) %></label>\r
+  <% end %>\r
+<% else %>\r
+  <%= check_box_tag 'scope[]', 'projects', (@scope.include? 'projects') %> <label><%= l(:label_project_plural) %></label>\r
+<% end %>\r
+<br />\r
+<%= check_box_tag 'all_words', 1, @all_words %> <%= l(:label_all_words) %></p>\r
+<%= submit_tag l(:button_submit), :name => 'submit' %>\r
+<% end %>\r
+</div>\r
+\r
+<% if @results %>\r
+    <h3><%= lwr(:label_result, @results.length) %></h3>\r
+    <ul>\r
+      <% @results.each do |e| %>\r
+        <li><p>\r
+        <% if e.is_a? Project %>\r
+            <%= link_to highlight_tokens(h(e.name), @tokens), :controller => 'projects', :action => 'show', :id => e %><br />\r
+            <%= highlight_tokens(e.description, @tokens) %>\r
+        <% elsif e.is_a? Issue %>\r
+            <%= link_to_issue e %>: <%= highlight_tokens(h(e.subject), @tokens) %><br />\r
+            <%= highlight_tokens(e.description, @tokens) %><br />\r
+            <i><%= e.author.name %>, <%= format_time(e.created_on) %></i>\r
+        <% elsif e.is_a? News %>\r
+            <%=l(:label_news)%>: <%= link_to highlight_tokens(h(e.title), @tokens), :controller => 'news', :action => 'show', :id => e %><br />\r
+            <%= highlight_tokens(e.description, @tokens) %><br />\r
+            <i><%= e.author.name %>, <%= format_time(e.created_on) %></i>\r
+        <% elsif e.is_a? Document %>\r
+            <%=l(:label_document)%>: <%= link_to highlight_tokens(h(e.title), @tokens), :controller => 'documents', :action => 'show', :id => e %><br />\r
+            <%= highlight_tokens(e.description, @tokens) %><br />\r
+            <i><%= format_time(e.created_on) %></i>\r
+        <% elsif e.is_a? WikiPage %>\r
+            <%=l(:label_wiki)%>: <%= link_to highlight_tokens(h(e.pretty_title), @tokens), :controller => 'wiki', :action => 'index', :id => @project, :page => e.title %><br />\r
+            <%= highlight_tokens(e.content.text, @tokens) %><br />\r
+            <i><%= e.content.author ? e.content.author.name : "Anonymous" %>, <%= format_time(e.content.updated_on) %></i>\r
+        <% elsif e.is_a? Changeset %>\r
+            <%=l(:label_revision)%> <%= link_to h(e.revision), :controller => 'repositories', :action => 'revision', :id => @project, :rev => e.revision %><br />\r
+            <%= highlight_tokens(e.comments, @tokens) %><br />\r
+            <em><%= e.committer.blank? ? e.committer : "Anonymous" %>, <%= format_time(e.committed_on) %></em>\r
+        <% end %>\r
+        </p></li>  \r
+      <% end %>\r
+    </ul>\r
+<% end %>
\ No newline at end of file
index b5c631e4b5eb1c3304220512c8d03bba19c0898a..2555b61f70749622ae3f57f03b887207e084479f 100644 (file)
@@ -66,6 +66,8 @@ font-weight:normal;
 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
 }
 
+#header a {color:#fff;}
+
 #navigation{
 height:2.2em;
 line-height:2.2em;
index 644da830d7b04dc207aa13099d33c30a968749f5..6e76be2d16d6cdfa816cceea0bb7a32a16af52be 100644 (file)
@@ -125,14 +125,4 @@ class ProjectsControllerTest < Test::Unit::TestCase
     assert_template 'activity'
     assert_not_nil assigns(:events_by_day)
   end
-  
-  def test_search
-    get :search, :id => 1
-    assert_response :success
-    assert_template 'search'
-    
-    get :search, :id => 1, :token => "can", :scope => ["issues", "news", "documents"]
-    assert_response :success
-    assert_template 'search'
-  end
 end
diff --git a/test/functional/search_controller_test.rb b/test/functional/search_controller_test.rb
new file mode 100644 (file)
index 0000000..8fa2e08
--- /dev/null
@@ -0,0 +1,48 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'search_controller'
+
+# Re-raise errors caught by the controller.
+class SearchController; def rescue_action(e) raise e end; end
+
+class SearchControllerTest < Test::Unit::TestCase
+  fixtures :projects, :issues
+  
+  def setup
+    @controller = SearchController.new
+    @request    = ActionController::TestRequest.new
+    @response   = ActionController::TestResponse.new
+  end
+  
+  def test_search_for_projects
+    get :index
+    assert_response :success
+    assert_template 'index'
+
+    get :index, :q => "cook"
+    assert_response :success
+    assert_template 'index'
+    assert assigns(:results).include?(Project.find(1))
+  end
+  
+  def test_search_in_project
+    get :index, :id => 1
+    assert_response :success
+    assert_template 'index'
+    assert_not_nil assigns(:project)
+    
+    get :index, :id => 1, :q => "can", :scope => ["issues", "news", "documents"]
+    assert_response :success
+    assert_template 'index'
+  end
+  
+  def test_quick_jump_to_issue
+    # issue of a public project
+    get :index, :q => "3"
+    assert_redirected_to 'issues/show/3'
+    
+    # issue of a private project
+    get :index, :q => "4"
+    assert_response :success
+    assert_template 'index'
+  end
+end