Merge pull request #746 from okriuchykhin/ok_SCI_1487

Improve import of repository records plus tsv file import fixes [SCI-1487]
This commit is contained in:
okriuchykhin 2017-07-21 11:01:31 +02:00 committed by GitHub
commit a49c35df9f
9 changed files with 79 additions and 56 deletions

View file

@ -59,6 +59,7 @@ gem 'sneaky-save', git: 'https://github.com/einzige/sneaky-save'
gem 'rails_autolink', '~> 1.1', '>= 1.1.6' gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
gem 'delayed_paperclip' gem 'delayed_paperclip'
gem 'rubyzip' gem 'rubyzip'
gem 'activerecord-import'
gem 'paperclip', '~> 4.3' # File attachment, image attachment library gem 'paperclip', '~> 4.3' # File attachment, image attachment library
gem 'aws-sdk', '~> 2.2.8' gem 'aws-sdk', '~> 2.2.8'

View file

@ -37,6 +37,8 @@ GEM
activemodel (= 4.2.5) activemodel (= 4.2.5)
activesupport (= 4.2.5) activesupport (= 4.2.5)
arel (~> 6.0) arel (~> 6.0)
activerecord-import (0.19.0)
activerecord (>= 3.2)
activesupport (4.2.5) activesupport (4.2.5)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
@ -338,6 +340,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
activerecord-import
ajax-datatables-rails (~> 0.3.1) ajax-datatables-rails (~> 0.3.1)
aspector aspector
auto_strip_attributes (~> 2.1) auto_strip_attributes (~> 2.1)

View file

