Skip to content

Commit

Permalink
Role based custom queries (#1019).
Browse files Browse the repository at this point in the history
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@11994 e93f8b46-1217-0410-a6f0-8f06a7374b81
  • Loading branch information
jplang committed Jul 11, 2013
1 parent 4545b90 commit 888c358
Show file tree
Hide file tree
Showing 18 changed files with 215 additions and 56 deletions.
6 changes: 3 additions & 3 deletions app/controllers/queries_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ def new
@query = IssueQuery.new
@query.user = User.current
@query.project = @project
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
end

def create
@query = IssueQuery.new(params[:query])
@query.user = User.current
@query.project = params[:query_is_for_all] ? nil : @project
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]

Expand All @@ -71,7 +71,7 @@ def edit
def update
@query.attributes = params[:query]
@query.project = nil if params[:query_is_for_all]
@query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.visibility = IssueQuery::VISIBILITY_PRIVATE unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin?
@query.build_from_params(params)
@query.column_names = nil if params[:default_columns]

Expand Down
4 changes: 2 additions & 2 deletions app/helpers/issues_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ def query_links(title, queries)

def render_sidebar_queries
out = ''.html_safe
out << query_links(l(:label_my_queries), sidebar_queries.reject(&:is_public?))
out << query_links(l(:label_query_plural), sidebar_queries.select(&:is_public?))
out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
out
end

Expand Down
45 changes: 41 additions & 4 deletions app/models/issue_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,25 @@ class IssueQuery < Query
scope :visible, lambda {|*args|
user = args.shift || User.current
base = Project.allowed_to_condition(user, :view_issues, *args)
user_id = user.logged? ? user.id : 0

includes(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id)
scope = includes(:project).where("#{table_name}.project_id IS NULL OR (#{base})")

if user.admin?
scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
elsif user.memberships.any?
scope.where("#{table_name}.visibility = ?" +
" OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
"SELECT DISTINCT q.id FROM #{table_name} q" +
" INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
" INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
" INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
" WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
" OR #{table_name}.user_id = ?",
VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
elsif user.logged?
scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
else
scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
end
}

def initialize(attributes=nil, *args)
Expand All @@ -57,7 +73,28 @@ def initialize(attributes=nil, *args)

# Returns true if the query is visible to +user+ or the current user.
def visible?(user=User.current)
(project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
return true if user.admin?
return false unless project.nil? || user.allowed_to?(:view_issues, project)
case visibility
when VISIBILITY_PUBLIC
true
when VISIBILITY_ROLES
if project
(user.roles_for_project(project) & roles).any?
else
Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
end
else
user == self.user
end
end

def is_private?
visibility == VISIBILITY_PRIVATE
end

def is_public?
!is_private?
end

def initialize_available_filters
Expand Down
19 changes: 17 additions & 2 deletions app/models/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,13 @@ class Query < ActiveRecord::Base
class StatementInvalid < ::ActiveRecord::StatementInvalid
end

VISIBILITY_PRIVATE = 0
VISIBILITY_ROLES = 1
VISIBILITY_PUBLIC = 2

belongs_to :project
belongs_to :user
has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id"
serialize :filters
serialize :column_names
serialize :sort_criteria, Array
Expand All @@ -126,7 +131,17 @@ class StatementInvalid < ::ActiveRecord::StatementInvalid

validates_presence_of :name
validates_length_of :name, :maximum => 255
validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] }
validate :validate_query_filters
validate do |query|
errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank?
end

after_save do |query|
if query.visibility_changed? && query.visibility != VISIBILITY_ROLES
query.roles.clear
end
end

