Merge branch 'master' into rails-5.1

Conflicts:
	Gemfile.lock
	app/datatables/repository_datatable.rb
	app/models/repository_cell.rb
This commit is contained in:
Luka Murn 2017-08-07 08:45:53 +02:00
commit ae5bccf709
36 changed files with 1332 additions and 1115 deletions

View file

@ -51,7 +51,7 @@ gem 'ajax-datatables-rails', '~> 0.3.1'
gem 'commit_param_routing' # Enables different submit actions in the same form to route to different actions in controller gem 'commit_param_routing' # Enables different submit actions in the same form to route to different actions in controller
gem 'kaminari' gem 'kaminari'
gem "i18n-js", ">= 3.0.0.rc11" # Localization in javascript files gem "i18n-js", ">= 3.0.0.rc11" # Localization in javascript files
gem 'roo', '~> 2.1.0' # Spreadsheet parser gem 'roo', '~> 2.7.1' # Spreadsheet parser
gem 'wicked_pdf' gem 'wicked_pdf'
gem 'silencer' # Silence certain Rails logs gem 'silencer' # Silence certain Rails logs
gem 'wkhtmltopdf-heroku' gem 'wkhtmltopdf-heroku'
@ -64,6 +64,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', '~> 5.1' # File attachment, image attachment library gem 'paperclip', '~> 5.1' # File attachment, image attachment library
gem 'aws-sdk', '~> 2' gem 'aws-sdk', '~> 2'

View file

@ -7,7 +7,7 @@ GIT
GIT GIT
remote: https://github.com/einzige/sneaky-save remote: https://github.com/einzige/sneaky-save
revision: e7c77674abe74d598dfd58db7c680dd85936f207 revision: 7e7596720e76a3c243042be2f5f916525b143a54
specs: specs:
sneaky-save (0.1.2) sneaky-save (0.1.2)
activerecord (>= 3.2.0) activerecord (>= 3.2.0)
@ -56,6 +56,8 @@ GEM
activemodel (= 5.1.1) activemodel (= 5.1.1)
activesupport (= 5.1.1) activesupport (= 5.1.1)
arel (~> 8.0) arel (~> 8.0)
activerecord-import (0.19.1)
activerecord (>= 3.2)
activesupport (5.1.1) activesupport (5.1.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7) i18n (~> 0.7)
@ -70,26 +72,28 @@ GEM
ast (2.3.0) ast (2.3.0)
auto_strip_attributes (2.1.0) auto_strip_attributes (2.1.0)
activerecord (>= 3.0) activerecord (>= 3.0)
autoprefixer-rails (7.1.1.2) autoprefixer-rails (7.1.2.4)
execjs execjs
autosize-rails (1.18.17) autosize-rails (1.18.17)
rails (>= 3.1) rails (>= 3.1)
awesome_print (1.8.0) awesome_print (1.8.0)
aws-sdk (2.2.37) aws-sdk (2.10.21)
aws-sdk-resources (= 2.2.37) aws-sdk-resources (= 2.10.21)
aws-sdk-core (2.2.37) aws-sdk-core (2.10.21)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-resources (2.2.37) aws-sdk-resources (2.10.21)
aws-sdk-core (= 2.2.37) aws-sdk-core (= 2.10.21)
aws-sigv4 (1.0.1)
babel-source (5.8.35) babel-source (5.8.35)
babel-transpiler (0.7.0) babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6) babel-source (>= 4.0, < 6)
execjs (~> 2.0) execjs (~> 2.0)
base62 (1.0.0) base62 (1.0.0)
bcrypt (3.1.11) bcrypt (3.1.11)
better_errors (2.1.1) better_errors (2.3.0)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubis (>= 2.6.6) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
@ -102,9 +106,9 @@ GEM
bootstrap_form (2.7.0) bootstrap_form (2.7.0)
builder (3.2.3) builder (3.2.3)
byebug (9.0.6) byebug (9.0.6)
capybara (2.14.4) capybara (2.15.1)
addressable addressable
mime-types (>= 1.16) mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
@ -169,15 +173,14 @@ GEM
devise (>= 4.0.0) devise (>= 4.0.0)
diff-lcs (1.3) diff-lcs (1.3)
docile (1.1.5) docile (1.1.5)
erubi (1.6.0) erubi (1.6.1)
erubis (2.7.0)
execjs (2.7.0) execjs (2.7.0)
factory_girl (4.8.0) factory_girl (4.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.8.0) factory_girl_rails (4.8.0)
factory_girl (~> 4.8.0) factory_girl (~> 4.8.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faker (1.7.3) faker (1.8.4)
i18n (~> 0.5) i18n (~> 0.5)
ffi (1.9.18) ffi (1.9.18)
figaro (1.1.1) figaro (1.1.1)
@ -188,8 +191,8 @@ GEM
globalid (0.4.0) globalid (0.4.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
hammerjs-rails (2.0.4) hammerjs-rails (2.0.4)
i18n (0.8.4) i18n (0.8.6)
i18n-js (3.0.0) i18n-js (3.0.1)
i18n (~> 0.6, >= 0.6.6) i18n (~> 0.6, >= 0.6.6)
introjs-rails (1.0.0) introjs-rails (1.0.0)
sass-rails (>= 3.2) sass-rails (>= 3.2)
@ -236,17 +239,18 @@ GEM
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.2) mimemagic (0.3.2)
mini_mime (0.1.3)
mini_portile2 (2.2.0) mini_portile2 (2.2.0)
minitest (5.10.2) minitest (5.10.3)
momentjs-rails (2.17.1) momentjs-rails (2.17.1)
railties (>= 3.1) railties (>= 3.1)
multi_json (1.12.1) multi_json (1.12.1)
multi_test (0.1.2) multi_test (0.1.2)
nested_form_fields (0.8) nested_form_fields (0.8.1)
coffee-rails (>= 3.2.1) coffee-rails (>= 3.2.1)
jquery-rails jquery-rails
rails (>= 3.2.0) rails (>= 3.2.0)
newrelic_rpm (4.2.0.334) newrelic_rpm (4.3.0.335)
nio4r (2.1.0) nio4r (2.1.0)
nokogiri (1.8.0) nokogiri (1.8.0)
mini_portile2 (~> 2.2.0) mini_portile2 (~> 2.2.0)
@ -260,7 +264,7 @@ GEM
cocaine (~> 0.5.5) cocaine (~> 0.5.5)
mime-types mime-types
mimemagic (~> 0.3.0) mimemagic (~> 0.3.0)
parallel (1.11.2) parallel (1.12.0)
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
pg (0.21.0) pg (0.21.0)
@ -318,7 +322,7 @@ GEM
rainbow (2.2.2) rainbow (2.2.2)
rake rake
rake (12.0.0) rake (12.0.0)
rb-fsevent (0.9.8) rb-fsevent (0.10.2)
rb-inotify (0.9.10) rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2) ffi (>= 0.5.0, < 2)
rdoc (4.3.0) rdoc (4.3.0)
@ -332,7 +336,7 @@ GEM
lazy_priority_queue (~> 0.1.0) lazy_priority_queue (~> 0.1.0)
stream (~> 0.5.0) stream (~> 0.5.0)
rkelly-remix (0.0.7) rkelly-remix (0.0.7)
roo (2.1.1) roo (2.7.1)
nokogiri (~> 1) nokogiri (~> 1)
rubyzip (~> 1.1, < 2.0.0) rubyzip (~> 1.1, < 2.0.0)
rspec-core (3.6.0) rspec-core (3.6.0)
@ -367,7 +371,7 @@ GEM
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1) nokogumbo (~> 1.4.1)
sass (3.4.24) sass (3.4.25)
sass-rails (5.0.6) sass-rails (5.0.6)
railties (>= 4.0.0, < 6) railties (>= 4.0.0, < 6)
sass (~> 3.1) sass (~> 3.1)
@ -380,7 +384,7 @@ GEM
sdoc (0.4.2) sdoc (0.4.2)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0) rdoc (~> 4.0)
shoulda-matchers (3.1.1) shoulda-matchers (3.1.2)
activesupport (>= 4.0.0) activesupport (>= 4.0.0)
silencer (1.0.1) silencer (1.0.1)
simple_token_authentication (1.15.1) simple_token_authentication (1.15.1)
@ -413,8 +417,8 @@ GEM
stream (0.5) stream (0.5)
thor (0.19.4) thor (0.19.4)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.7) tilt (2.0.8)
tinymce-rails (4.6.4) tinymce-rails (4.6.5)
railties (>= 3.1.1) railties (>= 3.1.1)
turbolinks (5.0.1) turbolinks (5.0.1)
turbolinks-source (~> 5) turbolinks-source (~> 5)
@ -446,6 +450,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)
@ -506,7 +511,7 @@ DEPENDENCIES
recaptcha recaptcha
remotipart (~> 1.2) remotipart (~> 1.2)
rgl rgl
roo (~> 2.1.0) roo (~> 2.7.1)
rspec-rails rspec-rails
rubocop rubocop
ruby-graphviz (~> 1.2) ruby-graphviz (~> 1.2)
@ -536,4 +541,4 @@ RUBY VERSION
ruby 2.4.1p111 ruby 2.4.1p111
BUNDLED WITH BUNDLED WITH
1.15.1 1.15.3

