scinote-web/app/models/team.rb

365 lines
11 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
2017-06-23 21:19:08 +08:00
class Team < ApplicationRecord
include SearchableModel
include ViewableModel
include TeamBySubjectModel
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
after_create :generate_template_project
scope :teams_select, -> { select(:id, :name).order(name: :asc) }
auto_strip_attributes :name, :description, nullify: false
2016-02-12 23:52:43 +08:00
validates :name,
length: { minimum: Constants::NAME_MIN_LENGTH,
maximum: Constants::NAME_MAX_LENGTH }
validates :description, length: { maximum: Constants::TEXT_MAX_LENGTH }
validates :space_taken, presence: true
2016-02-12 23:52:43 +08:00
2017-06-28 21:21:32 +08:00
belongs_to :created_by,
foreign_key: 'created_by_id',
class_name: 'User',
optional: true
belongs_to :last_modified_by,
foreign_key: 'last_modified_by_id',
class_name: 'User',
optional: true
2017-01-24 23:34:21 +08:00
has_many :user_teams, inverse_of: :team, dependent: :destroy
has_many :users, through: :user_teams
has_many :samples, inverse_of: :team
has_many :samples_tables, inverse_of: :team, dependent: :destroy
has_many :sample_groups, inverse_of: :team
has_many :sample_types, inverse_of: :team
has_many :projects, inverse_of: :team
has_many :custom_fields, inverse_of: :team
has_many :protocols, inverse_of: :team, dependent: :destroy
has_many :protocol_keywords, inverse_of: :team, dependent: :destroy
has_many :tiny_mce_assets, inverse_of: :team, dependent: :destroy
has_many :repositories, dependent: :destroy
has_many :reports, inverse_of: :team, dependent: :destroy
2019-02-25 22:21:13 +08:00
has_many :activities, inverse_of: :team, dependent: :destroy
2016-02-12 23:52:43 +08:00
2019-01-25 21:13:00 +08:00
attr_accessor :without_templates
2019-02-13 21:31:26 +08:00
attr_accessor :without_intro_demo
2019-01-24 20:40:53 +08:00
after_create :generate_intro_demo
2019-01-25 21:13:00 +08:00
def default_view_state
{ 'projects' =>
{ 'cards' => { 'sort' => 'new' },
'table' =>
{ 'time' => Time.now.to_i,
'order' => [[2, 'asc']],
'start' => 0,
'length' => 10 },
'filter' => 'active' } }
end
def validate_view_state(view_state)
unless %w(new old atoz ztoa).include?(view_state.state.dig('projects', 'cards', 'sort')) &&
%w(all active archived).include?(view_state.state.dig('projects', 'filter'))
view_state.errors.add(:state, :wrong_state)
end
end
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)
end
2016-02-12 23:52:43 +08:00
# Imports samples into db
# -1 == sample_name,
# -2 == sample_type,
# -3 == sample_group
# TODO: use constants
def import_samples(sheet, mappings, user)
2017-07-24 23:27:50 +08:00
errors = false
2016-02-12 23:52:43 +08:00
nr_of_added = 0
total_nr = 0
header_skipped = false
2016-02-12 23:52:43 +08:00
# First let's query for all custom_fields we're refering to
custom_fields = []
sname_index = -1
stype_index = -1
sgroup_index = -1
2017-07-24 23:27:50 +08:00
mappings.each.with_index do |(_, v), i|
if v == '-1'
2016-02-12 23:52:43 +08:00
# Fill blank space, so our indices stay the same
custom_fields << nil
sname_index = i
2017-07-24 23:27:50 +08:00
elsif v == '-2'
2016-02-12 23:52:43 +08:00
custom_fields << nil
stype_index = i
2017-07-24 23:27:50 +08:00
elsif v == '-3'
2016-02-12 23:52:43 +08:00
custom_fields << nil
2017-07-24 23:27:50 +08:00
sgroup_index = i
2016-02-12 23:52:43 +08:00
else
cf = CustomField.find_by_id(v)
# Even if doesn't exist we add nil value in order not to destroy our
# indices
custom_fields << cf
end
end
rows = SpreadsheetParser.spreadsheet_enumerator(sheet)
2016-02-12 23:52:43 +08:00
# Now we can iterate through sample data and save stuff into db
rows.each do |row|
# Skip empty rows
next if row.empty?
unless header_skipped
header_skipped = true
next
end
2016-02-12 23:52:43 +08:00
total_nr += 1
row = SpreadsheetParser.parse_row(row, sheet)
sample = Sample.new(name: row[sname_index],
team: self,
user: user)
sample.transaction do
unless sample.valid?
errors = true
raise ActiveRecord::Rollback
end
2016-02-12 23:52:43 +08:00
row.each.with_index do |value, index|
next unless value.present?
2016-02-12 23:52:43 +08:00
if index == stype_index
stype = SampleType.where(team: self)
.where('name ILIKE ?', value.strip)
.take
2017-07-24 23:27:50 +08:00
unless stype
stype = SampleType.new(name: value.strip, team: self)
unless stype.save
2017-07-24 23:27:50 +08:00
errors = true
raise ActiveRecord::Rollback
end
2016-02-12 23:52:43 +08:00
end
sample.sample_type = stype
2016-02-12 23:52:43 +08:00
elsif index == sgroup_index
sgroup = SampleGroup.where(team: self)
.where('name ILIKE ?', value.strip)
.take
2017-07-24 23:27:50 +08:00
unless sgroup
sgroup = SampleGroup.new(name: value.strip, team: self)
unless sgroup.save
2017-07-24 23:27:50 +08:00
errors = true
raise ActiveRecord::Rollback
2016-02-12 23:52:43 +08:00
end
end
sample.sample_group = sgroup
elsif value && custom_fields[index]
# we're working with CustomField
scf = SampleCustomField.new(
sample: sample,
custom_field: custom_fields[index],
value: value
)
unless scf.valid?
errors = true
raise ActiveRecord::Rollback
end
sample.sample_custom_fields << scf
2016-02-12 23:52:43 +08:00
end
end
if Sample.import([sample],
recursive: true,
validate: false).failed_instances.any?
errors = true
raise ActiveRecord::Rollback
end
2016-02-12 23:52:43 +08:00
nr_of_added += 1
end
end
2017-07-24 23:27:50 +08:00
if errors
return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
2016-02-12 23:52:43 +08:00
else
2017-07-24 23:27:50 +08:00
return { status: :ok, nr_of_added: nr_of_added, total_nr: total_nr }
2016-02-12 23:52:43 +08:00
end
end
def to_csv(samples, headers)
require "csv"
# Parse headers (magic numbers should be refactored - see
# sample-datatable.js)
header_names = []
headers.each do |header|
if header == "-1"
header_names << I18n.t("samples.table.sample_name")
elsif header == "-2"
header_names << I18n.t("samples.table.sample_type")
elsif header == "-3"
header_names << I18n.t("samples.table.sample_group")
elsif header == "-4"
header_names << I18n.t("samples.table.added_by")
elsif header == "-5"
header_names << I18n.t("samples.table.added_on")
else
cf = CustomField.find_by_id(header)
if cf
header_names << cf.name
else
header_names << nil
end
end
end
CSV.generate do |csv|
csv << header_names
samples.each do |sample|
sample_row = []
headers.each do |header|
if header == "-1"
sample_row << sample.name
elsif header == "-2"
sample_row << (sample.sample_type.nil? ? I18n.t("samples.table.no_type") : sample.sample_type.name)
elsif header == "-3"
sample_row << (sample.sample_group.nil? ? I18n.t("samples.table.no_group") : sample.sample_group.name)
elsif header == "-4"
sample_row << sample.user.full_name
elsif header == "-5"
sample_row << I18n.l(sample.created_at, format: :full)
else
scf = SampleCustomField.where(
custom_field_id: header,
sample_id: sample.id
).take
if scf
sample_row << scf.value
else
sample_row << nil
end
end
end
csv << sample_row
end
end
end
# Get all header fields for samples (used in importing for mappings - dropdowns)
def get_available_sample_fields
fields = {};
# First and foremost add sample name
fields["-1"] = I18n.t("samples.table.sample_name")
fields["-2"] = I18n.t("samples.table.sample_type")
fields["-3"] = I18n.t("samples.table.sample_group")
# Add all other custom fields
2017-01-24 23:34:21 +08:00
CustomField.where(team_id: id).order(:created_at).each do |cf|
2016-02-12 23:52:43 +08:00
fields[cf.id] = cf.name
end
fields
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 }
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|
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
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
def self.global_activity_filter(filters, search_query)
query = where('name ILIKE ?', "%#{search_query}%")
if filters[:users]
users_team = User.where(id: filters[:users]).joins(:user_teams).group(:team_id).pluck(:team_id)
query = query.where(id: users_team)
end
query = query.where(id: team_by_subject(filters[:subjects])) if filters[:subjects]
query.select(:id, :name).map { |i| { id: i[:id], name: ApplicationController.helpers.escape_input(i[:name]) } }
end
def self.find_by_object(obj)
case obj.class.name
when 'Protocol'
obj.team_id
when 'MyModule', 'Step'
obj.protocol.team_id
when 'ResultText'
obj.result.my_module.protocol.team_id
end
end
private
def generate_template_project
2019-01-25 21:13:00 +08:00
return if without_templates
TemplatesService.new.delay(queue: :templates).update_team(self)
end
2019-01-24 20:40:53 +08:00
include FirstTimeDataGenerator
def generate_intro_demo
2019-02-13 21:31:26 +08:00
return if without_intro_demo
2019-01-24 20:40:53 +08:00
user = User.find(created_by_id)
if user.created_teams.order(:created_at).first == self
seed_demo_data(user, self)
end
end
2016-02-12 23:52:43 +08:00
end