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'