changeset 739:b7ac21913927 feature_14

new files for project filtering and search (addresses also Feature #68); projects listing interface changes.
author luisf <luis.figueira@eecs.qmul.ac.uk>
date Fri, 04 Nov 2011 17:50:35 +0000
parents 3f87a3b61d9c
children 4acfc770e79f
files vendor/plugins/redmine_tags/app/views/projects/_filtered_projects.rhtml vendor/plugins/redmine_tags/app/views/projects/index.rhtml vendor/plugins/redmine_tags/init.rb vendor/plugins/redmine_tags/lib/redmine_project_filtering.rb vendor/plugins/redmine_tags/lib/redmine_tags/patches/project_patch.rb vendor/plugins/redmine_tags/lib/redmine_tags/patches/projects_controller_patch.rb vendor/plugins/redmine_tags/lib/redmine_tags/patches/projects_helper_patch.rb
diffstat 7 files changed, 267 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/plugins/redmine_tags/app/views/projects/_filtered_projects.rhtml	Fri Nov 04 17:50:35 2011 +0000
@@ -0,0 +1,19 @@
+<% if @featured_projects && @featured_projects.any? %>
+
+  <div class="splitcontentleft">
+    <%= render_project_hierarchy_with_filtering(@projects, @custom_fields, @question) %>
+  </div>
+
+  <div class="splitcontentright">
+    <h3 id="featured-projects-title"><%=l(:project_filtering_featured_projects_label) %></h3>
+    <div id="featured-projects-box" class="box">
+      <%= render_project_hierarchy_with_filtering(@featured_projects, @custom_fields, @question) %>
+    </div>
+  </div>
+
+<% else %>
+
+  <%= render_project_hierarchy_with_filtering(@projects, @custom_fields, @question) %>
+
+<% end %>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/plugins/redmine_tags/app/views/projects/index.rhtml	Fri Nov 04 17:50:35 2011 +0000
@@ -0,0 +1,67 @@
+<% content_for :header_tags do %>
+    <%= auto_discovery_link_tag(:atom, {:action => 'index', :format => 'atom', :key => User.current.rss_key}) %>
+<% end %>
+
+<div class="contextual">
+    <%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%>
+    <%= '| ' + link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add') if User.current.allowed_to?(:add_project, nil, :global => true) %>
+</div>
+
+<% form_tag('/projects', :method => :get, :id => :project_filtering) do %>
+  <fieldset id="filters" class="collapsible">
+    <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
+    <div>
+      <p class='q'>
+        <%= label_tag 'q', l('project_filtering_q_label') %>
+        <%= text_field_tag 'q', @question, :size => 30, :id => 'search-input' %>
+      </p>
+      <%= render :partial => 'custom_field', :collection => @custom_fields_used_for_project_filtering %>
+      <p class='buttons'><%= submit_tag( l(:button_send), :id => 'filter_button') -%></p>
+    </div>
+  </fieldset>
+<% end %>
+
+
+<%= javascript_tag "Field.focus('search-input');" %>
+<%= javascript_tag "$('filter_button').hide();" %>
+<%= observe_form( :project_filtering,
+  :frequency => 0.5,
+  :url => { :controller => :projects, :action => :index, :format => :js },
+  :method => :get
+  )
+%>
+
+<div id="projects">
+  <%= render :partial => 'filtered_projects' %>
+</div>
+
+
+<div style="clear:both;"></div>
+
+<% if User.current.logged? %>
+<p style="text-align:right;">
+<span class="my-project"><%= l(:label_my_projects) %></span>
+</p>
+<% end %>
+
+
+END
+
+<% if @user_projects %>  
+  <%= render_my_project_hierarchy(@user_projects)%>
+<% end %>
+
+<h2>
+  <%= l("label_project_all") %>
+</h2>
+
+<%= render_project_table(@projects) %>
+
+<p class="pagination"><%= pagination_links_full @project_pages, @project_count %></p>
+
+
+<% other_formats_links do |f| %>
+	<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
+<% end %>
+
+<% html_title(l(:label_project_plural)) -%>
--- a/vendor/plugins/redmine_tags/init.rb	Tue Nov 01 15:20:40 2011 +0000
+++ b/vendor/plugins/redmine_tags/init.rb	Fri Nov 04 17:50:35 2011 +0000
@@ -41,9 +41,16 @@
 require 'dispatcher'
 
 Dispatcher.to_prepare :redmine_tags do
+  
+  require_dependency 'redmine_project_filtering'
+  
   unless Project.included_modules.include?(RedmineTags::Patches::ProjectPatch)
     Project.send(:include, RedmineTags::Patches::ProjectPatch)
   end
+  
+  unless ProjectsHelper.included_modules.include?(RedmineTags::Patches::ProjectsHelperPatch)
+    ProjectsHelper.send(:include, RedmineTags::Patches::ProjectsHelperPatch)
+  end    
 
   unless Issue.included_modules.include?(RedmineTags::Patches::IssuePatch)
     Issue.send(:include, RedmineTags::Patches::IssuePatch)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/plugins/redmine_tags/lib/redmine_project_filtering.rb	Fri Nov 04 17:50:35 2011 +0000
@@ -0,0 +1,16 @@
+module RedmineProjectFiltering
+
+  # transforms a question and a list of custom fields into something that Project.search can process
+  def self.calculate_tokens(question, custom_fields=nil)
+    list = []
+    list << custom_fields.values if custom_fields.present?
+    list << question if question.present?
+
+    tokens = list.join(' ').scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)})
+    tokens = tokens.collect{ |m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '') }
+    
+    # tokens must be at least 2 characters long
+    tokens.select {|w| w.length > 1 }
+  end
+
+end
--- a/vendor/plugins/redmine_tags/lib/redmine_tags/patches/project_patch.rb	Tue Nov 01 15:20:40 2011 +0000
+++ b/vendor/plugins/redmine_tags/lib/redmine_tags/patches/project_patch.rb	Fri Nov 04 17:50:35 2011 +0000
@@ -48,6 +48,17 @@
       end
 
       module ClassMethods
