klastera 1.3 → 1.4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 999d5c067d080b90f56c82a36cbeb286e08000bf09af922845bdd67df023d16c
4
- data.tar.gz: e4e105b33d55b228cc4d515e0167586b7b962218860d78593a29f6a71b436712
3
+ metadata.gz: a400414db24740d7888bd0627051715a8b81c6d6f481e15b3819958b043a984f
4
+ data.tar.gz: 782575cb6ffd02a17dc9318878bc9b3b678e98760feb936038f18dd73dd99326
5
5
  SHA512:
6
- metadata.gz: 6902637aaf11219cff0b3ed2f9dcdf1f86c74d2e6c43211d683c559177da39498220d5fca6b987418b936a89c6b08dde5d49a0014d0b86d1fe3c258612912f92
7
- data.tar.gz: 1ec8aad0c6793c426c01969bcdb9f04deed692b06499b0726ee047f13932fda3fe5046067432604947cf85e68662269569d61c482971b8855595a92dc87d1d29
6
+ metadata.gz: c5fe5cd793facd2af1ed367485daaac3822b7f2dda768d90d5aa92e5bc2a296860585d220076de1277e4bc0b5716daa4c560f9d93cc1464ff0839fb48cfa4ba8
7
+ data.tar.gz: 9c01bd79e3df23f2e514ed9aab6b7e0c544a74bde7e2e88f98e18977d8e20833815e05ea0b85d36f9e72818ffe3edf34ee8c9ced19f5d81de7b4876f22a70e98
@@ -8,4 +8,39 @@
8
8
  background: none;
9
9
  }
10
10
  }