@ -31,6 +31,7 @@
disabledOptions = $("option[disabled='disabled']"); disabledOptions = $("option[disabled='disabled']");
disabledOptions.removeAttr('disabled'); disabledOptions.removeAttr('disabled');
loadingRecords = true; loadingRecords = true;
$('#parse-records-modal').modal('hide');
animateSpinner(); animateSpinner();
}).on('ajax:success', function(ev, data, status) { }).on('ajax:success', function(ev, data, status) {
// Simply reload page to show flash and updated repository records list // Simply reload page to show flash and updated repository records list

View file

@ -18,7 +18,7 @@
$('#form-records-file').on('ajax:success', function(ev, data) { $('#form-records-file').on('ajax:success', function(ev, data) {
$('#modal-import-records').modal('hide'); $('#modal-import-records').modal('hide');
$(data.html).appendTo('body').promise().done(function() { $(data.html).appendTo('body').promise().done(function() {
$('#parse-records_modal') $('#parse-records-modal')
.modal('show') .modal('show')
.on('hidden.bs.modal', function() { .on('hidden.bs.modal', function() {
animateSpinner(); animateSpinner();

View file

@ -110,7 +110,7 @@ class Repository < ActiveRecord::Base
# Imports records # Imports records
def import_records(sheet, mappings, user) def import_records(sheet, mappings, user)
errors = false errors = false
custom_fields = [] columns = []
name_index = -1 name_index = -1
total_nr = 0 total_nr = 0
nr_of_added = 0 nr_of_added = 0
@ -118,54 +118,71 @@ class Repository < ActiveRecord::Base
mappings.each.with_index do |(_k, value), index| mappings.each.with_index do |(_k, value), index|
if value == '-1' if value == '-1'
# Fill blank space, so our indices stay the same # Fill blank space, so our indices stay the same
custom_fields << nil columns << nil
name_index = index name_index = index
else else
cf = repository_columns.find_by_id(value) columns << repository_columns.find_by_id(value)
custom_fields << cf
end end
end end
# Now we can iterate through record data and save stuff into db # Check for duplicate columns
(2..sheet.last_row).each do |i| col_compact = columns.compact
total_nr += 1 unless col_compact.map(&:id).uniq.length == col_compact.length
cell_error = false return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
record_row = RepositoryRow.new(name: sheet.row(i)[name_index], end
repository: self,
created_by: user,
last_modified_by: user)
unless record_row.valid? # Now we can iterate through record data and save stuff into db
errors = true transaction do
next (2..sheet.last_row).each do |i|
end total_nr += 1
sheet.row(i).each.with_index do |value, index| record_row = RepositoryRow.new(name: sheet.row(i)[name_index],
if custom_fields[index] && value repository: self,
rep_column = RepositoryTextValue.new( created_by: user,
data: value, last_modified_by: user)
created_by: user, record_row.transaction(requires_new: true) do
last_modified_by: user, unless record_row.save
repository_cell_attributes: { errors = true
repository_row: record_row, raise ActiveRecord::Rollback
repository_column: custom_fields[index] end
}
) row_cell_values = []
cell_error = true unless rep_column.save
sheet.row(i).each.with_index do |value, index|
if columns[index] && value
cell_value = RepositoryTextValue.new(
data: value,
created_by: user,
last_modified_by: user,
repository_cell_attributes: {
repository_row: record_row,
repository_column: columns[index]
}
)
cell = RepositoryCell.new(repository_row: record_row,
repository_column: columns[index],
value: cell_value)
cell.skip_on_import = true
cell_value.repository_cell = cell
unless cell.valid? && cell_value.valid?
errors = true
raise ActiveRecord::Rollback
end
row_cell_values << cell_value
end
end
if RepositoryTextValue.import(row_cell_values,
recursive: true,
validate: false).failed_instances.any?
errors = true
raise ActiveRecord::Rollback
end
nr_of_added += 1
end end
end end
if cell_error
errors = true
record_row.destroy
else
nr_of_added += 1
record_row.save
end
end end
if errors if errors
return { status: :error, return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
nr_of_added: nr_of_added,
total_nr: total_nr }
end end
{ status: :ok, nr_of_added: nr_of_added, total_nr: total_nr } { status: :ok, nr_of_added: nr_of_added, total_nr: total_nr }
end end
@ -176,13 +193,11 @@ class Repository < ActiveRecord::Base
case File.extname(filename) case File.extname(filename)
when '.csv' when '.csv'
Roo::CSV.new(file_path, extension: :csv) Roo::CSV.new(file_path, extension: :csv)
when '.tdv' when '.tsv'
Roo::CSV.new(file_path, nil, :ignore, csv_options: { col_sep: '\t' }) Roo::CSV.new(file_path, csv_options: { col_sep: "\t" })
when '.txt' when '.txt'
# This assumption is based purely on biologist's habits # This assumption is based purely on biologist's habits
Roo::CSV.new(file_path, csv_options: { col_sep: '\t' }) Roo::CSV.new(file_path, csv_options: { col_sep: "\t" })
when '.xls'
Roo::Excel.new(file_path)
when '.xlsx' when '.xlsx'
Roo::Excelx.new(file_path) Roo::Excelx.new(file_path)
else else

View file

@ -1,11 +1,16 @@
class RepositoryCell < ActiveRecord::Base class RepositoryCell < ActiveRecord::Base
attr_accessor :skip_on_import
belongs_to :repository_row belongs_to :repository_row
belongs_to :repository_column belongs_to :repository_column
belongs_to :value, polymorphic: true, dependent: :destroy belongs_to :value, polymorphic: true, dependent: :destroy
validates :repository_column, presence: true validates :repository_column, presence: true
validates :value, presence: true
validate :repository_column_data_type validate :repository_column_data_type
validates :repository_row, uniqueness: { scope: :repository_column } validates :repository_row,
uniqueness: { scope: :repository_column },
unless: :skip_on_import
private private

View file

@ -37,16 +37,14 @@ class Team < ActiveRecord::Base
end end
case File.extname(filename) case File.extname(filename)
when ".csv" then when '.csv' then
Roo::CSV.new(file_path, extension: :csv) Roo::CSV.new(file_path, extension: :csv)
when ".tdv" then when '.tsv' then
Roo::CSV.new(file_path, nil, :ignore, csv_options: {col_sep: "\t"}) Roo::CSV.new(file_path, csv_options: { col_sep: "\t" })
when ".txt" then when '.txt' then
# This assumption is based purely on biologist's habits # This assumption is based purely on biologist's habits
Roo::CSV.new(file_path, csv_options: {col_sep: "\t"}) Roo::CSV.new(file_path, csv_options: { col_sep: "\t" })
when ".xls" then when '.xlsx' then
Roo::Excel.new(file_path)
when ".xlsx" then
Roo::Excelx.new(file_path) Roo::Excelx.new(file_path)
else else
raise TypeError raise TypeError

View file

@ -1,5 +1,5 @@
<div class="modal fade" <div class="modal fade"
id="parse-records_modal" id="parse-records-modal"
aria-labelledby="parse-modal-title" aria-labelledby="parse-modal-title"
role="dialog"> role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">

View file

@ -951,7 +951,7 @@ en:
title: 'Import items' title: 'Import items'
modal_import: modal_import:
title: 'Import items' title: 'Import items'
notice: 'You may upload .csv file (comma separated) or tab separated file (.txt or .tdv) or Excel file (.xls, .xlsx). First row should include header names, followed by rows with sample data.' notice: 'You may upload .csv file (comma separated) or tab separated file (.txt or .tsv) or Excel file (.xlsx). First row should include header names, followed by rows with sample data.'
upload: 'Upload file' upload: 'Upload file'
js: js:
permission_error: "You don't have permission to edit this item." permission_error: "You don't have permission to edit this item."
@ -1009,7 +1009,7 @@ en:
sample_type: "Sample type:" sample_type: "Sample type:"
modal_import: modal_import:
title: "Import samples" title: "Import samples"
notice: "You may upload .csv file (comma separated) or tab separated file (.txt or .tdv) or Excel file (.xls, .xlsx). First row should include header names, followed by rows with sample data." notice: "You may upload .csv file (comma separated) or tab separated file (.txt or .tsv) or Excel file (.xlsx). First row should include header names, followed by rows with sample data."
no_header_name: 'No column name' no_header_name: 'No column name'
upload: "Upload file" upload: "Upload file"
modal_delete: modal_delete: