Feature #33422 » 0001-ProjectQuery-filters-on-the-admin-project-list.patch
app/controllers/admin_controller.rb | ||
---|---|---|
26 | 26 | |
27 | 27 |
before_action :require_admin |
28 | 28 | |
29 |
helper :queries |
|
30 |
include QueriesHelper |
|
31 |
helper :projects_queries |
|
32 |
helper :projects |
|
33 | ||
29 | 34 |
def index |
30 | 35 |
@no_configuration_data = Redmine::DefaultData::Loader::no_data? |
31 | 36 |
end |
32 | 37 | |
33 | 38 |
def projects |
34 |
@status = params[:status] || 1 |
|
35 | ||
36 |
scope = Project.status(@status).sorted |
|
37 |
scope = scope.like(params[:name]) if params[:name].present? |
|
39 |
retrieve_query(ProjectQuery, false, :defaults => @default_columns_names) |
|
40 |
@query.admin_projects = 1 |
|
41 |
scope = @query.results_scope |
|
38 | 42 | |
39 |
@project_count = scope.count
|
|
40 |
@project_pages = Paginator.new @project_count, per_page_option, params['page']
|
|
41 |
@projects = scope.limit(@project_pages.per_page).offset(@project_pages.offset).to_a
|
|
43 |
@entry_count = scope.count
|
|
44 |
@entry_pages = Paginator.new @entry_count, per_page_option, params['page']
|
|
45 |
@projects = scope.limit(@entry_pages.per_page).offset(@entry_pages.offset).to_a
|
|
42 | 46 | |
43 | 47 |
render :action => "projects", :layout => false if request.xhr? |
44 | 48 |
end |
app/controllers/queries_controller.rb | ||
---|---|---|
52 | 52 |
@query.user = User.current |
53 | 53 |
@query.project = @project |
54 | 54 |
@query.build_from_params(params) |
55 |
render :layout => 'admin' if params[:admin_projects] |
|
55 | 56 |
end |
56 | 57 | |
57 | 58 |
def create |
... | ... | |
62 | 63 | |
63 | 64 |
if @query.save |
64 | 65 |
flash[:notice] = l(:notice_successful_create) |
65 |
redirect_to_items(:query_id => @query) |
|
66 |
redirect_to_items(:query_id => @query, :admin_projects => params[:admin_projects])
|
|
66 | 67 |
else |
67 | 68 |
render :action => 'new', :layout => !request.xhr? |
68 | 69 |
end |
69 | 70 |
end |
70 | 71 | |
71 | 72 |
def edit |
73 |
render :layout => 'admin' if params[:admin_projects] |
|
72 | 74 |
end |
73 | 75 | |
74 | 76 |
def update |
... | ... | |
76 | 78 | |
77 | 79 |
if @query.save |
78 | 80 |
flash[:notice] = l(:notice_successful_update) |
79 |
redirect_to_items(:query_id => @query) |
|
81 |
redirect_to_items(:query_id => @query, :admin_projects => params[:admin_projects])
|
|
80 | 82 |
else |
81 | 83 |
render :action => 'edit' |
82 | 84 |
end |
... | ... | |
110 | 112 |
@query ? @query.queried_class.to_s.underscore.pluralize.to_sym : nil |
111 | 113 |
end |
112 | 114 | |
115 |
def current_menu(project) |
|
116 |
super if params[:admin_projects].nil? |
|
117 |
end |
|
118 | ||
113 | 119 |
private |
114 | 120 | |
115 | 121 |
def find_query |
116 | 122 |
@query = Query.find(params[:id]) |
123 |
@query.admin_projects = params[:admin_projects] if @query.is_a?(ProjectQuery) |
|
117 | 124 |
@project = @query.project |
118 | 125 |
render_403 unless @query.editable_by?(User.current) |
119 | 126 |
rescue ActiveRecord::RecordNotFound |
... | ... | |
163 | 170 |
end |
164 | 171 | |
165 | 172 |
def redirect_to_project_query(options) |
166 |
redirect_to projects_path(options) |
|
173 |
if params[:admin_projects] |
|
174 |
redirect_to admin_projects_path(options) |
|
175 |
else |
|
176 |
redirect_to projects_path(options) |
|
177 |
end |
|
167 | 178 |
end |
168 | 179 | |
169 | 180 |
# Returns the Query subclass, IssueQuery by default |
app/helpers/queries_helper.rb | ||
---|---|---|
462 | 462 |
url_params = |
463 | 463 |
if controller_name == 'issues' |
464 | 464 |
{:controller => 'issues', :action => 'index', :project_id => @project} |
465 |
elsif controller_name == 'admin' && action_name == 'projects' |
|
466 |
{:admin_projects => '1'} |
|
465 | 467 |
else |
466 | 468 |
{} |
467 | 469 |
end |
app/models/project_query.rb | ||
---|---|---|
18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | 19 | |
20 | 20 |
class ProjectQuery < Query |
21 |
attr_accessor :admin_projects |
|
22 | ||
21 | 23 |
self.queried_class = Project |
22 | 24 |
self.view_permission = :search_project |
23 | 25 | |
... | ... | |
74 | 76 |
add_custom_fields_filters(project_custom_fields) |
75 | 77 |
end |
76 | 78 | |
79 |
def build_from_params(params, defaults={}) |
|
80 |
query = super |
|
81 |
query.admin_projects = params[:admin_projects] |
|
82 |
query |
|
83 |
end |
|
84 | ||
77 | 85 |
def available_columns |
78 | 86 |
return @available_columns if @available_columns |
79 | 87 | |
... | ... | |
84 | 92 |
end |
85 | 93 | |
86 | 94 |
def available_display_types |
87 |
['board', 'list'] |
|
95 |
if self.admin_projects |
|
96 |
['list'] |
|
97 |
else |
|
98 |
['board', 'list'] |
|
99 |
end |
|
100 |
end |
|
101 | ||
102 |
def display_type |
|
103 |
if self.admin_projects |
|
104 |
'list' |
|
105 |
else |
|
106 |
super |
|
107 |
end |
|
108 |
end |
|
109 | ||
110 |
def project_statuses_values |
|
111 |
values = super |
|
112 |
if self.admin_projects |
|
113 |
values << [l(:project_status_archived), Project::STATUS_ARCHIVED.to_s] |
|
114 |
end |
|
115 |
values |
|
88 | 116 |
end |
89 | 117 | |
90 | 118 |
def default_columns_names |
... | ... | |
100 | 128 |
end |
101 | 129 | |
102 | 130 |
def base_scope |
103 |
Project.visible.where(statement) |
|
131 |
if self.admin_projects |
|
132 |
Project.where(statement) |
|
133 |
else |
|
134 |
Project.visible.where(statement) |
|
135 |
end |
|
104 | 136 |
end |
105 | 137 | |
106 | 138 |
def results_scope(options={}) |
app/views/admin/projects.html.erb | ||
---|---|---|
2 | 2 |
<%= link_to l(:label_project_new), new_project_path, :class => 'icon icon-add' %> |
3 | 3 |
</div> |
4 | 4 | |
5 |
<%= title l(:label_project_plural) %>
|
|
5 |
<h2><%= @query.new_record? ? l(:label_project_plural) : @query.name %></h2>
|
|
6 | 6 | |
7 |
<%= form_tag({}, :method => :get) do %> |
|
8 |
<fieldset><legend><%= l(:label_filter_plural) %></legend> |
|
9 |
<label for='status'><%= l(:field_status) %>:</label> |
|
10 |
<%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> |
|
11 |
<label for='name'><%= l(:label_project) %>:</label> |
|
12 |
<%= text_field_tag 'name', params[:name], :size => 30 %> |
|
13 |
<%= submit_tag l(:button_apply), :class => "small", :name => nil %> |
|
14 |
<%= link_to l(:button_clear), admin_projects_path, :class => 'icon icon-reload' %> |
|
15 |
</fieldset> |
|
7 |
<%= form_tag(admin_projects_path(@project, nil), :method => :get, :id => 'query_form') do %> |
|
8 |
<%= hidden_field_tag 'admin_projects', '1' %> |
|
9 |
<%= render :partial => 'queries/query_form' %> |
|
16 | 10 |
<% end %> |
17 |
|
|
18 | 11 | |
19 |
<% if @projects.any? %> |
|
20 |
<div class="autoscroll"> |
|
21 |
<table class="list"> |
|
22 |
<thead><tr> |
|
23 |
<th><%=l(:label_project)%></th> |
|
24 |
<th><%=l(:field_is_public)%></th> |
|
25 |
<th><%=l(:field_created_on)%></th> |
|
26 |
<th></th> |
|
27 |
</tr></thead> |
|
28 |
<tbody> |
|
29 |
<% project_tree(@projects, :init_level => true) do |project, level| %> |
|
30 |
<tr class="<%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> |
|
31 |
<td class="name"><span><%= link_to_project_settings(project, {}, :title => project.short_description) %></span></td> |
|
32 |
<td><%= checked_image project.is_public? %></td> |
|
33 |
<td><%= format_date(project.created_on) %></td> |
|
34 |
<td class="buttons"> |
|
35 |
<%= link_to(l(:button_archive), archive_project_path(project, :status => params[:status]), :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-lock') unless project.archived? %> |
|
36 |
<%= link_to(l(:button_unarchive), unarchive_project_path(project, :status => params[:status]), :method => :post, :class => 'icon icon-unlock') if project.archived? %> |
|
37 |
<%= link_to(l(:button_copy), copy_project_path(project), :class => 'icon icon-copy') %> |
|
38 |
<%= link_to(l(:button_delete), project_path(project), :method => :delete, :class => 'icon icon-del') %> |
|
39 |
</td> |
|
40 |
</tr> |
|
12 |
<% if @query.valid? %> |
|
13 |
<%if @projects.any? %> |
|
14 |
<%= render :partial => 'projects/list', :locals => { :entries => @projects }%> |
|
15 |
<% else %> |
|
16 |
<p class="nodata"><%= l(:label_no_data) %></p> |
|
17 |
<% end %> |
|
41 | 18 |
<% end %> |
42 |
</tbody> |
|
43 |
</table> |
|
44 |
</div> |
|
45 |
<span class="pagination"><%= pagination_links_full @project_pages, @project_count %></span> |
|
46 |
<% else %> |
|
47 |
<p class="nodata"><%= l(:label_no_data) %></p> |
|
19 | ||
20 |
<% content_for :sidebar do %> |
|
21 |
<%= render :partial => 'projects/sidebar' %> |
|
48 | 22 |
<% end %> |
app/views/projects/_list.html.erb | ||
---|---|---|
6 | 6 |
<% @query.inline_columns.each do |column| %> |
7 | 7 |
<%= column_header(@query, column) %> |
8 | 8 |
<% end %> |
9 |
<% if controller_name == 'admin' && action_name == 'projects' %> |
|
10 |
<th></th> |
|
11 |
<% end %> |
|
9 | 12 |
</tr> |
10 | 13 |
</thead> |
11 | 14 |
<tbody> |
... | ... | |
23 | 26 |
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", |
24 | 27 |
"toggleAllRowGroups(this)", :class => 'toggle-all') %> |
25 | 28 |
</td> |
29 |
<% if controller_name == 'admin' && action_name == 'projects' %> |
|
30 |
<td></td> |
|
31 |
<% end %> |
|
26 | 32 |
</tr> |
27 | 33 |
<% end %> |
28 | 34 |
<tr id="project-<%= entry.id %>" class="<%= cycle('odd', 'even') %> <%= entry.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> |
29 | 35 |
<% @query.inline_columns.each do |column| %> |
30 | 36 |
<%= content_tag('td', column_content(column, entry), :class => column.css_classes) %> |
31 | 37 |
<% end %> |
38 |
<% if controller_name == 'admin' && action_name == 'projects' %> |
|
39 |
<td class="buttons"> |
|
40 |
<%= link_to(l(:button_archive), archive_project_path(entry, :status => params[:status]), :data => {:confirm => l(:text_are_you_sure)}, :method => :post, :class => 'icon icon-lock') unless entry.archived? %> |
|
41 |
<%= link_to(l(:button_unarchive), unarchive_project_path(entry, :status => params[:status]), :method => :post, :class => 'icon icon-unlock') if entry.archived? %> |
|
42 |
<%= link_to(l(:button_copy), copy_project_path(entry), :class => 'icon icon-copy') %> |
|
43 |
<%= link_to(l(:button_delete), project_path(entry), :method => :delete, :class => 'icon icon-del') %> |
|
44 |
</td> |
|
45 |
<% end %> |
|
32 | 46 |
</tr> |
33 | 47 |
<% end -%> |
34 | 48 |
</tbody> |
app/views/queries/_form.html.erb | ||
---|---|---|
4 | 4 |
<div class="tabular"> |
5 | 5 |
<%= hidden_field_tag 'gantt', '1' if params[:gantt] %> |
6 | 6 |
<%= hidden_field_tag 'calendar', '1' if params[:calendar] %> |
7 |
<%= hidden_field_tag 'admin_projects', '1' if params[:admin_projects] %> |
|
7 | 8 | |
8 | 9 |
<p><label for="query_name"><%=l(:field_name)%></label> |
9 | 10 |
<%= text_field 'query', 'name', :size => 80 %></p> |
... | ... | |
34 | 35 |
<p><label for='display_type'><%= l(:label_display_type) %></label> |
35 | 36 |
<%= available_display_types_tags(@query) %> |
36 | 37 |
</p> |
38 |
<% elsif @query.available_display_types.size == 1 %> |
|
39 |
<%= hidden_field_tag 'query[display_type]', @query.available_display_types.first %> |
|
37 | 40 |
<% end %> |
38 | 41 | |
39 | 42 |
<p id ="default_columns"><label for="query_default_columns"><%=l(:label_default_columns)%></label> |
app/views/queries/_query_form.html.erb | ||
---|---|---|
63 | 63 |
<% end %> |
64 | 64 |
<% else %> |
65 | 65 |
<% if @query.editable_by?(User.current) %> |
66 |
<%= link_to l(:button_edit_object, object_name: l(:label_query).downcase), edit_query_path(@query), :class => 'icon icon-edit' %> |
|
67 |
<%= delete_link query_path(@query), {}, l(:button_delete_object, object_name: l(:label_query).downcase) %> |
|
66 |
<% redirect_params = (controller_name == 'admin' && action_name == 'projects') ? {:admin_projects => 1} : {} %> |
|
67 |
<%= link_to l(:button_edit_object, object_name: l(:label_query).downcase), edit_query_path(@query, redirect_params), :class => 'icon icon-edit' %> |
|
68 |
<%= delete_link query_path(@query, redirect_params), {}, l(:button_delete_object, object_name: l(:label_query).downcase) %> |
|
68 | 69 |
<% end %> |
69 | 70 |
<% end %> |
70 | 71 |
</p> |
test/functional/admin_controller_test.rb | ||
---|---|---|
38 | 38 |
assert_select 'div.nodata' |
39 | 39 |
end |
40 | 40 | |
41 |
def test_projects |
|
41 |
def test_projects_should_show_only_active_projects_by_default |
|
42 |
p = Project.find(1) |
|
43 |
p.update_column :status, 5 |
|
44 | ||
42 | 45 |
get :projects |
43 | 46 |
assert_response :success |
44 | 47 |
assert_select 'tr.project.closed', 0 |
48 |
assert_select 'tr.project', 5 |
|
49 |
assert_select 'tr.project td.name', :text => 'OnlineStore' |
|
50 |
assert_select 'tr.project td.name', :text => p.name, :count => 0 |
|
45 | 51 |
end |
46 | 52 | |
47 | 53 |
def test_projects_with_status_filter |
54 |
p = Project.find(1) |
|
55 |
p.update_column :status, 5 |
|
48 | 56 |
get( |
49 | 57 |
:projects, |
50 | 58 |
:params => { |
51 |
:status => 1 |
|
59 |
'set_filter' => '1', |
|
60 |
'f' => ['status'], |
|
61 |
'op' => {'status' => '='}, |
|
62 |
'v' => {'status' => ['5']} |
|
52 | 63 |
} |
53 | 64 |
) |
54 | 65 |
assert_response :success |
55 |
assert_select 'tr.project.closed', 0 |
|
66 |
assert_select 'tr.project', 1 |
|
67 |
assert_select 'tr.project td.name', :text => p.name |
|
56 | 68 |
end |
57 | 69 | |
58 | 70 |
def test_projects_with_name_filter |
59 | 71 |
get( |
60 | 72 |
:projects, |
61 | 73 |
:params => { |
62 |
:name => 'store', |
|
63 |
:status => '' |
|
74 |
'set_filter' => '1', |
|
75 |
'f' => ['status', 'name'], |
|
76 |
'op' => {'status' => '=', 'name' => '~'}, |
|
77 |
'v' => {'status' => ['1'], 'name' => ['store']} |
|
64 | 78 |
} |
65 | 79 |
) |
66 | 80 |
assert_response :success |
test/functional/queries_controller_test.rb | ||
---|---|---|
585 | 585 |
assert q.valid? |
586 | 586 |
end |
587 | 587 | |
588 |
def test_create_admin_projects_query_should_redirect_to_admin_projects |
|
589 |
@request.session[:user_id] = 1 |
|
590 | ||
591 |
q = new_record(ProjectQuery) do |
|
592 |
post( |
|
593 |
:create, |
|
594 |
:params => { |
|
595 |
:type => 'ProjectQuery', |
|
596 |
:default_columns => '1', |
|
597 |
:f => ["status"], |
|
598 |
:op => { |
|
599 |
"status" => "=" |
|
600 |
}, |
|
601 |
:v => { |
|
602 |
"status" => ['1'] |
|
603 |
}, |
|
604 |
:query => { |
|
605 |
"name" => "test_new_project_public_query", "visibility" => "2" |
|
606 |
}, |
|
607 |
:admin_projects => 1 |
|
608 |
} |
|
609 |
) |
|
610 |
end |
|
611 | ||
612 |
assert_redirected_to :controller => 'admin', :action => 'projects', :query_id => q.id, :admin_projects => 1 |
|
613 |
end |
|
614 | ||
588 | 615 |
def test_edit_global_public_query |
589 | 616 |
@request.session[:user_id] = 1 |
590 | 617 |
get(:edit, :params => {:id => 4}) |
... | ... | |
690 | 717 |
assert q.valid? |
691 | 718 |
end |
692 | 719 | |
720 |
def test_update_admin_projects_query |
|
721 |
q = ProjectQuery.create(:name => 'project_query') |
|
722 |
@request.session[:user_id] = 1 |
|
723 | ||
724 |
put( |
|
725 |
:update, |
|
726 |
:params => { |
|
727 |
:id => q.id, |
|
728 |
:default_columns => '1', |
|
729 |
:fields => ["status"], |
|
730 |
:operators => { |
|
731 |
"status" => "=" |
|
732 |
}, |
|
733 |
:values => { |
|
734 |
"status" => ['1'] |
|
735 |
}, |
|
736 |
:query => { |
|
737 |
"name" => "test_project_query_updated", "visibility" => "2" |
|
738 |
}, |
|
739 |
:admin_projects => 1 |
|
740 |
} |
|
741 |
) |
|
742 | ||
743 |
assert_redirected_to :controller => 'admin', :action => 'projects', :query_id => q.id, :admin_projects => 1 |
|
744 |
assert Query.find_by_name('test_project_query_updated') |
|
745 |
end |
|
746 | ||
693 | 747 |
def test_update_with_failure |
694 | 748 |
@request.session[:user_id] = 1 |
695 | 749 |
put( |
test/unit/project_query_test.rb | ||
---|---|---|
62 | 62 |
assert_include :cf_3, query.available_columns.map(&:name) |
63 | 63 |
end |
64 | 64 | |
65 |
def test_available_display_types_should_returns_bord_and_list |
|
66 |
query = ProjectQuery.new |
|
67 |
query.admin_projects = nil |
|
68 |
assert_equal ['board', 'list'], query.available_display_types |
|
69 |
end |
|
70 | ||
71 |
def test_available_display_types_should_always_returns_list_when_admin_projects_is_set |
|
72 |
query = ProjectQuery.new |
|
73 |
query.admin_projects = 1 |
|
74 |
assert_equal ['list'], query.available_display_types |
|
75 |
end |
|
76 | ||
65 | 77 |
def test_display_type_default_should_equal_with_setting_project_list_display_type |
66 | 78 |
ProjectQuery.new.available_display_types.each do |t| |
67 | 79 |
with_settings :project_list_display_type => t do |
... | ... | |
104 | 116 | |
105 | 117 |
assert_nil ProjectQuery.default |
106 | 118 |
end |
119 | ||
120 |
def test_display_type_should_returns_list_when_admin_projects_is_set |
|
121 |
q = ProjectQuery.new |
|
122 |
q.admin_projects = 1 |
|
123 |
assert_equal 'list', q.display_type |
|
124 |
end |
|
125 | ||
126 |
def test_project_statuses_values_should_equal_ancestors_return |
|
127 |
ancestor = Query.new |
|
128 |
q = ProjectQuery.new |
|
129 |
assert_equal ancestor.project_statuses_values, q.project_statuses_values |
|
130 |
end |
|
131 | ||
132 |
def test_project_statuses_values_should_includes_project_status_archeved_when_admin_projects_is_set |
|
133 |
q = ProjectQuery.new |
|
134 |
q.admin_projects = 1 |
|
135 |
assert_includes q.project_statuses_values, [l(:project_status_archived), Project::STATUS_ARCHIVED.to_s] |
|
136 |
Query.new.project_statuses_values.each do |status| |
|
137 |
assert_includes q.project_statuses_values, status |
|
138 |
end |
|
139 |
end |
|
140 | ||
141 |
def test_base_scope_should_return_visible_projects |
|
142 |
q = ProjectQuery.new |
|
143 |
assert_equal Project.visible, q.base_scope |
|
144 |
end |
|
145 | ||
146 |
def test_base_scope_should_return_all_projects_when_admin_projects_is_set |
|
147 |
q = ProjectQuery.new |
|
148 |
q.admin_projects = 1 |
|
149 |
assert_equal Project.all, q.base_scope |
|
150 |
end |
|
107 | 151 |
end |