klastera 1.5.3 → 1.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +37 -0
- data/app/assets/javascripts/klastera/application.js +13 -0
- data/app/assets/javascripts/klastera/clusters.js +2 -0
- data/app/assets/stylesheets/klastera/clusters.scss +46 -0
- data/app/controllers/klastera/application_controller.rb +29 -0
- data/app/controllers/klastera/clusters_controller.rb +60 -0
- data/app/helpers/klastera/application_helper.rb +40 -0
- data/app/models/klastera/cluster.rb +5 -0
- data/app/models/klastera/cluster_entity.rb +5 -0
- data/app/models/klastera/cluster_filter.rb +5 -0
- data/app/models/klastera/cluster_user.rb +5 -0
- data/app/models/klastera/concerns/cluster.rb +67 -0
- data/app/models/klastera/concerns/cluster_entity.rb +18 -0
- data/app/models/klastera/concerns/cluster_filter.rb +22 -0
- data/app/models/klastera/concerns/cluster_user.rb +41 -0
- data/app/models/klastera/concerns/clusterizable.rb +57 -0
- data/app/models/klastera/concerns/organization.rb +49 -0
- data/app/models/klastera/concerns/transfer.rb +45 -0
- data/app/models/klastera/concerns/user.rb +77 -0
- data/app/models/klastera/transfer.rb +5 -0
- data/app/views/klastera/clusters/_filter.html.erb +0 -0
- data/app/views/klastera/clusters/_form.html.erb +22 -0
- data/app/views/klastera/clusters/_form_transfer.html.erb +34 -0
- data/app/views/klastera/clusters/_table.html.erb +33 -0
- data/app/views/klastera/clusters/create.js.erb +1 -0
- data/app/views/klastera/clusters/destroy.js.erb +6 -0
- data/app/views/klastera/clusters/edit.html.erb +10 -0
- data/app/views/klastera/clusters/index.html.erb +21 -0
- data/app/views/klastera/clusters/new.html.erb +10 -0
- data/app/views/klastera/clusters/transfer.html.erb +10 -0
- data/app/views/klastera/clusters/update.js.erb +1 -0
- data/app/views/layouts/klastera/_cluster_entity_fields.html.erb +10 -0
- data/app/views/layouts/klastera/_cluster_filter.html.erb +23 -0
- data/app/views/layouts/klastera/_cluster_role.html.erb +48 -0
- data/app/views/layouts/klastera/_cluster_selector.html.erb +21 -0
- data/app/views/layouts/klastera/_cluster_user_fields.html.erb +10 -0
- data/app/views/layouts/klastera/_nested_cluster_entity.html.erb +43 -0
- data/app/views/layouts/klastera/_nested_cluster_user.html.erb +22 -0
- data/app/views/layouts/klastera/_options.html.erb +21 -0
- data/config/locales/es.yml +96 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20200324203929_create_klastera_clusters.rb +12 -0
- data/db/migrate/20200326111219_add_cluster_options_to_organizations.rb +6 -0
- data/db/migrate/20200330010551_create_klastera_cluster_users.rb +9 -0
- data/db/migrate/20200330221601_add_order_field_to_clusters.rb +5 -0
- data/db/migrate/20200518142609_create_klastera_cluster_entities.rb +8 -0
- data/db/migrate/20200908180057_add_cluster_config_to_organization.rb +5 -0
- data/db/migrate/20220602222332_add_unique_index_to_cluster_entities.rb +5 -0
- data/lib/klastera/engine.rb +5 -0
- data/lib/klastera/version.rb +3 -0
- data/lib/klastera.rb +236 -0
- data/lib/tasks/klastera_tasks.rake +32 -0
- data/test/controllers/klastera/clusters_controller_test.rb +52 -0
- data/test/fixtures/klastera/cluster_users.yml +9 -0
- data/test/fixtures/klastera/clusters.yml +11 -0
- data/test/integration/navigation_test.rb +8 -0
- data/test/klastera_test.rb +7 -0
- data/test/models/klastera/cluster_test.rb +9 -0
- data/test/models/klastera/cluster_user_test.rb +9 -0
- data/test/test_helper.rb +21 -0
- metadata +77 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b05f393fbe67e0822de3526d5b46dc7081a79226f45b5fe63985424d73331742
|
4
|
+
data.tar.gz: '09623883ce4911b3cd0df6382c558c7d82f080eb504f3e0dae4e74262acd8df7'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9589942522f4442bf44304cb735a022819bb40f0885c077d77289791caf79f266a1f258d3db7f14fc242d0d80858a8c82027b0314f2faf5ef156e45bcf1c89a7
|
7
|
+
data.tar.gz: 3b5d0a9505bd241d3374077ab01d8ddcd2ca83d3f4787763de1658c56f362680b3cb2350e44992a2041834181b7b379c5ad506d4ba83278d140ba6a7d6a775da
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2020 Gino Barahona
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Klastera'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,46 @@
|
|
1
|
+
.nested-cluster-user,
|
2
|
+
.nested-cluster-entity {
|
3
|
+
.panel {
|
4
|
+
margin: 0;
|
5
|
+
border-bottom: 0;
|
6
|
+
.panel-heading {
|
7
|
+
padding: 10px 12px 10px 15px;
|
8
|
+
background: none;
|
9
|
+
}
|
10
|
+
}
|
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; }
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Klastera
|
2
|
+
class ApplicationController < ::ApplicationController
|
3
|
+
protect_from_forgery with: :exception
|
4
|
+
layout :disable_in_xhr_requests
|
5
|
+
before_action :set_xhr_render_session
|
6
|
+
before_action :check_access_to_cluster_admin
|
7
|
+
|
8
|
+
def check_access_to_cluster_admin
|
9
|
+
unless cluster_user.can_admin_clusters?
|
10
|
+
redirect_to main_app.root_path, flash: { alert: t('klastera.messages.access_cluster_admin') }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
if respond_to?(:skip_after_action)
|
15
|
+
skip_after_action :verify_authorized
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def disable_in_xhr_requests
|
20
|
+
request.xhr? ? false : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_xhr_render_session
|
24
|
+
return unless request.get?
|
25
|
+
# request.xhr? return nil when this is not a XHR request, but we need a boolean
|
26
|
+
session[:xhr_render] = request.xhr?.present?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_dependency "klastera/application_controller"
|
2
|
+
|
3
|
+
module Klastera
|
4
|
+
class ClustersController < ApplicationController
|
5
|
+
before_action :set_cluster, only: %i[ edit update destroy transfer ]
|
6
|
+
before_action :set_clusters, only: %i[ index ]
|
7
|
+
|
8
|
+
def index; end
|
9
|
+
|
10
|
+
def new
|
11
|
+
@cluster = ::Cluster.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def edit; end
|
15
|
+
|
16
|
+
def transfer
|
17
|
+
@transfer = Transfer.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@cluster = ::Cluster.new(cluster_params)
|
22
|
+
if @cluster.save
|
23
|
+
@cluster = ::Cluster.new
|
24
|
+
set_clusters
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def update
|
29
|
+
if @cluster.update(cluster_params)
|
30
|
+
set_clusters
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def destroy
|
35
|
+
new_cluster_id = params.require(:transfer).permit(:new_cluster_id).values.first rescue nil
|
36
|
+
@transfer = Transfer.new( current_cluster: @cluster, new_cluster_id: new_cluster_id )
|
37
|
+
if @transfer.valid?
|
38
|
+
if @transfer.apply!
|
39
|
+
@cluster.destroy
|
40
|
+
end
|
41
|
+
set_clusters
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def set_cluster
|
47
|
+
@cluster = ::Cluster.find(params[:id])
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_clusters
|
51
|
+
@clusters = cluster_organization.clusters
|
52
|
+
end
|
53
|
+
|
54
|
+
def cluster_params
|
55
|
+
parameters = params.require(:cluster).permit(:nid, :name, :color, :order)
|
56
|
+
parameters[:organization_id]=cluster_organization.id
|
57
|
+
parameters
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Klastera
|
2
|
+
module ApplicationHelper
|
3
|
+
def ac_errors_as_html_list(errors)
|
4
|
+
array_as_html_list(
|
5
|
+
errors.messages.inject([]) do |array,(attr,errors_array)|
|
6
|
+
array.concat(errors_array)
|
7
|
+
array
|
8
|
+
end
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def array_as_html_list(array)
|
13
|
+
"<ul style='padding:0;'><li>#{array.join("</li><li>")}</li></ul>".html_safe
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
|
18
|
+
if main_app.respond_to?(method)
|
19
|
+
main_app.send(method, *args)
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to?(method, include_priv=false)
|
29
|
+
if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
|
30
|
+
if main_app.respond_to?(method,include_priv)
|
31
|
+
true
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Klastera::Concerns::Cluster
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
# 'included do' causes the included code to be evaluated in the
|
4
|
+
# context where it is included (cluster.rb), rather than being
|
5
|
+
# executed in the module's context (klastera/concerns/models/cluster).
|
6
|
+
included do
|
7
|
+
self.table_name = 'clusters'
|
8
|
+
|
9
|
+
attr_reader :last_record
|
10
|
+
|
11
|
+
REQUIRED_MODE = :required_suborganization.freeze
|
12
|
+
OPTIONAL_MODE = :optional_suborganization.freeze
|
13
|
+
MODES = [ REQUIRED_MODE, OPTIONAL_MODE ].freeze
|
14
|
+
|
15
|
+
belongs_to :organization, class_name: Klastera.organization_class
|
16
|
+
has_many :cluster_users
|
17
|
+
has_many :cluster_entities, dependent: :destroy
|
18
|
+
|
19
|
+
scope :of, -> (organization,except_ids=[]) {
|
20
|
+
_scope = where(organization: organization)
|
21
|
+
_scope = _scope.where.not(id: except_ids) unless except_ids.blank?
|
22
|
+
_scope
|
23
|
+
}
|
24
|
+
|
25
|
+
validates :name, presence: true
|
26
|
+
validates :nid, presence: true
|
27
|
+
validates :organization_id, presence: true
|
28
|
+
|
29
|
+
validates_uniqueness_of :nid, scope: [:nid, :organization_id]
|
30
|
+
|
31
|
+
before_destroy do |record|
|
32
|
+
self.can_transfer_and_destroy?
|
33
|
+
end
|
34
|
+
|
35
|
+
def siblings
|
36
|
+
::Cluster.of(self.organization,[self.id])
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_the_last_record_in_required_suborganization_mode?
|
40
|
+
self.organization.required_suborganization_mode? && self.siblings.blank?
|
41
|
+
end
|
42
|
+
|
43
|
+
def required_transfer?
|
44
|
+
self.organization.required_suborganization_mode?
|
45
|
+
end
|
46
|
+
|
47
|
+
def has_related_entities_using_it?
|
48
|
+
self.cluster_entities.size > 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def can_transfer_and_destroy?
|
52
|
+
can_destroy = true
|
53
|
+
if is_the_last_record_in_required_suborganization_mode?
|
54
|
+
errors.add(:last_record, I18n.t('klastera.messages.cant_delete_the_last_record_in_required_suborganization_mode'))
|
55
|
+
can_destroy = false
|
56
|
+
end
|
57
|
+
can_destroy
|
58
|
+
end
|
59
|
+
|
60
|
+
def display_name_nid
|
61
|
+
"#{name} (#{nid==Klastera::UNCLUSTERED_ENTITY ? I18n.t("klastera.#{ Klastera::UNCLUSTERED_ENTITY}") : nid})"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module ClassMethods
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Klastera::Concerns::ClusterEntity
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
self.table_name = 'cluster_entities'
|
6
|
+
belongs_to :entity, polymorphic: true
|
7
|
+
belongs_to :cluster, class_name: "::Cluster"
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def left_join_sources_of(scope_klass)
|
12
|
+
scope_klass_arel_table = scope_klass.arel_table
|
13
|
+
scope_klass_arel_table.join(arel_table, Arel::Nodes::OuterJoin).on(
|
14
|
+
scope_klass_arel_table[:id].eq(arel_table[:entity_id]), arel_table[:entity_type].eq(scope_klass.name)
|
15
|
+
).join_sources
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Klastera::Concerns::ClusterFilter
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
include ActiveModel::Model
|
6
|
+
include ActiveModel::Validations::Callbacks
|
7
|
+
|
8
|
+
attr_accessor :cluster_id
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def attr_accessor(*vars)
|
13
|
+
@attributes ||= []
|
14
|
+
@attributes.concat vars
|
15
|
+
super(*vars)
|
16
|
+
end
|
17
|
+
|
18
|
+
def attributes
|
19
|
+
@attributes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Klastera::Concerns::ClusterUser
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
self.table_name = 'cluster_users'
|
6
|
+
belongs_to :user
|
7
|
+
belongs_to :cluster
|
8
|
+
validates :cluster_id, presence: true
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
##
|
13
|
+
# Return a Cluster::ActiveRecord_Relation of organization (and) user
|
14
|
+
#
|
15
|
+
def clusters_of(organization,and_user=nil)
|
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) ).order(order: :asc)
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Return a User::ActiveRecord_Relation of organization (and) user
|
22
|
+
#
|
23
|
+
def users_of(organization)
|
24
|
+
::User.eager_load(:cluster_users).where(users: { organization_id: organization } )
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Return a hash of users and its clusters
|
29
|
+
#
|
30
|
+
def users_hash_of(organization)
|
31
|
+
users = {}
|
32
|
+
rows = self.users_of(organization).pluck("users.id AS user_id","cluster_users.cluster_id").uniq
|
33
|
+
rows.each do |row|
|
34
|
+
user_id = row.first
|
35
|
+
users[user_id] ||= []
|
36
|
+
users[user_id] << row.last
|
37
|
+
end
|
38
|
+
users
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Klastera::Concerns::Clusterizable
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class MutipleClustersOperationError < StandardError; end
|
6
|
+
|
7
|
+
belongs_to :cluster
|
8
|
+
|
9
|
+
has_many :cluster_entities, as: :entity, class_name: "Klastera::ClusterEntity"
|
10
|
+
accepts_nested_attributes_for :cluster_entities, reject_if: :all_blank, allow_destroy: true
|
11
|
+
|
12
|
+
validates :cluster_id, presence: true, if: -> { cluster_id.present? && organization.present? && organization.required_suborganization_mode? }
|
13
|
+
validate :at_least_one_cluster_entity, if: -> { organization.present? && organization.required_suborganization_mode? }
|
14
|
+
validate :uniqueness_of_cluster_entity_record
|
15
|
+
|
16
|
+
scope :includes_cluster, -> { includes(cluster_entities: :cluster) }
|
17
|
+
|
18
|
+
def at_least_one_cluster_entity
|
19
|
+
if cluster_entities.length == 0 || cluster_entities.reject{|cluster_entity| cluster_entity._destroy == true}.empty?
|
20
|
+
return errors.add(:cluster_entities, I18n.t('klastera.messages.at_least_one_cluster_entity'))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def allow_multiple_clusters?(default=:one)
|
25
|
+
organization.present? && organization.cluster_cardinality_of(self.class, default: default) == :many
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# This is a legacy method and we don't recommend using it.
|
30
|
+
# Implement directly Klastera.entity_clusters_string_list instead of this method.
|
31
|
+
# TODO: In order to deprecate it, you will need to perform some changes in the main app
|
32
|
+
##
|
33
|
+
def clusters_string_separated_by(separator,attribute=:name)
|
34
|
+
Klastera.entity_clusters_string_list!(
|
35
|
+
cluster_entities, separator, attribute
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_one_cluster_entity
|
40
|
+
if cluster_entities.length > 1
|
41
|
+
return errors.add(:cluster_entities, I18n.t('klastera.messages.has_one_cluster_entity'))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def uniqueness_of_cluster_entity_record
|
46
|
+
if cluster_entities.map(&:cluster_id).uniq.size != cluster_entities.size
|
47
|
+
return errors.add(:cluster_entities, I18n.t('klastera.messages.duplicated_cluster_entity') )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
def cluster_entity_params
|
54
|
+
[ :cluster_id, { cluster_entities_attributes: [:id, :cluster_id, :_destroy] } ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Klastera::Concerns::Organization
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
included do
|
4
|
+
serialize :cluster_config
|
5
|
+
has_many :clusters
|
6
|
+
|
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
|
+
|
9
|
+
# Return a symbol version of use_cluster_as value
|
10
|
+
def cluster_mode
|
11
|
+
self.use_cluster_as.to_s.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def cluster_cardinality_of(entity, default: :many)
|
15
|
+
if cluster_config.is_a?(Hash)
|
16
|
+
entities_cardinality = cluster_config[:entities]&.[](:cardinality)||{}
|
17
|
+
entity = entity.to_s.to_sym
|
18
|
+
if entities_cardinality.has_key?(entity)
|
19
|
+
default = entities_cardinality[entity]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
default.to_s.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Return a boolean if one of three of options was set in organization
|
27
|
+
# As useless option you can retrieve the value passing false as argument.
|
28
|
+
#
|
29
|
+
##
|
30
|
+
def is_in_cluster_mode?(return_the_mode=false)
|
31
|
+
is_active = cluster_mode.present? && ::Cluster::MODES.include?(cluster_mode)
|
32
|
+
( return_the_mode ? ( is_active ? cluster_mode : false ) : is_active )
|
33
|
+
end
|
34
|
+
|
35
|
+
def required_suborganization_mode?
|
36
|
+
cluster_mode == ::Cluster::REQUIRED_MODE
|
37
|
+
end
|
38
|
+
|
39
|
+
def optional_suborganization_mode?
|
40
|
+
cluster_mode == ::Cluster::OPTIONAL_MODE
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
def cluster_params
|
46
|
+
[ :use_cluster_as ]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Klastera::Concerns::Transfer
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveModel::Model
|
8
|
+
include ActiveModel::Validations::Callbacks
|
9
|
+
|
10
|
+
attr_accessor :current_cluster, :new_cluster_id
|
11
|
+
|
12
|
+
validates :current_cluster, presence: true
|
13
|
+
validates :new_cluster_id, presence: true, if: proc { self.required_transfer? }
|
14
|
+
|
15
|
+
validate do
|
16
|
+
new_cluster = ::Cluster.find(self.new_cluster_id.to_i) rescue nil
|
17
|
+
|
18
|
+
if current_cluster.class.name != 'Cluster' || current_cluster.try(:is_the_last_record_in_required_suborganization_mode?)
|
19
|
+
errors.add(:current_cluster, I18n.t('klastera.messages.current_cluster.cant_transfer'))
|
20
|
+
elsif self.required_transfer? && new_cluster_id.present? && new_cluster.nil?
|
21
|
+
errors.add(:new_cluster_id, I18n.t('klastera.messages.new_cluster_id.nil'))
|
22
|
+
elsif current_cluster.id == new_cluster.try(:id)
|
23
|
+
errors.add(:new_cluster_id, I18n.t('klastera.messages.new_cluster_id.same'))
|
24
|
+
elsif new_cluster.present? && current_cluster.organization_id != new_cluster.organization_id
|
25
|
+
# Clusters from another organization do not exist
|
26
|
+
errors.add(:new_cluster_id, I18n.t('klastera.messages.new_cluster_id.nil'))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def required_transfer?
|
31
|
+
self.current_cluster.required_transfer? && self.current_cluster.has_related_entities_using_it?
|
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
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Klastera::Concerns::User
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
|
6
|
+
CLUSTER_ROOT = 'root'.freeze
|
7
|
+
CLUSTER_ADMIN = 'admin'.freeze
|
8
|
+
CLUSTER_USER = 'user'.freeze
|
9
|
+
|
10
|
+
CLUSTER_ROLES = [ CLUSTER_ROOT, CLUSTER_ADMIN, CLUSTER_USER ].freeze
|
11
|
+
|
12
|
+
has_many :cluster_users, inverse_of: :user, class_name: ::ClusterUser.to_s
|
13
|
+
accepts_nested_attributes_for :cluster_users, reject_if: :all_blank, allow_destroy: true
|
14
|
+
validates :cluster_role, presence: true, if: -> { organization.present? && organization.is_in_cluster_mode? }
|
15
|
+
validates :cluster_role, inclusion: { in: CLUSTER_ROLES , message: I18n.t('klastera.clusters.wrong_option') }, if: -> { cluster_role.present? }
|
16
|
+
validate :at_least_one_role, if: -> { use_cluster? && try(:need_cluster_assignation?) }
|
17
|
+
|
18
|
+
def use_cluster?; cluster_role.present?; end
|
19
|
+
|
20
|
+
def is_a_cluster_root?; cluster_role == CLUSTER_ROOT; end
|
21
|
+
def is_a_cluster_admin?; cluster_role == CLUSTER_ADMIN; end
|
22
|
+
def is_a_cluster_user?; cluster_role == CLUSTER_USER; end
|
23
|
+
|
24
|
+
def need_cluster_assignation?
|
25
|
+
organization.present? && organization.required_suborganization_mode? && is_a_cluster_user?
|
26
|
+
end
|
27
|
+
|
28
|
+
def can_admin_clusters?
|
29
|
+
organization.present? && organization.is_in_cluster_mode? && ( is_a_cluster_admin? || is_a_cluster_root? )
|
30
|
+
end
|
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
|
+
! ( is_a_cluster_admin? || is_a_cluster_root? || ( organization.present? && organization.optional_suborganization_mode? && cluster_users.blank? ) )
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# We will try to create a cluster_user record whether
|
41
|
+
# the organization is using force mode (if cluster_id isn't present, it will raise a validation error)
|
42
|
+
# or
|
43
|
+
# cluster_id is present (on show/use mode organizations).
|
44
|
+
#
|
45
|
+
def try_to_clusterify!(role,cluster_ids)
|
46
|
+
if organization.present? && organization.is_in_cluster_mode?
|
47
|
+
self.cluster_role = role
|
48
|
+
if ( organization.present? && organization.required_suborganization_mode? ) || cluster_ids.present?
|
49
|
+
timestamp = DateTime.now.to_i
|
50
|
+
nested_attributes = {}
|
51
|
+
cluster_ids.each do |cluster_id|
|
52
|
+
nested_attributes["#{cluster_id}_#{timestamp}"] = {
|
53
|
+
cluster_id: cluster_id,
|
54
|
+
_destroy: false
|
55
|
+
}
|
56
|
+
end
|
57
|
+
self.cluster_users_attributes = nested_attributes
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def at_least_one_role
|
65
|
+
if cluster_users.none?
|
66
|
+
errors.add(:base, I18n.t('klastera.user.you_need_add_at_least_one_cluster'))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
module ClassMethods
|
73
|
+
def cluster_params
|
74
|
+
[ :cluster_role, { cluster_users_attributes: [:id, :cluster_id, :_destroy] } ]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|