1
VERSION Normal file
View file

@ -0,0 +1 @@
1.12.3

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();
@ -26,6 +26,11 @@
}); });
repositoryRecordsImporter(); repositoryRecordsImporter();
}); });
}).on('ajax:error', function(ev, data) {
$(this).find('.form-group').addClass('has-error');
$(this).find('.form-group').find('.help-block').remove();
$(this).find('.form-group').append("<span class='help-block'>" +
data.responseJSON.message + '</span>');
}); });
} }
@ -41,8 +46,10 @@
success: function (data) { success: function (data) {
var tabBody = $(pane.context.hash).find(".tab-content-body"); var tabBody = $(pane.context.hash).find(".tab-content-body");
tabBody.html(data.html); tabBody.html(data.html);
pane.tab('show').promise().done(function() { pane.tab('show').promise().done(function(el) {
initImportRecordsModal(); initImportRecordsModal();
RepositoryDatatable.destroy()
RepositoryDatatable.init(el.attr('data-repo-table'));
}); });
}, },
error: function (error) { error: function (error) {

View file

@ -0,0 +1,10 @@
(function() {
'use strict';
// initialze repository datatable
$(document).ready(function() {
RepositoryDatatable.destroy()
RepositoryDatatable.init($('#content').attr('data-repo-id'));
onClickToggleAssignedRecords();
});
})();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
(function() {
'use strict';
$(document).ready(function() {
$("[data-trigger='about-modal']").on('click', function() {
$('[data-role=about-modal]').modal('show');
});
});
})();

View file

@ -424,6 +424,11 @@ var SmartAnnotation = (function() {
init: init init: init
}; };
} }
// Closes the atwho popup * needed in repositories to close the popup
// if nothing is selected and the user leaves the form *
function closePopup() {
$('.atwho-header-res').find('.glyphicon-remove').click();
}
function initialize(field) { function initialize(field) {
var atWho = new setAtWho(field); var atWho = new setAtWho(field);
@ -432,7 +437,8 @@ var SmartAnnotation = (function() {
var publicApi = Object.freeze({ var publicApi = Object.freeze({
init: initialize, init: initialize,
preventPropagation: atwhoStopPropagation preventPropagation: atwhoStopPropagation,
closePopup: closePopup
}); });
return publicApi; return publicApi;

View file

@ -184,27 +184,27 @@ class RepositoriesController < ApplicationController
def parse_sheet def parse_sheet
repository = current_team.repositories.find_by_id(params[:id]) repository = current_team.repositories.find_by_id(params[:id])
parsed_file = ImportRepository::ParseRepository.new(
file: params[:file],
repository: repository,
session: session
)
respond_to do |format| unless params[:file]
unless params[:file] repository_response(t('teams.parse_sheet.errors.no_file_selected'))
repository_response(t('teams.parse_sheet.errors.no_file_selected')) return
return end
end begin
begin parsed_file = ImportRepository::ParseRepository.new(
if parsed_file.too_large? file: params[:file],
repository_response(t('general.file.size_exceeded', repository: repository,
file_size: Constants::FILE_MAX_SIZE_MB)) session: session
elsif parsed_file.empty? )
flash[:notice] = t('teams.parse_sheet.errors.empty_file') if parsed_file.too_large?
redirect_to back and return repository_response(t('general.file.size_exceeded',
else file_size: Constants::FILE_MAX_SIZE_MB))
@import_data = parsed_file.data elsif parsed_file.empty?
if parsed_file.generated_temp_file? flash[:notice] = t('teams.parse_sheet.errors.empty_file')
redirect_to back and return
else
@import_data = parsed_file.data
if parsed_file.generated_temp_file?
respond_to do |format|
format.json do format.json do
render json: { render json: {
html: render_to_string( html: render_to_string(
@ -212,16 +212,16 @@ class RepositoriesController < ApplicationController
) )
} }
end end
else
repository_response(t('teams.parse_sheet.errors.temp_file_failure'))
end end
else
repository_response(t('teams.parse_sheet.errors.temp_file_failure'))
end end
rescue ArgumentError, CSV::MalformedCSVError
repository_response(t('teams.parse_sheet.errors.invalid_file',
encoding: ''.encoding))
rescue TypeError
repository_response(t('teams.parse_sheet.errors.invalid_extension'))
end end
rescue ArgumentError, CSV::MalformedCSVError
repository_response(t('teams.parse_sheet.errors.invalid_file',
encoding: ''.encoding))
rescue TypeError
repository_response(t('teams.parse_sheet.errors.invalid_extension'))
end end
end end
@ -238,8 +238,9 @@ class RepositoriesController < ApplicationController
number_of_rows: status[:nr_of_added]) number_of_rows: status[:nr_of_added])
render json: {}, status: :ok render json: {}, status: :ok
else else
flash[:alert] = t('repositories.import_records.error_flash', flash[:alert] =
message: status[:errors]) t('repositories.import_records.partial_success_flash',
nr: status[:nr_of_added], total_nr: status[:total_nr])
render json: {}, status: :unprocessable_entity render json: {}, status: :unprocessable_entity
end end
else else
@ -320,13 +321,15 @@ class RepositoriesController < ApplicationController
end end
def repository_response(message) def repository_response(message)
format.html do respond_to do |format|
flash[:alert] = message format.html do
redirect_to :back flash[:alert] = message
end redirect_to :back
format.json do end
render json: { message: message }, format.json do
status: :unprocessable_entity render json: { message: message },
status: :unprocessable_entity
end
end end
end end

View file

@ -19,9 +19,7 @@ class RepositoryRowsController < ApplicationController
record.transaction do record.transaction do
record.name = record_params[:name] unless record_params[:name].blank? record.name = record_params[:name] unless record_params[:name].blank?
unless record.save errors[:default_fields] = record.errors.messages unless record.save
errors[:default_fields] = record.errors.messages
end
if params[:repository_cells] if params[:repository_cells]
params[:repository_cells].each do |key, value| params[:repository_cells].each do |key, value|
column = @repository.repository_columns.detect do |c| column = @repository.repository_columns.detect do |c|
@ -94,9 +92,7 @@ class RepositoryRowsController < ApplicationController
@record.transaction do @record.transaction do
@record.name = record_params[:name].blank? ? nil : record_params[:name] @record.name = record_params[:name].blank? ? nil : record_params[:name]
unless @record.save errors[:default_fields] = @record.errors.messages unless @record.save
errors[:default_fields] = @record.errors.messages
end
if params[:repository_cells] if params[:repository_cells]
params[:repository_cells].each do |key, value| params[:repository_cells].each do |key, value|
existing = @record.repository_cells.detect do |c| existing = @record.repository_cells.detect do |c|
@ -119,7 +115,7 @@ class RepositoryRowsController < ApplicationController
column = @repository.repository_columns.detect do |c| column = @repository.repository_columns.detect do |c|
c.id == key.to_i c.id == key.to_i
end end
value = RepositoryTextValue.new( cell_value = RepositoryTextValue.new(
data: value, data: value,
created_by: current_user, created_by: current_user,
last_modified_by: current_user, last_modified_by: current_user,
@ -128,15 +124,15 @@ class RepositoryRowsController < ApplicationController
repository_column: column repository_column: column
} }
) )
if value.save if cell_value.save
record_annotation_notification(@record, value.repository_cell) record_annotation_notification(@record,
cell_value.repository_cell)
else else
errors[:repository_cells] << { errors[:repository_cells] << {
"#{column.id}": value.errors.messages "#{column.id}": cell_value.errors.messages
} }
end end
end end
raise ActiveRecord::Rollback if errors[:repository_cells].any?
end end
# Clean up empty cells, not present in updated record # Clean up empty cells, not present in updated record
@record.repository_cells.each do |cell| @record.repository_cells.each do |cell|

