2017-06-23 21:19:08 +08:00
|
|
|
class Project < ApplicationRecord
|
2016-02-12 23:52:43 +08:00
|
|
|
include ArchivableModel, SearchableModel
|
|
|
|
|
|
|
|
enum visibility: { hidden: 0, visible: 1 }
|
|
|
|
|
2016-09-21 21:35:23 +08:00
|
|
|
auto_strip_attributes :name, 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 },
|
2018-06-07 22:42:11 +08:00
|
|
|
uniqueness: { scope: :team, case_sensitive: false }
|
2016-02-12 23:52:43 +08:00
|
|
|
validates :visibility, presence: true
|
2017-01-24 23:34:21 +08:00
|
|
|
validates :team, 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
|
|
|
|
belongs_to :archived_by,
|
|
|
|
foreign_key: 'archived_by_id',
|
|
|
|
class_name: 'User',
|
|
|
|
optional: true
|
|
|
|
belongs_to :restored_by,
|
|
|
|
foreign_key: 'restored_by_id',
|
|
|
|
class_name: 'User',
|
|
|
|
optional: true
|
|
|
|
belongs_to :team, inverse_of: :projects, optional: true
|
2016-02-12 23:52:43 +08:00
|
|
|
has_many :user_projects, inverse_of: :project
|
|
|
|
has_many :users, through: :user_projects
|
2016-07-22 16:43:55 +08:00
|
|
|
has_many :experiments, inverse_of: :project
|
2018-06-01 19:49:02 +08:00
|
|
|
has_many :active_experiments, -> { where(archived: false) },
|
|
|
|
class_name: 'Experiment'
|
2017-03-08 20:18:20 +08:00
|
|
|
has_many :project_comments, foreign_key: :associated_id, dependent: :destroy
|
2016-02-12 23:52:43 +08:00
|
|
|
has_many :activities, inverse_of: :project
|
|
|
|
has_many :tags, inverse_of: :project
|
|
|
|
has_many :reports, inverse_of: :project, dependent: :destroy
|
|
|
|
has_many :report_elements, inverse_of: :project, dependent: :destroy
|
|
|
|
|
2018-04-18 22:47:52 +08:00
|
|
|
after_commit do
|
2018-04-23 18:26:21 +08:00
|
|
|
Views::Datatables::DatatablesReport.refresh_materialized_view
|
2018-04-18 22:47:52 +08:00
|
|
|
end
|
|
|
|
|
2018-05-21 20:45:18 +08:00
|
|
|
def self.visible_from_user_by_name(user, team, name)
|
|
|
|
if user.is_admin_of_team? team
|
|
|
|
return where('projects.archived IS FALSE AND projects.name ILIKE ?',
|
|
|
|
"%#{name}%")
|
|
|
|
end
|
2018-05-24 21:28:54 +08:00
|
|
|
joins(:user_projects)
|
|
|
|
.where('user_projects.user_id = ? OR projects.visibility = 1', user.id)
|
|
|
|
.where('projects.archived IS FALSE AND projects.name ILIKE ?',
|
|
|
|
"%#{name}%")
|
2018-05-21 20:45:18 +08:00
|
|
|
end
|
2018-04-18 22:47:52 +08:00
|
|
|
|
2017-01-11 21:56:47 +08:00
|
|
|
def self.search(
|
|
|
|
user,
|
|
|
|
include_archived,
|
|
|
|
query = nil,
|
|
|
|
page = 1,
|
2017-05-05 22:41:23 +08:00
|
|
|
current_team = nil,
|
|
|
|
options = {}
|
2017-01-11 21:56:47 +08:00
|
|
|
)
|
2016-07-21 19:11:15 +08:00
|
|
|
|
2017-01-24 23:34:21 +08:00
|
|
|
if current_team
|
2017-02-13 21:15:53 +08:00
|
|
|
new_query =
|
|
|
|
Project
|
|
|
|
.distinct
|
|
|
|
.joins(:user_projects)
|
|
|
|
.where('projects.team_id = ?', current_team.id)
|
|
|
|
unless user.user_teams.find_by(team: current_team).try(:admin?)
|
|
|
|
# Admins see all projects in the team
|
|
|
|
new_query = new_query.where(
|
|
|
|
'projects.visibility = 1 OR user_projects.user_id = ?',
|
|
|
|
user.id
|
|
|
|
)
|
|
|
|
end
|
2017-05-05 22:41:23 +08:00
|
|
|
new_query = new_query.where_attributes_like(:name, query, options)
|
2017-01-20 00:09:25 +08:00
|
|
|
|
2017-01-16 23:50:18 +08:00
|
|
|
if include_archived
|
|
|
|
return new_query
|
|
|
|
else
|
|
|
|
return new_query.where('projects.archived = ?', false)
|
|
|
|
end
|
2016-02-12 23:52:43 +08:00
|
|
|
else
|
2017-02-13 21:15:53 +08:00
|
|
|
new_query = Project
|
|
|
|
.distinct
|
|
|
|
.joins(team: :user_teams)
|
|
|
|
.where('user_teams.user_id = ?', user.id)
|
2017-01-11 21:56:47 +08:00
|
|
|
|
|
|
|
if include_archived
|
2017-02-13 21:15:53 +08:00
|
|
|
new_query =
|
|
|
|
new_query
|
|
|
|
.joins(:user_projects)
|
|
|
|
.where(
|
|
|
|
'user_teams.role = 2 OR projects.visibility = 1 OR ' \
|
|
|
|
'user_projects.user_id = ?',
|
|
|
|
user.id
|
|
|
|
)
|
2017-05-05 22:41:23 +08:00
|
|
|
.where_attributes_like('projects.name', query, options)
|
2017-01-11 21:56:47 +08:00
|
|
|
|
|
|
|
else
|
2017-02-13 21:15:53 +08:00
|
|
|
new_query =
|
|
|
|
new_query
|
|
|
|
.joins(:user_projects)
|
|
|
|
.where(
|
|
|
|
'user_teams.role = 2 OR projects.visibility = 1 OR ' \
|
|
|
|
'user_projects.user_id = ?',
|
|
|
|
user.id
|
|
|
|
)
|
2017-05-05 22:41:23 +08:00
|
|
|
.where_attributes_like('projects.name', query, options)
|
2017-02-13 21:15:53 +08:00
|
|
|
.where('projects.archived = ?', false)
|
2017-01-11 21:56:47 +08:00
|
|
|
end
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Show all results if needed
|
2016-10-05 23:45:20 +08:00
|
|
|
if page == Constants::SEARCH_NO_LIMIT
|
2016-02-12 23:52:43 +08:00
|
|
|
new_query
|
|
|
|
else
|
|
|
|
new_query
|
2016-10-05 23:45:20 +08:00
|
|
|
.limit(Constants::SEARCH_LIMIT)
|
|
|
|
.offset((page - 1) * Constants::SEARCH_LIMIT)
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-10-13 16:00:36 +08:00
|
|
|
def last_activities(count = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
|
2016-07-21 19:11:15 +08:00
|
|
|
activities.order(created_at: :desc).first(count)
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Get project comments order by created_at time. Results are paginated
|
|
|
|
# using last comment id and per_page parameters.
|
2016-10-05 23:45:20 +08:00
|
|
|
def last_comments(last_id = 1, per_page = Constants::COMMENTS_SEARCH_LIMIT)
|
2016-10-13 17:05:11 +08:00
|
|
|
last_id = Constants::INFINITY if last_id <= 1
|
2017-03-08 20:18:20 +08:00
|
|
|
comments = ProjectComment.joins(:project)
|
|
|
|
.where(projects: { id: id })
|
|
|
|
.where('comments.id < ?', last_id)
|
|
|
|
.order(created_at: :desc)
|
|
|
|
.limit(per_page)
|
2016-09-27 15:00:02 +08:00
|
|
|
comments.reverse
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def unassigned_users
|
2016-07-21 19:11:15 +08:00
|
|
|
User
|
2017-01-24 23:34:21 +08:00
|
|
|
.joins('INNER JOIN user_teams ON users.id = user_teams.user_id')
|
|
|
|
.where('user_teams.team_id = ?', team)
|
2016-08-18 19:24:44 +08:00
|
|
|
.where.not(confirmed_at: nil)
|
|
|
|
.where('users.id NOT IN (?)',
|
|
|
|
UserProject.where(project: self).select(:user_id).distinct)
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def user_role(user)
|
2018-09-21 16:39:20 +08:00
|
|
|
user_projects.find_by_user_id(user)&.role
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
|
2018-06-01 19:49:02 +08:00
|
|
|
def sorted_active_experiments(sort_by = :new)
|
2016-12-23 22:40:01 +08:00
|
|
|
sort = case sort_by
|
|
|
|
when 'old' then { created_at: :asc }
|
|
|
|
when 'atoz' then { name: :asc }
|
|
|
|
when 'ztoa' then { name: :desc }
|
|
|
|
else { created_at: :desc }
|
|
|
|
end
|
|
|
|
experiments.is_archived(false).order(sort)
|
2016-07-29 21:47:41 +08:00
|
|
|
end
|
|
|
|
|
2016-08-08 20:19:35 +08:00
|
|
|
def archived_experiments
|
|
|
|
experiments.is_archived(true)
|
|
|
|
end
|
|
|
|
|
2016-07-25 17:25:10 +08:00
|
|
|
def project_my_modules
|
2016-07-26 14:44:09 +08:00
|
|
|
MyModule.where('"experiment_id" IN (?)', experiments.select(:id))
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def space_taken
|
|
|
|
st = 0
|
2016-07-22 20:31:09 +08:00
|
|
|
project_my_modules.find_each do |my_module|
|
2016-02-12 23:52:43 +08:00
|
|
|
st += my_module.space_taken
|
|
|
|
end
|
|
|
|
st
|
|
|
|
end
|
|
|
|
|
2016-08-03 18:57:51 +08:00
|
|
|
def assigned_samples
|
|
|
|
Sample.joins(:my_modules).where(my_modules: {
|
|
|
|
id: my_modules_ids.split(',')
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
def my_modules_ids
|
|
|
|
ids = active_experiments.map do |exp|
|
|
|
|
exp.my_modules.pluck(:id) if exp.my_modules
|
|
|
|
end
|
|
|
|
ids.delete_if { |i| i.flatten.empty? }
|
|
|
|
ids.join(', ')
|
|
|
|
end
|
2016-08-17 19:17:40 +08:00
|
|
|
|
|
|
|
def assigned_modules(user)
|
|
|
|
role = user_role(user)
|
|
|
|
if role.blank?
|
|
|
|
MyModule.none
|
|
|
|
elsif role == 'owner'
|
2016-08-18 16:55:29 +08:00
|
|
|
project_my_modules
|
|
|
|
.joins(:experiment)
|
|
|
|
.where('experiments.archived=false')
|
2016-08-18 17:53:57 +08:00
|
|
|
.where('my_modules.archived=false')
|
|
|
|
|
2016-08-17 19:17:40 +08:00
|
|
|
else
|
|
|
|
project_my_modules
|
|
|
|
.joins(:user_my_modules)
|
2016-08-18 16:55:29 +08:00
|
|
|
.joins(:experiment)
|
|
|
|
.where('experiments.archived=false AND user_my_modules.user_id IN (?)',
|
|
|
|
user.id)
|
2016-08-18 17:53:57 +08:00
|
|
|
.where('my_modules.archived=false')
|
2016-08-17 19:17:40 +08:00
|
|
|
.distinct
|
|
|
|
end
|
|
|
|
end
|
2017-05-10 23:28:37 +08:00
|
|
|
|
|
|
|
def notifications_count(user)
|
|
|
|
res = 0
|
|
|
|
assigned_modules(user).find_each do |t|
|
|
|
|
res += 1 if t.is_overdue? || t.is_one_day_prior?
|
|
|
|
end
|
|
|
|
res
|
|
|
|
end
|
2018-10-15 04:52:54 +08:00
|
|
|
|
2018-10-15 12:52:39 +08:00
|
|
|
def generate_report_pdf(user, team, pdf_name, obj_filenames = nil)
|
2018-10-15 04:52:54 +08:00
|
|
|
ActionController::Renderer::RACK_KEY_TRANSLATION['warden'] ||= 'warden'
|
|
|
|
proxy = Warden::Proxy.new({}, Warden::Manager.new({}))
|
|
|
|
renderer = ApplicationController.renderer.new(warden: proxy)
|
|
|
|
|
2018-10-15 11:54:19 +08:00
|
|
|
report = Report.generate_whole_project_report(self, user, team)
|
2018-10-15 04:52:54 +08:00
|
|
|
|
2018-10-15 11:54:19 +08:00
|
|
|
page_html_string =
|
|
|
|
renderer.render 'reports/new.html.erb',
|
|
|
|
locals: { export_all: true,
|
|
|
|
obj_filenames: obj_filenames },
|
|
|
|
assigns: { project: self, report: report }
|
2018-10-15 04:52:54 +08:00
|
|
|
parsed_page_html = Nokogiri::HTML(page_html_string)
|
|
|
|
parsed_pdf_html = parsed_page_html.at_css('#report-content')
|
|
|
|
report.destroy
|
|
|
|
|
2018-09-24 13:21:32 +08:00
|
|
|
tables = parsed_pdf_html.css('.hot-table-contents')
|
|
|
|
.zip(parsed_pdf_html.css('.hot-table-container'))
|
|
|
|
tables.each do |table_input, table_container|
|
|
|
|
table_vals = JSON.parse(table_input['value'])
|
|
|
|
table_data = table_vals['data']
|
|
|
|
table_headers = table_vals['headers']
|
|
|
|
table_headers ||= ('A'..'Z').first(table_data[0].count)
|
|
|
|
|
|
|
|
table_el = table_container.add_child('<table></table>').first
|
|
|
|
row_el = table_el.add_child('<tr></tr>').first
|
|
|
|
row_el.add_child('<th></th>').first
|
|
|
|
table_headers.each do |col|
|
|
|
|
row_el.add_child("<th>#{col}</th>").first
|
|
|
|
end
|
|
|
|
|
|
|
|
table_data.each.with_index(1) do |row, index|
|
|
|
|
row_el = table_el.add_child('<tr></tr>').first
|
|
|
|
row_el.add_child("<td>#{index}</td>").first
|
|
|
|
row.each do |col|
|
|
|
|
row_el.add_child("<td>#{col}</td>").first
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-10-15 04:52:54 +08:00
|
|
|
parsed_pdf = ApplicationController.render(
|
2018-10-15 12:52:39 +08:00
|
|
|
pdf: pdf_name,
|
2018-10-15 04:52:54 +08:00
|
|
|
header: { right: '[page] of [topage]' },
|
|
|
|
locals: { content: parsed_pdf_html.to_s },
|
|
|
|
template: 'reports/report.pdf.erb',
|
|
|
|
disable_javascript: true,
|
|
|
|
disable_internal_links: false,
|
|
|
|
current_user: user,
|
|
|
|
current_team: team
|
|
|
|
)
|
|
|
|
# Dirty workaround to convert absolute links back to relative ones, since
|
|
|
|
# WickedPdf does the opposite, based on the path where the file parsing is
|
|
|
|
# done
|
2018-10-15 05:53:51 +08:00
|
|
|
parsed_pdf.gsub('/URI (file:////tmp/', '/URI (')
|
2018-10-15 04:52:54 +08:00
|
|
|
end
|
2016-02-12 23:52:43 +08:00
|
|
|
end
|