2019-05-08 20:53:34 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-06-23 21:19:08 +08:00
|
|
|
class Team < ApplicationRecord
|
2017-01-04 17:54:02 +08:00
|
|
|
include SearchableModel
|
2018-10-12 22:14:24 +08:00
|
|
|
include ViewableModel
|
2022-05-13 21:45:24 +08:00
|
|
|
include Assignable
|
|
|
|
include PermissionCheckableModel
|
2019-11-12 18:26:18 +08:00
|
|
|
include TinyMceImages
|
2017-01-04 17:54:02 +08:00
|
|
|
|
2016-02-12 23:52:43 +08:00
|
|
|
# Not really MVC-compliant, but we just use it for logger
|
|
|
|
# output in space_taken related functions
|
|
|
|
include ActionView::Helpers::NumberHelper
|
|
|
|
|
2023-07-03 21:54:11 +08:00
|
|
|
before_save -> { shareable_links.destroy_all }, if: -> { !shareable_links_enabled? }
|
2019-01-25 17:50:08 +08:00
|
|
|
after_create :generate_template_project
|
2022-08-08 16:06:00 +08:00
|
|
|
after_create :create_default_label_templates
|
2019-03-06 17:34:04 +08:00
|
|
|
scope :teams_select, -> { select(:id, :name).order(name: :asc) }
|
2020-01-30 20:42:02 +08:00
|
|
|
scope :ordered, -> { order('LOWER(name)') }
|
2019-01-25 17:50:08 +08:00
|
|
|
|
2016-09-21 21:54:12 +08:00
|
|
|
auto_strip_attributes :name, :description, nullify: false
|
2016-02-12 23:52:43 +08:00
|
|
|
validates :name,
|
2016-10-05 23:45:20 +08:00
|
|
|
length: { minimum: Constants::NAME_MIN_LENGTH,
|
|
|
|
maximum: Constants::NAME_MAX_LENGTH }
|
|
|
|
validates :description, length: { maximum: Constants::TEXT_MAX_LENGTH }
|
2016-02-12 23:52:43 +08:00
|
|
|
|
2024-02-05 20:36:12 +08:00
|
|
|
belongs_to :created_by, class_name: 'User', optional: true
|
|
|
|
belongs_to :last_modified_by, class_name: 'User', optional: true
|
|
|
|
has_many :users, through: :user_assignments, dependent: :destroy
|
|
|
|
has_many :projects, inverse_of: :team, dependent: :destroy
|
2020-10-29 22:44:21 +08:00
|
|
|
has_many :project_folders, inverse_of: :team, dependent: :destroy
|
2017-01-24 23:34:21 +08:00
|
|
|
has_many :protocols, inverse_of: :team, dependent: :destroy
|
2022-05-17 20:20:12 +08:00
|
|
|
has_many :repository_protocols,
|
|
|
|
(lambda do
|
2023-02-28 22:15:55 +08:00
|
|
|
where(protocol_type: %i(in_repository_published_original
|
|
|
|
in_repository_draft
|
|
|
|
in_repository_published_version))
|
2022-05-17 20:20:12 +08:00
|
|
|
end),
|
2024-02-05 20:36:12 +08:00
|
|
|
class_name: 'Protocol',
|
|
|
|
dependent: :destroy
|
2017-01-24 23:34:21 +08:00
|
|
|
has_many :protocol_keywords, inverse_of: :team, dependent: :destroy
|
2017-04-25 20:24:05 +08:00
|
|
|
has_many :tiny_mce_assets, inverse_of: :team, dependent: :destroy
|
2017-05-18 20:21:00 +08:00
|
|
|
has_many :repositories, dependent: :destroy
|
2018-04-18 22:47:52 +08:00
|
|
|
has_many :reports, inverse_of: :team, dependent: :destroy
|
2019-02-25 22:21:13 +08:00
|
|
|
has_many :activities, inverse_of: :team, dependent: :destroy
|
2019-07-12 21:47:15 +08:00
|
|
|
has_many :assets, inverse_of: :team, dependent: :destroy
|
2022-08-08 16:06:00 +08:00
|
|
|
has_many :label_templates, dependent: :destroy
|
2022-06-29 21:22:22 +08:00
|
|
|
has_many :team_shared_objects, inverse_of: :team, dependent: :destroy
|
|
|
|
has_many :team_shared_repositories,
|
|
|
|
-> { where(shared_object_type: 'RepositoryBase') },
|
|
|
|
class_name: 'TeamSharedObject',
|
2024-02-05 20:36:12 +08:00
|
|
|
inverse_of: :team,
|
|
|
|
dependent: :destroy
|
|
|
|
has_many :shared_repositories,
|
|
|
|
through: :team_shared_objects,
|
|
|
|
source: :shared_object,
|
|
|
|
source_type: 'RepositoryBase',
|
|
|
|
dependent: :destroy
|
2022-06-28 19:13:36 +08:00
|
|
|
has_many :repository_sharing_user_assignments,
|
|
|
|
(lambda do |team|
|
|
|
|
joins(
|
|
|
|
"INNER JOIN repositories "\
|
|
|
|
"ON user_assignments.assignable_type = 'RepositoryBase' "\
|
|
|
|
"AND user_assignments.assignable_id = repositories.id"
|
|
|
|
).where(team_id: team.id)
|
|
|
|
.where.not('user_assignments.team_id = repositories.team_id')
|
|
|
|
end),
|
2024-02-05 20:36:12 +08:00
|
|
|
class_name: 'UserAssignment',
|
|
|
|
dependent: :destroy
|
2022-06-29 21:22:22 +08:00
|
|
|
has_many :shared_by_user_repositories,
|
2022-06-28 19:13:36 +08:00
|
|
|
through: :repository_sharing_user_assignments,
|
|
|
|
source: :assignable,
|
2024-02-05 20:36:12 +08:00
|
|
|
source_type: 'RepositoryBase',
|
|
|
|
dependent: :destroy
|
2023-07-03 21:54:11 +08:00
|
|
|
has_many :shareable_links, inverse_of: :team, dependent: :destroy
|
2024-07-23 14:50:17 +08:00
|
|
|
has_many :storage_locations, dependent: :destroy
|
2016-02-12 23:52:43 +08:00
|
|
|
|
2019-01-25 21:13:00 +08:00
|
|
|
attr_accessor :without_templates
|
|
|
|
|
2018-10-10 22:11:52 +08:00
|
|
|
def default_view_state
|
2020-12-17 01:32:07 +08:00
|
|
|
{
|
|
|
|
projects: {
|
|
|
|
active: { sort: 'new' },
|
2021-02-11 18:50:59 +08:00
|
|
|
archived: { sort: 'new' },
|
2023-12-20 21:19:52 +08:00
|
|
|
view_type: 'table'
|
2020-12-17 01:32:07 +08:00
|
|
|
}
|
|
|
|
}
|
2018-10-10 22:11:52 +08:00
|
|
|
end
|
|
|
|
|
2019-06-11 16:08:33 +08:00
|
|
|
def validate_view_state(view_state)
|
2022-11-26 05:18:54 +08:00
|
|
|
if %w(new old atoz ztoa id_asc id_desc).exclude?(view_state.state.dig('projects', 'active', 'sort')) ||
|
|
|
|
%w(new old atoz ztoa id_asc id_desc archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort')) ||
|
2021-02-11 18:50:59 +08:00
|
|
|
%w(cards table).exclude?(view_state.state.dig('projects', 'view_type'))
|
2019-06-11 16:08:33 +08:00
|
|
|
view_state.errors.add(:state, :wrong_state)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-04-13 22:14:12 +08:00
|
|
|
def search_users(query = nil)
|
|
|
|
a_query = "%#{query}%"
|
|
|
|
users.where.not(confirmed_at: nil)
|
2017-04-26 19:32:03 +08:00
|
|
|
.where('full_name ILIKE ? OR email ILIKE ?', a_query, a_query)
|
2017-01-04 17:54:02 +08:00
|
|
|
end
|
|
|
|
|
2020-03-17 23:22:27 +08:00
|
|
|
def storage_used
|
|
|
|
by_assets = Asset.joins(:file_blob)
|
|
|
|
.where(assets: { team_id: id })
|
|
|
|
.select('active_storage_blobs.byte_size')
|
|
|
|
|
|
|
|
by_tiny_mce_assets = TinyMceAsset.joins(:image_blob)
|
|
|
|
.where(tiny_mce_assets: { team_id: id })
|
|
|
|
.select('active_storage_blobs.byte_size')
|
|
|
|
|
|
|
|
ActiveStorage::Blob
|
|
|
|
.from("((#{by_assets.to_sql}) UNION ALL (#{by_tiny_mce_assets.to_sql})) AS active_storage_blobs")
|
|
|
|
.sum(:byte_size)
|
|
|
|
end
|
|
|
|
|
2017-01-24 23:34:21 +08:00
|
|
|
# (re)calculate the space taken by this team
|
2016-02-12 23:52:43 +08:00
|
|
|
def calculate_space_taken
|
|
|
|
st = 0
|
|
|
|
projects.includes(
|
2016-08-30 19:06:49 +08:00
|
|
|
experiments: {
|
|
|
|
my_modules: {
|
|
|
|
protocols: { steps: :assets },
|
|
|
|
results: { result_asset: :asset }
|
|
|
|
}
|
|
|
|
}
|
2016-02-12 23:52:43 +08:00
|
|
|
).find_each do |project|
|
2016-07-22 21:36:48 +08:00
|
|
|
project.project_my_modules.find_each do |my_module|
|
2016-07-21 19:11:15 +08:00
|
|
|
my_module.protocol.steps.find_each do |step|
|
2016-02-12 23:52:43 +08:00
|
|
|
step.assets.find_each { |asset| st += asset.estimated_size }
|
2017-04-24 18:28:27 +08:00
|
|
|
step.tiny_mce_assets.find_each { |tiny| st += tiny.estimated_size }
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
my_module.results.find_each do |result|
|
2017-04-24 18:28:27 +08:00
|
|
|
st += result.asset.estimated_size if result.is_asset
|
|
|
|
if result.is_text
|
|
|
|
tiny_assets = TinyMceAsset.where(result_text: result.result_text)
|
|
|
|
tiny_assets.find_each { |tiny| st += tiny.estimated_size }
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-08-09 20:26:08 +08:00
|
|
|
# project.experiments.each |experiment|
|
2017-01-25 19:30:11 +08:00
|
|
|
self.space_taken = [st, Constants::MINIMAL_TEAM_SPACE_TAKEN].max
|
2017-01-24 23:34:21 +08:00
|
|
|
Rails::logger.info "Team #{self.id}: " +
|
2016-02-12 23:52:43 +08:00
|
|
|
"space (re)calculated to: " +
|
|
|
|
"#{self.space_taken}B (#{number_to_human_size(self.space_taken)})"
|
|
|
|
end
|
|
|
|
|
|
|
|
# Take specified amount of bytes
|
|
|
|
def take_space(space)
|
|
|
|
orig_space = self.space_taken
|
|
|
|
self.space_taken += space
|
2017-01-24 23:34:21 +08:00
|
|
|
Rails::logger.info "Team #{self.id}: " +
|
2016-02-12 23:52:43 +08:00
|
|
|
"space taken: " +
|
|
|
|
"#{orig_space}B + #{space}B = " +
|
|
|
|
"#{self.space_taken}B (#{number_to_human_size(self.space_taken)})"
|
|
|
|
end
|
|
|
|
|
|
|
|
# Release specified amount of bytes
|
|
|
|
def release_space(space)
|
|
|
|
orig_space = self.space_taken
|
2016-10-05 23:45:20 +08:00
|
|
|
self.space_taken = [space_taken - space,
|
2017-01-25 19:30:11 +08:00
|
|
|
Constants::MINIMAL_TEAM_SPACE_TAKEN].max
|
2017-01-24 23:34:21 +08:00
|
|
|
Rails::logger.info "Team #{self.id}: " +
|
2016-02-12 23:52:43 +08:00
|
|
|
"space released: " +
|
|
|
|
"#{orig_space}B - #{space}B = " +
|
|
|
|
"#{self.space_taken}B (#{number_to_human_size(self.space_taken)})"
|
|
|
|
end
|
2016-07-21 19:11:15 +08:00
|
|
|
|
|
|
|
def protocol_keywords_list
|
2017-01-24 23:34:21 +08:00
|
|
|
ProtocolKeyword.where(team: self).pluck(:name)
|
2016-07-21 19:11:15 +08:00
|
|
|
end
|
2019-01-25 17:50:08 +08:00
|
|
|
|
2023-09-14 20:22:01 +08:00
|
|
|
def self.by_activity_subjects(subjects)
|
|
|
|
team_ids = []
|
|
|
|
valid_subjects = subjects.select { |k| Extends::SEARCHABLE_ACTIVITY_SUBJECT_TYPES.include?(k.to_s) }
|
|
|
|
valid_subjects.each do |subject_name, subject_id|
|
|
|
|
subject = subject_name.to_s.constantize.find_by(id: subject_id)
|
|
|
|
next if subject.blank?
|
|
|
|
|
|
|
|
team_ids << subject.team.id
|
|
|
|
end
|
|
|
|
where(id: team_ids.uniq)
|
|
|
|
end
|
|
|
|
|
2019-04-09 15:08:27 +08:00
|
|
|
def self.global_activity_filter(filters, search_query)
|
2023-09-14 20:22:01 +08:00
|
|
|
query = where_attributes_like(:name, search_query)
|
2019-04-09 15:08:27 +08:00
|
|
|
if filters[:users]
|
2023-07-20 16:57:29 +08:00
|
|
|
user_teams = User.where(id: filters[:users]).joins(:teams).group(:team_id).select(:team_id)
|
|
|
|
query = query.where(id: user_teams)
|
2019-04-09 15:08:27 +08:00
|
|
|
end
|
2023-09-14 20:22:01 +08:00
|
|
|
query = query.by_activity_subjects(filters[:subjects]) if filters[:subjects].present?
|
|
|
|
query
|
2019-04-09 15:08:27 +08:00
|
|
|
end
|
|
|
|
|
2019-09-25 19:45:34 +08:00
|
|
|
def self.search_by_object(obj)
|
2019-07-05 22:56:05 +08:00
|
|
|
find(
|
|
|
|
case obj.class.name
|
|
|
|
when 'Protocol'
|
|
|
|
obj.team_id
|
2022-05-11 18:58:10 +08:00
|
|
|
when 'StepText'
|
|
|
|
obj.step.protocol.team_id
|
2019-07-05 22:56:05 +08:00
|
|
|
when 'MyModule', 'Step'
|
|
|
|
obj.protocol.team_id
|
|
|
|
when 'ResultText'
|
|
|
|
obj.result.my_module.protocol.team_id
|
|
|
|
end
|
|
|
|
)
|
2019-05-20 18:44:16 +08:00
|
|
|
end
|
|
|
|
|
2023-06-28 17:47:41 +08:00
|
|
|
def number_of_task_shared
|
2023-07-03 21:54:11 +08:00
|
|
|
shareable_links.count
|
2023-06-28 17:47:41 +08:00
|
|
|
end
|
|
|
|
|
2019-01-25 17:50:08 +08:00
|
|
|
private
|
|
|
|
|
|
|
|
def generate_template_project
|
2019-01-25 21:13:00 +08:00
|
|
|
return if without_templates
|
2019-02-26 20:32:00 +08:00
|
|
|
TemplatesService.new.delay(queue: :templates).update_team(self)
|
2019-01-25 17:50:08 +08:00
|
|
|
end
|
2022-08-08 16:06:00 +08:00
|
|
|
|
|
|
|
def create_default_label_templates
|
|
|
|
ZebraLabelTemplate.default.update(team: self, default: true)
|
|
|
|
FluicsLabelTemplate.default.update(team: self, default: true)
|
|
|
|
end
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|