View file

@ -36,8 +36,7 @@ class TeamsController < ApplicationController
# Get data (it will trigger any errors as well) # Get data (it will trigger any errors as well)
@header = sheet.row(1) @header = sheet.row(1)
@rows = []; @columns = sheet.row(2)
@rows << Hash[[@header, sheet.row(2)].transpose]
# Fill in fields for dropdown # Fill in fields for dropdown
@available_fields = @team.get_available_sample_fields @available_fields = @team.get_available_sample_fields

View file

@ -70,11 +70,11 @@ class WopiController < ActionController::Base
UserCanNotWriteRelative: true, UserCanNotWriteRelative: true,
CloseUrl: @close_url, CloseUrl: @close_url,
DownloadUrl: url_for(controller: 'assets', action: 'download', DownloadUrl: url_for(controller: 'assets', action: 'download',
id: @asset.id), id: @asset.id, host: ENV['WOPI_USER_HOST']),
HostEditUrl: url_for(controller: 'assets', action: 'edit', HostEditUrl: url_for(controller: 'assets', action: 'edit',
id: @asset.id), id: @asset.id, host: ENV['WOPI_USER_HOST']),
HostViewUrl: url_for(controller: 'assets', action: 'view', HostViewUrl: url_for(controller: 'assets', action: 'view',
id: @asset.id), id: @asset.id, host: ENV['WOPI_USER_HOST']),
BreadcrumbBrandName: @breadcrumb_brand_name, BreadcrumbBrandName: @breadcrumb_brand_name,
BreadcrumbBrandUrl: @breadcrumb_brand_url, BreadcrumbBrandUrl: @breadcrumb_brand_url,
BreadcrumbFolderName: @breadcrumb_folder_name, BreadcrumbFolderName: @breadcrumb_folder_name,
@ -82,7 +82,7 @@ class WopiController < ActionController::Base
} }
response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL'] response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL']
response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL'] response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL']
response.headers['X-WOPI-ServerVersion'] = Constants::APP_VERSION response.headers['X-WOPI-ServerVersion'] = Scinote::Application::VERSION
render json: msg and return render json: msg and return
end end
@ -286,21 +286,21 @@ class WopiController < ActionController::Base
if @protocol.in_module? if @protocol.in_module?
@close_url = protocols_my_module_url(@protocol.my_module, @close_url = protocols_my_module_url(@protocol.my_module,
only_path: false, only_path: false,
host: ENV['WOPI_BREADCRUMBS_HOST']) host: ENV['WOPI_USER_HOST'])
project = @protocol.my_module.experiment.project project = @protocol.my_module.experiment.project
@breadcrumb_brand_name = project.name @breadcrumb_brand_name = project.name
@breadcrumb_brand_url = project_url(project, @breadcrumb_brand_url = project_url(project,
only_path: false, only_path: false,
host: ENV['WOPI_BREADCRUMBS_HOST']) host: ENV['WOPI_USER_HOST'])
@breadcrumb_folder_name = @protocol.my_module.name @breadcrumb_folder_name = @protocol.my_module.name
else else
@close_url = protocols_url(only_path: false, @close_url = protocols_url(only_path: false,
host: ENV['WOPI_BREADCRUMBS_HOST']) host: ENV['WOPI_USER_HOST'])
@breadcrump_brand_name = 'Projects' @breadcrump_brand_name = 'Projects'
@breadcrumb_brand_url = root_url(only_path: false, @breadcrumb_brand_url = root_url(only_path: false,
host: ENV['WOPI_BREADCRUMBS_HOST']) host: ENV['WOPI_USER_HOST'])
@breadcrumb_folder_name = 'Protocol managament' @breadcrumb_folder_name = 'Protocol managament'
end end
@breadcrumb_folder_url = @close_url @breadcrumb_folder_url = @close_url
@ -310,12 +310,12 @@ class WopiController < ActionController::Base
@close_url = results_my_module_url(@my_module, @close_url = results_my_module_url(@my_module,
only_path: false, only_path: false,
host: ENV['WOPI_BREADCRUMBS_HOST']) host: ENV['WOPI_USER_HOST'])
@breadcrumb_brand_name = @my_module.experiment.project.name @breadcrumb_brand_name = @my_module.experiment.project.name
@breadcrumb_brand_url = project_url(@my_module.experiment.project, @breadcrumb_brand_url = project_url(@my_module.experiment.project,
only_path: false, only_path: false,
host: ENV['WOPI_BREADCRUMBS_HOST']) host: ENV['WOPI_USER_HOST'])
@breadcrumb_folder_name = @my_module.name @breadcrumb_folder_name = @my_module.name
@breadcrumb_folder_url = @close_url @breadcrumb_folder_url = @close_url
end end

