diff --git a/app/assets/javascripts/projects/index.js b/app/assets/javascripts/projects/index.js index 9e1605d9e..c7c76f486 100644 --- a/app/assets/javascripts/projects/index.js +++ b/app/assets/javascripts/projects/index.js @@ -27,6 +27,10 @@ var projectActionsModalBody = null; var projectActionsModalFooter = null; + var exportProjectsModal = null; + var exportProjectsModalHeader = null; + var exportProjectsModalBody = null; + var projectsViewMode = 'cards'; var projectsViewFilter = $('.projects-view-filter.active').data('filter'); var projectsViewFilterChanged = false; @@ -210,6 +214,44 @@ }); } + /** + * Initialize the JS for export projects modal to work. + */ + function initExportProjectsModal() { + $exportProjectsBtn = $('#export-projects-button') + $exportProjectsBtn.click(function() { + + // Load HTML to refresh users list + $.ajax({ + url: $exportProjectsBtn.data('export-projects-url'), + type: 'POST', + dataType: 'json', + data: { + project_ids: selectedProjects + }, + success: function(data) { + // Update modal title + exportProjectsModalHeader.html(data.title); + + // Set modal body + exportProjectsModalBody.html(data.html); + + // Show the modal + exportProjectsModal.modal('show'); + }, + error: function() { + // TODO + } + }); + }); + + // Remove modal content when modal window is closed. + exportProjectsModal.on('hidden.bs.modal', function() { + exportProjectsModalHeader.html(''); + exportProjectsModalBody.html(''); + }); + } + // Initialize reloading manage user modal content after posting new // user. @@ -295,10 +337,15 @@ projectActionsModalBody = projectActionsModal.find('.modal-body'); projectActionsModalFooter = projectActionsModal.find('.modal-footer'); + exportProjectsModal = $('#export-projects-modal'); + exportProjectsModalHeader = exportProjectsModal.find('.modal-title'); + exportProjectsModalBody = exportProjectsModal.find('.modal-body'); + updateSelectedCards(); initNewProjectModal(); initEditProjectModal(); initManageUsersModal(); + initExportProjectsModal(); Comments.initCommentOptions('ul.content-comments', true); Comments.initEditComments('.panel-project .tab-content'); Comments.initDeleteComments('.panel-project .tab-content'); diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb index b3a363189..81427015b 100644 --- a/app/controllers/teams_controller.rb +++ b/app/controllers/teams_controller.rb @@ -1,11 +1,11 @@ class TeamsController < ApplicationController - before_action :load_vars, only: %i(:parse_sheet :import_samples - :export_samples :export_all) + before_action :load_vars, only: %i(parse_sheet import_samples + export_samples export_projects) before_action :check_create_samples_permissions, only: %i(parse_sheet import_samples) before_action :check_view_samples_permission, only: [:export_samples] - before_action :check_export_all_permission, only: [:export_all] + before_action :check_export_projects_permissions, only: [:export_projects] def parse_sheet session[:return_to] ||= request.referer @@ -225,9 +225,36 @@ class TeamsController < ApplicationController redirect_back(fallback_location: root_path) end - def export_all - if export_params[:project_ids] - TeamZipExporter.generate_zip(export_params, @team, current_user) + def export_projects + if export_projects_params[:project_ids] + # Check if user has enough requests for the day + limit = ENV['EXPORT_ALL_LIMIT_24_HOURS'].to_i || 3 + if current_user.export_vars['num_of_export_all_last_24_hours'] >= limit + render json: { + html: render_to_string( + partial: 'projects/export/error.html.erb', + locals: { limit: limit } + ), + title: t('projects.export_projects.modal_title_error') + } + else + current_user.export_vars['num_of_export_all_last_24_hours'] += 1 + current_user.save + + ids = generate_export_projects_zip + curr_num = current_user.export_vars['num_of_export_all_last_24_hours'] + + render json: { + html: render_to_string( + partial: 'projects/export/success.html.erb', + locals: { num_projects: ids.length, + limit: limit, + num_of_requests_left: limit - curr_num } + ), + title: t('projects.export_projects.modal_title_success') + } + end + return else flash[:alert] = t('zip_export.export_error') end @@ -270,6 +297,10 @@ class TeamsController < ApplicationController params.permit(sample_ids: [], header_ids: []).to_h end + def export_projects_params + params.permit(:id, project_ids: []).to_h + end + def check_create_samples_permissions render_403 unless can_create_samples?(@team) end @@ -280,13 +311,13 @@ class TeamsController < ApplicationController end end - def check_export_all_permission + def check_export_projects_permissions render_403 unless can_read_team?(@team) - if export_params[:project_ids] - projects = Project.where(id: export_params[:project_ids]) + if export_projects_params[:project_ids] + projects = Project.where(id: export_projects_params[:project_ids]) projects.each do |project| - render_403 unless can_read_project(current_user, project) + render_403 unless can_read_project?(current_user, project) end end end @@ -302,4 +333,20 @@ class TeamsController < ApplicationController :samples ) end + + def generate_export_projects_zip + ids = Project.where(id: export_projects_params[:project_ids], + team_id: @team) + .index_by(&:id) + + options = { team: @team } + zip = TeamZipExport.create(user: current_user) + zip.generate_exportable_zip( + current_user, + ids, + :teams, + options + ) + ids + end end diff --git a/app/models/concerns/variables_model.rb b/app/models/concerns/variables_model.rb new file mode 100644 index 000000000..637161b8e --- /dev/null +++ b/app/models/concerns/variables_model.rb @@ -0,0 +1,22 @@ +module VariablesModel + extend ActiveSupport::Concern + + @@default_variables = HashWithIndifferentAccess.new + + included do + serialize :variables, JsonbHashSerializer + after_initialize :init_default_variables, if: :new_record? + end + + class_methods do + def default_variables(dfs) + @@default_variables.merge!(dfs) + end + end + + protected + + def init_default_variables + self.variables = @@default_variables + end +end diff --git a/app/models/user.rb b/app/models/user.rb index c124181ca..96cc4dc5e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,5 @@ class User < ApplicationRecord - include SearchableModel, SettingsModel + include SearchableModel, SettingsModel, VariablesModel include User::TeamRoles, User::ProjectRoles acts_as_token_authenticatable @@ -47,6 +47,14 @@ class User < ApplicationRecord } ) + store_accessor :variables, :export_vars + + default_variables( + export_vars: { + num_of_export_all_last_24_hours: 0 + } + ) + # Relations has_many :user_identities, inverse_of: :user has_many :user_teams, inverse_of: :user diff --git a/app/services/team_zip_exporter.rb b/app/services/team_zip_exporter.rb deleted file mode 100644 index a985f69f6..000000000 --- a/app/services/team_zip_exporter.rb +++ /dev/null @@ -1,19 +0,0 @@ -module TeamZipExporter - def self.generate_zip(params, team, current_user) - Rails.logger.info('Exporting team zip') - - project_ids = params[:project_ids] - ids = Project.where(id: project_ids, - team_id: team) - .index_by(&:id) - - options = { team: team } - zip = TeamZipExport.create(user: current_user) - zip.generate_exportable_zip( - current_user, - ids, - :team, - options - ) - end -end diff --git a/app/views/projects/export/_error.html.erb b/app/views/projects/export/_error.html.erb new file mode 100644 index 000000000..2f1bd5365 --- /dev/null +++ b/app/views/projects/export/_error.html.erb @@ -0,0 +1,6 @@ +

+ It looks like you have exceeded your daily export limit. The number of exports is limited to <%= limit %> requests per day - you currently have 0 export requests left. +

+ +

Please repeat the desired action tomorrow, when your daily limit will rise back to <%= limit %> export requests. +

diff --git a/app/views/projects/export/_success.html.erb b/app/views/projects/export/_success.html.erb new file mode 100644 index 000000000..12f05de3e --- /dev/null +++ b/app/views/projects/export/_success.html.erb @@ -0,0 +1,12 @@ +

Your export request for <%= num_projects %> projects in <%= @team.name %> team is being processed and will soon be exported in a .zip file format. It may take anywhere from 5 minutes up to several hours, depending on the file size. Please also note, that any new data in the projects from this point will not be included in the exported file. +

+ +

+ When it's ready, you will receive a confirmation e-mail and the link to your file will appear in your SciNote notifications. For security reasons, the link will expire in 7 days. +

+ +

+ + Please note that the number of exports is limited to <%= limit %> requests per day - you currently have <%= num_of_requests_left %> more. + +

diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index 402d85ba1..0a57952b4 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -77,6 +77,23 @@ + + +
@@ -115,6 +132,14 @@
+ + + diff --git a/config/locales/en.yml b/config/locales/en.yml index cc3726d36..65fe4bee2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -283,6 +283,10 @@ en: invite_users_link: "Invite users" invite_users_details: "to team %{team}." contact_admins: "To invite additional users to team %{team}, contact its administrator/s." + export_projects: + export_button: "Export projects..." + modal_title_success: 'Your export is being prepared' + modal_title_error: 'Your export is denied' table: status: "Status" name: "Project name" diff --git a/config/routes.rb b/config/routes.rb index 9cc9295f4..0c70c2a81 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -178,6 +178,7 @@ Rails.application.routes.draw do # post 'import_samples' # post 'export_samples' post 'export_repository', to: 'repositories#export_repository' + post 'export_projects' # Used for atwho (smart annotations) get 'atwho_users', to: 'at_who#users' get 'atwho_repositories', to: 'at_who#repositories' diff --git a/db/migrate/20180930205254_add_variables_to_users.rb b/db/migrate/20180930205254_add_variables_to_users.rb new file mode 100644 index 000000000..9b40123fa --- /dev/null +++ b/db/migrate/20180930205254_add_variables_to_users.rb @@ -0,0 +1,19 @@ +class AddVariablesToUsers < ActiveRecord::Migration[5.1] + def up + add_column :users, :variables, :jsonb, default: {}, null: false + + User.find_each do |user| + variables = { + export_vars: { + num_of_export_all_last_24_hours: 0 + } + } + + user.update(variables: variables) + end + end + + def down + remove_column :users, :variables, :jsonb + end +end diff --git a/db/schema.rb b/db/schema.rb index ce0aa9260..f99bbd8b0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180524091143) do +ActiveRecord::Schema.define(version: 20180930205254) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -786,6 +786,7 @@ ActiveRecord::Schema.define(version: 20180524091143) do t.integer "current_team_id" t.string "authentication_token", limit: 30 t.jsonb "settings", default: {}, null: false + t.jsonb "variables", default: {}, null: false t.index "trim_html_tags((full_name)::text) gin_trgm_ops", name: "index_users_on_full_name", using: :gin t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true