+
+
+        def search_by_question(question)
+          if question.length > 1
+            search(RedmineProjectFiltering.calculate_tokens(question), nil, :all_words => true).first.sort_by(&:lft)
+          else
+            all(:order => 'lft')
+          end
+        end
+
+
         # Returns available issue tags
         # === Parameters
         # * <i>options</i> = (optional) Options hash of
--- a/vendor/plugins/redmine_tags/lib/redmine_tags/patches/projects_controller_patch.rb	Tue Nov 01 15:20:40 2011 +0000
+++ b/vendor/plugins/redmine_tags/lib/redmine_tags/patches/projects_controller_patch.rb	Fri Nov 04 17:50:35 2011 +0000
@@ -8,6 +8,7 @@
         base.class_eval do          
           unloadable 
           before_filter :add_tags_to_project, :only => [:save, :update]
+          before_filter :filter_projects, :only => :index          
         end
       end
 
@@ -20,8 +21,60 @@
             unless (old_tags == new_tags)
               @project.tag_list = new_tags
             end
-          end                    
+          end
         end
+  
+  
+         def calculate_project_filtering_settings
+            @project_filtering_settings = Setting[:plugin_redmine_project_filtering]
+          end
+
+          def filter_projects
+            
+            logger.error { "FILTRA PA!" }
+            
+            respond_to do |format|
+              format.any(:html, :xml) { 
+                calculate_filtered_projects
+              }
+              format.js {
+                calculate_filtered_projects
+                render :update do |page|
+                  page.replace_html 'projects', :partial => 'filtered_projects'
+                end
+              }
+              format.atom {
+                projects = Project.visible.find(:all, :order => 'created_on DESC',
+                                                      :limit => Setting.feeds_limit.to_i)
+                render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
+              }
+            end
+          end
+
+          private
+
+          def calculate_filtered_projects
+
+            @question = (params[:q] || "").strip
+            @custom_fields = params[:custom_fields] || {}
+
+            @projects = Project.visible
+
+            unless @custom_fields.empty?
+              @projects = @projects.with_custom_values(params[:custom_fields])
+            end
+
+            @featured_projects = @projects.featured if Project.respond_to? :featured
+
+            @projects = @projects.search_by_question(@question)
+            debugger                                  
+            @featured_projects = @featured_projects.search_by_question(@question) if @featured_projects
+
+          end
+  
+  
+  
+        
       end
     end
   end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/plugins/redmine_tags/lib/redmine_tags/patches/projects_helper_patch.rb	Fri Nov 04 17:50:35 2011 +0000