11
- }
11
+ }
12
+
13
+ #cluster-remote-table {
14
+ .cluster-id { width: 70px; }
15
+ .cluster-order { width: 70px; }
16
+ .cluster-color { width: 70px; }
17
+ .odd .colorless { color: #F9F9F9 }
18
+ .even .colorless { color: #FFF }
19
+ tr:hover .colorless { color: #444; opacity: 0.2;}
20
+ }
21
+
22
+ #cluster-remote-form {
23
+ h4.blank-modal-title {
24
+ margin: 0;
25
+ line-height: 28px;
26
+ letter-spacing: 0.05rem;
27
+ font-size: 21px;
28
+ font-weight: 400;
29
+ color: #666;
30
+ }
31
+ }
32
+
33
+
34
+ ul.klastera-option-help {
35
+ padding-left: 20px;
36
+ list-style-type: disc;
37
+ }
38
+
39
+ ul.klastera-option-help li {
40
+ margin-bottom: 10px;
41
+ padding-bottom: 5px;
42
+ border-bottom: 1px solid #DDD;
43
+ }
44
+
45
+ ul.klastera-option-help li b { color: #8A8A8A; font-weight: 400; }
46
+ ul.klastera-option-help li b:first-child { color: #2E5F9B; font-weight: bold; }
@@ -33,13 +33,11 @@ module Klastera
33
33
 
34
34
  def destroy
35
35
  new_cluster_id = params.require(:transfer).permit(:new_cluster_id).values.first rescue nil
36
- @transfer = Transfer.new(
37
- current_cluster: @cluster,
38
- new_cluster_id: new_cluster_id
39
- )
36
+ @transfer = Transfer.new( current_cluster: @cluster, new_cluster_id: new_cluster_id )
40
37
  if @transfer.valid?
41
- @transfer.to!(::Cluster.related_entities.map{|re|re.constantize})
42
- @cluster.destroy
38
+ if @transfer.apply!
39
+ @cluster.destroy
40
+ end
43
41
  set_clusters
44
42
  end
45
43
  end
@@ -8,10 +8,13 @@ module Klastera::Concerns::Cluster
8
8
 
9
9
  attr_reader :last_record
10
10
 
11
- MODES = [ :required_suborganization, :optional_suborganization, :optional_filter ].freeze
11
+ REQUIRED_MODE = :required_suborganization.freeze
12
+ OPTIONAL_MODE = :optional_suborganization.freeze
13
+ MODES = [ REQUIRED_MODE, OPTIONAL_MODE ].freeze
12
14
 
13
15
  belongs_to :organization, class_name: Klastera.organization_class
14
16
  has_many :cluster_users
17
+ has_many :cluster_entities, dependent: :destroy
15
18
 
16
19
  scope :of, -> (organization,except_ids=[]) {
17
20
  _scope = where(organization: organization)
@@ -42,7 +45,7 @@ module Klastera::Concerns::Cluster
42
45
  end
43
46
 
44
47
  def has_related_entities_using_it?
45
- ::Cluster.total_records_assign_to(self) > 0
48
+ self.cluster_entities.size > 0
46
49
  end
47
50
 
48
51
  def can_transfer_and_destroy?
@@ -55,29 +58,10 @@ module Klastera::Concerns::Cluster
55
58
  end
56
59
 
57
60
  def display_name_nid
58
- "#{name} (#{nid})"
61
+ "#{name} (#{nid==Klastera::UNCLUSTERED_ENTITY ? I18n.t("klastera.#{ Klastera::UNCLUSTERED_ENTITY}") : nid})"
59
62
  end
60
63
  end
61
64
 
62
65
  module ClassMethods
63
- def related_entities(attr_needed: :class_name, macro: :has_many)
64
- ::Cluster.reflections.map do |association_name, reflection|
65
- reflection.send(attr_needed) if reflection.macro == macro
66
- end.compact
67
- end
68
-
69
- def total_records_assign_to(cluster_instance)
70
- related_entities(attr_needed: :name).inject(0) do |total, association_name|
71
- entity = cluster_instance.send(association_name)
72
- if entity.respond_to?(:cluster_id)
73
- total+=entity.where(cluster_id: cluster_instance.id).count
74
- end
75
- total
76
- end
77
- end
78
-
79
- def modes_as_strings
80
- ::Cluster::MODES.map{|m|m.to_s}
81
- end
82
66
  end
83
67
  end
@@ -1,7 +1,6 @@
1
1
  module Klastera::Concerns::Clusterizable
2
2
  extend ActiveSupport::Concern
3
3
  included do
4
- attr_accessor :lonely_cluster
5
4
  belongs_to :cluster
6
5
 
7
6
  has_many :cluster_entities, as: :entity, class_name: Klastera::ClusterEntity
@@ -11,6 +10,8 @@ module Klastera::Concerns::Clusterizable
11
10
  validate :at_least_one_cluster_entity, if: proc { self.organization.required_suborganization_mode? }
12
11
  validate :uniqueness_of_cluster_entity_record
13
12
 
13
+ scope :includes_cluster, -> { includes(cluster_entities: :cluster) }
14
+
14
15
  def at_least_one_cluster_entity
15
16
  if cluster_entities.length == 0 || cluster_entities.reject{|cluster_entity| cluster_entity._destroy == true}.empty?
16
17
  return errors.add(:cluster_entities, I18n.t('klastera.messages.at_least_one_cluster_entity'))
@@ -43,7 +44,7 @@ module Klastera::Concerns::Clusterizable
43
44
 
44
45
  module ClassMethods
45
46
  def cluster_entity_params
46
- [ :cluster_id, :lonely_cluster, { cluster_entities_attributes: [:id, :cluster_id, :_destroy] } ]
47
+ [ :cluster_id, { cluster_entities_attributes: [:id, :cluster_id, :_destroy] } ]
47
48
  end
48
49
  end
49
50
  end
@@ -4,7 +4,7 @@ module Klastera::Concerns::Organization
4
4
 
5
5
  has_many :clusters
6
6
 
7
- validates :use_cluster_as, inclusion: { in: ::Cluster.modes_as_strings , message: I18n.t('klastera.clusters.wrong_option') }, if: proc{ use_cluster_as.present? }
7
+ validates :use_cluster_as, inclusion: { in: ::Cluster::MODES.map{|m|m.to_s}, message: I18n.t('klastera.clusters.wrong_option') }, if: proc{ use_cluster_as.present? }
8
8
 
9
9
  # Return a symbol version of use_cluster_as value
10
10
  def cluster_mode
@@ -22,19 +22,11 @@ module Klastera::Concerns::Organization
22
22
  end
23
23
 
24
24
  def required_suborganization_mode?
25
- cluster_mode == ::Cluster::MODES.first
25
+ cluster_mode == ::Cluster::REQUIRED_MODE
26
26
  end
27
27
 
28
28
  def optional_suborganization_mode?
29
- cluster_mode == ::Cluster::MODES.second
30
- end
31
-
32
- def optional_filter_mode?
33
- cluster_mode == ::Cluster::MODES.third
34
- end
35
-
36
- def optional_mode?
37
- optional_suborganization_mode? || optional_filter_mode?
29
+ cluster_mode == ::Cluster::OPTIONAL_MODE
38
30
  end
39
31
  end
40
32
 
@@ -7,20 +7,14 @@ module Klastera::Concerns::Transfer
7
7
  include ActiveModel::Model
8
8
  include ActiveModel::Validations::Callbacks
9
9
 
10
- attr_accessor :current_cluster, :new_cluster_id, :entities_transfered
10
+ attr_accessor :current_cluster, :new_cluster_id
11
11
 
12
12
  validates :current_cluster, presence: true
13
13
  validates :new_cluster_id, presence: true, if: proc { self.required_transfer? }
14
14
 
15
15
  validate do
16
16
  new_cluster = ::Cluster.find(self.new_cluster_id.to_i) rescue nil
17
- #
18
- # In my time, to_a? didnt work
19
- # current_cluster.class returned:
20
- # Cluster(id: integer, name: string, nid: text, organization_id: integer, created_at: datetime, updated_at: datetime, color: string)
21
- #
22
- # If you see this, please fixe it. Thanks
23
- #
17
+
24
18
  if current_cluster.class.name != 'Cluster' || current_cluster.try(:is_the_last_record_in_required_suborganization_mode?)
25
19
  errors.add(:current_cluster, I18n.t('klastera.messages.current_cluster.cant_transfer'))
26
20
  elsif self.required_transfer? && new_cluster_id.present? && new_cluster.nil?
@@ -33,22 +27,19 @@ module Klastera::Concerns::Transfer
33
27
  end
34
28
  end
35
29
 
36
- ##
37
- #
38
- #
39
- def to!(related_entities)
40
- self.entities_transfered = 0
41
- related_entities.each do |entity|
42
- if entity.is_a?(Class) && entity.respond_to?(:cluster_id)
43
- self.entities_transfered += entity.where(
44
- cluster_id: self.current_cluster.id
45
- ).update_all(cluster_id: self.new_cluster_id)
46
- end
47
- end
48
- end
49
-
50
30
  def required_transfer?
51
31
  self.current_cluster.required_transfer? && self.current_cluster.has_related_entities_using_it?
52
32
  end
33
+
34
+ ##
35
+ # A returned boolean is expected. It should always be true even nothing is
36
+ # transfered, and it only will return false if creation fails.
37
+ #
38
+ def apply!
39
+ Klastera::ClusterEntity.create(current_cluster.cluster_entities.map{ |relation|
40
+ next if self.new_cluster_id.blank?
41
+ { cluster_id: self.new_cluster_id, entity_id: relation.entity_id, entity_type: relation.entity_type }
42
+ }.compact)
43
+ end
53
44
  end
54
45
  end
@@ -21,10 +21,6 @@ module Klastera::Concerns::User
21
21
  def is_a_cluster_admin?; self.cluster_role == CLUSTER_ADMIN; end
22
22
  def is_a_cluster_user?; self.cluster_role == CLUSTER_USER; end
23
23
 
24
- def is_not_a_cluster_root?; ! self.is_a_cluster_root?; end
25
- def is_not_a_cluster_admin?; ! self.is_a_cluster_admin?; end
26
- def is_not_a_cluster_user?; ! self.is_a_cluster_user?; end
27
-
28
24
  def need_cluster_assignation?
29
25
  self.try(:organization).try(:required_suborganization_mode?) && self.is_a_cluster_user?
30
26
  end
@@ -33,6 +29,13 @@ module Klastera::Concerns::User
33
29
  self.organization.is_in_cluster_mode? && ( self.is_a_cluster_admin? || self.is_a_cluster_root? )
34
30
  end
35
31
 
32
+ #
33
+ # It tells if this user should see what they cluster role or organization dictates
34
+ # Adminers and users from show cluster modes skip cluster clause
35
+ def cannot_skip_cluster_clause?
36
+ ! ( self.is_a_cluster_admin? || self.is_a_cluster_root? || ( self.organization.optional_suborganization_mode? && self.cluster_users.blank? ) )
37
+ end
38
+
36
39
  #
37
40
  # We will try to create a cluster_user record whether
38
41
  # the organization is using force mode (if cluster_id isn't present, it will raise a validation error)
@@ -3,16 +3,16 @@
3
3
 
4
4
  <div class="<%=classes_for_remote_modal_body()%>">
5
5
  <div class="row">
6
- <div class="col-xs-12">
6
+ <div class="col-xs-6">
7
7
  <%= f.input :name, as: :string, label: t('klastera.cluster_name') %>
8
8
  </div>
9
- <div class="col-xs-12">
9
+ <div class="col-xs-6">
10
10
  <%= f.input :nid, as: :string, label: t('klastera.cluster_nid') %>
11
11
  </div>
12
- <div class="col-xs-12">
12
+ <div class="col-xs-6">
13
13
  <%= f.input :color, as: :string, label: t('klastera.cluster_color'), input_html: { class: 'color-picker'} %>
14
14
  </div>
15
- <div class="col-xs-12">
15
+ <div class="col-xs-6">
16
16
  <%= f.input :order, as: :string, label: t('klastera.cluster_order') %>
17
17
  </div>
18
18
  </div>
@@ -2,8 +2,8 @@
2
2
  <%= simple_form_for( @transfer, url: cluster_path(@cluster), method: :delete, remote: true, html: { autocomplete: :off, class: 'destroy-form slim-form-field' } ) do |f| %>
3
3
  <%=render 'layouts/remote_form/header', o: @cluster, title: title, fa: :qrcode %>
4
4
  <div class="<%=classes_for_remote_modal_body()%>">
5
- <h4 class="text-center">Esta acción es irreversible.<br/>¿Está seguro?</h4>
6
- <% if ::Cluster.total_records_assign_to(@cluster) > 0 %>
5
+ <h4 class="blank-modal-title text-center">Esta acción es irreversible<br/>¿Está seguro?</h4>
6
+ <% if @cluster.cluster_entities.size > 0 %>
7
7
  <br />
8
8
  <div class="row">
9
9
  <% if can_transfer_and_destroy %>
@@ -1,4 +1,4 @@
1
- <table class="table table-striped table-hover table-condensed table-bordered datatable">
1
+ <table class="table table-striped table-hover table-condensed table-bordered datatable table">
2
2
  <thead>
3
3
  <tr>
4
4
  <th class="text-center cluster-id"><%=t('klastera.cluster_id')%></th>
@@ -6,7 +6,7 @@
6
6
  <th class="text-center cluster-color"><%=t('klastera.cluster_color')%></th>
7
7
  <th class="text-center cluster-name"><%=t('klastera.cluster_name')%></th>
8
8
  <th class="text-center cluster-nid"><%=t('klastera.cluster_nid')%></th>
9
- <th class="cogs-actions four-icons"><span class="fa fa-cogs"></span></th>
9
+ <th class="cogs-actions two-icons"><span class="fa fa-cogs"></span></th>
10
10
  </tr>
11
11
  </thead>
12
12
  <tbody>
@@ -19,7 +19,7 @@
19
19
  </td>
20
20
  <td class="text-center cluster-name"><%= cluster.name %></td>
21
21
  <td class="text-center cluster-nid"><%= cluster.nid %></td>
22
- <td class="cogs-actions four-icons">
22
+ <td class="cogs-actions two-icons">
23
23
  <%= link_to edit_cluster_path(cluster), class:'btn btn-primary btn-xs', title: t('shared.actions.edit'), "data-toggle": "modal", "data-target": "#remote-modal-block" do %>
24
24
  <span class="fa fa-pencil-square-o"></span>
25
25
  <% end %>
@@ -30,12 +30,4 @@
30
30
  </tr>
31
31
  <% end %>
32
32
  </tbody>
33
- </table>
34
- <style type="text/css">
35
- .cluster-id { width: 70px; }
36
- .cluster-order { width: 70px; }
37
- .cluster-color { width: 70px; }
38
- .odd .colorless { color: #F9F9F9 }
39
- .even .colorless { color: #FFF }
40
- tr:hover .colorless { color: #F5F5F5 }
41
- </style>
33
+ </table>
@@ -18,4 +18,4 @@
18
18
 
19
19
  </div>
20
20
 
21
- <%= render 'layouts/remote_form/modal'%>
21
+ <%= render 'layouts/remote_form/modal', width: 'md' %>
@@ -1,4 +1,4 @@
1
- <%= simple_form_for(@filter, url: url, remote: true) do |f| %>
1
+ <%= simple_form_for(@cluster_filter, url: url, remote: true) do |f| %>
2
2
 
3
3
  <div class="inline-buttons-block top">
4
4
  <%= button_tag(t('klastera.actions.filter'), class: 'btn btn-primary btn-sm float-right', data: { disable_with: "<i class='fa fa-spinner spin'></i>" }) %>
@@ -7,7 +7,7 @@
7
7
  <%=yield(f) if block_given? %>
8
8
 
9
9
  <% if cluster_organization.is_in_cluster_mode? %>
10
- <% cluster_collection = cluster_of_my_own.map{|c|[c.name,(c.id||c.nid)]} %>
10
+ <% cluster_collection = cluster_list.map{|c|[c.name,(c.id||c.nid)]} %>
11
11
  <% if cluster_collection.size > 1 %>
12
12
  <div class="inline-label-control-block">
13
13
  <%= f.input :cluster_id, collection: cluster_collection, prompt: t('klastera.clusters.all'), label: false, wrapper: false %>
@@ -1,8 +1,27 @@
1
- <% user_cluster_roles = user_cluster_roles.nil? ? User::CLUSTER_ROLES : user_cluster_roles %>
2
- <% other_visibility_reason = other_visibility_reason.nil? ? false : other_visibility_reason %>
1
+ <%
2
+ user_cluster_roles = user_cluster_roles.nil? ? User::CLUSTER_ROLES : user_cluster_roles
3
+ other_visibility_reason = other_visibility_reason.nil? ? false : other_visibility_reason
4
+ if @user.try(:organization).present?
5
+ normal_user_help = @user.try(:organization).optional_suborganization_mode? ? :optional : :required
6
+ else
7
+ normal_user_help = :new
8
+ end
9
+ %>
3
10
  <% if @user.try(:organization).try(:is_in_cluster_mode?) || other_visibility_reason %>
4
11
  <div class="form-group">
5
12
  <%= f.label t('klastera.cluster_role') %>
13
+ <span
14
+ class="klastera-user-cluster-rol"
15
+ data-title="<%= t('klastera.help.popover.title')%>"
16
+ data-toggle="popover"
17
+ data-content="<%=
18
+ t('klastera.help.popover.content.cluster_role',
19
+ m1: t('klastera.help.roles.description', r: t('klastera.roles.admin'), d: t('klastera.help.roles.admin')),
20
+ m2: t('klastera.help.roles.description', r: t('klastera.roles.user'), d: t("klastera.help.roles.user.#{normal_user_help}"))
21
+ )%>"
22
+ >
23
+ <%= fa_icon 'info-circle', class: 'btn-link' %>
24
+ </span>
6
25
  <%= f.select :cluster_role, user_cluster_roles.map{|r|[t("klastera.roles.#{r}"),r]}, { include_blank: true }, { class: 'form-control' } %>
7
26
  </div>
8
27
 
@@ -23,5 +42,7 @@
23
42
  });
24
43
  }
25
44
  });
45
+ var popoverTemplate = '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
46
+ $('.klastera-user-cluster-rol').popover({ template: popoverTemplate, html: true, trigger: 'click', });
26
47
  </script>
27
48
  <% end %>
@@ -4,26 +4,18 @@
4
4
  %>
5
5
  <% if cluster_organization.is_in_cluster_mode? || other_visibility_reason %>
6
6
  <%
7
- cluster_collection = @clusters_session || cluster_of_my_own
7
+ cluster_collection = @clusters_session || cluster_list
8
8
  label = t('klastera.cluster.title')
9
9
  %>
10
10
  <% if f.nil? %>
11
11
  <%= label_tag(:cluster_id, label, class: 'control-label') %>
12
12
  <%= select_tag(:cluster_id, options_from_collection_for_select(cluster_collection, 'id', 'display_name_nid'), class: 'form-control', prompt: 'Seleccione') %>
13
13
  <% elsif f.is_a?(SimpleForm::FormBuilder) %>
14
- <% if cluster_collection.size == 1 %>
15
- <%= f.input :lonely_cluster, as: :hidden, html_input: { value: true } %>
16
- <% else %>
17
- <%= f.input :cluster_id, collection: cluster_collection.map{|c|[c.name,c.id]}, label: label %>
18
- <% end %>
14
+ <%= f.input :cluster_id, collection: cluster_collection.map{|c|[c.name,c.id]}, label: label %>
19
15
  <% else %>
20
- <% if cluster_collection.size == 1 %>
21
- <%= f.hidden_field :lonely_cluster, value: true %>
22
- <% else %>
23
- <div class="form-group">
24
- <%= f.label label %>
25
- <%= f.select :cluster_id, cluster_collection.map{|c|[c.name,c.id]}, { include_blank: true }, { class: 'form-control' } %>
26
- </div>
27
- <% end %>
16
+ <div class="form-group">
17
+ <%= f.label label %>
18
+ <%= f.select :cluster_id, cluster_collection.map{|c|[c.name,c.id]}, { include_blank: true }, { class: 'form-control' } %>
19
+ </div>
28
20
  <% end %>
29
21
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <% if cluster_organization.is_in_cluster_mode? %>
2
- <% @clusters_session = cluster_of_my_own %>
2
+ <% @clusters_session = cluster_list(false) %>
3
3
  <div class="col-xs-12">
4
4
  <% if hide_title||=false %>
5
5
  <div class="form-group file required <%=f.object.class.name.parameterize%>_cluster_entity<%=' has-error' if f.object.errors.has_key?(:cluster_entities)%>">
@@ -5,10 +5,9 @@
5
5
  data-title="<%= t('klastera.help.popover.title')%>"
6
6
  data-toggle="popover"
7
7
  data-content="<%=
8
- t('klastera.help.popover.content',
8
+ t('klastera.help.popover.content.user_as_cluster_as',
9
9
  m1:t('klastera.help.required_suborganization', t: t('klastera.required_suborganization'), e: t('klastera.clusters.entities')),
10
- m2:t('klastera.help.optional_suborganization', t: t('klastera.optional_suborganization'), e: t('klastera.clusters.entities')),
11
- m3:t('klastera.help.optional_filter', t: t('klastera.optional_filter'), e: t('klastera.clusters.entities'))
10
+ m2:t('klastera.help.optional_suborganization', t: t('klastera.optional_suborganization'), e: t('klastera.clusters.entities'))
12
11
  )%>"
13
12
  >
14
13
  <%= fa_icon 'info-circle', class: 'btn-link' %>
@@ -19,20 +18,4 @@
19
18
  <script type="text/javascript">
20
19
  var popoverTemplate = '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
21
20
  $('.klastera-cluster-option').popover({ template: popoverTemplate, html: true, trigger: 'click', });
22
- </script>
23
-
24
- <style type="text/css">
25
- ul.klastera-option-help {
26
- padding-left: 20px;
27
- list-style-type: disc;
28
- }
29
-
30
- ul.klastera-option-help li {
31
- margin-bottom: 10px;
32
- padding-bottom: 5px;
33
- border-bottom: 1px solid #DDD;
34
- }
35
-
36
- ul.klastera-option-help li b { color: #8A8A8A; font-weight: 400; }
37
- ul.klastera-option-help li b:first-child { color: #2E5F9B; font-weight: bold; }
38
- </style>
21
+ </script>
@@ -9,24 +9,23 @@ es:
9
9
  you_need_add_at_least_one_cluster: Esta organización usa clusters en modo obligatorio. Asigne un cluster al usuario por favor.
10
10
  assign_cluster_role: Asignar role
11
11
  cluster_root_asignation:
12
- title: Asignación Routing Super Admin
13
- text: Este rol es sólo para usuarios Routing
12
+ title: Asignación Super Admin
13
+ text: Este rol es sólo para usuarios super admin
14
14
  roles:
15
- root: Routing Super Admin
16
- admin: Admin Cliente [ Ignora asignación, ve todos los clusters ]
17
- user: Usuario normal [ Ve sólo los clusters asignados ]
15
+ root: Super Admin
16
+ admin: Admin Cluster
17
+ user: Usuario Cluster
18
18
  using_cluster_as: Usando cluster como
19
19
  cluster_order: Orden
20
20
  cluster_id: ID
21
- cluster_role: Cluster rol
21
+ cluster_role: Rol de clusters
22
22
  cluster_name: Nombre
23
23
  cluster_nid: Código
24
24
  cluster_color: Color
25
25
  organization_name: Organización
26
- use_cluster_as: Activar y usar cluster como
27
- required_suborganization: Sub-organización Obligatorio
28
- optional_suborganization: Sub-organización Opcional
29
- optional_filter: Filtro opcional
26
+ use_cluster_as: Activar y usar cluster en modo
27
+ required_suborganization: Obligatorio
28
+ optional_suborganization: Opcional
30
29
  clusters:
31
30
  all: Todos
32
31
  title: Clusters
@@ -71,10 +70,18 @@ es:
71
70
  help:
72
71
  popover:
73
72
  title: Descripción de cada opción
74
- content: "<ul class='klastera-option-help'><li>%{m1}</li><li>%{m2}</li><li>%{m3}</li></ul>"
75
- required_suborganization: "<b>%{t}</b>: Obliga definir un cluster en las entidades %{e} ."
76
- optional_suborganization: "<b>%{t}</b>: Permite definir un cluster en las entidades %{e}."
77
- optional_filter: "<b>%{t}</b>: Permite definir un cluster en las entidades %{e} de forma opcional."
73
+ content:
74
+ user_as_cluster_as: "<ul class='klastera-option-help'><li>%{m1}</li><li>%{m2}</li></ul>"
75
+ required_suborganization: "<b>%{t}</b>: Obliga asignar un cluster a las entidades %{e} ."
76
+ optional_suborganization: "<b>%{t}</b>: Permite asociar un cluster a las entidades %{e}."
77
+ roles:
78
+ description: "<b>%{r}</b>: %{d}."
79
+ root: Ve todos los clusters y no necesita asignación
80
+ admin: Ve todos los clusters y no necesita asignación
81
+ user:
82
+ new: Usuario al que se le puede restringir la cosas que puede ver a través de su asignación con los clusters
83
+ required: Necesita que se le asigne uno o más clusters, de lo contrario no verá las entidades clusterizadas.
84
+ optional: Si no tiene clusters asignados verá todas la entidades CON y SIN clusters. Si se le asigna un cluster, verá las entidades de ese cluster + las entidades SIN cluster.
78
85
  activemodel:
79
86
  attributes:
80
87
  'klastera/transfer':
@@ -5,114 +5,11 @@ module Klastera
5
5
 
6
6
  extend ActiveSupport::Concern
7
7
 
8
- UNCLUSTERED_ENTITY = 'without_cluster'
9
- KLSTR_HELPERS = %i[
10
- cluster_user cluster_organization user_has_more_than_one_cluster cluster_scope cluster_of_my_own
11
- user_clusters_string_list set_collection_before_group_by_entity
12
- ]
8
+ UNCLUSTERED_ENTITY = 'without_cluster'.freeze
9
+ KLSTR_HELPERS = %i[ cluster_user cluster_organization cluster_list cluster_scope cluster_scope_through_of user_clusters_string_list ].freeze
13
10
 
14
11
  class << self
15
12
 
16
- ##
17
- #
18
- #
19
- def set_cluster_entities_attributes!(entity,array_cluster_ids)
20
- cluster_entities_attributes = {}
21
- entity_cluster_entities = entity.try(:cluster_entities) || []
22
- entity_clusters = entity_cluster_entities.map(&:cluster_id).sort
23
- array_cluster_ids = array_cluster_ids.map(&:id).sort
24
- if array_cluster_ids != entity_clusters
25
- now = DateTime.now
26
- entity_cluster_entities.each_with_index do |ec,index|
27
- remove_cluster = true
28
- if array_cluster_ids.include?(ec.cluster_id)
29
- remove_cluster = false
30
- array_cluster_ids.delete(ec.cluster_id)
31
- end
32
- cluster_entities_attributes[index] = { id: ec.id, cluster_id: ec.cluster_id, _destroy: remove_cluster }
33
- end
34
- array_cluster_ids.each_with_index do |cluster_id,index|
35
- cluster_entities_attributes[now.to_i+index] = { cluster_id: cluster_id, _destroy: false }
36
- end
37
- end
38
- cluster_entities_attributes
39
- end
40
-
41
- ##
42
- #
43
- #
44
- def set_collection_before_group_by_entity!(active_record_collection,entity_params,organization)
45
- entity_params_keys = [:entity_name,:entity_attribute,:entity_id,:entity_id_attribute,:unamed]
46
- entity_params[:entity_id] ||= nil #Ensures the entity_id attribute presence even if there is no filter
47
- entity_params = entity_params.slice(*entity_params_keys).values
48
- model_class = active_record_collection.model.base_class
49
- model_relations = model_class.reflections.keys
50
- if model_relations.include?(entity_params[0])
51
- entity_params << "#{entity_params[0]}_id".to_sym
52
- if entity_params[0] == 'cluster'
53
- entity_params << :unclustered if organization.is_in_cluster_mode?
54
- active_record_collection = Klastera.filter_clusterized!(
55
- active_record_collection,
56
- entity_params[2],
57
- organization
58
- )
59
- end
60
- yield( active_record_collection, entity_params_keys.zip(entity_params).to_h )
61
- end
62
- end
63
-
64
- #
65
- # In order to this work, the active_record_collection argument should be a cluster_scope! return.
66
- #
67
- def filter_clusterized!(active_record_collection,cluster_id,organization)
68
- if organization.is_in_cluster_mode? && cluster_id.present?
69
- # Ensures that an array of ids is used in the statement
70
- cluster_array = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
71
- # If we receive a UNCLUSTERED_ENTITY request, it means that we should filter by entities without clusters.
72
- # The optional_mode?(show/use) condition add to this method an ambivalent use,
73
- # where you can filter a unique value or multiple including NULL ones.
74
- cluster_array << nil if cluster_array.delete(UNCLUSTERED_ENTITY) || organization.optional_mode?
75
- active_record_collection = active_record_collection.where(cluster_entities: { cluster_id: cluster_array.uniq })
76
- # You should use a block with clusterable data only
77
- yield(active_record_collection) if block_given?
78
- end
79
- active_record_collection
80
- end
81
-
82
- ##
83
- # If the cluster mode is not active, this returns a scope filtered by clusters of its organization/users/cluster
84
- #
85
- def cluster_scope!(scope,user,organization,cluster_id=nil)
86
- scope_klass = scope_class(scope).where(organization_id: organization)
87
- if organization.is_in_cluster_mode?
88
-
89
- if cluster_id.present?
90
- # Ensures that an array of id is used in the statement
91
- cluster_ids = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
92
- else
93
- clusters = cluster_of!(user,organization)
94
- cluster_ids = clusters.map(&:id).compact
95
- end
96
-
97
- scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*")
98
-
99
- if organization.required_suborganization_mode?
100
- scope_klass = scope_klass.joins(:cluster_entities).where( cluster_entities: { cluster_id: cluster_ids } )
101
- else
102
- or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
103
- scope_klass = scope_klass.joins("
104
- LEFT OUTER JOIN cluster_entities
105
- ON entity_id = #{scope.table_name}.id
106
- AND entity_type = '#{scope}'
107
- ").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
108
- end
109
-
110
- # Provisional fix to avoid unresolved SQL clashes with the main application due to DISTINCT ON clause
111
- scope_klass = scope_class(scope).eager_load(:cluster_entities).where(id: scope_klass.map(&:id), organization_id: organization)
112
- end
113
- scope_klass
114
- end
115
-
116
13
  ##
117
14
  # TODO:
118
15
  # Implement a validation to ensure that
@@ -125,16 +22,19 @@ module Klastera
125
22
  end
126
23
 
127
24
  ##
128
- # Returns a Cluster::ActiveRecord_Relation if the organization is using the cluster mode.
129
- # Use this only to get current_user/organization clusters
25
+ # Returns which clusters a user can see avoiding unnecessary queries if the cluster restraint doesn't apply
130
26
  #
131
- def cluster_of!(user,organization)
132
- # A cluster user with role Root or Admin retrieve every cluster of its organizations
133
- and_this_user = user.can_admin_clusters? ? nil : user
134
- # We weill return a cluster active record collection that can be filtered
135
- active_record_collection = ::ClusterUser.clusters_of(organization,and_this_user)
136
- # Add a empty cluster instance to handle models without a cluster assignation.
137
- if organization.optional_mode? # For use and show modes only
27
+ def cluster_list!(organization,user,include_unclustered=true)
28
+ # Only the cluster mode on and the mandatory user-cluster relation will use a join clause to get the clusters list
29
+ if organization.is_in_cluster_mode? && user.cannot_skip_cluster_clause?
30
+ active_record_collection = ::ClusterUser.clusters_of(organization,user)
31
+ else
32
+ active_record_collection = ::Cluster.where({ organization_id: organization })
33
+ end
34
+
35
+ active_record_collection = active_record_collection.order(order: :asc)
36
+
37
+ if include_unclustered && organization.optional_suborganization_mode? # For show and use modes only
138
38
  active_record_collection << ::Cluster.new({nid: UNCLUSTERED_ENTITY, name: I18n.t("klastera.#{UNCLUSTERED_ENTITY}")})
139
39
  end
140
40
  active_record_collection
@@ -155,74 +55,124 @@ module Klastera
155
55
  end
156
56
 
157
57
  ##
158
- # cluster_of! needs a user and a organization. that why we perfomed this logic here
58
+ # cluster_list! needs a user and a organization. that why we perfomed this logic here
159
59
  #
160
60
  def user_clusters_string_list!(user,organization,cluster_entities,separator,attribute=:name)
161
- @clusters_session ||= self.cluster_of!(user,organization)
61
+ @clusters_session ||= Klastera.cluster_list!(organization,user)
162
62
  self.entity_clusters_string_list!(cluster_entities, separator, attribute, @clusters_session.map(&:id))
163
63
  end
164
- end
165
64
 
166
- def cluster_user
167
- current_user
168
- end
65
+ #
66
+ # We will try to avoid cluster clause except when:
67
+ # 1.- cluster mode is active
68
+ # AND
69
+ # 2a.- cluster_filter is present (someone wants to filter by cluster)
70
+ # OR
71
+ # 2b.- the current user has some limitations and must checks they cluster relation
72
+ # - User is having clusters in optional_suborganization mode
73
+ # - User IS NOT having clusters in required_suborganization mode
74
+ #
75
+ # For the other hand, with force_cluster_clause we can skip the previous logic if
76
+ # cluster_filter_id is present when the optional_suborganization mode is on. BUT!
77
+ # Be aware that if the cluster_filter is not present, the value of force_cluster_clause
78
+ # will be overridden by the returned value of cannot_skip_cluster_clause? method.
79
+ #
80
+ def cluster_scope!(scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
81
+ scope_klass = scope_class(scope_klass)
82
+ if organization.is_in_cluster_mode? && ( cluster_filter.present? || force_cluster_clause = user.cannot_skip_cluster_clause? ) # yes, this is an assignation
83
+ cluster_ids = []
84
+ # Set another variable as array to get the cluster id(s)
85
+ if cluster_filter.present?
86
+ cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
87
+ elsif force_cluster_clause
88
+ cluster_ids = ::ClusterUser.clusters_of(organization,user).map(&:id).sort
89
+ end
90
+ # We will avoid the query unless cluster_ids is having values OR force_cluster_clause is set (see method description)
91
+ if cluster_ids.present? || force_cluster_clause
92
+ scope_klass = scope_klass.eager_load(:organization,cluster_entities: :cluster)
93
+ # We add the unclustered if the value of cluster_filter have the special without_cluster string or as method description says
94
+ if cluster_ids.delete(UNCLUSTERED_ENTITY) || ( force_cluster_clause && organization.optional_suborganization_mode? )
95
+ cluster_ids << nil
96
+ end
97
+ scope_klass = scope_klass.where( cluster_entities: { cluster_id: cluster_ids } )
98
+ end
99
+ end
100
+ scope_klass.where(organization_id: organization)
101
+ end
169
102
 
170
- def cluster_organization
171
- current_organization
172
- end
103
+
104
+ # Filter non-clustered entity through a clusterized one
105
+ #
106
+ def cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
107
+ unclusterized_scope = scope_class(scope_klass)
173
108
 
174
- def user_has_more_than_one_cluster
175
- @clusters_session ||= cluster_of_my_own
176
- @clusters_session.size > 1
177
- end
109
+ if organization.is_in_cluster_mode? && ( force_cluster_clause || user.cannot_skip_cluster_clause? )
110
+ unclusterized_scope = unclusterized_scope.joins(relation => :cluster_entities)
111
+ end
112
+
113
+ if scope_klass.respond_to?(:organization)
114
+ unclusterized_scope = unclusterized_scope.where(organization_id: organization)
115
+ end
178
116
 
179
- def set_the_lonely_cluster
180
- form_model = @form_record ? model_name_from_record_or_class(@form_record).param_key : params[:controller].singularize
181
- parameters = params.require( form_model ) rescue nil
182
- lonely_cluster = parameters.blank? ? false : parameters.permit( :lonely_cluster ).present?
183
- if lonely_cluster
184
- params[form_model][:cluster_id] = cluster_of_my_own.first.try(:id)
117
+ unclusterized_scope.where("#{relation}_id" => cluster_scope!(cluster_entity_klass, user, organization, cluster_filter, force_cluster_clause))
118
+ end
119
+
120
+
121
+ # Returns an array with a clusterized scoped result and its grouped version
122
+ #
123
+ def group_by_cluster_scope!(scope_klass, user, organization, cluster_filter=[], scope_scopes=[])
124
+ cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
125
+ kluster_scope = cluster_scope!(scope_klass, user, organization, cluster_ids, organization.optional_suborganization_mode? )
126
+ scope_scopes.each do |tuple_scope|
127
+ scope_name, scope_arg = tuple_scope
128
+ kluster_scope = scope_arg.present? ? kluster_scope.send(scope_name,scope_arg) : kluster_scope.send(scope_name)
129
+ end
130
+ [
131
+ kluster_scope,
132
+ kluster_scope.order(:cluster_id).group_by do |e|
133
+ e.cluster.present? ? e.cluster.name : I18n.t("klastera.#{UNCLUSTERED_ENTITY}")
134
+ end
135
+ ]
185
136
  end
186
137
  end
187
138
 
188
- def set_cluster_filter
189
- @filter = ::ClusterFilter.new(cluster_filter_params)
139
+ ##################################################################################################################################################
140
+
141
+ def cluster_user
142
+ current_user
190
143
  end
191
144
 
192
- def cluster_filter_params
193
- parameters = params.require(:cluster_filter) rescue nil
194
- return {} if parameters.blank?
195
- parameters.permit(*cluster_filter_permit_params)
145
+ def cluster_organization
146
+ current_organization
196
147
  end
197
148
 
198
- def cluster_filter_permit_params
199
- [ :cluster_id ].concat( ::ClusterFilter.attributes )
149
+ def set_cluster_filter
150
+ cluster_filter_params = params.require(:cluster_filter) rescue {}
151
+ @cluster_filter = ::ClusterFilter.new(
152
+ cluster_filter_params.present? ? cluster_filter_params.permit(
153
+ [ :cluster_id ].concat( ::ClusterFilter.attributes )
154
+ ) : {}
155
+ )
200
156
  end
201
157
 
202
- def filter_clusterized(active_record_collection,cluster_id)
203
- Klastera.filter_clusterized!(active_record_collection, cluster_id, cluster_organization)
158
+ def cluster_list(include_unclustered=true)
159
+ Klastera.cluster_list!(cluster_organization, cluster_user, include_unclustered)
204
160
  end
205
161
 
206
- def cluster_scope(scope,cluster_id=nil)
207
- Klastera.cluster_scope!(scope, cluster_user, cluster_organization, cluster_id)
162
+ def cluster_scope(scope_klass, cluster_filter=nil, force_cluster_clause=false)
163
+ Klastera.cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
208
164
  end
209
165
 
210
- def cluster_of_my_own
211
- Klastera.cluster_of!(cluster_user, cluster_organization)
166
+ def cluster_scope_through_of(relation, cluster_entity_klass, scope_klass, cluster_filter=nil, force_cluster_clause=false)
167
+ Klastera.cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
212
168
  end
213
169
 
214
- def user_clusters_string_list(object_entity,separator,attribute=:name)
215
- Klastera.user_clusters_string_list!(
216
- cluster_user,
217
- cluster_organization,
218
- object_entity.try(:cluster_entities),
219
- separator,
220
- attribute
221
- )
170
+ def group_by_cluster_scope(scope_klass, cluster_filter=[], scope_scopes=[])
171
+ Klastera.group_by_cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, scope_scopes)
222
172
  end
223
173
 
224
- def set_collection_before_group_by_entity(active_record_collection,entity_params,&block)
225
- Klastera.set_collection_before_group_by_entity!(active_record_collection, params, cluster_organization, &block)
174
+ def user_clusters_string_list(object_entity, separator, attribute=:name)
175
+ Klastera.user_clusters_string_list!(cluster_user, cluster_organization, object_entity.try(:cluster_entities), separator, attribute)
226
176
  end
227
177
 
228
178
  included do
@@ -235,6 +185,5 @@ module Klastera
235
185
  hide_action("#{helper}=")
236
186
  end
237
187
  end
238
- before_action :set_the_lonely_cluster, only: %i[ create update ]
239
188
  end
240
189
  end
@@ -1,3 +1,3 @@
1
1
  module Klastera
2
- VERSION = "1.3"
3
- end
2
+ VERSION = "1.4.0.1"
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klastera
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.3'
4
+ version: 1.4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gino Barahona
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-16 00:00:00.000000000 Z
11
+ date: 2020-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -127,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
127
  - !ruby/object:Gem::Version
128
128
  version: '0'
129
129
  requirements: []
130
- rubygems_version: 3.1.2
130
+ rubygems_version: 3.0.8
131
131
  signing_key:
132
132
  specification_version: 4
133
133
  summary: Clusterization Engine