diff --git a/.rubocop.yml b/.rubocop.yml
index c69a64de8..295ae2d66 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -3,7 +3,7 @@ AllCops:
- "vendor/**/*"
- "db/schema.rb"
UseCache: false
- TargetRubyVersion: 2.2
+ TargetRubyVersion: 2.4
##################### Style ####################################
diff --git a/app/controllers/my_modules_controller.rb b/app/controllers/my_modules_controller.rb
index 1e372fc6d..eb9cd313b 100644
--- a/app/controllers/my_modules_controller.rb
+++ b/app/controllers/my_modules_controller.rb
@@ -382,7 +382,7 @@ class MyModulesController < ApplicationController
current_user,
@my_module)
@assigned_rows = records.assigned_rows
- @repository_row_count = records.repository_rows.count
+ @repository_row_count = records.repository_rows.length
@columns_mappings = records.mappings
@repository_rows = records.repository_rows.page(page).per(per_page)
render 'repository_rows/index.json'
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ba78e9ed4..975ab6cb9 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -9,7 +9,7 @@ class ProjectsController < ApplicationController
notifications reports
samples experiment_archive
delete_samples samples_index)
- before_action :load_projects_tree, only: %i(index show samples archive
+ before_action :load_projects_tree, only: %i(show samples archive
experiment_archive)
before_action :load_archive_vars, only: :archive
before_action :check_view_permissions, only: %i(show reports notifications
@@ -25,14 +25,32 @@ class ProjectsController < ApplicationController
DELETE_SAMPLES = 'Delete'.freeze
def index
- if params[:team]
- current_team_switch(Team.find_by_id(params[:team]))
+ respond_to do |format|
+ format.json do
+ @current_team = current_team if current_team
+ @current_team ||= current_user.teams.first
+ @projects = ProjectsOverviewService.new(@current_team, current_user)
+ .project_cards(params)
+ end
+ format.html do
+ current_team_switch(Team.find_by_id(params[:team])) if params[:team]
+ @teams = current_user.teams
+ # New project for create new project modal
+ @project = Project.new
+ load_projects_tree
+ end
end
+ end
- @teams = current_user.teams
-
- # New project for create new project modal
- @project = Project.new
+ def index_dt
+ respond_to do |format|
+ format.json do
+ @current_team = current_team if current_team
+ @current_team ||= current_user.teams.first
+ @projects = ProjectsOverviewService.new(@current_team, current_user)
+ .projects_datatable(params)
+ end
+ end
end
def archive
diff --git a/app/controllers/repository_rows_controller.rb b/app/controllers/repository_rows_controller.rb
index 942634b2f..305b33762 100644
--- a/app/controllers/repository_rows_controller.rb
+++ b/app/controllers/repository_rows_controller.rb
@@ -23,7 +23,7 @@ class RepositoryRowsController < ApplicationController
params,
current_user)
@assigned_rows = records.assigned_rows
- @repository_row_count = records.repository_rows.count
+ @repository_row_count = records.repository_rows.length
@columns_mappings = records.mappings
@repository_rows = records.repository_rows.page(page).per(per_page)
end
diff --git a/app/models/my_module.rb b/app/models/my_module.rb
index f38fa7b5e..012899d3e 100644
--- a/app/models/my_module.rb
+++ b/app/models/my_module.rb
@@ -66,6 +66,13 @@ class MyModule < ApplicationRecord
has_many :protocols, inverse_of: :my_module, dependent: :destroy
scope :is_archived, ->(is_archived) { where('archived = ?', is_archived) }
+ scope :active, -> { where(archived: false) }
+ scope :overdue, -> { where('my_modules.due_date < ?', Time.current.utc) }
+ scope :one_day_prior, (lambda do
+ where('my_modules.due_date > ? AND my_modules.due_date < ?',
+ Time.current.utc,
+ Time.current.utc + 1.day)
+ end)
# A module takes this much space in canvas (x, y) in database
WIDTH = 30
diff --git a/app/models/project.rb b/app/models/project.rb
index 2d5c9aafa..355aefdda 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -150,11 +150,7 @@ class Project < ApplicationRecord
end
def user_role(user)
- unless self.users.include? user
- return nil
- end
-
- return (self.user_projects.select { |up| up.user == user }).first.role
+ user_projects.find_by_user_id(user)&.role
end
def sorted_active_experiments(sort_by = :new)
diff --git a/app/services/projects_overview_service.rb b/app/services/projects_overview_service.rb
new file mode 100644
index 000000000..5223d1cd1
--- /dev/null
+++ b/app/services/projects_overview_service.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+class ProjectsOverviewService
+ def initialize(team, user)
+ @team = team
+ @user = user
+ end
+
+ def project_cards(params)
+ records = fetch_records
+ records = records.where(archived: true) if params[:archived] == 'true'
+ records = records.where(archived: false) if params[:archived] == 'false'
+ return records unless params[:sort]
+ case params[:sort]
+ when 'new'
+ records.order(created_at: :desc)
+ when 'old'
+ records.order(:created_at)
+ when 'atoz'
+ records.order(:name)
+ when 'ztoa'
+ records.order(name: :desc)
+ else
+ records
+ end
+ end
+
+ def projects_datatable(params)
+ per_page = params[:length] == '-1' ? 10 : params[:length].to_i
+ page = params[:start] ? (params[:start].to_i / per_page) + 1 : 1
+ records = fetch_dt_records
+ records = records.where(archived: true) if params[:archived] == 'true'
+ records = records.where(archived: false) if params[:archived] == 'false'
+ search_value = params.dig(:search, :value)
+ records = search(records, search_value) if search_value.present?
+ sort(records, params).page(page).per(per_page)
+ end
+
+ private
+
+ def fetch_records
+ due_modules =
+ MyModule.active
+ .overdue
+ .or(MyModule.one_day_prior)
+ .distinct
+ .joins(experiment: :project)
+ .joins('LEFT OUTER JOIN user_projects ON '\
+ 'user_projects.project_id = projects.id')
+ .left_outer_joins(:user_my_modules)
+ .where('user_my_modules.user_id = :user_id '\
+ 'OR (user_projects.role = 0 '\
+ 'AND user_projects.user_id = :user_id)', user_id: @user.id)
+ .select('my_modules.id', 'my_modules.experiment_id')
+ projects = @team.projects.joins(
+ 'LEFT OUTER JOIN experiments ON experiments.project_id = projects.id '\
+ 'AND experiments.archived = false'
+ ).joins(
+ "LEFT OUTER JOIN (#{due_modules.to_sql}) due_modules "\
+ "ON due_modules.experiment_id = experiments.id"
+ ).left_outer_joins(:user_projects, :project_comments)
+
+ # Only admins see all projects of the team
+ unless @user.is_admin_of_team?(@team)
+ projects = projects.where(
+ 'visibility = 1 OR user_projects.user_id = :user_id', user_id: @user.id
+ )
+ end
+ projects = projects
+ .select('projects.*')
+ .select('COUNT(DISTINCT user_projects.id) AS user_count')
+ .select('COUNT(DISTINCT comments.id) AS comment_count')
+ .select('COUNT(DISTINCT due_modules.id) AS notification_count')
+ .group('projects.id')
+ .limit(1_000_000)
+ Project.from(projects, 'projects')
+ end
+
+ def fetch_dt_records
+ projects = @team.projects.joins(
+ 'LEFT OUTER JOIN user_projects ON user_projects.project_id = projects.id'
+ ).joins(
+ 'LEFT OUTER JOIN experiments ON experiments.project_id = projects.id'\
+ ' AND experiments.archived = projects.archived'
+ ).joins(
+ 'LEFT OUTER JOIN my_modules ON my_modules.experiment_id = experiments.id'\
+ ' AND my_modules.archived = projects.archived'
+ )
+
+ # Only admins see all projects of the team
+ unless @user.is_admin_of_team?(@team)
+ projects = projects.where(
+ 'visibility = 1 OR user_projects.user_id = :user_id', user_id: @user.id
+ )
+ end
+ projects = projects
+ .select('projects.*')
+ .select('COUNT(DISTINCT user_projects.id) AS user_count')
+ .select('COUNT(DISTINCT experiments.id) AS experiment_count')
+ .select('COUNT(DISTINCT my_modules.id) AS task_count')
+ .group('projects.id')
+ Project.from(projects, 'projects')
+ end
+
+ def search(records, value)
+ records.where_attributes_like('projects.name', value)
+ end
+
+ def sortable_columns
+ {
+ '1' => 'projects.archived',
+ '2' => 'projects.name',
+ '3' => 'projects.created_at',
+ '4' => 'projects.visibility',
+ '5' => 'projects.user_count',
+ '6' => 'projects.experiment_count',
+ '7' => 'projects.task_count'
+ }
+ end
+
+ def sort(records, params)
+ order = params[:order]&.values&.first
+ if order
+ dir = order[:dir] == 'DESC' ? 'DESC' : 'ASC'
+ column_index = order[:column]
+ else
+ dir = 'ASC'
+ column_index = '1'
+ end
+ sort_column = sortable_columns[column_index]
+ sort_column ||= sortable_columns['1']
+ records.order("#{sort_column} #{dir}")
+ end
+end
diff --git a/app/views/projects/index.json.jbuilder b/app/views/projects/index.json.jbuilder
new file mode 100644
index 000000000..ed9ca1a82
--- /dev/null
+++ b/app/views/projects/index.json.jbuilder
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+json.data do
+ json.array! @projects do |project|
+ json.set! 'id', project.id
+ json.set! 'archived', project.archived
+ json.set! 'name', project.name
+ json.set! 'created_at', I18n.l(project.created_at, format: :full)
+ json.set! 'visibility', if project.visibility == 'hidden'
+ '' +
+ I18n.t('projects.index.hidden')
+ else
+ '' +
+ I18n.t('projects.index.visible')
+ end
+ json.set! 'user_count', project.user_count
+ json.set! 'notification_count', project.notification_count
+ json.set! 'comment_count', project.comment_count
+ end
+end
diff --git a/app/views/projects/index_dt.json.jbuilder b/app/views/projects/index_dt.json.jbuilder
new file mode 100644
index 000000000..ccc92d125
--- /dev/null
+++ b/app/views/projects/index_dt.json.jbuilder
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+json.recordsTotal @projects.total_count
+json.recordsFiltered @projects.length
+json.data do
+ json.array! @projects do |project|
+ json.set! 'DT_RowId', project.id
+ json.set! '1', if project.archived
+ '' +
+ I18n.t('projects.index.archived')
+ else
+ '' +
+ I18n.t('projects.index.active')
+ end
+ json.set! '2', project.name
+ json.set! '3', I18n.l(project.created_at, format: :full)
+ json.set! '4', if project.visibility == 'hidden'
+ '' +
+ I18n.t('projects.index.hidden')
+ else
+ '' +
+ I18n.t('projects.index.visible')
+ end
+ json.set! '5', project.user_count
+ json.set! '6', project.experiment_count
+ json.set! '7', project.task_count
+ end
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8dee84d14..271c5a76f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -211,6 +211,10 @@ en:
index:
head_title: "Home"
archive: "Archive"
+ archived: "Archived"
+ active: "Active"
+ hidden: "Project members only"
+ visible: "All team members"
no_projects:
text: "You don't have any active projects."
title: "Please create your Project"
diff --git a/config/routes.rb b/config/routes.rb
index 785cd4805..e52433274 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -188,6 +188,7 @@ Rails.application.routes.draw do
end
get 'projects/archive', to: 'projects#archive', as: 'projects_archive'
+ get 'projects/index_dt', to: 'projects#index_dt', as: 'projects_index_dt'
resources :reports, only: :index
get 'reports/datatable', to: 'reports#datatable'