From 9b24d008fe932a0c81afc44e6e36c7daea299def Mon Sep 17 00:00:00 2001 From: Marius BALTEANU Date: Wed, 5 Dec 2018 22:57:40 +0000 Subject: [PATCH 1/6] Filters for Projects page --- app/controllers/projects_controller.rb | 18 +++-- app/controllers/queries_controller.rb | 6 +- app/models/project_query.rb | 68 +++++++++++++++++++ app/models/query.rb | 1 + app/views/projects/_sidebar.html.erb | 2 + app/views/projects/index.html.erb | 30 ++++---- app/views/queries/_form.html.erb | 38 +++++++---- app/views/queries/_query_form.html.erb | 62 +++++++++-------- config/locales/en.yml | 1 - public/stylesheets/application.css | 1 + test/functional/queries_controller_test.rb | 79 ++++++++++++++++++++++ test/unit/project_query_test.rb | 49 ++++++++++++++ 12 files changed, 294 insertions(+), 61 deletions(-) create mode 100644 app/models/project_query.rb create mode 100644 app/views/projects/_sidebar.html.erb create mode 100644 test/unit/project_query_test.rb 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 bd2b0c5c1..dc7553966 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -716,6 +716,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 %> -
<%= form_tag({}, :method => :get) do %> - <% end %> <%= render_project_action_links %>
-

<%= l(:label_project_plural) %>

+

<%= @query.new_record? ? l(:label_project_plural) : @query.name %>

-
-<%= render_project_hierarchy(@projects) %> -
+<%= form_tag(projects_path(@project, nil), :method => :get, :id => 'query_form') do %> +<%= render :partial => 'queries/query_form' %> +<% end %> + +<% if @query.valid? %> + <% if @projects.empty? %> +

<%= l(:label_no_data) %>

+ <% else %> +
+ <%= render_project_hierarchy(@projects) %> +
+ <% end %> +<% end %> <% if User.current.logged? %>

@@ -24,6 +26,10 @@

<% 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 @@

<%= text_field 'query', 'name', :size => 80 %>

-<% 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) %>

- - <% Role.givable.sorted.each do |role| %> - + <% unless @query.type == 'ProjectQuery' %> + + <% Role.givable.sorted.each do |role| %> + + <% end %> + <%= hidden_field_tag 'query[role_ids][]', '' %> <% end %> - <%= hidden_field_tag 'query[role_ids][]', '' %>

<% end %> -

-<%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %>

+<% unless @query.type == 'ProjectQuery' %> +

+ <%= check_box_tag 'query_is_for_all', 1, @query.project.nil?, :class => (User.current.admin? ? '' : 'disable-unless-private') %>

+<% end %>
<%= l(:label_options) %>

@@ -28,14 +34,18 @@ :data => {:disables => "#columns, .block_columns input"} %>

<% unless params[:gantt] %> -

-<%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %>

+

+ <%= select 'query', 'group_by', @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}, :include_blank => true %>

-

-<%= available_block_columns_tags(@query) %>

+ <% unless @query.available_block_columns.empty? %> +

+ <%= available_block_columns_tags(@query) %>

+ <% end %> -

-<%= available_totalable_columns_tags(@query) %>

+ <% unless @query.available_totalable_columns.empty? %> +

+ <%= available_totalable_columns_tags(@query) %>

+ <% end %> <% else %>

<%= hidden_field_tag 'query[draw_relations]', '0' %> @@ -54,7 +64,7 @@

<% unless params[:gantt] %> -
<%= l(:label_sort) %> +
<%= l(:label_sort) %> <% 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 @@
- + <% if @query.available_columns.any? %> + + <% end %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 8fcc2262a..ece1c40bc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -977,7 +977,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 800415f91..042417252 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -649,6 +649,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 -- 2.22.0