1
0
mirror of https://github.com/meineerde/redmine.git synced 2025-12-23 08:51:13 +00:00

Filters for Projects page (#29482).

Patch by Marius BALTEANU.

git-svn-id: http://svn.redmine.org/redmine/trunk@18761 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2019-10-19 11:36:13 +00:00
parent 868578c0f2
commit 6c5440a21b
12 changed files with 294 additions and 61 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
<%= render_sidebar_queries(ProjectQuery, @project) %>
<%= call_hook(:view_projects_sidebar_queries_bottom) %>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 => {

View File

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