diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index b1724734b..a0bd1893f 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -185,6 +185,14 @@ class RepositoriesController < ApplicationController end end + def parse_sheet + byebug + # import_repository = ImportRepository.new(file: params, repository: params) + end + + def import_repository + end + private def load_vars diff --git a/app/helpers/permission_helper.rb b/app/helpers/permission_helper.rb index d2b264af5..4ef376015 100644 --- a/app/helpers/permission_helper.rb +++ b/app/helpers/permission_helper.rb @@ -1090,6 +1090,10 @@ module PermissionHelper is_normal_user_or_admin_of_team(repository.team) end + def can_import_repository_records(repository) + is_normal_user_or_admin_of_team(repository.team) + end + def can_edit_repository_records(repository) is_normal_user_or_admin_of_team(repository.team) end diff --git a/app/models/repository.rb b/app/models/repository.rb index b026bd047..61822bbc0 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -15,6 +15,28 @@ class Repository < ActiveRecord::Base validates :team, presence: true validates :created_by, presence: true + def open_spreadsheet(file) + filename = file.original_filename + file_path = file.path + + if file.class == Paperclip::Attachment && file.is_stored_on_s3? + fa = file.fetch + file_path = fa.path + end + generate_file(filename, file_path) + end + + def available_repository_fields + fields = {} + # First and foremost add sample name + fields['-1'] = I18n.t('samples.table.sample_name') + # Add all other custom columns + repository_columns.order(:created_at).each do |rc| + fields[rc.id] = rc.name + end + fields + end + def copy(created_by, name) new_repo = nil @@ -41,4 +63,24 @@ class Repository < ActiveRecord::Base # If everything is okay, return new_repo new_repo end + + private + + def generate_file(filename, file_path) + case File.extname(filename) + when '.csv' + Roo::CSV.new(file_path, extension: :csv) + when '.tdv' + Roo::CSV.new(file_path, nil, :ignore, csv_options: { col_sep: '\t' }) + when '.txt' + # This assumption is based purely on biologist's habits + Roo::CSV.new(file_path, csv_options: { col_sep: '\t' }) + when '.xls' + Roo::Excel.new(file_path) + when '.xlsx' + Roo::Excelx.new(file_path) + else + raise TypeError + end + end end diff --git a/app/services/import_repository.rb b/app/services/import_repository.rb new file mode 100644 index 000000000..91116170c --- /dev/null +++ b/app/services/import_repository.rb @@ -0,0 +1,377 @@ +class ImportRepository + + def initialize(options) + @file = options.fetch(:file) + @repository = options.fetch(:repository) + @sheet = @repository.open_spreadsheet(@file ) + end + + def data + # Get data (it will trigger any errors as well) + header = @sheet.row(1) + rows = [] + rows << Hash[[header, @sheet.row(2)].transpose] + + # Fill in fields for dropdown + available_fields = @repository.available_repository_fields.map do |name| + name.truncate(Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) + end + { header: header, rows: rows, available_fields: available_fields } + end + + def check_file_size + return unless @file.size > Constants::FILE_MAX_SIZE_MB.megabytes + respond_to do |format| + error = t('general.file.size_exceeded', + file_size: Constants::FILE_MAX_SIZE_MB) + + format.html do + flash[:alert] = error + redirect_to session.delete(:return_to) + end + format.json do + render json: { message: error }, + status: :unprocessable_entity + end + end + end + + def check_spreadsheet_rows(sheet) + if @sheet.last_row.between?(0, 1) + flash[:notice] = t('teams.parse_sheet.errors.empty_file') + redirect_to session.delete(:return_to) and return + end + end + + def generate_temp_file + # Save file for next step (importing) + @temp_file = TempFile.new( + session_id: session.id, + file: @file + ) + respond_to do |format| + if @temp_file.save + @temp_file.destroy_obsolete + # format.html + format.json do + render json: { + html: render_to_string({ + partial: 'samples/parse_samples_modal.html.erb' + }) + } + end + else + error = t('teams.parse_sheet.errors.temp_file_failure') + format.html do + flash[:alert] = error + redirect_to session.delete(:return_to) + end + format.json do + render json: { message: error }, + status: :unprocessable_entity + end + end + end + end + + def argument_error_callback + error = t('teams.parse_sheet.errors.invalid_file', + encoding: ''.encoding) + respond_to do |format| + format.html do + flash[:alert] = error + redirect_to session.delete(:return_to) + end + format.json do + render json: { message: error }, + status: :unprocessable_entity + end + end + end + + def type_error_callback + error = t('teams.parse_sheet.errors.invalid_extension') + respond_to do |format| + format.html do + flash[:alert] = error + redirect_to session.delete(:return_to) + end + format.json do + render json: {message: error}, + status: :unprocessable_entity + end + end + end + + def no_file_callback + error = t('teams.parse_sheet.errors.no_file_selected') + respond_to do |format| + format.html do + flash[:alert] = error + session[:return_to] ||= request.referer + redirect_to session.delete(:return_to) + end + format.json do + render json: { message: error }, + status: :unprocessable_entity + end + end + end + + # def import_repository + # session[:return_to] ||= request.referer + # + # respond_to do |format| + # if params[:file_id] + # @temp_file = TempFile.find_by_id(params[:file_id]) + # + # if @temp_file + # # Check if session_id is equal to prevent file stealing + # if @temp_file.session_id == session.id + # # Check if mappings exists or else we don't have anything to parse + # if params[:mappings] + # @sheet = Team.open_spreadsheet(@temp_file.file) + # + # # Check for duplicated values + # h1 = params[:mappings].clone.delete_if { |k, v| v.empty? } + # if h1.length == h1.invert.length + # + # # Check if there exist mapping for sample name (it's mandatory) + # if params[:mappings].has_value?("-1") + # result = @team.import_samples(@sheet, params[:mappings], current_user) + # nr_of_added = result[:nr_of_added] + # total_nr = result[:total_nr] + # + # if result[:status] == :ok + # # If no errors are present, redirect back + # # to samples table + # flash[:success] = t( + # "teams.import_samples.success_flash", + # nr: nr_of_added, + # samples: t( + # "teams.import_samples.sample", + # count: total_nr + # ) + # ) + # @temp_file.destroy + # format.html { + # redirect_to session.delete(:return_to) + # } + # format.json { + # flash.keep(:success) + # render json: { status: :ok } + # } + # else + # # Otherwise, also redirect back, + # # but display different message + # flash[:alert] = t( + # "teams.import_samples.partial_success_flash", + # nr: nr_of_added, + # samples: t( + # "teams.import_samples.sample", + # count: total_nr + # ) + # ) + # @temp_file.destroy + # format.html { + # redirect_to session.delete(:return_to) + # } + # format.json { + # flash.keep(:alert) + # render json: { status: :unprocessable_entity } + # } + # end + # else + # # This is currently the only AJAX error response + # flash_alert = t( + # "teams.import_samples.errors.no_sample_name") + # format.html { + # flash[:alert] = flash_alert + # redirect_to session.delete(:return_to) + # } + # format.json { + # render json: { + # html: render_to_string({ + # partial: "parse_error.html.erb", + # locals: { error: flash_alert } + # }) + # }, + # status: :unprocessable_entity + # } + # end + # else + # # This code should never execute unless user tampers with + # # JS (selects same column in more than one dropdown) + # flash_alert = t( + # "teams.import_samples.errors.duplicated_values") + # format.html { + # flash[:alert] = flash_alert + # redirect_to session.delete(:return_to) + # } + # format.json { + # render json: { + # html: render_to_string({ + # partial: "parse_error.html.erb", + # locals: { error: flash_alert } + # }) + # }, + # status: :unprocessable_entity + # } + # end + # else + # @temp_file.destroy + # flash[:alert] = t( + # "teams.import_samples.errors.no_data_to_parse") + # format.html { + # redirect_to session.delete(:return_to) + # } + # format.json { + # flash.keep(:alert) + # render json: { status: :unprocessable_entity } + # } + # end + # else + # @temp_file.destroy + # flash[:alert] = t( + # "teams.import_samples.errors.session_expired") + # format.html { + # redirect_to session.delete(:return_to) + # } + # format.json { + # flash.keep(:alert) + # render json: { status: :unprocessable_entity } + # } + # end + # else + # # No temp file to begin with, so no need to destroy it + # flash[:alert] = t( + # "teams.import_samples.errors.temp_file_not_found") + # format.html { + # redirect_to session.delete(:return_to) + # } + # format.json { + # flash.keep(:alert) + # render json: { status: :unprocessable_entity } + # } + # end + # else + # flash[:alert] = t( + # "teams.import_samples.errors.temp_file_not_found") + # format.html { + # redirect_to session.delete(:return_to) + # } + # format.json { + # flash.keep(:alert) + # render json: { status: :unprocessable_entity } + # } + # end + # end + # end + # + # def parse_sheet + # session[:return_to] ||= request.referer + # + # respond_to do |format| + # if params[:file] + # begin + # + # if params[:file].size > Constants::FILE_MAX_SIZE_MB.megabytes + # error = t 'general.file.size_exceeded', + # file_size: Constants::FILE_MAX_SIZE_MB + # + # format.html { + # flash[:alert] = error + # redirect_to session.delete(:return_to) + # } + # format.json { + # render json: {message: error}, + # status: :unprocessable_entity + # } + # + # else + # sheet = Team.open_spreadsheet(params[:file]) + # + # # Check if we actually have any rows (last_row > 1) + # if sheet.last_row.between?(0, 1) + # flash[:notice] = t( + # "teams.parse_sheet.errors.empty_file") + # redirect_to session.delete(:return_to) and return + # end + # + # # Get data (it will trigger any errors as well) + # @header = sheet.row(1) + # @rows = []; + # @rows << Hash[[@header, sheet.row(2)].transpose] + # + # # Fill in fields for dropdown + # @available_fields = @team.get_available_sample_fields + # # Truncate long fields + # @available_fields.update(@available_fields) do |_k, v| + # v.truncate(Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) + # end + # + # # Save file for next step (importing) + # @temp_file = TempFile.new( + # session_id: session.id, + # file: params[:file] + # ) + # + # if @temp_file.save + # @temp_file.destroy_obsolete + # # format.html + # format.json { + # render :json => { + # :html => render_to_string({ + # :partial => "samples/parse_samples_modal.html.erb" + # }) + # } + # } + # else + # error = t("teams.parse_sheet.errors.temp_file_failure") + # format.html { + # flash[:alert] = error + # redirect_to session.delete(:return_to) + # } + # format.json { + # render json: {message: error}, + # status: :unprocessable_entity + # } + # end + # end + # rescue ArgumentError, CSV::MalformedCSVError + # error = t('teams.parse_sheet.errors.invalid_file', + # encoding: ''.encoding) + # format.html { + # flash[:alert] = error + # redirect_to session.delete(:return_to) + # } + # format.json { + # render json: {message: error}, + # status: :unprocessable_entity + # } + # rescue TypeError + # error = t("teams.parse_sheet.errors.invalid_extension") + # format.html { + # flash[:alert] = error + # redirect_to session.delete(:return_to) + # } + # format.json { + # render json: {message: error}, + # status: :unprocessable_entity + # } + # end + # else + # error = t("teams.parse_sheet.errors.no_file_selected") + # format.html { + # flash[:alert] = error + # session[:return_to] ||= request.referer + # redirect_to session.delete(:return_to) + # } + # format.json { + # render json: {message: error}, + # status: :unprocessable_entity + # } + # end + # end + # end +end diff --git a/app/views/repositories/_import_repository_records.html.erb b/app/views/repositories/_import_repository_records.html.erb new file mode 100644 index 000000000..09cdac007 --- /dev/null +++ b/app/views/repositories/_import_repository_records.html.erb @@ -0,0 +1,23 @@ + diff --git a/app/views/repositories/_repository.html.erb b/app/views/repositories/_repository.html.erb index 3d26baaf2..0318db156 100644 --- a/app/views/repositories/_repository.html.erb +++ b/app/views/repositories/_repository.html.erb @@ -11,6 +11,12 @@ <% end %> + <% if can_import_repository_records(repository) %> + + <% end %>