View file

@ -152,7 +152,7 @@ class RepositoryDatatable < CustomDatatable
# Make mappings of custom columns, so we have same id for every column # Make mappings of custom columns, so we have same id for every column
i = 5 i = 5
@columns_mappings = {} @columns_mappings = {}
@repository.repository_columns.each do |column| @repository.repository_columns.order(:id).each do |column|
@columns_mappings[column.id] = i.to_s @columns_mappings[column.id] = i.to_s
i += 1 i += 1
end end
@ -230,13 +230,68 @@ class RepositoryDatatable < CustomDatatable
# Override default sort method if needed # Override default sort method if needed
def sort_records(records) def sort_records(records)
if sort_column(order_params) == ASSIGNED_SORT_COL if params[:order].present? && params[:order].length == 1
# If "assigned" column is sorted if sort_column(params[:order].values[0]) == ASSIGNED_SORT_COL
direction = sort_null_direction(order_params) # If "assigned" column is sorted when viewing assigned items
if @my_module return records if @my_module && params[:assigned] == 'assigned'
# Depending on the sort, order nulls first or # If "assigned" column is sorted
# nulls last on repository_cells association direction = sort_null_direction(params[:order].values[0])
return records if dt_params[:assigned] == 'assigned' if @my_module
# Depending on the sort, order nulls first or
# nulls last on repository_cells association
records.joins(
"LEFT OUTER JOIN my_module_repository_rows ON
(repository_rows.id = my_module_repository_rows.repository_row_id
AND (my_module_repository_rows.my_module_id = #{@my_module.id} OR
my_module_repository_rows.id IS NULL))"
).order("my_module_repository_rows.id NULLS #{direction}")
else
sort_assigned_records(records, params[:order].values[0]['dir'])
end
elsif sorting_by_custom_column
# Check if have to filter records first
# if params[:search].present? && params[:search][:value].present?
# # Couldn't force ActiveRecord to yield the same query as below because
# # Rails apparently forgets to join stuff in subqueries -
# # #justrailsthings
# conditions = build_conditions_for(params[:search][:value])
#
# filter_query = %(SELECT "samples"."id" FROM "samples"
# LEFT OUTER JOIN "sample_custom_fields" ON
# "sample_custom_fields"."sample_id" = "samples"."id"
# LEFT OUTER JOIN "users" ON "users"."id" = "repository_row"."user_id"
# WHERE "samples"."team_id" = #{@team.id} AND #{conditions.to_sql})
#
# records = records.where("samples.id IN (#{filter_query})")
# end
ci = sortable_displayed_columns[
params[:order].values[0][:column].to_i - 1
]
column_id = @columns_mappings.key((ci.to_i + 1).to_s)
dir = sort_direction(params[:order].values[0])
# Because repository records can have multiple custom cells,
# we first group them by samples.id and inside that group we sort them by column_id. Because
# we sort them ASC, sorted columns will be on top. Distinct then only
# takes the first row and cuts the rest of every group and voila we have
# 1 row for every sample, which are not sorted yet ...
# records = records.select('DISTINCT ON (repository_rows.id) *')
# .order("repository_rows.id, CASE WHEN repository_cells.repository_column_id = #{column_id} THEN 1 ELSE 2 END ASC")
# ... this little gem (pun intended) then takes the records query, sorts it again
# and paginates it. sq.t0_* are determined empirically and are crucial -
# imagine A -> B -> C transitive relation but where A and C are the
# same. Useless right? But not when you acknowledge that find_by_sql
# method does some funky stuff when your query spans multiple queries -
# Sample object might have id from SampleType, name from
# User ... chaos ensues basically. If something changes in db this might
# change.
# formated_date = (I18n.t 'time.formats.datatables_date').gsub!(/^\"|\"?$/, '')
# Sample.find_by_sql("SELECT sq.t0_r0 as id, sq.t0_r1 as name, to_char( sq.t0_r4, '#{ formated_date }' ) as created_at, sq.t0_r5, s, sq.t0_r2 as user_id, sq.custom_field_id FROM (#{records.to_sql})
# as sq ORDER BY CASE WHEN sq.custom_field_id = #{column_id} THEN 1 ELSE 2 END #{dir}, sq.value #{dir}
# LIMIT #{per_page} OFFSET #{offset}")
records.joins( records.joins(
"LEFT OUTER JOIN my_module_repository_rows ON "LEFT OUTER JOIN my_module_repository_rows ON
(repository_rows.id = my_module_repository_rows.repository_row_id (repository_rows.id = my_module_repository_rows.repository_row_id
@ -307,4 +362,21 @@ class RepositoryDatatable < CustomDatatable
@sortable_displayed_columns = sort_order @sortable_displayed_columns = sort_order
end end
def sort_assigned_records(records, direction)
assigned = records.joins(:my_module_repository_rows).distinct.pluck(:id)
unassigned = records.where.not(id: assigned).pluck(:id)
if direction == 'asc'
ids = assigned + unassigned
elsif direction == 'desc'
ids = unassigned + assigned
end
order_by_index = ActiveRecord::Base.send(
:sanitize_sql_array,
["position((',' || repository_rows.id || ',') in ?)",
ids.join(',') + ',']
)
records.order(order_by_index)
end
end end

View file

@ -0,0 +1,8 @@
module AddonsHelper
def list_all_addons
Rails::Engine
.subclasses
.select { |c| c.name.start_with?('Scinote') }
.map(&:parent)
end
end

View file

@ -1075,11 +1075,13 @@ module PermissionHelper
end end
def can_delete_column_in_repository(column) def can_delete_column_in_repository(column)
is_normal_user_or_admin_of_team(column.repository.team) column.created_by == current_user ||
is_admin_of_team(column.repository.team)
end end
def can_edit_column_in_repository(column) def can_edit_column_in_repository(column)
is_normal_user_or_admin_of_team(column.repository.team) column.created_by == current_user ||
is_admin_of_team(column.repository.team)
end end
def can_create_repository_records(repository) def can_create_repository_records(repository)

View file

@ -384,10 +384,17 @@ class Asset < ApplicationRecord
action = get_action(file_ext, action) action = get_action(file_ext, action)
if !action.nil? if !action.nil?
action_url = action.urlsrc action_url = action.urlsrc
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/, if ENV['WOPI_BUSINESS_USERS'] && ENV['WOPI_BUSINESS_USERS']=='true'
'IsLicensedUser=0&') action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/,
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER>/, 'IsLicensedUser=1&')
'IsLicensedUser=0') action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER>/,
'IsLicensedUser=1')
else
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER&>/,
'IsLicensedUser=0&')
action_url = action_url.gsub(/<IsLicensedUser=BUSINESS_USER>/,
'IsLicensedUser=0')
end
action_url = action_url.gsub(/<.*?=.*?>/, '') action_url = action_url.gsub(/<.*?=.*?>/, '')
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url( rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(

View file

@ -111,57 +111,82 @@ class Repository < ApplicationRecord
# Imports records # Imports records
def import_records(sheet, mappings, user) def import_records(sheet, mappings, user)
errors = [] errors = false
custom_fields = [] columns = []
name_index = -1 name_index = -1
total_nr = 0
nr_of_added = 0 nr_of_added = 0
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
# Check for duplicate columns
col_compact = columns.compact
unless col_compact.map(&:id).uniq.length == col_compact.length
return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
end
# Now we can iterate through record data and save stuff into db # Now we can iterate through record data and save stuff into db
(2..sheet.last_row).each do |i| transaction do
error = [] (2..sheet.last_row).each do |i|
record_row = RepositoryRow.new(name: sheet.row(i)[name_index], total_nr += 1
repository: self, record_row = RepositoryRow.new(name: sheet.row(i)[name_index],
created_by: user, repository: self,
last_modified_by: user) created_by: user,
last_modified_by: user)
record_row.transaction(requires_new: true) do
unless record_row.save
errors = true
raise ActiveRecord::Rollback
end
next unless record_row.valid? row_cell_values = []
sheet.row(i).each.with_index do |value, index|
if custom_fields[index] && value sheet.row(i).each.with_index do |value, index|
rep_column = RepositoryTextValue.new( if columns[index] && value
data: value, cell_value = RepositoryTextValue.new(
created_by: user, data: value,
last_modified_by: user, created_by: user,
repository_cell_attributes: { last_modified_by: user,
repository_row: record_row, repository_cell_attributes: {
repository_column: custom_fields[index] repository_row: record_row,
} repository_column: columns[index]
) }
error << rep_column.errors.messages unless rep_column.save )
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 error.any?
record_row.destroy
else
nr_of_added += 1
record_row.save
end
end end
if errors.count > 0 if errors
return { status: :error, errors: errors, nr_of_added: nr_of_added } return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
end end
{ status: :ok, nr_of_added: nr_of_added } { status: :ok, nr_of_added: nr_of_added, total_nr: total_nr }
end end
private private
@ -170,13 +195,11 @@ class Repository < ApplicationRecord
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,15 @@
class RepositoryCell < ApplicationRecord class RepositoryCell < ActiveRecord::Base
belongs_to :repository_row, optional: true attr_accessor :skip_on_import
belongs_to :repository_column, optional: true
belongs_to :value, polymorphic: true, dependent: :destroy, optional: true belongs_to :repository_row
belongs_to :repository_column
belongs_to :value, polymorphic: true, dependent: :destroy
validates :repository_column, presence: true validates :repository_column, 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

@ -43,16 +43,14 @@ class Team < ApplicationRecord
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
@ -71,7 +69,7 @@ class Team < ApplicationRecord
# -3 == sample_group # -3 == sample_group
# TODO: use constants # TODO: use constants
def import_samples(sheet, mappings, user) def import_samples(sheet, mappings, user)
errors = [] errors = false
nr_of_added = 0 nr_of_added = 0
total_nr = 0 total_nr = 0
@ -80,17 +78,17 @@ class Team < ApplicationRecord
sname_index = -1 sname_index = -1
stype_index = -1 stype_index = -1
sgroup_index = -1 sgroup_index = -1
mappings.each.with_index do |(k, v), i| mappings.each.with_index do |(_, v), i|
if v == "-1" if v == '-1'
# Fill blank space, so our indices stay the same # Fill blank space, so our indices stay the same
custom_fields << nil custom_fields << nil
sname_index = i sname_index = i
elsif v == "-2" elsif v == '-2'
custom_fields << nil custom_fields << nil
stype_index = i stype_index = i
elsif v == "-3" elsif v == '-3'
custom_fields << nil custom_fields << nil
sgroup_index = i sgroup_index = i
else else
cf = CustomField.find_by_id(v) cf = CustomField.find_by_id(v)
@ -99,87 +97,70 @@ class Team < ApplicationRecord
custom_fields << cf custom_fields << cf
end end
end end
# Now we can iterate through sample data and save stuff into db # Now we can iterate through sample data and save stuff into db
(2..sheet.last_row).each do |i| (2..sheet.last_row).each do |i|
error = []
total_nr += 1 total_nr += 1
sample = Sample.new(name: sheet.row(i)[sname_index],
team: self,
user: user)
sample = Sample.new( sample.transaction do
name: sheet.row(i)[sname_index], unless sample.valid?
team_id: id, errors = true
user: user raise ActiveRecord::Rollback
) end
if sample.save
sheet.row(i).each.with_index do |value, index| sheet.row(i).each.with_index do |value, index|
# We need to have sample saved before messing with custom fields (they
# need sample id)
if index == stype_index if index == stype_index
stype = SampleType.where(name: value, team_id: id).take stype = SampleType.where(name: value, team: self).take
if stype unless stype
sample.sample_type = stype stype = SampleType.new(name: value, team: self)
else unless stype.save
sample.create_sample_type( errors = true
name: value, raise ActiveRecord::Rollback
team_id: id
)
end
sample.save
elsif index == sgroup_index
sgroup = SampleGroup.where(name: value, team_id: id).take
if sgroup
sample.sample_group = sgroup
else
sample.create_sample_group(
name: value,
team_id: id
)
end
sample.save
elsif value and mappings[index.to_s].strip.present? and index != sname_index
if custom_fields[index]
# we're working with CustomField
scf = SampleCustomField.new(
sample_id: sample.id,
custom_field_id: custom_fields[index].id,
value: value
)
if !scf.save
error << scf.errors.messages
end end
else
# This custom_field does not exist
error << {"#{mappings[index]}": "Does not exists"}
end end
sample.sample_type = stype
elsif index == sgroup_index
sgroup = SampleGroup.where(name: value, team: self).take
unless sgroup
sgroup = SampleGroup.new(name: value, team: self)
unless sgroup.save
errors = true
raise ActiveRecord::Rollback
end
end
sample.sample_group = sgroup
elsif value && custom_fields[index]
# we're working with CustomField
scf = SampleCustomField.new(
sample: sample,
custom_field: custom_fields[index],
value: value
)
unless scf.valid?
errors = true
raise ActiveRecord::Rollback
end
sample.sample_custom_fields << scf
end end
end end
else if Sample.import([sample],
error << sample.errors.messages recursive: true,
end validate: false).failed_instances.any?
if error.present? errors = true
errors << { "#{i}": error} raise ActiveRecord::Rollback
else end
nr_of_added += 1 nr_of_added += 1
end end
end end
if errors.count > 0 then if errors
return { return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
status: :error,
errors: errors,
nr_of_added: nr_of_added,
total_nr: total_nr
}
else else
return { return { 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
end end

View file

@ -11,15 +11,14 @@ module ImportRepository
def data def data
# Get data (it will trigger any errors as well) # Get data (it will trigger any errors as well)
header = @sheet.row(1) header = @sheet.row(1)
rows = [] columns = @sheet.row(2)
rows << Hash[[header, @sheet.row(2)].transpose]
# Fill in fields for dropdown # Fill in fields for dropdown
@repository.available_repository_fields.transform_values! do |name| @repository.available_repository_fields.transform_values! do |name|
truncate(name, length: Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) truncate(name, length: Constants::NAME_TRUNCATION_LENGTH_DROPDOWN)
end end
@temp_file = TempFile.create(session_id: @session.id, file: @file) @temp_file = TempFile.create(session_id: @session.id, file: @file)
Data.new(header, Data.new(header,
rows, columns,
@repository.available_repository_fields, @repository.available_repository_fields,
@repository, @repository,
@temp_file) @temp_file)
@ -47,7 +46,7 @@ module ImportRepository
end end
Data = Struct.new( Data = Struct.new(
:header, :rows, :available_fields, :repository, :temp_file :header, :columns, :available_fields, :repository, :temp_file
) )
end end
end end

View file

@ -45,6 +45,9 @@
</div> </div>
</div> </div>
<!-- About us modal -->
<%= render "shared/about_modal" %>
<%= render "shared/navigation" %> <%= render "shared/navigation" %>
<div id="notifications"> <div id="notifications">

View file

@ -31,7 +31,8 @@
<% end %> <% end %>
</div> </div>
<div id="content"> <div id="content"
data-repo-id="#repository-table-<%= @repository.id %>">
<%= render partial: "repositories/repository_table", <%= render partial: "repositories/repository_table",
locals: { locals: {
repository: @repository, repository: @repository,
@ -40,3 +41,7 @@
} }
%> %>
</div> </div>
<%= stylesheet_link_tag 'datatables' %>
<%= javascript_include_tag 'repositories/repository_datatable' %>
<%= javascript_include_tag 'repositories/my_module_repository' %>

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">
@ -30,29 +30,31 @@
include_blank: t('teams.parse_sheet.do_not_include_column'), include_blank: t('teams.parse_sheet.do_not_include_column'),
hide_label: true) %> hide_label: true) %>
<br /> <br />
<% if th.length > Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %> <% if th.nil? %>
<div class="modal-tooltip"> <i><%= t('repositories.import_records.no_header_name') %></i>
<%= truncate(th, length: Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) %>
</div>
<% else %> <% else %>
<%= th %> <% if th.length > Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %>
<div class="modal-tooltip">
<%= truncate(th, length: Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) %>
</div>
<% else %>
<%= th %>
<% end %>
<% end %> <% end %>
</th> </th>
<% end %> <% end %>
</thead> </thead>
<tbody> <tbody>
<% @import_data.rows.each do |row| %> <tr>
<tr> <td>
<p><%= t('teams.parse_sheet.example_value') %></p>
</td>
<% @import_data.columns.each do |td| %>
<td> <td>
<p><%= t('teams.parse_sheet.example_value') %></p> <%= td %>
</td> </td>
<% row.each do |td| %> <% end %>
<td> </tr>
<%= td[1] %>
</td>
<% end %>
</tr>
<% end %>
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -1,5 +1,5 @@
<div class="repository-table"> <div class="repository-table">
<table id="repository-table" class="table" <table id="repository-table-<%= repository.id %>" class="table"
data-current-uri="<%= request.original_url %>" data-current-uri="<%= request.original_url %>"
data-repository-id="<%= repository.id %>" data-repository-id="<%= repository.id %>"
data-source="<%= repository_index_link %>" data-source="<%= repository_index_link %>"
@ -19,7 +19,7 @@
<th id="row-name"><%= t("repositories.table.row_name") %></th> <th id="row-name"><%= t("repositories.table.row_name") %></th>
<th id="added-on"><%= t("repositories.table.added_on") %></th> <th id="added-on"><%= t("repositories.table.added_on") %></th>
<th id="added-by"><%= t("repositories.table.added_by") %></th> <th id="added-by"><%= t("repositories.table.added_by") %></th>
<% repository.repository_columns.each do |column| %> <% repository.repository_columns.order(:id).each do |column| %>
<th class="repository-column" id="<%= column.id %>" <th class="repository-column" id="<%= column.id %>"
<%= 'data-editable' if can_edit_column_in_repository(column) %> <%= 'data-editable' if can_edit_column_in_repository(column) %>
<%= 'data-deletable' if can_delete_column_in_repository(column) %> <%= 'data-deletable' if can_delete_column_in_repository(column) %>
@ -35,6 +35,3 @@
<tbody></tbody> <tbody></tbody>
</table> </table>
</div> </div>
<%= stylesheet_link_tag 'datatables' %>
<%= javascript_include_tag('repositories/repository_datatable') %>

View file

@ -10,6 +10,7 @@
<li role="presentation"> <li role="presentation">
<a href="#custom_repo_<%= repo.id %>" <a href="#custom_repo_<%= repo.id %>"
data-toggle="tab" data-toggle="tab"
data-repo-table="#repository-table-<%= repo.id %>"
aria-controls="custom_repo_<%= repo.id %>" aria-controls="custom_repo_<%= repo.id %>"
data-url="<%=team_repository_show_tab_path(current_team, repo)%>" data-url="<%=team_repository_show_tab_path(current_team, repo)%>"
title="<%=repo.name%>"><%= truncate(repo.name, length: Constants::NAME_TRUNCATION_LENGTH) %></a> title="<%=repo.name%>"><%= truncate(repo.name, length: Constants::NAME_TRUNCATION_LENGTH) %></a>
@ -53,4 +54,6 @@
</div> </div>
<% end %> <% end %>
<%= stylesheet_link_tag 'datatables' %>
<%= javascript_include_tag 'repositories/repository_datatable' %>
<%= javascript_include_tag "repositories/index", "data-turbolinks-track" => true %> <%= javascript_include_tag "repositories/index", "data-turbolinks-track" => true %>

View file

@ -25,29 +25,31 @@
include_blank: t('teams.parse_sheet.do_not_include_column'), include_blank: t('teams.parse_sheet.do_not_include_column'),
hide_label: true) %> hide_label: true) %>
<br /> <br />
<% if th.length > Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %> <% if th.nil? %>
<div class="modal-tooltip"> <i><%= t('samples.modal_import.no_header_name') %></i>
<%= truncate(th, length: Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) %>
</div>
<% else %> <% else %>
<%= th %> <% if th.length > Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %>
<div class="modal-tooltip">
<%= truncate(th, length: Constants::NAME_TRUNCATION_LENGTH_DROPDOWN) %>
</div>
<% else %>
<%= th %>
<% end %>
<% end %> <% end %>
</th> </th>
<% end %> <% end %>
</thead> </thead>
<tbody> <tbody>
<% @rows.each do |row| %> <tr>
<tr> <td>
<p><%= t('teams.parse_sheet.example_value') %></p>
</td>
<% @columns.each do |td| %>
<td> <td>
<p><%= t('teams.parse_sheet.example_value') %></p> <%= td %>
</td> </td>
<% row.each do |td| %> <% end %>
<td> </tr>
<%= td[1] %>
</td>
<% end %>
</tr>
<% end %>
</tbody> </tbody>
</table> </table>
</div> </div>

View file

@ -0,0 +1,28 @@
<div class="modal" id="aboutModal" tabindex="-1" role="dialog" aria-labelledby="aboutModal" data-role="about-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><%= t('about.modal_title') %></h4>
</div>
<div class="modal-body">
<strong><%= t('about.core_version') %></strong>
<br />
<%= Scinote::Application::VERSION %>
<br />
<br />
<div data-hook="about-modal-addon-versions">
<strong><%= t('about.addon_versions') %></strong>
<br />
<% list_all_addons.each do |addon| %>
<%= "#{addon.name}:" %>
<br />
<%= addon::VERSION %>
<br />
<br />
<% end %>
</div>
</div>
</div>
</div>
</div>

View file

@ -19,7 +19,7 @@
<% if show_version %> <% if show_version %>
<%= image_tag('/images/logo.png', class: 'with-version', id: 'logo') %> <%= image_tag('/images/logo.png', class: 'with-version', id: 'logo') %>
<span class="version"> <span class="version">
<%= Constants::APP_VERSION %> <%= Scinote::Application::VERSION %>
</span> </span>
<% else %> <% else %>
<%= image_tag('/images/logo.png', id: 'logo') %> <%= image_tag('/images/logo.png', id: 'logo') %>
@ -234,6 +234,12 @@
<li><%= link_to t('nav.help.contact'), <li><%= link_to t('nav.help.contact'),
Constants::CONTACT_URL, Constants::CONTACT_URL,
target: "_blank" %></li> target: "_blank" %></li>
<li role="separator" class="divider"></li>
<li>
<%= link_to '#', data: { trigger: 'about-modal' } do %>
<%= t('nav.help.about') %>
<% end %>
</li>
</ul> </ul>
</li> </li>

View file

@ -203,7 +203,10 @@
<ul class="dropdown-menu repositories-dropdown-menu" aria-labelledby="repositoriesDropdownMenuLink"> <ul class="dropdown-menu repositories-dropdown-menu" aria-labelledby="repositoriesDropdownMenuLink">
<% @my_module.experiment.project.team.repositories.order(created_at: :asc).each do |repository| %> <% @my_module.experiment.project.team.repositories.order(created_at: :asc).each do |repository| %>
<li> <li>
<a class="dropdown-item" href="<%= repository_my_module_url(id: @my_module, repository_id: repository) %>" title="<%= repository.name %>"> <a class="dropdown-item"
href="<%= repository_my_module_url(id: @my_module, repository_id: repository) %>"
title="<%= repository.name %>"
data-no-turbolink="true">
<%= truncate(repository.name) %> <%= truncate(repository.name) %>
</a> </a>
</li> </li>

View file

@ -29,5 +29,8 @@ module Scinote
csv: 'text/plain', csv: 'text/plain',
wopitest: ['text/plain', 'inode/x-empty'] wopitest: ['text/plain', 'inode/x-empty']
} }
# sciNote Core Application version
VERSION = File.read(Rails.root.join('VERSION')).strip.freeze
end end
end end

View file

@ -80,6 +80,8 @@ Rails.application.config.assets.precompile += %w(repositories/index.js)
Rails.application.config.assets.precompile += %w(repositories/edit.js) Rails.application.config.assets.precompile += %w(repositories/edit.js)
Rails.application.config.assets.precompile += Rails.application.config.assets.precompile +=
%w(repositories/repository_datatable.js) %w(repositories/repository_datatable.js)
Rails.application.config.assets.precompile +=
%w(repositories/my_module_repository.js)
# Libraries needed for Handsontable formulas # Libraries needed for Handsontable formulas
Rails.application.config.assets.precompile += %w(lodash.js) Rails.application.config.assets.precompile += %w(lodash.js)