@@ -0,0 +1,93 @@
+module RedmineTags
+  module Patches
+    module ProjectsHelperPatch
+
+      def self.included(base) # :nodoc:
+        base.send(:include, InstanceMethods)
+        base.class_eval do
+          unloadable
+        end
+      end
+
+      module InstanceMethods
+        # Renders a tree of projects as a nested set of unordered lists
+        # The given collection may be a subset of the whole project tree
+        # (eg. some intermediate nodes are private and can not be seen)
+        def render_project_hierarchy_with_filtering(projects,custom_fields,question)
+          s = []
+          if projects.any?
+            tokens = RedmineProjectFiltering.calculate_tokens(question, custom_fields)
+            debugger
+            
+
+            ancestors = []
+            original_project = @project
+            projects.each do |project|
+              # set the project environment to please macros.
+              @project = project
+              if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
+                s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>"
+              else
+                ancestors.pop
+                s << "</li>"
+                while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) 
+                  ancestors.pop
+                  s << "</ul></li>"
+                end
+              end
+              classes = (ancestors.empty? ? 'root' : 'child')
+              s << "<li class='#{classes}'><div class='#{classes}'>" +
+                link_to( highlight_tokens(project.name, tokens), 
+                  {:controller => 'projects', :action => 'show', :id => project},
+                  :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}"
+                )
+              s << "<ul class='filter_fields'>"
+
+           #  CustomField.usable_for_project_filtering.each do |field|
+           #    value_model = project.custom_value_for(field.id)
+           #    value = value_model.present? ? value_model.value : nil
+           #    s << "<li><b>#{field.name.humanize}:</b> #{highlight_tokens(value, tokens)}</li>" if value.present?
+           #  end
+              
+              s << "</ul>"
+              s << "<div class='clear'></div>"
+              unless project.description.blank?
+                s << "<div class='wiki description'>"
+                s << "<b>#{ t(:field_description) }:</b>"
+                s << highlight_tokens(textilizable(project.short_description, :project => project), tokens)
+                s << "\n</div>"
+              end
+              s << "</div>"
+              ancestors << project
+            end
+            ancestors.size.times{ s << "</li></ul>" }
+            @project = original_project
+          end
+          s.join "\n"
+        end
+        
+        private
+        
+        # copied from search_helper. This one doesn't escape html or limit the text length
+        def highlight_tokens(text, tokens)
+          return text unless text && tokens && !tokens.empty?
+          re_tokens = tokens.collect {|t| Regexp.escape(t)}
+          regexp = Regexp.new "(#{re_tokens.join('|')})", Regexp::IGNORECASE    
+          result = ''
+          text.split(regexp).each_with_index do |words, i|
+            words = words.mb_chars
+            if i.even?
+              result << words
+            else
+              t = (tokens.index(words.downcase) || 0) % 4
+              result << content_tag('span', words, :class => "highlight token-#{t}")
+            end
+          end
+          result
+        end
+      
+      end
+    end
+  end
+end
+