helper :custom_fields
helper :issues
helper :queries
+ include QueriesHelper
helper :repositories
helper :members
helper :trackers
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 {
# 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
@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
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
--- /dev/null
+# 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
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 }
--- /dev/null
+<%= render_sidebar_queries(ProjectQuery, @project) %>
+<%= call_hook(:view_projects_sidebar_queries_bottom) %>
-<% 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;">
</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 %>
<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>
: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' %>
</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,
</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">
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
-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 {
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 => {
}
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
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 => {
--- /dev/null
+# 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