View file

@ -196,9 +196,6 @@ class Constants
# Other # Other
#============================================================================= #=============================================================================
# Application version
APP_VERSION = '1.12.1'.freeze
TEXT_EXTRACT_FILE_TYPES = [ TEXT_EXTRACT_FILE_TYPES = [
'application/pdf', 'application/pdf',
'application/rtf', 'application/rtf',

View file

@ -105,7 +105,7 @@ Devise.setup do |config|
# The period the generated invitation token is valid, after # The period the generated invitation token is valid, after
# this period, the invited resource won't be able to accept the invitation. # this period, the invited resource won't be able to accept the invitation.
# When invite_for is 0 (the default), the invitation won't expire. # When invite_for is 0 (the default), the invitation won't expire.
config.invite_for = 3.days config.invite_for = 7.days
# Number of invitations users can send. # Number of invitations users can send.
# - If invitation_limit is nil, there is no limit for invitations, users can # - If invitation_limit is nil, there is no limit for invitations, users can
@ -163,7 +163,7 @@ Devise.setup do |config|
# their account can't be confirmed with the token any more. # their account can't be confirmed with the token any more.
# Default is nil, meaning there is no restriction on how long a user can take # Default is nil, meaning there is no restriction on how long a user can take
# before confirming their account. # before confirming their account.
config.confirm_within = 3.days config.confirm_within = 7.days
# If true, requires any email changes to be confirmed (exactly the same way as # If true, requires any email changes to be confirmed (exactly the same way as
# initial account confirmation) to be applied. Requires additional unconfirmed_email # initial account confirmation) to be applied. Requires additional unconfirmed_email

View file

@ -81,7 +81,7 @@ module Paperclip
# Determine file content type from its name # Determine file content type from its name
def content_types_from_name def content_types_from_name
@content_types_from_name ||= @content_types_from_name ||=
Paperclip.run('mimetype', '-b :file_name', file_name: @name).chomp Paperclip.run('mimetype', '-b -- :file_name', file_name: @name).chomp
end end
# Determine file media type from its name # Determine file media type from its name
@ -92,7 +92,7 @@ module Paperclip
# Determine file content type from mimetype command # Determine file content type from mimetype command
def type_from_mimetype_command def type_from_mimetype_command
@type_from_mimetype_command ||= @type_from_mimetype_command ||=
Paperclip.run('mimetype', '-b :file', file: @file.path).chomp Paperclip.run('mimetype', '-b -- :file', file: @file.path).chomp
end end
# Determine file media type from mimetype command # Determine file media type from mimetype command
@ -105,7 +105,7 @@ module Paperclip
def type_from_file_command def type_from_file_command
unless defined? @type_from_file_command unless defined? @type_from_file_command
@type_from_file_command = @type_from_file_command =
Paperclip.run('file', '-b --mime :file', file: @file.path) Paperclip.run('file', '-b --mime -- :file', file: @file.path)
.split(/[:;]\s+/).first .split(/[:;]\s+/).first
if allowed_spoof_exception?(@type_from_file_command, if allowed_spoof_exception?(@type_from_file_command,

View file

@ -72,6 +72,7 @@ en:
support: "Customer support" support: "Customer support"
premium: "Premium" premium: "Premium"
contact: "Contact us" contact: "Contact us"
about: "About sciNote"
activities: activities:
none: "No activities!" none: "No activities!"
label: label:
@ -87,6 +88,11 @@ en:
info: "Info" info: "Info"
account: "Account" account: "Account"
about:
modal_title: "About sciNote"
core_version: "sciNote core version"
addon_versions: "Addon versions"
sidebar: sidebar:
title: "Navigation" title: "Navigation"
no_module_group: "No workflow" no_module_group: "No workflow"
@ -905,8 +911,9 @@ en:
add_new_record: "Add new item" add_new_record: "Add new item"
import_records: import_records:
import: 'Import' import: 'Import'
no_header_name: 'No column name'
success_flash: "%{number_of_rows} new item(s) successfully imported." success_flash: "%{number_of_rows} new item(s) successfully imported."
error_flash: "Something went wrong: %{message}" partial_success_flash: "%{nr} of %{total_nr} successfully imported. Other rows contained errors."
error_message: error_message:
temp_file_not_found: "This file could not be found. Your session might expire." temp_file_not_found: "This file could not be found. Your session might expire."
session_expired: "Your session expired. Please try again." session_expired: "Your session expired. Please try again."
@ -944,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."
@ -1002,7 +1009,8 @@ 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'
upload: "Upload file" upload: "Upload file"
modal_delete: modal_delete:
title: "Delete samples" title: "Delete samples"

View file

@ -114,12 +114,16 @@ class AddonGenerator < Rails::Generators::NamedBase
gsub_file(file_path, '${ADDON_NAME}', @addon_name) gsub_file(file_path, '${ADDON_NAME}', @addon_name)
# lib/.../version.rb # lib/.../version.rb
dots = @modules.map { '/..' }.join
create_file( create_file(
"addons/#{@addon_name}/lib/" \ "addons/#{@addon_name}/lib/" \
"#{@folders_path}/version.rb" "#{@folders_path}/version.rb"
) do ) do
embed_into_modules do embed_into_modules do
"VERSION = '0.0.1'.freeze\n" "VERSION =\n" \
" File.read(\n" \
" \"\#{File.dirname(__FILE__)}#{dots}/../VERSION\"\n" \
" ).strip.freeze\n"
end end
end end
@ -182,6 +186,7 @@ class AddonGenerator < Rails::Generators::NamedBase
gsub_file(file_path, '${FULL_UNDERSCORE_NAME}', @full_underscore_name) gsub_file(file_path, '${FULL_UNDERSCORE_NAME}', @full_underscore_name)
gsub_file(file_path, '${NAME}', name) gsub_file(file_path, '${NAME}', name)
gsub_file(file_path, '${FOLDERS_PATH}', @folders_path) gsub_file(file_path, '${FOLDERS_PATH}', @folders_path)
create_file("addons/#{@addon_name}/VERSION") { '0.0.1' }
# Rakefile # Rakefile
file_path = "addons/#{@addon_name}/Rakefile" file_path = "addons/#{@addon_name}/Rakefile"