##// END OF EJS Templates
Adds updated_by and last_updated_by filters on issues (#17720)....
Jean-Philippe Lang -
r15846:151a215ea45c
parent child
Show More
@@ -165,6 +165,14 class IssueQuery < Query
165
165
166 add_available_filter "issue_id", :type => :integer, :label => :label_issue
166 add_available_filter "issue_id", :type => :integer, :label => :label_issue
167
167
168 add_available_filter("updated_by",
169 :type => :list, :values => lambda { author_values }
170 )
171
172 add_available_filter("last_updated_by",
173 :type => :list, :values => lambda { author_values }
174 )
175
168 Tracker.disabled_core_fields(trackers).each {|field|
176 Tracker.disabled_core_fields(trackers).each {|field|
169 delete_available_filter field
177 delete_available_filter field
170 }
178 }
@@ -341,6 +349,27 class IssueQuery < Query
341 raise StatementInvalid.new(e.message)
349 raise StatementInvalid.new(e.message)
342 end
350 end
343
351
352 def sql_for_updated_by_field(field, operator, value)
353 neg = (operator == '!' ? 'NOT' : '')
354 subquery = "SELECT 1 FROM #{Journal.table_name}" +
355 " WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" +
356 " AND (#{sql_for_field field, '=', value, Journal.table_name, 'user_id'})" +
357 " AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)})"
358
359 "#{neg} EXISTS (#{subquery})"
360 end
361
362 def sql_for_last_updated_by_field(field, operator, value)
363 neg = (operator == '!' ? 'NOT' : '')
364 subquery = "SELECT 1 FROM #{Journal.table_name} sj" +
365 " WHERE sj.journalized_type='Issue' AND sj.journalized_id=#{Issue.table_name}.id AND (#{sql_for_field field, '=', value, 'sj', 'user_id'})" +
366 " AND sj.id = (SELECT MAX(#{Journal.table_name}.id) FROM #{Journal.table_name}" +
367 " WHERE #{Journal.table_name}.journalized_type='Issue' AND #{Journal.table_name}.journalized_id=#{Issue.table_name}.id" +
368 " AND (#{Journal.visible_notes_condition(User.current, :skip_pre_condition => true)}))"
369
370 "#{neg} EXISTS (#{subquery})"
371 end
372
344 def sql_for_watcher_id_field(field, operator, value)
373 def sql_for_watcher_id_field(field, operator, value)
345 db_table = Watcher.table_name
374 db_table = Watcher.table_name
346 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
375 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
@@ -47,10 +47,11 class Journal < ActiveRecord::Base
47
47
48 scope :visible, lambda {|*args|
48 scope :visible, lambda {|*args|
49 user = args.shift || User.current
49 user = args.shift || User.current
50 private_notes_condition = Project.allowed_to_condition(user, :view_private_notes, *args)
50 options = args.shift || {}
51
51 joins(:issue => :project).
52 joins(:issue => :project).
52 where(Issue.visible_condition(user, *args)).
53 where(Issue.visible_condition(user, options)).
53 where("(#{Journal.table_name}.private_notes = ? OR #{Journal.table_name}.user_id = ? OR (#{private_notes_condition}))", false, user.id)
54 where(Journal.visible_notes_condition(user, :skip_pre_condition => true))
54 }
55 }
55
56
56 safe_attributes 'notes',
57 safe_attributes 'notes',
@@ -58,6 +59,12 class Journal < ActiveRecord::Base
58 safe_attributes 'private_notes',
59 safe_attributes 'private_notes',
59 :if => lambda {|journal, user| user.allowed_to?(:set_notes_private, journal.project)}
60 :if => lambda {|journal, user| user.allowed_to?(:set_notes_private, journal.project)}
60
61
62 # Returns a SQL condition to filter out journals with notes that are not visible to user
63 def self.visible_notes_condition(user=User.current, options={})
64 private_notes_permission = Project.allowed_to_condition(user, :view_private_notes, options)
65 sanitize_sql_for_conditions(["(#{table_name}.private_notes = ? OR #{table_name}.user_id = ? OR (#{private_notes_permission}))", false, user.id])
66 end
67
61 def initialize(*args)
68 def initialize(*args)
62 super
69 super
63 if journalized
70 if journalized
@@ -173,13 +173,14 class Project < ActiveRecord::Base
173 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
173 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
174 #
174 #
175 # Valid options:
175 # Valid options:
176 # * :project => limit the condition to project
176 # * :skip_pre_condition => true don't check that the module is enabled (eg. when the condition is already set elsewhere in the query)
177 # * :with_subprojects => limit the condition to project and its subprojects
177 # * :project => project limit the condition to project
178 # * :member => limit the condition to the user projects
178 # * :with_subprojects => true limit the condition to project and its subprojects
179 # * :member => true limit the condition to the user projects
179 def self.allowed_to_condition(user, permission, options={})
180 def self.allowed_to_condition(user, permission, options={})
180 perm = Redmine::AccessControl.permission(permission)
181 perm = Redmine::AccessControl.permission(permission)
181 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
182 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
182 if perm && perm.project_module
183 if !options[:skip_pre_condition] && perm && perm.project_module
183 # If the permission belongs to a project module, make sure the module is enabled
184 # If the permission belongs to a project module, make sure the module is enabled
184 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
185 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
185 end
186 end
@@ -802,7 +802,7 class Query < ActiveRecord::Base
802 operator = operator_for(field)
802 operator = operator_for(field)
803
803
804 # "me" value substitution
804 # "me" value substitution
805 if %w(assigned_to_id author_id user_id watcher_id).include?(field)
805 if %w(assigned_to_id author_id user_id watcher_id updated_by last_updated_by).include?(field)
806 if v.delete("me")
806 if v.delete("me")
807 if User.current.logged?
807 if User.current.logged?
808 v.push(User.current.id.to_s)
808 v.push(User.current.id.to_s)
@@ -370,6 +370,8 en:
370 field_default_version: Default version
370 field_default_version: Default version
371 field_remote_ip: IP address
371 field_remote_ip: IP address
372 field_textarea_font: Font used for text areas
372 field_textarea_font: Font used for text areas
373 field_updated_by: Updated by
374 field_last_updated_by: Last updated by
373
375
374 setting_app_title: Application title
376 setting_app_title: Application title
375 setting_app_subtitle: Application subtitle
377 setting_app_subtitle: Application subtitle
@@ -382,6 +382,8 fr:
382 field_total_estimated_hours: Temps estimΓ© total
382 field_total_estimated_hours: Temps estimΓ© total
383 field_default_version: Version par dΓ©faut
383 field_default_version: Version par dΓ©faut
384 field_textarea_font: Police utilisΓ©e pour les champs texte
384 field_textarea_font: Police utilisΓ©e pour les champs texte
385 field_updated_by: Mise Γ  jour par
386 field_last_updated_by: Dernière mise à jour par
385
387
386 setting_app_title: Titre de l'application
388 setting_app_title: Titre de l'application
387 setting_app_subtitle: Sous-titre de l'application
389 setting_app_subtitle: Sous-titre de l'application
@@ -719,6 +719,93 class QueryTest < ActiveSupport::TestCase
719 end
719 end
720 end
720 end
721
721
722 def test_filter_updated_by
723 user = User.generate!
724 Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
725 Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
726 Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')
727
728 query = IssueQuery.new(:name => '_')
729 filter_name = "updated_by"
730 assert_include filter_name, query.available_filters.keys
731
732 query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
733 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
734
735 query.filters = {filter_name => {:operator => '!', :values => [user.id]}}
736 assert_equal (Issue.ids.sort - [2, 3]), find_issues_with_query(query).map(&:id).sort
737 end
738
739 def test_filter_updated_by_should_ignore_private_notes_that_are_not_visible
740 user = User.generate!
741 Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)
742 Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
743
744 query = IssueQuery.new(:name => '_')
745 filter_name = "updated_by"
746 assert_include filter_name, query.available_filters.keys
747
748 with_current_user User.anonymous do
749 query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
750 assert_equal [3], find_issues_with_query(query).map(&:id).sort
751 end
752 end
753
754 def test_filter_updated_by_me
755 user = User.generate!
756 Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
757
758 with_current_user user do
759 query = IssueQuery.new(:name => '_')
760 filter_name = "updated_by"
761 assert_include filter_name, query.available_filters.keys
762
763 query.filters = {filter_name => {:operator => '=', :values => ['me']}}
764 assert_equal [2], find_issues_with_query(query).map(&:id).sort
765 end
766 end
767
768 def test_filter_last_updated_by
769 user = User.generate!
770 Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
771 Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
772 Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')
773
774 query = IssueQuery.new(:name => '_')
775 filter_name = "last_updated_by"
776 assert_include filter_name, query.available_filters.keys
777
778 query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
779 assert_equal [2], find_issues_with_query(query).map(&:id).sort
780 end
781
782 def test_filter_last_updated_by_should_ignore_private_notes_that_are_not_visible
783 user1 = User.generate!
784 user2 = User.generate!
785 Journal.create!(:user_id => user1.id, :journalized => Issue.find(2), :notes => 'Notes')
786 Journal.create!(:user_id => user2.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)
787
788 query = IssueQuery.new(:name => '_')
789 filter_name = "last_updated_by"
790 assert_include filter_name, query.available_filters.keys
791
792 with_current_user User.anonymous do
793 query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
794 assert_equal [2], find_issues_with_query(query).map(&:id).sort
795
796 query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
797 assert_equal [], find_issues_with_query(query).map(&:id).sort
798 end
799
800 with_current_user User.find(2) do
801 query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
802 assert_equal [], find_issues_with_query(query).map(&:id).sort
803
804 query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
805 assert_equal [2], find_issues_with_query(query).map(&:id).sort
806 end
807 end
808
722 def test_user_custom_field_filtered_on_me
809 def test_user_custom_field_filtered_on_me
723 User.current = User.find(2)
810 User.current = User.find(2)
724 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
811 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
General Comments 0
You need to be logged in to leave comments. Login now