class_attribute :operators
self.operators = {
Expand Down Expand Up @@ -245,9 +260,9 @@ def add_filter_error(field, message)
def editable_by?(user)
return false unless user
# Admin can edit them all and regular users can edit their private queries
return true if user.admin? || (!is_public && self.user_id == user.id)
return true if user.admin? || (is_private? && self.user_id == user.id)
# Members can not edit public queries that are for all project (only admin is allowed to)
is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
end

def trackers
Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ def remove_references_before_destroy
Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
# Remove private queries and keep public ones
::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
Token.delete_all ['user_id = ?', id]
Expand Down
23 changes: 20 additions & 3 deletions app/views/queries/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
<%= text_field 'query', 'name', :size => 80 %></p>

<% if User.current.admin? || User.current.allowed_to?(:manage_public_queries, @project) %>
<p><label for="query_is_public"><%=l(:field_is_public)%></label>
<%= check_box 'query', 'is_public',
:onchange => (User.current.admin? ? nil : 'if (this.checked) {$("#query_is_for_all").removeAttr("checked"); $("#query_is_for_all").attr("disabled", true);} else {$("#query_is_for_all").removeAttr("disabled");}') %></p>
<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_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 %>
<label class="block"><%= radio_button 'query', 'visibility', Query::VISIBILITY_PUBLIC %> <%= l(:label_visibility_public) %></label>
<%= 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?,
:disabled => (!@query.new_record? && (@query.project.nil? || (@query.is_public? && !User.current.admin?))) %></p>

<fieldset><legend><%= l(:label_options) %></legend>
<p><label for="query_default_columns"><%=l(:label_default_columns)%></label>
<%= check_box_tag 'default_columns', 1, @query.has_default_columns?, :id => 'query_default_columns',
:onclick => 'if (this.checked) {$("#columns").hide();} else {$("#columns").show();}' %></p>
Expand All @@ -24,6 +31,7 @@

<p><label><%= l(:button_show) %></label>
<%= available_block_columns_tags(@query) %></p>
</fieldset>
</div>

<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend>
Expand Down Expand Up @@ -53,3 +61,12 @@
<% end %>

</div>

<%= javascript_tag do %>
$(document).ready(function(){
$("input[name='query[visibility]']").change(function(){
var checked = $('#query_visibility_1').is(':checked');
$("input[name='query[role_ids][]'][type=checkbox]").attr('disabled', !checked);
}).trigger('change');
});
<% end %>
2 changes: 1 addition & 1 deletion app/views/queries/index.api.rsb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ api.array :queries, api_meta(:total_count => @query_count, :offset => @offset, :
api.query do
api.id query.id
api.name query.name
api.is_public query.is_public
api.is_public query.is_public?
api.project_id query.project_id
end
end
Expand Down
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,9 @@ en:
label_cross_project_hierarchy: With project hierarchy
label_cross_project_system: With all projects
label_gantt_progress_line: Progress line
label_visibility_private: to me only
label_visibility_roles: to these roles only
label_visibility_public: to any users

button_login: Login
button_submit: Submit
Expand Down
3 changes: 3 additions & 0 deletions config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,9 @@ fr:
label_cross_project_hierarchy: Avec toute la hiérarchie
label_cross_project_system: Avec tous les projets
label_gantt_progress_line: Ligne de progression
label_visibility_private: par moi uniquement
label_visibility_roles: par ces roles uniquement
label_visibility_public: par tout le monde

button_login: Connexion
button_submit: Soumettre
Expand Down
13 changes: 13 additions & 0 deletions db/migrate/20130602092539_create_queries_roles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CreateQueriesRoles < ActiveRecord::Migration
def self.up
create_table :queries_roles, :id => false do |t|
t.column :query_id, :integer, :null => false
t.column :role_id, :integer, :null => false
end
add_index :queries_roles, [:query_id, :role_id], :unique => true, :name => :queries_roles_ids
end

def self.down
drop_table :queries_roles
end
end
13 changes: 13 additions & 0 deletions db/migrate/20130710182539_add_queries_visibility.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class AddQueriesVisibility < ActiveRecord::Migration
def up
add_column :queries, :visibility, :integer, :default => 0
Query.where(:is_public => true).update_all(:visibility => 2)
remove_column :queries, :is_public
end

def down
add_column :queries, :is_public, :boolean, :default => true, :null => false
Query.where('visibility <> ?', 2).update_all(:is_public => false)
remove_column :queries, :visibility
end
end
2 changes: 2 additions & 0 deletions public/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@ input.autocomplete.ajax-loading {
background-image: url(../images/loading.gif);
}

.role-visibility {padding-left:2em;}

/***** Flash & error messages ****/
#errorExplanation, div.flash, .nodata, .warning, .conflict {
padding: 4px 4px 4px 30px;
Expand Down
18 changes: 9 additions & 9 deletions test/fixtures/queries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ queries_001:
id: 1
type: IssueQuery
project_id: 1
is_public: true
visibility: 2
name: Multiple custom fields query
filters: |
---
Expand All @@ -26,7 +26,7 @@ queries_002:
id: 2
type: IssueQuery
project_id: 1
is_public: false
visibility: 0
name: Private query for cookbook
filters: |
---
Expand All @@ -45,7 +45,7 @@ queries_003:
id: 3
type: IssueQuery
project_id:
is_public: false
visibility: 0
name: Private query for all projects
filters: |
---
Expand All @@ -60,7 +60,7 @@ queries_004:
id: 4
type: IssueQuery
project_id:
is_public: true
visibility: 2
name: Public query for all projects
filters: |
---
Expand All @@ -75,7 +75,7 @@ queries_005:
id: 5
type: IssueQuery
project_id:
is_public: true
visibility: 2
name: Open issues by priority and tracker
filters: |
---
Expand All @@ -96,7 +96,7 @@ queries_006:
id: 6
type: IssueQuery
project_id:
is_public: true
visibility: 2
name: Open issues grouped by tracker
filters: |
---
Expand All @@ -116,7 +116,7 @@ queries_007:
id: 7
type: IssueQuery
project_id: 2
is_public: true
visibility: 2
name: Public query for project 2
filters: |
---
Expand All @@ -131,7 +131,7 @@ queries_008:
id: 8
type: IssueQuery
project_id: 2
is_public: false
visibility: 0
name: Private query for project 2
filters: |
---
Expand All @@ -146,7 +146,7 @@ queries_009:
id: 9
type: IssueQuery
project_id:
is_public: true
visibility: 2
name: Open issues grouped by list custom field
filters: |
---
Expand Down
2 changes: 1 addition & 1 deletion test/functional/calendars_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def test_show
end

def test_show_should_run_custom_queries
@query = IssueQuery.create!(:name => 'Calendar', :is_public => true)
@query = IssueQuery.create!(:name => 'Calendar', :visibility => IssueQuery::VISIBILITY_PUBLIC)

get :show, :query_id => @query.id
assert_response :success
Expand Down
Loading

0 comments on commit 888c358

Please sign in to comment.