klastera 1.3.3.1 → 1.4.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 +4 -4
- data/app/models/klastera/concerns/cluster.rb +4 -2
- data/app/models/klastera/concerns/cluster_user.rb +1 -1
- data/app/models/klastera/concerns/clusterizable.rb +3 -2
- data/app/models/klastera/concerns/organization.rb +2 -10
- data/app/models/klastera/concerns/user.rb +7 -4
- data/app/views/layouts/klastera/_cluster_filter.html.erb +2 -2
- data/app/views/layouts/klastera/_cluster_role.html.erb +23 -2
- data/app/views/layouts/klastera/_cluster_selector.html.erb +6 -14
- data/app/views/layouts/klastera/_nested_cluster_entity.html.erb +1 -1
- data/app/views/layouts/klastera/_options.html.erb +2 -3
- data/config/locales/es.yml +21 -14
- data/lib/klastera.rb +147 -206
- data/lib/klastera/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f45dc24f8870c8dc07dc2e7702f6021b66b5c71b97529ca4aece12ce47399348
|
|
4
|
+
data.tar.gz: 1c8673aaef5fb0d562376e80cdf787e302601672e4a4285066f3be4da04fb925
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c59493dadf8ac3a4b52a235f3a4f96c52cef9668ec215bf9f857bad90c72241ad8564fe11c86f3a212587fba37440c87230487ce10da9bdd09519be907a433ca
|
|
7
|
+
data.tar.gz: 838347a85ffdfb1144dad6cf83094ff11fa9a8d78398e70f382a65e51df31d80bc557b3022175e7d13a8ddcbd88567f5c8af1b8a7ca6056194f475ed5d57b12d
|
|
@@ -8,7 +8,9 @@ module Klastera::Concerns::Cluster
|
|
|
8
8
|
|
|
9
9
|
attr_reader :last_record
|
|
10
10
|
|
|
11
|
-
|
|
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
|
|
@@ -56,7 +58,7 @@ module Klastera::Concerns::Cluster
|
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def display_name_nid
|
|
59
|
-
"#{name} (#{nid})"
|
|
61
|
+
"#{name} (#{nid==Klastera::UNCLUSTERED_ENTITY ? I18n.t("klastera.#{ Klastera::UNCLUSTERED_ENTITY}") : nid})"
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
|
|
@@ -14,7 +14,7 @@ module Klastera::Concerns::ClusterUser
|
|
|
14
14
|
#
|
|
15
15
|
def clusters_of(organization,and_user=nil)
|
|
16
16
|
and_user_id = and_user.present? ? { users: { id: and_user } } : {}
|
|
17
|
-
::Cluster.eager_load(cluster_users: :user).where({ organization_id: organization }.merge(and_user_id) )
|
|
17
|
+
::Cluster.eager_load(cluster_users: :user).where({ organization_id: organization }.merge(and_user_id) ).order(order: :asc)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
##
|
|
@@ -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,
|
|
47
|
+
[ :cluster_id, { cluster_entities_attributes: [:id, :cluster_id, :_destroy] } ]
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
end
|
|
@@ -22,19 +22,11 @@ module Klastera::Concerns::Organization
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def required_suborganization_mode?
|
|
25
|
-
cluster_mode == ::Cluster::
|
|
25
|
+
cluster_mode == ::Cluster::REQUIRED_MODE
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def optional_suborganization_mode?
|
|
29
|
-
cluster_mode == ::Cluster::
|
|
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
|
|
|
@@ -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)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= simple_form_for(@
|
|
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 =
|
|
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
|
-
<%
|
|
2
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
<%= f.
|
|
22
|
-
|
|
23
|
-
|
|
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 =
|
|
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' %>
|
data/config/locales/es.yml
CHANGED
|
@@ -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
|
|
13
|
-
text: Este rol es sólo para usuarios
|
|
12
|
+
title: Asignación Super Admin
|
|
13
|
+
text: Este rol es sólo para usuarios super admin
|
|
14
14
|
roles:
|
|
15
|
-
root:
|
|
16
|
-
admin: Admin
|
|
17
|
-
user: Usuario
|
|
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:
|
|
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
|
|
27
|
-
required_suborganization:
|
|
28
|
-
optional_suborganization:
|
|
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:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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':
|
data/lib/klastera.rb
CHANGED
|
@@ -5,154 +5,12 @@ module Klastera
|
|
|
5
5
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
user_clusters_string_list set_collection_before_group_by_entity same_cluster_scope
|
|
12
|
-
cluster_scope_through_of
|
|
13
|
-
]
|
|
8
|
+
UNCLUSTERED_POSITION = 9999
|
|
9
|
+
UNCLUSTERED_ENTITY = 'without_cluster'.freeze
|
|
10
|
+
KLSTR_HELPERS = %i[ cluster_user cluster_organization cluster_list cluster_scope cluster_scope_through_of user_clusters_string_list cluster_scope_left_join ].freeze
|
|
14
11
|
|
|
15
12
|
class << self
|
|
16
13
|
|
|
17
|
-
##
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
def set_cluster_entities_attributes!(entity,array_cluster_ids)
|
|
21
|
-
cluster_entities_attributes = {}
|
|
22
|
-
entity_cluster_entities = entity.try(:cluster_entities) || []
|
|
23
|
-
entity_clusters = entity_cluster_entities.map(&:cluster_id).sort
|
|
24
|
-
array_cluster_ids = array_cluster_ids.map(&:id).sort
|
|
25
|
-
if array_cluster_ids != entity_clusters
|
|
26
|
-
now = DateTime.now
|
|
27
|
-
entity_cluster_entities.each_with_index do |ec,index|
|
|
28
|
-
remove_cluster = true
|
|
29
|
-
if array_cluster_ids.include?(ec.cluster_id)
|
|
30
|
-
remove_cluster = false
|
|
31
|
-
array_cluster_ids.delete(ec.cluster_id)
|
|
32
|
-
end
|
|
33
|
-
cluster_entities_attributes[index] = { id: ec.id, cluster_id: ec.cluster_id, _destroy: remove_cluster }
|
|
34
|
-
end
|
|
35
|
-
array_cluster_ids.each_with_index do |cluster_id,index|
|
|
36
|
-
cluster_entities_attributes[now.to_i+index] = { cluster_id: cluster_id, _destroy: false }
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
cluster_entities_attributes
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
##
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
def set_collection_before_group_by_entity!(active_record_collection,entity_params,organization,include_nil_by_rule=true)
|
|
46
|
-
entity_params_keys = [:entity_name,:entity_attribute,:entity_id,:entity_id_attribute,:unamed]
|
|
47
|
-
entity_params[:entity_id] ||= nil #Ensures the entity_id attribute presence even if there is no filter
|
|
48
|
-
entity_params = entity_params.slice(*entity_params_keys).values
|
|
49
|
-
model_class = active_record_collection.model.base_class
|
|
50
|
-
model_relations = model_class.reflections.keys
|
|
51
|
-
if model_relations.include?(entity_params[0])
|
|
52
|
-
entity_params << "#{entity_params[0]}_id".to_sym
|
|
53
|
-
if entity_params[0] == 'cluster'
|
|
54
|
-
entity_params << I18n.t("klastera.#{UNCLUSTERED_ENTITY}") if organization.is_in_cluster_mode?
|
|
55
|
-
active_record_collection = Klastera.filter_clusterized!(
|
|
56
|
-
active_record_collection,
|
|
57
|
-
entity_params[2],
|
|
58
|
-
organization,
|
|
59
|
-
include_nil_by_rule
|
|
60
|
-
)
|
|
61
|
-
end
|
|
62
|
-
yield( active_record_collection, entity_params_keys.zip(entity_params).to_h )
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
# In order to this work, the active_record_collection argument should be a cluster_scope! return.
|
|
68
|
-
#
|
|
69
|
-
def filter_clusterized!(active_record_collection,cluster_id,organization,include_nil_by_rule=true)
|
|
70
|
-
if organization.is_in_cluster_mode? && cluster_id.present?
|
|
71
|
-
# Ensures that an array of ids is used in the statement
|
|
72
|
-
cluster_array = cluster_id.is_a?(Array) ? cluster_id.dup : [cluster_id]
|
|
73
|
-
# If we receive a UNCLUSTERED_ENTITY request, it means that we should filter by entities without clusters.
|
|
74
|
-
# The optional_mode?(show/use) condition add to this method an ambivalent use,
|
|
75
|
-
# where you can filter a unique value or multiple including NULL ones.
|
|
76
|
-
cluster_array << nil if include_nil_by_rule || cluster_id == UNCLUSTERED_ENTITY && ( cluster_array.delete(UNCLUSTERED_ENTITY) || organization.optional_mode? )
|
|
77
|
-
active_record_collection = active_record_collection.where(cluster_entities: { cluster_id: cluster_array.uniq })
|
|
78
|
-
# You should use a block with clusterable data only
|
|
79
|
-
yield(active_record_collection) if block_given?
|
|
80
|
-
end
|
|
81
|
-
active_record_collection
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
##
|
|
85
|
-
# If the cluster mode is not active, this returns a scope filtered by clusters of its organization/users/cluster
|
|
86
|
-
#
|
|
87
|
-
def cluster_scope!(scope,user,organization,cluster_id=nil)
|
|
88
|
-
scope_klass = scope_class(scope).where(organization_id: organization)
|
|
89
|
-
if organization.is_in_cluster_mode?
|
|
90
|
-
|
|
91
|
-
if cluster_id.present?
|
|
92
|
-
# Ensures that an array of id is used in the statement
|
|
93
|
-
cluster_ids = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
|
|
94
|
-
else
|
|
95
|
-
clusters = cluster_of!(organization,user)
|
|
96
|
-
cluster_ids = clusters.map(&:id).compact
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
scope_klass = scope_klass.select("DISTINCT ON (#{scope.table_name}.id) #{scope.table_name}.id, #{scope.table_name}.*")
|
|
100
|
-
|
|
101
|
-
if organization.required_suborganization_mode?
|
|
102
|
-
scope_klass = scope_klass.joins(:cluster_entities).where( cluster_entities: { cluster_id: cluster_ids } )
|
|
103
|
-
else
|
|
104
|
-
or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
|
|
105
|
-
scope_klass = scope_klass.joins("
|
|
106
|
-
LEFT OUTER JOIN cluster_entities
|
|
107
|
-
ON entity_id = #{scope.table_name}.id
|
|
108
|
-
AND entity_type = '#{scope}'
|
|
109
|
-
").where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Provisional fix to avoid unresolved SQL clashes with the main application due to DISTINCT ON clause
|
|
113
|
-
scope_klass = scope_class(scope).eager_load(:cluster_entities).where(id: scope_klass.map(&:id), organization_id: organization)
|
|
114
|
-
end
|
|
115
|
-
scope_klass
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
# Optimized version of cluster_scope!
|
|
120
|
-
#
|
|
121
|
-
def same_cluster_scope!(scope,user,organization,cluster_id=nil)
|
|
122
|
-
scope_klass = scope_class(scope)
|
|
123
|
-
if organization.is_in_cluster_mode?
|
|
124
|
-
scope_klass = scope_klass.eager_load(:organization,cluster_entities: :cluster)
|
|
125
|
-
|
|
126
|
-
if cluster_id.present?
|
|
127
|
-
cluster_ids = cluster_id.is_a?(Array) ? cluster_id : [cluster_id]
|
|
128
|
-
else
|
|
129
|
-
clusters = cluster_of!(organization,user)
|
|
130
|
-
cluster_ids = clusters.map(&:id).compact.sort
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
if organization.required_suborganization_mode?# || cluster_id # If cluster_id is passed NULL clusters are expected, rigth?
|
|
134
|
-
scope_klass = scope_klass.where( cluster_entities: { cluster_id: cluster_ids } )
|
|
135
|
-
else
|
|
136
|
-
or_these_cluster_ids = cluster_ids.present? ? " OR cluster_entities.cluster_id IN (#{cluster_ids.join(",")})" : ""
|
|
137
|
-
scope_klass = scope_klass.where("cluster_entities.id IS NULL#{or_these_cluster_ids}")
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
scope_klass.where(organization_id: organization)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
#
|
|
145
|
-
# Filter non-clustered entity through a clusterized one
|
|
146
|
-
#
|
|
147
|
-
def cluster_scope_through_of!(relation,cluster_entity_klass,scope_klass,user,organization,cluster_id=nil)
|
|
148
|
-
scope_class(scope_klass).joins(relation => :cluster_entities).where(
|
|
149
|
-
organization_id: organization,
|
|
150
|
-
"#{relation}_id" => same_cluster_scope!(
|
|
151
|
-
cluster_entity_klass, user, organization, cluster_id
|
|
152
|
-
)
|
|
153
|
-
)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
14
|
##
|
|
157
15
|
# TODO:
|
|
158
16
|
# Implement a validation to ensure that
|
|
@@ -165,19 +23,22 @@ module Klastera
|
|
|
165
23
|
end
|
|
166
24
|
|
|
167
25
|
##
|
|
168
|
-
# Returns a
|
|
169
|
-
# Use this only to get current_user/organization clusters
|
|
26
|
+
# Returns which clusters a user can see avoiding unnecessary queries if the cluster restraint doesn't apply
|
|
170
27
|
#
|
|
171
|
-
def
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
28
|
+
def cluster_list!(organization,user,include_unclustered=true)
|
|
29
|
+
# Only the cluster mode on and the mandatory user-cluster relation will use a join clause to get the clusters list
|
|
30
|
+
if organization.is_in_cluster_mode? && user.cannot_skip_cluster_clause?
|
|
31
|
+
active_record_collection = ::ClusterUser.clusters_of(organization,user)
|
|
32
|
+
else
|
|
33
|
+
active_record_collection = ::Cluster.where({ organization_id: organization }).order(order: :asc)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
active_record_collection = active_record_collection.order(order: :asc)
|
|
37
|
+
|
|
38
|
+
if include_unclustered && organization.optional_suborganization_mode? # For show and use modes only
|
|
39
|
+
active_record_collection.append(
|
|
40
|
+
::Cluster.new({nid: UNCLUSTERED_ENTITY, name: I18n.t("klastera.#{UNCLUSTERED_ENTITY}")}, order: UNCLUSTERED_POSITION )
|
|
41
|
+
)
|
|
181
42
|
end
|
|
182
43
|
active_record_collection
|
|
183
44
|
end
|
|
@@ -197,82 +58,163 @@ module Klastera
|
|
|
197
58
|
end
|
|
198
59
|
|
|
199
60
|
##
|
|
200
|
-
#
|
|
61
|
+
# cluster_list! needs a user and a organization. that why we perfomed this logic here
|
|
201
62
|
#
|
|
202
63
|
def user_clusters_string_list!(user,organization,cluster_entities,separator,attribute=:name)
|
|
203
|
-
@clusters_session ||= Klastera.
|
|
64
|
+
@clusters_session ||= Klastera.cluster_list!(organization,user)
|
|
204
65
|
self.entity_clusters_string_list!(cluster_entities, separator, attribute, @clusters_session.map(&:id))
|
|
205
66
|
end
|
|
206
|
-
end
|
|
207
67
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
68
|
+
#
|
|
69
|
+
# We will try to avoid cluster clause except when:
|
|
70
|
+
# 1.- cluster mode is active
|
|
71
|
+
# AND
|
|
72
|
+
# 2a.- cluster_filter is present (someone wants to filter by cluster)
|
|
73
|
+
# OR
|
|
74
|
+
# 2b.- the current user has some limitations and must checks they cluster relation
|
|
75
|
+
# - User is having clusters in optional_suborganization mode
|
|
76
|
+
# - User IS NOT having clusters in required_suborganization mode
|
|
77
|
+
#
|
|
78
|
+
# For the other hand, with force_cluster_clause we can skip the previous logic if
|
|
79
|
+
# cluster_filter_id is present when the optional_suborganization mode is on. BUT!
|
|
80
|
+
# Be aware that if the cluster_filter is not present, the value of force_cluster_clause
|
|
81
|
+
# will be overridden by the returned value of cannot_skip_cluster_clause? method.
|
|
82
|
+
#
|
|
83
|
+
def cluster_scope!(scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
|
|
84
|
+
scope_klass = scope_class(scope_klass)
|
|
85
|
+
if organization.is_in_cluster_mode? && ( cluster_filter.present? || force_cluster_clause = user.cannot_skip_cluster_clause? ) # yes, this is an assignation
|
|
86
|
+
cluster_ids = []
|
|
87
|
+
# Set another variable as array to get the cluster id(s)
|
|
88
|
+
if cluster_filter.present?
|
|
89
|
+
cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
|
|
90
|
+
elsif force_cluster_clause
|
|
91
|
+
cluster_ids = ::ClusterUser.clusters_of(organization,user).map(&:id)
|
|
92
|
+
end
|
|
93
|
+
# We will avoid the query unless cluster_ids is having values OR force_cluster_clause is set (see method description)
|
|
94
|
+
if cluster_ids.present? || force_cluster_clause
|
|
95
|
+
scope_klass = scope_klass.eager_load(:organization,cluster_entities: :cluster)
|
|
96
|
+
# We add the unclustered if the value of cluster_filter have the special without_cluster string or as method description says
|
|
97
|
+
if cluster_ids.delete(UNCLUSTERED_ENTITY) || ( force_cluster_clause && organization.optional_suborganization_mode? )
|
|
98
|
+
cluster_ids << nil
|
|
99
|
+
end
|
|
100
|
+
scope_klass = scope_klass.where( cluster_entities: { cluster_id: cluster_ids } )
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
scope_klass.where(organization_id: organization)
|
|
104
|
+
end
|
|
211
105
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
106
|
+
#
|
|
107
|
+
# Filter non-clustered entity through a clusterized one
|
|
108
|
+
#
|
|
109
|
+
def cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, user, organization, cluster_filter=nil, force_cluster_clause=false)
|
|
110
|
+
unclusterized_scope = scope_class(scope_klass)
|
|
215
111
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
end
|
|
112
|
+
if organization.is_in_cluster_mode? && ( force_cluster_clause || user.cannot_skip_cluster_clause? )
|
|
113
|
+
unclusterized_scope = unclusterized_scope.joins(relation => :cluster_entities)
|
|
114
|
+
end
|
|
220
115
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
params[form_model][:cluster_id] = cluster_of_my_own.first.try(:id)
|
|
116
|
+
if scope_klass.respond_to?(:organization)
|
|
117
|
+
unclusterized_scope = unclusterized_scope.where(organization_id: organization)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
unclusterized_scope.where("#{relation}_id" => cluster_scope!(cluster_entity_klass, user, organization, cluster_filter, force_cluster_clause))
|
|
227
121
|
end
|
|
228
|
-
end
|
|
229
122
|
|
|
230
|
-
|
|
231
|
-
|
|
123
|
+
#
|
|
124
|
+
# Returns an array with a clusterized scoped result and its grouped version
|
|
125
|
+
#
|
|
126
|
+
def group_by_cluster_scope!(scope_klass, user, organization, cluster_filter=[], scope_scopes=[])
|
|
127
|
+
cluster_ids = cluster_filter.is_a?(Array) ? cluster_filter : [cluster_filter]
|
|
128
|
+
kluster_scope = cluster_scope!(scope_klass, user, organization, cluster_ids, organization.is_in_cluster_mode? )
|
|
129
|
+
|
|
130
|
+
scope_scopes.each do |tuple_scope|
|
|
131
|
+
scope_name, scope_arg = tuple_scope
|
|
132
|
+
kluster_scope = scope_arg.present? ? kluster_scope.send(scope_name,scope_arg) : kluster_scope.send(scope_name)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
group_by_block = ->(o) {
|
|
136
|
+
if organization.is_in_cluster_mode?
|
|
137
|
+
o.cluster.present? ? o.cluster.name : UNCLUSTERED_POSITION
|
|
138
|
+
else
|
|
139
|
+
I18n.t("klastera.group_by_cluster_scope.#{scope_klass.model_name.plural}")
|
|
140
|
+
end
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
grouped_cluster_scope = kluster_scope.group_by(&group_by_block).sort_by{|k,v|k.to_s}
|
|
144
|
+
|
|
145
|
+
grouped_cluster_scope.dup.each do |group|
|
|
146
|
+
if group.first == UNCLUSTERED_POSITION
|
|
147
|
+
grouped_cluster_scope.delete(group)
|
|
148
|
+
group[0] = I18n.t("klastera.#{UNCLUSTERED_ENTITY}")
|
|
149
|
+
grouped_cluster_scope.append(group)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
[ kluster_scope, grouped_cluster_scope ]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
#
|
|
157
|
+
# A helper that returns a CLUSTER SCOPE to build queries that need explicit LEFT OUTER JOIN clause,
|
|
158
|
+
# instead of the default INNER JOIN provide by ActiveRecord's joins method
|
|
159
|
+
#
|
|
160
|
+
def cluster_scope_left_join!(scope_klass,organization)
|
|
161
|
+
scope_klass_arel_table = scope_klass.arel_table
|
|
162
|
+
cluster_entities_arel_table = Klastera::ClusterEntity.arel_table
|
|
163
|
+
|
|
164
|
+
scope_klass_cluster_entities = scope_klass_arel_table.join(cluster_entities_arel_table, Arel::Nodes::OuterJoin).on(
|
|
165
|
+
scope_klass_arel_table[:id].eq(cluster_entities_arel_table[:entity_id]), cluster_entities_arel_table[:entity_type].eq(scope_klass.name)
|
|
166
|
+
).join_sources
|
|
167
|
+
|
|
168
|
+
cluster_arel_table = ::Cluster.arel_table
|
|
169
|
+
cluster_entities_cluster = cluster_entities_arel_table.join(cluster_arel_table, Arel::Nodes::OuterJoin).on(
|
|
170
|
+
cluster_entities_arel_table[:cluster_id].eq(cluster_arel_table[:id]),
|
|
171
|
+
).join_sources
|
|
172
|
+
|
|
173
|
+
scope_class(scope_klass).where(organization_id: organization).joins(scope_klass_cluster_entities).joins(cluster_entities_cluster)
|
|
174
|
+
end
|
|
232
175
|
end
|
|
233
176
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
177
|
+
##################################################################################################################################################
|
|
178
|
+
|
|
179
|
+
def cluster_user
|
|
180
|
+
current_user
|
|
238
181
|
end
|
|
239
182
|
|
|
240
|
-
def
|
|
241
|
-
|
|
183
|
+
def cluster_organization
|
|
184
|
+
current_organization
|
|
242
185
|
end
|
|
243
186
|
|
|
244
|
-
def
|
|
245
|
-
|
|
187
|
+
def set_cluster_filter
|
|
188
|
+
cluster_filter_params = params.require(:cluster_filter) rescue {}
|
|
189
|
+
@cluster_filter = ::ClusterFilter.new(
|
|
190
|
+
cluster_filter_params.present? ? cluster_filter_params.permit(
|
|
191
|
+
[ :cluster_id ].concat( ::ClusterFilter.attributes )
|
|
192
|
+
) : {}
|
|
193
|
+
)
|
|
246
194
|
end
|
|
247
195
|
|
|
248
|
-
def
|
|
249
|
-
Klastera.
|
|
196
|
+
def cluster_list(include_unclustered=true)
|
|
197
|
+
Klastera.cluster_list!(cluster_organization, cluster_user, include_unclustered)
|
|
250
198
|
end
|
|
251
199
|
|
|
252
|
-
def
|
|
253
|
-
Klastera.
|
|
200
|
+
def cluster_scope(scope_klass, cluster_filter=nil, force_cluster_clause=false)
|
|
201
|
+
Klastera.cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
|
|
254
202
|
end
|
|
255
203
|
|
|
256
|
-
def cluster_scope_through_of(relation,cluster_entity_klass,scope_klass,
|
|
257
|
-
Klastera.cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, cluster_user, cluster_organization,
|
|
204
|
+
def cluster_scope_through_of(relation, cluster_entity_klass, scope_klass, cluster_filter=nil, force_cluster_clause=false)
|
|
205
|
+
Klastera.cluster_scope_through_of!(relation, cluster_entity_klass, scope_klass, cluster_user, cluster_organization, cluster_filter, force_cluster_clause)
|
|
258
206
|
end
|
|
259
207
|
|
|
260
|
-
def
|
|
261
|
-
Klastera.
|
|
208
|
+
def group_by_cluster_scope(scope_klass, cluster_filter=[], scope_scopes=[])
|
|
209
|
+
Klastera.group_by_cluster_scope!(scope_klass, cluster_user, cluster_organization, cluster_filter, scope_scopes)
|
|
262
210
|
end
|
|
263
211
|
|
|
264
|
-
def user_clusters_string_list(object_entity,separator,attribute=:name)
|
|
265
|
-
Klastera.user_clusters_string_list!(
|
|
266
|
-
cluster_user,
|
|
267
|
-
cluster_organization,
|
|
268
|
-
object_entity.try(:cluster_entities),
|
|
269
|
-
separator,
|
|
270
|
-
attribute
|
|
271
|
-
)
|
|
212
|
+
def user_clusters_string_list(object_entity, separator, attribute=:name)
|
|
213
|
+
Klastera.user_clusters_string_list!(cluster_user, cluster_organization, object_entity.try(:cluster_entities), separator, attribute)
|
|
272
214
|
end
|
|
273
215
|
|
|
274
|
-
def
|
|
275
|
-
Klastera.
|
|
216
|
+
def cluster_scope_left_join(scope_klass)
|
|
217
|
+
Klastera.cluster_scope_left_join!(scope_klas,cluster_organization)
|
|
276
218
|
end
|
|
277
219
|
|
|
278
220
|
included do
|
|
@@ -285,6 +227,5 @@ module Klastera
|
|
|
285
227
|
hide_action("#{helper}=")
|
|
286
228
|
end
|
|
287
229
|
end
|
|
288
|
-
before_action :set_the_lonely_cluster, only: %i[ create update ]
|
|
289
230
|
end
|
|
290
231
|
end
|
data/lib/klastera/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.4.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-
|
|
11
|
+
date: 2020-08-05 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.
|
|
130
|
+
rubygems_version: 3.1.2
|
|
131
131
|
signing_key:
|
|
132
132
|
specification_version: 4
|
|
133
133
|
summary: Clusterization Engine
|