diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2019-10-19 11:36:13 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2019-10-19 11:36:13 +0000 |
commit | 6c5440a21b491982c02531c82b4416ef212eb3dc (patch) | |
tree | 27d069aac9d26f8f6b76b697e8033b18fa65eaa3 | |
parent | 868578c0f297875eeac324af8a8c7f1445593ecd (diff) | |
download | redmine-6c5440a21b491982c02531c82b4416ef212eb3dc.tar.gz redmine-6c5440a21b491982c02531c82b4416ef212eb3dc.zip |
Filters for Projects page (#29482).
Patch by Marius BALTEANU.
git-svn-id: http://svn.redmine.org/redmine/trunk@18761 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r-- | app/controllers/projects_controller.rb | 18 | ||||
-rw-r--r-- | app/controllers/queries_controller.rb | 6 | ||||
-rw-r--r-- | app/models/project_query.rb | 68 | ||||
-rw-r--r-- | app/models/query.rb | 1 | ||||
-rw-r--r-- | app/views/projects/_sidebar.html.erb | 2 | ||||
-rw-r--r-- | app/views/projects/index.html.erb | 30 | ||||
-rw-r--r-- | app/views/queries/_form.html.erb | 38 | ||||
-rw-r--r-- | app/views/queries/_query_form.html.erb | 62 | ||||
-rw-r--r-- | config/locales/en.yml | 1 | ||||
-rw-r--r-- | public/stylesheets/application.css | 1 | ||||
-rw-r--r-- | test/functional/queries_controller_test.rb | 79 | ||||
-rw-r--r-- | test/unit/project_query_test.rb | 49 |
12 files changed, 294 insertions, 61 deletions
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 98571b8f9..e7a79e495 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -33,6 +33,7 @@ class ProjectsController < ApplicationController helper :custom_fields helper :issues helper :queries + include QueriesHelper helper :repositories helper :members helper :trackers @@ -44,13 +45,11 @@ class ProjectsController < ApplicationController return end - scope = Project.visible.sorted + retrieve_project_query + scope = project_scope respond_to do |format| format.html { - unless params[:closed] - scope = scope.active - end @projects = scope.to_a } format.api { @@ -257,4 +256,15 @@ class ProjectsController < ApplicationController # hide project in layout @project = nil end + + private + + # Returns the ProjectEntry scope for index + def project_scope(options={}) + @query.results_scope(options) + end + + def retrieve_project_query + retrieve_query(ProjectQuery, false) + end end diff --git a/app/controllers/queries_controller.rb b/app/controllers/queries_controller.rb index 10051ab02..348806198 100644 --- a/app/controllers/queries_controller.rb +++ b/app/controllers/queries_controller.rb @@ -126,7 +126,7 @@ class QueriesController < ApplicationController @query.column_names = nil if params[:default_columns] @query.sort_criteria = (params[:query] && params[:query][:sort_criteria]) || @query.sort_criteria @query.name = params[:query] && params[:query][:name] - if User.current.allowed_to?(:manage_public_queries, @query.project) || User.current.admin? + if User.current.allowed_to?(:manage_public_queries, @query.project) || User.current.admin? || (@query.type == 'ProjectQuery' && User.current.allowed_to?(:manage_public_queries, @query.project, :global => true)) @query.visibility = (params[:query] && params[:query][:visibility]) || Query::VISIBILITY_PRIVATE @query.role_ids = params[:query] && params[:query][:role_ids] else @@ -156,6 +156,10 @@ class QueriesController < ApplicationController redirect_to _time_entries_path(@project, nil, options) end + def redirect_to_project_query(options) + redirect_to projects_path(options) + end + # Returns the Query subclass, IssueQuery by default # for compatibility with previous behaviour def query_class diff --git a/app/models/project_query.rb b/app/models/project_query.rb new file mode 100644 index 000000000..ca8e9fb82 --- /dev/null +++ b/app/models/project_query.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2017 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 ProjectQuery < Query + + self.queried_class = Project + + self.available_columns = [] + + def initialize(attributes=nil, *args) + super attributes + self.filters ||= { 'status' => {:operator => "=", :values => ['1']} } + end + + def initialize_available_filters + add_available_filter "status", + :type => :list, :values => lambda { project_statuses_values } + add_available_filter "name", :type => :text + add_available_filter "description", :type => :text + add_available_filter "is_public", + :type => :list, + :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] + add_available_filter "created_on", :type => :date_past + end + + def available_columns + [] + end + + def base_scope + Project.visible.where(statement) + end + + def results_scope(options={}) + order_option = [group_by_sort_order, (options[:order] || sort_clause)].flatten.reject(&:blank?) + + order_option << "#{Project.table_name}.id ASC" + scope = base_scope. + order(order_option). + joins(joins_for_order_statement(order_option.join(','))) + + if has_custom_field_column? + scope = scope.preload(:custom_values) + end + + if has_column?(:parent_id) + scope = scope.preload(:parent) + end + + scope + end +end diff --git a/app/models/query.rb b/app/models/query.rb index 3b37a8dd0..17271d773 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -717,6 +717,7 @@ class Query < ActiveRecord::Base end def columns + return [] if available_columns.empty? # preserve the column_names order cols = (has_default_columns? ? default_columns_names : column_names).collect do |name| available_columns.find { |col| col.name == name } diff --git a/app/views/projects/_sidebar.html.erb b/app/views/projects/_sidebar.html.erb new file mode 100644 index 000000000..f9518e609 --- /dev/null +++ b/app/views/projects/_sidebar.html.erb @@ -0,0 +1,2 @@ +<%= render_sidebar_queries(ProjectQuery, @project) %> +<%= call_hook(:view_projects_sidebar_queries_bottom) %> diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index 13e3332c3..3c2543b43 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -1,22 +1,24 @@ -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, {:action => 'index', :format => 'atom', :key => User.current.rss_key}) %> -<% end %> - <div class="contextual"> <%= form_tag({}, :method => :get) do %> - <label for="closed"> - <%= check_box_tag 'closed', 1, params[:closed], :onchange => "this.form.submit();" %> - <%= l(:label_show_closed_projects) %> - </label> <% end %> <%= render_project_action_links %> </div> -<h2><%= l(:label_project_plural) %></h2> +<h2><%= @query.new_record? ? l(:label_project_plural) : @query.name %></h2> -<div id="projects-index"> -<%= render_project_hierarchy(@projects) %> -</div> +<%= form_tag(projects_path(@project, nil), :method => :get, :id => 'query_form') do %> +<%= render :partial => 'queries/query_form' %> +<% end %> + +<% if @query.valid? %> + <% if @projects.empty? %> + <p class="nodata"><%= l(:label_no_data) %></p> + <% else %> + <div id="projects-index"> + <%= render_project_hierarchy(@projects) %> + </div> + <% end %> +<% end %> <% if User.current.logged? %> <p style="text-align:right;"> @@ -24,6 +26,10 @@ </p> <% end %> +<% content_for :sidebar do %> + <%= render :partial => 'projects/sidebar' %> +<% end %> + <% other_formats_links do |f| %> <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> <% end %> diff --git a/app/views/queries/_form.html.erb b/app/views/queries/_form.html.erb index f1ef5ecac..a2dd9f589 100644 --- a/app/views/queries/_form.html.erb +++ b/app/views/queries/_form.html.erb @@ -7,20 +7,26 @@ <p><label for="query_name"><%=l(:field_name)%></label> <%= text_field 'query', 'name', :size => 80 %></p> -<% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @query.project) %> +<% if User.current.admin? || + User.current.allowed_to?(:manage_public_queries, @query.project) || + @query.type == 'ProjectQuery' && User.current.allowed_to?(:manage_public_queries, @query.project, :global => true) %> <p><label><%=l(:field_visible)%></label> <label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PRIVATE %> <%= l(:label_visibility_private) %></label> <label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PUBLIC %> <%= l(:label_visibility_public) %></label> - <label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_ROLES %> <%= l(:label_visibility_roles) %>:</label> - <% Role.givable.sorted.each do |role| %> - <label class="block role-visibility"><%= check_box_tag 'query[role_ids][]', role.id, @query.roles.include?(role), :id => nil %> <%= role.name %></label> + <% unless @query.type == 'ProjectQuery' %> + <label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_ROLES %> <%= l(:label_visibility_roles) %>:</label> + <% Role.givable.sorted.each do |role| %> + <label class="block role-visibility"><%= check_box_tag 'query[role_ids][]', role.id, @query.roles.include?(role), :id => nil %> <%= role.name %></label> + <% end %> + <%= hidden_field_tag 'query[role_ids][]', '' %> <% end %> - <%= hidden_field_tag 'query[role_ids][]', '' %> </p> <% end %> -<p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label> -<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %></p> +<% unless @query.type == 'ProjectQuery' %> + <p><label for="query_is_for_all"><%=l(:field_is_for_all)%></label> + <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %></p> +<% end %> <fieldset id="options"><legend><%= l(:label_options) %></legend> <p><label for="query_default_columns"><%=l(:label_default_columns)%></label> @@ -28,14 +34,18 @@ :data => {:disables => "#columns, .block_columns input"} %></p> <% unless params[:gantt] %> -<p><label for="query_group_by"><%= l(:field_group_by) %></label> -<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> + <p><label for="query_group_by"><%= l(:field_group_by) %></label> + <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %></p> -<p class="block_columns"><label><%= l(:button_show) %></label> -<%= available_block_columns_tags(@query) %></p> + <% unless @query.available_block_columns.empty? %> + <p class="block_columns"><label><%= l(:button_show) %></label> + <%= available_block_columns_tags(@query) %></p> + <% end %> -<p><label><%= l(:label_total_plural) %></label> -<%= available_totalable_columns_tags(@query) %></p> + <% unless @query.available_totalable_columns.empty? %> + <p class="totable_columns"><label><%= l(:label_total_plural) %></label> + <%= available_totalable_columns_tags(@query) %></p> + <% end %> <% else %> <p><label><%= l(:button_show) %></label> <%= hidden_field_tag 'query[draw_relations]', '0' %> @@ -54,7 +64,7 @@ </fieldset> <% unless params[:gantt] %> -<fieldset><legend><%= l(:label_sort) %></legend> +<fieldset id="sort"><legend><%= l(:label_sort) %></legend> <% 3.times do |i| %> <%= content_tag(:span, "#{i+1}:", :class => 'query_sort_criteria_count')%> <%= label_tag "query_sort_criteria_attribute_" + i.to_s, diff --git a/app/views/queries/_query_form.html.erb b/app/views/queries/_query_form.html.erb index c8be6b771..65bcc3eb5 100644 --- a/app/views/queries/_query_form.html.erb +++ b/app/views/queries/_query_form.html.erb @@ -11,35 +11,39 @@ </div> </fieldset> - <fieldset id="options" class="collapsible collapsed"> - <legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend> - <div style="display: none;"> - <table> - <tr> - <td class="field"><%= l(:field_column_names) %></td> - <td><%= render_query_columns_selection(@query) %></td> - </tr> - <% if @query.groupable_columns.any? %> - <tr> - <td class="field"><label for='group_by'><%= l(:field_group_by) %></label></td> - <td><%= group_by_column_select_tag(@query) %></td> - </tr> - <% end %> - <% if @query.available_block_columns.any? %> - <tr> - <td class="field"><%= l(:button_show) %></td> - <td><%= available_block_columns_tags(@query) %></td> - </tr> - <% end %> - <% if @query.available_totalable_columns.any? %> - <tr> - <td><%= l(:label_total_plural) %></td> - <td><%= available_totalable_columns_tags(@query) %></td> - </tr> - <% end %> - </table> - </div> - </fieldset> + <% if @query.available_columns.any? %> + <fieldset id="options" class="collapsible collapsed"> + <legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend> + <div style="display: none;"> + <table> + <% if @query.available_columns.any? %> + <tr> + <td class="field"><%= l(:field_column_names) %></td> + <td><%= render_query_columns_selection(@query) %></td> + </tr> + <% end %> + <% if @query.groupable_columns.any? %> + <tr> + <td class="field"><label for='group_by'><%= l(:field_group_by) %></label></td> + <td><%= group_by_column_select_tag(@query) %></td> + </tr> + <% end %> + <% if @query.available_block_columns.any? %> + <tr> + <td class="field"><%= l(:button_show) %></td> + <td><%= available_block_columns_tags(@query) %></td> + </tr> + <% end %> + <% if @query.available_totalable_columns.any? %> + <tr> + <td><%= l(:label_total_plural) %></td> + <td><%= available_totalable_columns_tags(@query) %></td> + </tr> + <% end %> + </table> + </div> + </fieldset> + <% end %> </div> <p class="buttons"> diff --git a/config/locales/en.yml b/config/locales/en.yml index 33bf510da..4c90deb9d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -978,7 +978,6 @@ en: label_completed_versions: Completed versions label_search_for_watchers: Search for watchers to add label_session_expiration: Session expiration - label_show_closed_projects: View closed projects label_status_transitions: Status transitions label_fields_permissions: Fields permissions label_readonly: Read-only diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index f9ec3f086..7aa1bca54 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -654,6 +654,7 @@ ul.projects div.description li {list-style-type:initial;} -moz-column-count: auto; -moz-column-width: 400px; -moz-column-gap : 0.5rem; + margin-bottom: 1.2em; } #projects-index li.root ul.projects { border-left: 3px solid #e0e0e0; padding-left:1em;} #projects-index ul.projects li.root { diff --git a/test/functional/queries_controller_test.rb b/test/functional/queries_controller_test.rb index 1439a908e..95afdbe4e 100644 --- a/test/functional/queries_controller_test.rb +++ b/test/functional/queries_controller_test.rb @@ -69,6 +69,26 @@ class QueriesControllerTest < Redmine::ControllerTest assert_response 404 end + def test_new_should_not_render_show_inline_columns_option_for_query_without_available_inline_columns + @request.session[:user_id] = 1 + get :new, :params => { + :type => 'ProjectQuery' + } + + assert_response :success + assert_select 'p[class=?]', 'block_columns', 0 + end + + def test_new_should_not_render_show_totals_option_for_query_without_totable_columns + @request.session[:user_id] = 1 + get :new, :params => { + :type => 'ProjectQuery' + } + + assert_response :success + assert_select 'p[class=?]', 'totables_columns', 0 + end + def test_new_time_entry_query @request.session[:user_id] = 2 get :new, :params => { @@ -77,6 +97,39 @@ class QueriesControllerTest < Redmine::ControllerTest } assert_response :success assert_select 'input[name=type][value=?]', 'TimeEntryQuery' + assert_select 'p[class=?]', 'totable_columns', 1 + assert_select 'p[class=?]', 'block_columns', 0 + end + + def test_new_project_query_for_projects + @request.session[:user_id] = 1 + get :new, :params => { + :type => 'ProjectQuery' + } + assert_response :success + assert_select 'input[name=type][value=?]', 'ProjectQuery' + end + + def test_new_project_query_should_not_render_roles_visibility_options + @request.session[:user_id] = 1 + get :new, :params => { + :type => 'ProjectQuery' + } + + assert_response :success + assert_select 'input[id=?]', 'query_visibility_0', 1 + assert_select 'input[id=?]', 'query_visibility_2', 1 + assert_select 'input[id=?]', 'query_visibility_1', 0 + end + + def test_new_project_query_should_not_render_for_all_projects_option + @request.session[:user_id] = 1 + get :new, :params => { + :type => 'ProjectQuery' + } + + assert_response :success + assert_select 'input[name=?]', 'for_all_projects', 0 end def test_new_time_entry_query_should_select_spent_time_from_main_menu @@ -441,6 +494,32 @@ class QueriesControllerTest < Redmine::ControllerTest assert q.valid? end + def test_create_public_project_query + @request.session[:user_id] = 2 + + q = new_record(ProjectQuery) do + post :create, :params => { + :type => 'ProjectQuery', + :default_columns => '1', + :f => ["status"], + :op => { + "status" => "=" + }, + :v => { + "status" => ['1'] + }, + :query => { + "name" => "test_new_project_public_query", "visibility" => "2" + } + } + end + + assert_redirected_to :controller => 'projects', :action => 'index', :query_id => q.id + + assert q.is_public? + assert q.valid? + end + def test_edit_global_public_query @request.session[:user_id] = 1 get :edit, :params => { diff --git a/test/unit/project_query_test.rb b/test/unit/project_query_test.rb new file mode 100644 index 000000000..f4182e560 --- /dev/null +++ b/test/unit/project_query_test.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2017 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. + +require File.expand_path('../../test_helper', __FILE__) + +class ProjectQueryTest < ActiveSupport::TestCase + fixtures :projects, :users, + :members, :roles, :member_roles, + :issue_categories, :enumerations, + :groups_users, + :enabled_modules + + def test_filter_values_be_arrays + q = ProjectQuery.new + assert_nil q.project + + q.available_filters.each do |name, filter| + values = filter.values + assert (values.nil? || values.is_a?(Array)), + "#values for #{name} filter returned a #{values.class.name}" + end + end + + def test_project_statuses_filter_should_return_project_statuses + query = ProjectQuery.new(:name => '_') + query.filters = {'status' => {:operator => '=', :values => []}} + + values = query.available_filters['status'][:values] + assert_equal ['active', 'closed'], values.map(&:first) + assert_equal ['1', '5'], values.map(&:second) + + end +end |