Skip to content

Commit

Permalink
fixes #6150 - users need taxonomy added on "all Users"
Browse files Browse the repository at this point in the history
With this commit, if "All users" is ticked then
locations and organizations are added to users. On edit user page,
you can not edit this taxonomy as they are disabled with tooltip
"Select all option enabled for this taxonomy".
  • Loading branch information
kgaikwad committed Dec 21, 2019
1 parent 6ecab45 commit d74da02
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 69 deletions.
56 changes: 30 additions & 26 deletions app/assets/javascripts/jquery.multi-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,44 +20,48 @@ function multiSelectOnLoad(){

function multiSelectToolTips(){
$('select[multiple]').each(function(i,item){
var mismatches = $(item).attr('data-mismatches');
var inheriteds = $(item).attr('data-inheriteds');
var descendants = $(item).attr('data-descendants');
var useds = $(item).attr('data-useds');
var msid = '#ms-'+item.id;
var mismatches = $(item).attr('data-mismatches'),
inheriteds = $(item).attr('data-inheriteds'),
descendants = $(item).attr('data-descendants'),
useds = $(item).attr('data-useds'),
used_all = $(item).attr('data-used-all'),
msid = '#ms-'+item.id;
// it an <li> items match multiple tooltips, then only the first tooltip will show
if (!(used_all == null || used_all == 'undefined')) {
addTooltipForElements(msid, used_all,
[{class_to_find_li: 'selection', clname: 'selected_taxonomy', label: "Select all option enabled for this taxonomy", position: 'right'}]);
}
if (!(mismatches == null || mismatches == 'undefined')) {
var missing_ids = $.parseJSON(mismatches);
$.each(missing_ids, function(index,missing_id){
opt_id = sanitize(missing_id+'');
$(msid).find('li#'+opt_id+'-selectable').addClass('delete').tooltip({container: 'body', title: __("Select this since it belongs to a host"), placement: "left"});
})
addTooltipForElements(msid, mismatches,
[{class_to_find_li: 'selectable', clname: 'delete', label: "Select this since it belongs to a host", position: 'left'}]);
}
if (!(useds == null || descendants == 'useds')) {
var used_ids = $.parseJSON(useds);
$.each(used_ids, function(index,used_id){
opt_id = sanitize(used_id+'');
$(msid).find('li#'+opt_id+'-selection').addClass('used_by_hosts').tooltip({container: 'body', title: __("This is used by a host"), placement: "right"});
})
addTooltipForElements(msid, useds,
[{class_to_find_li: 'selection', clname: 'used_by_hosts', label: "This is used by a host", position: 'right'}]);
}
if (!(inheriteds == null || inheriteds == 'undefined')) {
var inherited_ids = $.parseJSON(inheriteds);
$.each(inherited_ids, function(index,inherited_id){
opt_id = sanitize(inherited_id+'');
$(msid).find('li#'+opt_id+'-selection').addClass('inherited').tooltip({container: 'body', title: __("This is inherited from parent"), placement: "right"});
})
addTooltipForElements(msid, inheriteds,
[{class_to_find_li: 'selection', clname: 'inherited', label: "This is inherited from parent", position: 'right'}]);
}
if (!(descendants == null || descendants == 'undefined')) {
var descendant_ids = $.parseJSON(descendants);
$.each(descendant_ids, function(index,descendant_id){
opt_id = sanitize(descendant_id+'');
$(msid).find('li#'+opt_id+'-selection').addClass('descendants').tooltip({container: 'body', title: __("Parent is already selected"), placement: "right"});
$(msid).find('li#'+opt_id+'-selectable').addClass('descendants').tooltip({container: 'body', title: __("Parent is already selected"), placement: "left"});
})
addTooltipForElements(msid, descendants,
[{class_to_find_li: 'selection', clname: 'descendants', label: 'Parent is already selected', position: 'right'},
{class_to_find_li: 'selectable', clname: 'descendants', label: 'Parent is already selected', position: 'left'}]);
}
})
}

function addTooltipForElements(msid, json_ids, tooltips_with_options) {
var ids = $.parseJSON(json_ids);
$.each(ids, function(index, id){
opt_id = sanitize(id+'');
$.each(tooltips_with_options, function(tooltip_index, tooltip_opts) {
$(msid).find('li#'+opt_id+'-' + tooltip_opts.class_to_find_li).addClass(tooltip_opts.clname).tooltip(
{container: 'body', title: __(tooltip_opts.label), placement: tooltip_opts.position});
});
});
}

// function below is copy/paste from source of multi-select-rails gem
// it takes the option value and returns a value used in css id
function sanitize(value){
Expand Down
6 changes: 6 additions & 0 deletions app/assets/javascripts/taxonomy_edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ $(function() {
$(':checkbox').each(function(i, item) {
ignore_checked(item);
});

$('form').on('submit', function() {
$('select.without_select2').prop('disabled', false);
$('.ms-selection li.ms-elem-selection').removeClass('disabled');
});
});

function ignore_checked(item) {
Expand All @@ -11,6 +16,7 @@ function ignore_checked(item) {

if ($(item).is(':checked')) {
current_select.attr('disabled', 'disabled');
current_select.find("option").prop('selected', true);
} else {
current_select.removeAttr('disabled');
}
Expand Down
22 changes: 9 additions & 13 deletions app/helpers/form_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,9 @@ def multiple_checkboxes(f, attr, klass, associations, options = {}, html_options
# add hidden field for options[:disabled]
def multiple_selects(f, attr, associations, selected_ids, options = {}, html_options = {})
options[:size] = "col-md-10"
case attr
when :organizations
klass = Organization
when :locations
klass = Location
else
klass = nil
klass = nil
if [:organizations, :locations].include?(attr)
klass = attr.to_s.classify.constantize
end
authorized = AssociationAuthorizer.authorized_associations(associations.reorder(nil), klass).all

Expand All @@ -78,12 +74,12 @@ def multiple_selects(f, attr, associations, selected_ids, options = {}, html_opt
attr_ids = attr.to_s
attr_ids = (attr_ids.singularize + '_ids').to_sym unless attr_ids.end_with?('_ids')
hidden_fields = ''
html_options["data-useds"] ||= "[]"
JSON.parse(html_options["data-useds"]).each do |disabled_value|
hidden_fields += f.hidden_field(attr_ids, :multiple => true, :value => disabled_value, :id => '')
end
unauthorized.each do |unauthorized_value|
hidden_fields += f.hidden_field(attr_ids, :multiple => true, :value => unauthorized_value, :id => '')
html_options["data-useds"] ||= '[]'
html_options["data-used-all"] ||= '[]'
[JSON.parse(html_options["data-useds"]), JSON.parse(html_options["data-used-all"]), unauthorized].each do |optns|
optns.each do |value|
hidden_fields += f.hidden_field(attr_ids, :multiple => true, :value => value, :id = '')
end
end
hidden_fields + f.collection_select(attr_ids, authorized.sort_by { |a| a.to_s },
:id, options.delete(:object_label_method) || :to_label, options.merge(:selected => selected_ids),
Expand Down
18 changes: 17 additions & 1 deletion app/models/concerns/taxonomix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Taxonomix
:through => TAXONOMY_JOIN_TABLE, :source => :taxonomy,
:validate => false
after_initialize :set_current_taxonomy
before_save :add_taxonomies_with_ignore_type_enabled

scoped_search :relation => :locations, :on => :name, :rename => :location, :complete_value => true, :only_explicit => true
scoped_search :relation => :locations, :on => :id, :rename => :location_id, :complete_enabled => false, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER
Expand Down Expand Up @@ -83,7 +84,6 @@ def taxable_ids(loc = which_location, org = which_organization, inner_method = w
# Passing a nil or [] value as taxonomy equates to "Any context".
# Any other value will be understood as 'IDs available in this taxonomy'.
def inner_ids(taxonomy, taxonomy_class, inner_method)
return unscoped.pluck("#{table_name}.id") if taxonomy_class.ignore?(to_s)
return inner_select(taxonomy, inner_method) if taxonomy.present?
return [] unless User.current.present?
# Any available taxonomy to the current user
Expand Down Expand Up @@ -211,6 +211,22 @@ def ensure_taxonomies_not_escalated
end
end

def add_taxonomies_with_ignore_type_enabled
Taxonomy.enabled_taxonomies.each do |taxonomy_pluralize_str|
taxonomy_class_name = taxonomy_pluralize_str.classify
obj_ids = taxonomy_class_name.constantize.taxonomy_ids_by_ignore_type(self.class.to_s)
assign_taxonomy_ids(taxonomy_class_name, obj_ids) if obj_ids.present?
end
end

def assign_taxonomy_ids(taxonomy_class_name, taxonomy_ids_by_ignore_type)
taxonomy_ids_meth = taxonomy_class_name.underscore + '_ids'
existing_obj_ids = self.send(taxonomy_ids_meth)
return if (taxonomy_ids_by_ignore_type - existing_obj_ids).blank?
taxonomy_ids_by_ignore_type.concat(existing_obj_ids).uniq!
self.send("#{taxonomy_ids_meth}=", taxonomy_ids_by_ignore_type)
end

protected

def taxonomy_foreign_key_conditions
Expand Down
47 changes: 26 additions & 21 deletions app/models/taxonomy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Taxonomy < ApplicationRecord
validate :parent_id_does_not_escalate, :if => Proc.new { |t| t.ancestry_changed? && t.parent_id != 0 && t.parent.present? }
validates :name, :presence => true, :uniqueness => {:scope => [:ancestry, :type], :case_sensitive => false}

before_save :add_references_for_selected_ignore_types

def self.inherited(child)
child.instance_eval do
scoped_search :on => :description, :complete_enabled => :false, :only_explicit => true
Expand All @@ -56,6 +58,8 @@ def self.inherited(child)
end
}

scope :taxonomies_by_ignore_type, ->(taxable_type) { where("ignore_types LIKE ?", "%#{taxable_type}%") }

def self.no_taxonomy_scope
as_taxonomy nil, nil do
yield if block_given?
Expand Down Expand Up @@ -86,6 +90,10 @@ def self.ignore?(taxable_type)
end
end

def self.taxonomy_ids_by_ignore_type(ignore_type_str)
taxonomies_by_ignore_type(ignore_type_str).pluck(:id)
end

# if taxonomy e.g. organization was not set by current context (e.g. Any organization)
# then we have to compute what this context mean for current user (what organizations
# they are assigned to)
Expand Down Expand Up @@ -134,25 +142,6 @@ def dup
new
end

# overwrite *_ids since need to check if ignored? - don't overwrite location_ids and organization_ids since these aren't ignored
(TaxHost::HASH_KEYS - [:location_ids, :organization_ids]).each do |key|
# def domain_ids
# if ignore?("Domain")
# Domain.pluck(:id)
# else
# super() # self.domain_ids
# end
define_method(key) do
klass = hash_key_to_class(key)
if ignore?(klass)
return User.unscoped.except_admin.except_hidden.map(&:id) if klass == "User"
return klass.constantize.pluck(:id)
else
super()
end
end
end

def expire_topbar_cache
(users + User.only_admin).each { |u| u.expire_topbar_cache }
end
Expand Down Expand Up @@ -214,8 +203,13 @@ def destroy_taxable_taxonomies
:to => :tax_host

def assign_default_templates
Template.where(:default => true).group_by { |t| t.class.to_s.underscore.pluralize }.each do |association, templates|
self.send("#{association}=", self.send(association) + templates.select(&:valid?))
# Template.where(:default => true).group_by { |t| t.class.to_s.underscore.pluralize }.each do |association, templates|
# self.send("#{association}=", self.send(association) + templates.select(&:valid?))
Template.where(:default => true).select(&:valid?).find_each do |template|
classify_template_str = template.class.to_s
unless self.ignore_types.include?(classify_template_str)
self.send(classify_template_str.underscore.pluralize.to_s) << template
end
end
end

Expand Down Expand Up @@ -243,4 +237,15 @@ def parent_id_does_not_escalate
false
end
end

def add_references_for_selected_ignore_types
ignore_types = self.ignore_types
ignore_types.each do |klass|
klass_obj_ids = klass.constantize.unscoped.pluck(:id)
klass_ids_meth = klass.underscore + '_ids'
existing_ids = self.send(klass_ids_meth)
next if (klass_obj_ids - existing_ids).blank?
self.send("#{klass_ids_meth}=", klass_obj_ids)
end
end
end
19 changes: 11 additions & 8 deletions app/views/taxonomies/_loc_org_tabs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,30 @@
<% if show_location_tab? %>
<div class="tab-pane" id="locations">
<% location_ids_with_select_all_for_ignore_type = Location.taxonomy_ids_by_ignore_type(obj.class.to_s) %>
<%= location_selects f, obj.used_or_selected_location_ids,
{:disabled => obj.used_location_ids }.merge!(select_options),
{:disabled => obj.used_location_ids.concat(location_ids_with_select_all_for_ignore_type)}.merge!(select_options),
{'data-descendants' => obj.children_of_selected_location_ids.to_json,
'data-useds' => obj.used_location_ids.to_json }.merge!(html_options[:location]) %>
'data-useds' => obj.used_location_ids.to_json,
'data-used-all' => location_ids_with_select_all_for_ignore_type.to_json }.merge!(html_options[:location]) %>
<% if obj.kind_of? User %>
<%= select_f f, :default_location_id, (obj.admin? ? Location.all : obj.my_locations), :id, :title,
<%= select_f f, :default_location_id, (obj.admin? ? Location.all : obj.my_locations), :id, :title,
{ :include_blank => true }, { :label => _('Default on login') } %>
<% end %>
</div>
<% end %>
<% if show_organization_tab? %>
<div class="tab-pane" id="organizations">
<% organization_ids_with_select_all_for_ignore_type = Organization.taxonomy_ids_by_ignore_type(obj.class.to_s) %>
<%= organization_selects f, obj.used_or_selected_organization_ids,
{:disabled => obj.used_organization_ids }.merge!(select_options),
{:disabled => obj.used_organization_ids.concat(organization_ids_with_select_all_for_ignore_type)}.merge!(select_options),
{'data-descendants' => obj.children_of_selected_organization_ids.to_json,
'data-useds' => obj.used_organization_ids.to_json }.merge!(html_options[:organization]) %>
'data-useds' => obj.used_organization_ids.to_json,
'data-used-all' => organization_ids_with_select_all_for_ignore_type.to_json }.merge!(html_options[:organization]) %>
<% if obj.kind_of? User %>
<%= select_f f, :default_organization_id,(obj.admin? ? Organization.all : obj.my_organizations), :id, :title,
{ :include_blank => true }, { :label => _('Default on login') } %>
{ :include_blank => true }, { :label => _('Default on login') } %>
<% end %>
</div>
<% end %>

<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class UpdateTaxonomiesWithSelectedIgnoreTypesObjects < ActiveRecord::Migration[4.2]
def up
say "This Migration will take time for updating organization & location records with all objects of selected ignore_types."
Rake::Task['taxonomy:update_taxonomy'].invoke
end

def down
end
end
18 changes: 18 additions & 0 deletions lib/tasks/taxonomy.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace :taxonomy do
desc <<-END_DESC
This task will update organization & location records with ignore_types.
END_DESC
task :update_taxonomy => :environment do
User.as_anonymous_admin do
Organization.unscoped.each do |org|
success = org.save
puts "Failed to save Organization #{org.id}- #{org.errors.full_messages.inspect}" unless success
end

Location.unscoped.each do |loc|
success = loc.save
puts "Failed to save Location #{loc.id}- #{loc.errors.full_messages.inspect}" unless success
end
end
end
end
20 changes: 20 additions & 0 deletions test/models/taxonomy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,24 @@ class TaxonomyTest < ActiveSupport::TestCase
location.save
assert_match /expecting organizations/, location.errors.messages[:organizations].first
end

test "#taxonomy_ids_by_ignore_type should return ids of taxonomy_type for which 'Select All' option is checked for a resource." do
FactoryBot.create(:organization, :ignore_types => [ 'User' ])
user = FactoryBot.create(:user, :id => 20)
organization_ids_with_select_all = Organization.taxonomy_ids_by_ignore_type(user.class.to_s)
assert_not organization_ids_with_select_all.empty?
assert_equal organization_ids_with_select_all.count, 1
end

test "#add_references_for_selected_ignore_types assigns all objects of selected ignore_types to taxonomy" do
user = FactoryBot.create(:user, :login => 'foo_test_u1')
org1 = FactoryBot.create(:organization, :ignore_types => ['User'])
assert_includes org1.user_ids, user.id
end

test "taxonomy created with ignore_type Domain, this taxonomy will get assign to all new domains created after it" do
org1 = FactoryBot.create(:organization, :ignore_types => ['Domain'], :name => "Test_OrgA")
domain1 = FactoryBot.create(:domain, :name => 'test-domain.com')
assert_includes domain1.organization_ids, org1.id
end
end

0 comments on commit d74da02

Please sign in to comment.