diff --git a/Gemfile b/Gemfile
index d2605325a..85ecc1e71 100644
--- a/Gemfile
+++ b/Gemfile
@@ -49,6 +49,7 @@ gem 'commit_param_routing' # Enables different submit actions in the same form t
gem 'kaminari'
gem "i18n-js", ">= 3.0.0.rc11" # Localization in javascript files
gem 'roo', '~> 2.7.1' # Spreadsheet parser
+gem 'creek'
gem 'wicked_pdf'
gem 'silencer' # Silence certain Rails logs
gem 'wkhtmltopdf-heroku'
@@ -68,6 +69,7 @@ gem 'activerecord-import'
gem 'paperclip', '~> 5.1' # File attachment, image attachment library
gem 'aws-sdk', '~> 2'
+gem 'aws-sdk-v1'
gem 'delayed_job_active_record'
gem 'devise-async',
diff --git a/Gemfile.lock b/Gemfile.lock
index de910acd1..dcd6bd846 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -73,7 +73,7 @@ GEM
activemodel (= 5.1.1)
activesupport (= 5.1.1)
arel (~> 8.0)
- activerecord-import (0.19.1)
+ activerecord-import (0.20.2)
activerecord (>= 3.2)
activesupport (5.1.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -87,32 +87,36 @@ GEM
arel (8.0.0)
aspector (0.14.0)
ast (2.3.0)
- auto_strip_attributes (2.1.0)
+ auto_strip_attributes (2.2.0)
activerecord (>= 3.0)
- autoprefixer-rails (7.1.2.4)
+ autoprefixer-rails (7.1.6)
execjs
autosize-rails (1.18.17)
rails (>= 3.1)
awesome_print (1.8.0)
- aws-sdk (2.10.21)
- aws-sdk-resources (= 2.10.21)
- aws-sdk-core (2.10.21)
+ aws-sdk (2.10.69)
+ aws-sdk-resources (= 2.10.69)
+ aws-sdk-core (2.10.69)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
- aws-sdk-resources (2.10.21)
- aws-sdk-core (= 2.10.21)
- aws-sigv4 (1.0.1)
+ aws-sdk-resources (2.10.69)
+ aws-sdk-core (= 2.10.69)
+ aws-sdk-v1 (1.67.0)
+ json (~> 1.4)
+ nokogiri (~> 1)
+ aws-sigv4 (1.0.2)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
+ backports (3.10.3)
base62 (1.0.0)
bcrypt (3.1.11)
- better_errors (2.3.0)
+ better_errors (2.4.0)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
- binding_of_caller (0.7.2)
+ binding_of_caller (0.7.3)
debug_inspector (>= 0.0.1)
bootstrap-sass (3.3.7)
autoprefixer-rails (>= 5.2.1)
@@ -125,7 +129,7 @@ GEM
bullet (5.6.1)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
- byebug (9.0.6)
+ byebug (9.1.0)
capybara (2.13.0)
addressable
mime-types (>= 1.16)
@@ -141,7 +145,7 @@ GEM
climate_control (0.2.0)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
- coderay (1.1.1)
+ coderay (1.1.2)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
railties (>= 4.0.0)
@@ -152,22 +156,31 @@ GEM
commit_param_routing (0.0.1)
concurrent-ruby (1.0.5)
crass (1.0.2)
- cucumber (2.4.0)
+ creek (2.0)
+ httparty (~> 0.15.5)
+ nokogiri (~> 1.7.0)
+ rubyzip (>= 1.0.0)
+ cucumber (3.0.1)
builder (>= 2.1.2)
- cucumber-core (~> 1.5.0)
+ cucumber-core (~> 3.0.0)
+ cucumber-expressions (~> 4.0.3)
cucumber-wire (~> 0.0.1)
- diff-lcs (>= 1.1.3)
+ diff-lcs (~> 1.3)
gherkin (~> 4.0)
multi_json (>= 1.7.5, < 2.0)
multi_test (>= 0.1.2)
- cucumber-core (1.5.0)
- gherkin (~> 4.0)
+ cucumber-core (3.0.0)
+ backports (>= 3.8.0)
+ cucumber-tag_expressions (>= 1.0.1)
+ gherkin (>= 4.1.3)
+ cucumber-expressions (4.0.4)
cucumber-rails (1.5.0)
capybara (>= 1.1.2, < 3)
cucumber (>= 1.3.8, < 4)
mime-types (>= 1.17, < 4)
nokogiri (~> 1.5)
railties (>= 4, < 5.2)
+ cucumber-tag_expressions (1.0.1)
cucumber-wire (0.0.1)
database_cleaner (1.6.1)
debug_inspector (0.0.3)
@@ -192,9 +205,9 @@ GEM
devise (>= 4.0.0)
diff-lcs (1.3)
docile (1.1.5)
- erubi (1.6.1)
+ erubi (1.7.0)
execjs (2.7.0)
- factory_girl (4.8.0)
+ factory_girl (4.8.1)
activesupport (>= 3.0.0)
factory_girl_rails (4.8.0)
factory_girl (~> 4.8.0)
@@ -209,9 +222,12 @@ GEM
gherkin (4.1.3)
globalid (0.4.0)
activesupport (>= 4.2.0)
- hammerjs-rails (2.0.4)
+ hammerjs-rails (2.0.8)
headless (2.3.1)
- i18n (0.8.6)
+ httparty (0.15.6)
+ multi_xml (>= 0.5.2)
+ i18n (0.9.0)
+ concurrent-ruby (~> 1.0)
i18n-js (3.0.1)
i18n (~> 0.6, >= 0.6.6)
introjs-rails (1.0.0)
@@ -233,18 +249,18 @@ GEM
js_cookie_rails (2.1.4)
railties (>= 3.1)
json (1.8.6)
- kaminari (1.0.1)
+ kaminari (1.1.1)
activesupport (>= 4.1.0)
- kaminari-actionview (= 1.0.1)
- kaminari-activerecord (= 1.0.1)
- kaminari-core (= 1.0.1)
- kaminari-actionview (1.0.1)
+ kaminari-actionview (= 1.1.1)
+ kaminari-activerecord (= 1.1.1)
+ kaminari-core (= 1.1.1)
+ kaminari-actionview (1.1.1)
actionview
- kaminari-core (= 1.0.1)
- kaminari-activerecord (1.0.1)
+ kaminari-core (= 1.1.1)
+ kaminari-activerecord (1.1.1)
activerecord
- kaminari-core (= 1.0.1)
- kaminari-core (1.0.1)
+ kaminari-core (= 1.1.1)
+ kaminari-core (1.1.1)
lazy_priority_queue (0.1.1)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
@@ -254,27 +270,29 @@ GEM
logging (2.0.0)
little-plugger (~> 1.1)
multi_json (~> 1.10)
- loofah (2.0.3)
+ loofah (2.1.1)
+ crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.6.6)
mime-types (>= 1.16, < 4)
- method_source (0.8.2)
+ method_source (0.9.0)
mime-types (1.25.1)
mimemagic (0.3.2)
- mini_portile2 (2.3.0)
+ mini_portile2 (2.1.0)
minitest (5.10.3)
momentjs-rails (2.17.1)
railties (>= 3.1)
- multi_json (1.12.1)
+ multi_json (1.12.2)
multi_test (0.1.2)
- nested_form_fields (0.8.1)
+ multi_xml (0.6.0)
+ nested_form_fields (0.8.2)
coffee-rails (>= 3.2.1)
jquery-rails
rails (>= 3.2.0)
- newrelic_rpm (4.3.0.335)
+ newrelic_rpm (4.5.0.337)
nio4r (2.1.0)
- nokogiri (1.8.1)
- mini_portile2 (~> 2.3.0)
+ nokogiri (1.7.2)
+ mini_portile2 (~> 2.1.0)
nokogumbo (1.4.13)
nokogiri
oj (2.18.5)
@@ -291,17 +309,16 @@ GEM
pg (0.21.0)
polyglot (0.3.5)
powerpack (0.1.1)
- pry (0.10.4)
+ pry (0.11.2)
coderay (~> 1.1.0)
- method_source (~> 0.8.1)
- slop (~> 3.4)
- pry-byebug (3.4.2)
- byebug (~> 9.0)
+ method_source (~> 0.9.0)
+ pry-byebug (3.5.0)
+ byebug (~> 9.1)
pry (~> 0.10)
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (3.0.0)
- puma (3.9.1)
+ puma (3.10.0)
rack (2.0.3)
rack-test (0.6.3)
rack (>= 1.0)
@@ -341,12 +358,12 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
- rake (12.0.0)
+ rake (12.1.0)
rb-fsevent (0.10.2)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rdoc (4.3.0)
- recaptcha (4.3.1)
+ recaptcha (4.6.2)
json
remotipart (1.3.1)
responders (2.4.0)
@@ -359,32 +376,32 @@ GEM
roo (2.7.1)
nokogiri (~> 1)
rubyzip (~> 1.1, < 2.0.0)
- rspec-core (3.6.0)
- rspec-support (~> 3.6.0)
- rspec-expectations (3.6.0)
+ rspec-core (3.7.0)
+ rspec-support (~> 3.7.0)
+ rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.6.0)
- rspec-mocks (3.6.0)
+ rspec-support (~> 3.7.0)
+ rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.6.0)
- rspec-rails (3.6.0)
+ rspec-support (~> 3.7.0)
+ rspec-rails (3.7.1)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 3.6.0)
- rspec-expectations (~> 3.6.0)
- rspec-mocks (~> 3.6.0)
- rspec-support (~> 3.6.0)
- rspec-support (3.6.0)
- rubocop (0.49.1)
+ rspec-core (~> 3.7.0)
+ rspec-expectations (~> 3.7.0)
+ rspec-mocks (~> 3.7.0)
+ rspec-support (~> 3.7.0)
+ rspec-support (3.7.0)
+ rubocop (0.51.0)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
- rainbow (>= 1.99.1, < 3.0)
+ rainbow (>= 2.2.2, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-graphviz (1.2.3)
- ruby-progressbar (1.8.1)
+ ruby-progressbar (1.9.0)
ruby_dep (1.5.0)
rubyzip (1.2.1)
sanitize (4.5.0)
@@ -398,7 +415,7 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
- scss_lint (0.54.0)
+ scss_lint (0.55.0)
rake (>= 0.9, < 13)
sass (~> 3.4.20)
sdoc (0.4.2)
@@ -414,19 +431,18 @@ GEM
actionmailer (>= 3.2.6, < 6)
actionpack (>= 3.2.6, < 6)
devise (>= 3.2, < 6)
- simplecov (0.14.1)
+ simplecov (0.15.1)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
- simplecov-html (0.10.1)
- slop (3.6.0)
+ simplecov-html (0.10.2)
sourcemap (0.1.1)
spinjs-rails (1.4)
rails (>= 3.1)
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.2.0)
+ sprockets-rails (3.2.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
@@ -438,10 +454,10 @@ GEM
ruby-progressbar (~> 1.8)
sourcemap (~> 0.1)
stream (0.5)
- thor (0.19.4)
+ thor (0.20.0)
thread_safe (0.3.6)
tilt (2.0.8)
- tinymce-rails (4.6.5)
+ tinymce-rails (4.6.7)
railties (>= 3.1.1)
turbolinks (5.0.1)
turbolinks-source (~> 5)
@@ -481,6 +497,7 @@ DEPENDENCIES
autosize-rails
awesome_print
aws-sdk (~> 2)
+ aws-sdk-v1
base62
bcrypt (~> 3.1.10)
better_errors
@@ -494,6 +511,7 @@ DEPENDENCIES
capybara
capybara-webkit (~> 1.14)
commit_param_routing
+ creek
cucumber-rails (~> 1.5)
database_cleaner
deface (~> 1.0)
diff --git a/VERSION b/VERSION
index 81f363239..e0a6b34fb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.12.3
+1.12.5
diff --git a/app/assets/javascripts/my_modules/results.js.erb b/app/assets/javascripts/my_modules/results.js.erb
index 079815e61..b56218e41 100644
--- a/app/assets/javascripts/my_modules/results.js.erb
+++ b/app/assets/javascripts/my_modules/results.js.erb
@@ -166,8 +166,9 @@
var $nameInput = $form.find('#result_name');
var nameValid = textValidator(ev, $nameInput, 0,
<%= Constants::NAME_MAX_LENGTH %>);
- var $textInput = TinyMCE.getContent();
- textValidator(ev, $textInput, 1, <%= Constants::TEXT_MAX_LENGTH %>, false, true);
+ var $descrTextarea = $form.find("#result_result_text_attributes_text");
+ var $tinyMCEInput = TinyMCE.getContent();
+ textValidator(ev, $descrTextarea, 1, <%= Constants::TEXT_MAX_LENGTH %>, false, $tinyMCEInput);
break;
case ResultTypeEnum.COMMENT:
var $commentInput = $form.find('#comment_message');
diff --git a/app/assets/javascripts/projects/canvas.js.erb b/app/assets/javascripts/projects/canvas.js.erb
index 65375ca2a..bd070ad7a 100644
--- a/app/assets/javascripts/projects/canvas.js.erb
+++ b/app/assets/javascripts/projects/canvas.js.erb
@@ -163,7 +163,6 @@ function initializeEdit() {
// Read permissions from the data attributes of the form
var canEditModules = _.isEqual($("#update-canvas").data("can-edit-modules"), "yes");
- var canEditModuleGroups = _.isEqual($("#update-canvas").data("can-edit-module-groups"), "yes");
var canCreateModules = _.isEqual($("#update-canvas").data("can-create-modules"), "yes");
var canCloneModules = _.isEqual($("#update-canvas").data("can-clone-modules"), "yes");
var canMoveModules = _.isEqual($("#update-canvas").data("can-move-modules"), "yes");
@@ -211,11 +210,6 @@ function initializeEdit() {
$(".edit-module").on("click touchstart", editModuleHandler);
}
- if (canEditModuleGroups) {
- initEditModuleGroups();
- $(".edit-module-group").on("click touchstart", editModuleGroupHandler);
- }
-
if (canCloneModules) {
bindCloneModuleAction(
$(".module-options a.clone-module"),
@@ -1220,22 +1214,6 @@ function updateModuleHtml(module, id, name, gridDistX, gridDistY) {
// Add click handler for the edit module
$(editModuleLink).on("click touchstart", editModuleHandler);
}
- if (_.isEqual($("#update-canvas").data("can-edit-module-groups"), "yes")) {
- var editModuleGroupItem = document.createElement("li");
- $(editModuleGroupItem).appendTo(dropdownMenu);
- $(editModuleGroupItem).hide();
-
- var editModuleGroupLink = document.createElement("a");
- $(editModuleGroupLink)
- .attr("href", "")
- .attr("data-module-id", id)
- .addClass("edit-module-group")
- .html($("#edit-group-link-placeholder").text().trim())
- .appendTo(editModuleGroupItem);
-
- // Add click handler for the edit module group
- $(editModuleGroupLink).on("click touchstart", editModuleGroupHandler);
- }
// Add clone links if neccesary
if (_.isEqual($("#update-canvas").data("can-clone-modules"), "yes")) {
@@ -1636,95 +1614,11 @@ editModuleHandler = function(ev) {
/**
* Initialize editing of module groups.
*/
-function initEditModuleGroups() {
- function handleRenameConfirm(modal, ev) {
- var input = modal.find("#edit-module-group-name-input");
- // Validate module group name
- var moduleNameValid = textValidator(ev, input, 1,
- <%= Constants::NAME_MAX_LENGTH %>, true);
- if (moduleNameValid) {
- var newModuleGroupName = input.val();
- var moduleId = modal.attr("data-module-id");
- var moduleEl = $("#" + moduleId);
- // Update the module group name for all modules
- // currently in the module group
- var ids = connectedComponents(graph, moduleEl.attr("data-module-id"));
- _.each(ids, function(id) {
- $("#" + id).attr("data-module-group-name", newModuleGroupName);
- });
-
- // Hide modal
- modal.modal("hide");
- }
- }
-
- $("#modal-edit-module-group")
- .on("show.bs.modal", function (event) {
- var modal = $(this);
- var moduleId = modal.attr("data-module-id");
- var moduleEl = $("#" + moduleId);
- var input = modal.find("#edit-module-group-name-input");
-
- // Set the input to the current module's name
- input
- .attr("value", moduleEl.attr("data-module-group-name"));
- input.val(moduleEl.attr("data-module-group-name"));
-
- // Bind on enter button
- input.keydown(function(ev) {
- if (ev.keyCode == 13) {
- // "Submit" modal
- handleRenameConfirm(modal, ev);
-
- // In any case, prevent form submission
- ev.preventDefault();
- ev.stopPropagation();
- return false;
- }
- });
- })
- .on("shown.bs.modal", function (event) {
- $(this).find("#edit-module-group-name-input").focus();
- })
- .on("hide.bs.modal", function (event) {
- // Remove potential error classes
- $(this).find("#edit-module-group-name-input").parent().removeClass("has-error");
- $(this).find("span.help-block").remove();
-
- $(this).find("#edit-module-group-name-input").off("keydown");
-
- // When hiding modal, re-enable events
- toggleCanvasEvents(true);
- });
-
- // Bind the confirm button on modal
- $("#modal-edit-module-group").find("button[data-action='confirm']").on("click", function(ev) {
- var modal = $(this).closest(".modal");
- handleRenameConfirm(modal, ev);
- });
-}
/**
* Handler when editing a module group.
*/
-editModuleGroupHandler = function(ev) {
- var modal = $("#modal-edit-module-group");
- var moduleEl = $(this).closest(".module");
-
- // Set modal's module id
- modal.attr("data-module-id", moduleEl.attr("data-module-id"));
-
- // Disable dragging & zooming events on canvas temporarily
- toggleCanvasEvents(false);
-
- // Show modal
- modal.modal("show");
-
- ev.preventDefault();
- ev.stopPropagation();
- return false;
-};
function initMoveModules() {
function handleMoveConfirm(modal) {
@@ -1978,7 +1872,6 @@ function deleteModule(id, linkConnections) {
_.each (ins, function(inEdge) {
if (graph.degree(inEdge[0]) === 0) {
tempModuleEl = $("#" + inEdge[0]);
- tempModuleEl.find(".edit-module-group").parents("li").hide();
tempModuleEl.find(".clone-module-group").parents("li").hide();
tempModuleEl.find(".move-module-group").parents("li").hide();
tempModuleEl.find(".delete-module-group").parents("li").hide();
@@ -1989,7 +1882,6 @@ function deleteModule(id, linkConnections) {
_.each (outs, function(outEdge) {
if (graph.degree(outEdge[1]) === 0) {
tempModuleEl = $("#" + outEdge[1]);
- tempModuleEl.find(".edit-module-group").parents("li").hide();
tempModuleEl.find(".clone-module-group").parents("li").hide();
tempModuleEl.find(".move-module-group").parents("li").hide();
tempModuleEl.find(".delete-module-group").parents("li").hide();
@@ -2361,7 +2253,6 @@ cloneModuleGroupHandler = function(moduleId, modulesSel, gridDistX, gridDistY) {
var nm = cloneModule(m, gridDistX, gridDistY, elLeft(m), elTop(m) + height + offset - gridDistY);
//Show module group options
- nm.find(".edit-module-group").parents("li").show();
nm.find(".clone-module-group").parents("li").show();
nm.find(".move-module-group").parents("li").show();
nm.find(".delete-module-group").parents("li").show();
@@ -2963,12 +2854,10 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
//Modules should belong to module group now
//Show module group options for target and source
- srcModuleEl.find(".edit-module-group").parents("li").show();
srcModuleEl.find(".clone-module-group").parents("li").show();
srcModuleEl.find(".move-module-group").parents("li").show();
srcModuleEl.find(".delete-module-group").parents("li").show();
- targetModuleEl.find(".edit-module-group").parents("li").show();
targetModuleEl.find(".clone-module-group").parents("li").show();
targetModuleEl.find(".move-module-group").parents("li").show();
targetModuleEl.find(".delete-module-group").parents("li").show();
@@ -3001,13 +2890,11 @@ function initJsPlumb(containerSel, containerChildSel, modulesSel, params) {
var targetModuleEl = $("#" + c.targetId);
//First source
if (graph.degree(c.sourceId) === 0) {
- srcModuleEl.find(".edit-module-group").parents("li").hide();
srcModuleEl.find(".clone-module-group").parents("li").hide();
srcModuleEl.find(".move-module-group").parents("li").hide();
srcModuleEl.find(".delete-module-group").parents("li").hide();
}
if (graph.degree(c.targetId) === 0) {
- targetModuleEl.find(".edit-module-group").parents("li").hide();
targetModuleEl.find(".clone-module-group").parents("li").hide();
targetModuleEl.find(".move-module-group").parents("li").hide();
targetModuleEl.find(".delete-module-group").parents("li").hide();
diff --git a/app/assets/javascripts/protocols/steps.js.erb b/app/assets/javascripts/protocols/steps.js.erb
index 3458fecb2..93ca6d6b2 100644
--- a/app/assets/javascripts/protocols/steps.js.erb
+++ b/app/assets/javascripts/protocols/steps.js.erb
@@ -540,8 +540,9 @@
var nameValid = textValidator(ev, $nameInput, 1,
<%= Constants::NAME_MAX_LENGTH %>);
var $descrTextarea = $form.find("#step_description");
+ var $tinyMCEInput = TinyMCE.getContent();
var descriptionValid = textValidator(ev, $descrTextarea, 0,
- <%= Constants::TEXT_MAX_LENGTH %>);
+ <%= Constants::TEXT_MAX_LENGTH %>, false, $tinyMCEInput);
if (DragNDropSteps.filesStatus() &&
checklistsValid &&
diff --git a/app/assets/javascripts/sitewide/drag_n_drop.js.erb b/app/assets/javascripts/sitewide/drag_n_drop.js.erb
index 0f1c62068..f585c3aaa 100644
--- a/app/assets/javascripts/sitewide/drag_n_drop.js.erb
+++ b/app/assets/javascripts/sitewide/drag_n_drop.js.erb
@@ -21,12 +21,13 @@
// loops through a list of files and display each file in a separate panel
function listItems() {
- droppedFiles = droppedFiles.filter(Boolean);
+ totalSize = 0;
+ _enableSubmitButton();
$('.panel-step-attachment-new').remove();
_dragNdropAssetsOff();
for(var i = 0; i < droppedFiles.length; i++) {
$('#new-step-assets')
- .append(_uploadedAseetPreview(droppedFiles[i], i))
+ .append(_uploadedAssetPreview(droppedFiles[i], i))
.promise()
.done(function() {
_removeItemHandler(i);
@@ -42,7 +43,6 @@
var prevEls = $('input').filter(function() {
return this.name.match(regex);
});
- droppedFiles = droppedFiles.filter(Boolean);
var fd = new FormData($(ev.target).closest('form').get(0));
for(var i = 0; i < droppedFiles.length; i++) {
var index = i + prevEls.length;
@@ -56,30 +56,55 @@
return fd;
}
- function _validateFilesSize(file) {
- var maxSize = file.size/1048576;
- if(maxSize > <%= Constants::FILE_MAX_SIZE_MB %> && filesValid) {
- return "
<%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %>
";
+ function _disableSubmitButton() {
+ $('.step-save').prop('disabled', true);
+ }
+
+ function _enableSubmitButton() {
+ $('.step-save').prop('disabled', false);
+ }
+
+ function _filerAndCheckFiles() {
+ for(var i = 0; i < droppedFiles.length; i++) {
+ if(droppedFiles[i].isValid == false) {
+ return false;
+ }
+ }
+ return (droppedFiles.length > 0);
+ }
+
+ function _validateFilesSize(file) {
+ var fileSize = file.size;
+ totalSize += parseInt(fileSize);
+ if(fileSize > <%= Constants::FILE_MAX_SIZE_MB.megabyte %>) {
+ file.isValid = false;
+ _disableSubmitButton();
+ return "<%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %>
";
}
- totalSize += parseInt(maxSize);
return '';
}
function _validateTotalSize() {
- if(totalSize > <%= Constants::FILE_MAX_SIZE_MB %>) {
+ if(totalSize > <%= Constants::FILE_MAX_SIZE_MB.megabyte %>) {
filesValid = false;
+ _disableSubmitButton();
$.each($('.panel-step-attachment-new'), function() {
- $(this)
- .find('.panel-body')
- .append("<%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %>
");
+ if(!$(this).find('p').hasClass('dnd-total-error')) {
+ $(this)
+ .find('.panel-body')
+ .append("<%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %>
");
+ }
});
} else {
- $('.dnd-error').remove();
- filesValid = true;
+ $('.dnd-total-error').remove();
+ if(_filerAndCheckFiles()) {
+ filesValid = true;
+ _enableSubmitButton();
+ }
}
}
- function _uploadedAseetPreview(asset, i) {
+ function _uploadedAssetPreview(asset, i) {
var html = '';
html += '
';
html += '
';
@@ -104,10 +129,9 @@
e.stopPropagation();
var $el = $(this);
var index = $el.data('item-id');
- totalSize -= parseInt(droppedFiles[index]/1048576);
- droppedFiles[index] = null;
- $el.closest('.panel-step-attachment-new').remove();
- _validateTotalSize();
+ totalSize -= parseInt(droppedFiles[index].size);
+ droppedFiles.splice(index, 1);
+ listItems();
});
}
@@ -131,11 +155,10 @@
var totalSize = 0;
function init(files) {
- var filesPresent = droppedFiles.length;
for(var i = 0; i < files.length; i++) {
droppedFiles.push(files[i]);
}
- listItems(filesPresent);
+ listItems();
}
// return the status of files if they are ready to submit
@@ -144,18 +167,24 @@
}
// loops through a list of files and display each file in a separate panel
- function listItems(index) {
- _dragNdropAssetsOff();
- for(var i = index; i < droppedFiles.length; i++) {
- $('#new-result-assets-select')
- .after(_uploadedAseetPreview(droppedFiles[i], i))
- .promise()
- .done(function() {
- _removeItemHandler(i);
- });
+ function listItems() {
+ totalSize = 0;
+ $('.panel-result-attachment-new').remove();
+ if(droppedFiles.length < 1) {
+ _disableSubmitButton();
+ } else {
+ _dragNdropAssetsOff();
+ for(var i = 0; i < droppedFiles.length; i++) {
+ $('#new-result-assets-select')
+ .after(_uploadedAssetPreview(droppedFiles[i], i))
+ .promise()
+ .done(function() {
+ _removeItemHandler(i);
+ });
+ }
+ _validateTotalSize();
+ dragNdropAssetsInit('results');
}
- _validateTotalSize();
- dragNdropAssetsInit('results');
}
// appent the files to the form before submit
@@ -181,31 +210,51 @@
return fd;
}
+ function _disableSubmitButton() {
+ $('.save-result').prop('disabled', true);
+ }
+
+ function _enableSubmitButton() {
+ $('.save-result').prop('disabled', false);
+ }
+
function _filerAndCheckFiles() {
- droppedFiles = droppedFiles.filter(Boolean);
+ for(var i = 0; i < droppedFiles.length; i++) {
+ if(droppedFiles[i].isValid == false) {
+ return false;
+ }
+ }
return (droppedFiles.length > 0);
}
function _validateFilesSize(file) {
- var maxSize = file.size/1048576;
- if(maxSize > <%= Constants::FILE_MAX_SIZE_MB %> && isValid) {
- return "
<%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %>
";
+ var fileSize = file.size;
+ totalSize += parseInt(fileSize);
+ if(fileSize > <%= Constants::FILE_MAX_SIZE_MB.megabyte %>) {
+ file.isValid = false;
+ _disableSubmitButton();
+ return "
<%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %>
";
}
- totalSize += parseInt(maxSize);
return '';
}
function _validateTotalSize() {
- if(totalSize > <%= Constants::FILE_MAX_SIZE_MB %>) {
+ if(totalSize > <%= Constants::FILE_MAX_SIZE_MB.megabyte %>) {
isValid = false;
+ _disableSubmitButton();
$.each($('.panel-result-attachment-new'), function() {
- $(this)
- .find('.panel-body')
- .append("
<%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %>
");
+ if(!$(this).find('p').hasClass('dnd-total-error')) {
+ $(this)
+ .find('.panel-body')
+ .append("
<%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %>
");
+ }
});
} else {
- $('.dnd-error').remove();
- isValid = true;
+ $('.dnd-total-error').remove();
+ if(_filerAndCheckFiles()) {
+ isValid = true;
+ _enableSubmitButton();
+ }
}
}
@@ -220,7 +269,7 @@
}
}
- function _uploadedAseetPreview(asset, i) {
+ function _uploadedAssetPreview(asset, i) {
var html = '
';
html += '
';
html += '
';
@@ -249,10 +298,9 @@
e.stopPropagation();
var $el = $(this);
var index = $el.data('item-id');
- totalSize -= parseInt(droppedFiles[index]/1048576);
- droppedFiles[index] = null;
- $el.closest('.panel-result-attachment-new').remove();
- _validateTotalSize();
+ totalSize -= parseInt(droppedFiles[index].size);
+ droppedFiles.splice(index, 1);
+ listItems();
});
}
diff --git a/app/assets/javascripts/sitewide/form_validators.js.erb b/app/assets/javascripts/sitewide/form_validators.js.erb
index 323457e5d..aca5ef0f3 100644
--- a/app/assets/javascripts/sitewide/form_validators.js.erb
+++ b/app/assets/javascripts/sitewide/form_validators.js.erb
@@ -22,11 +22,11 @@ $.fn.onSubmitValidator = function(validatorCb) {
* @param {boolean} clearErr Set clearErr to true if this is the only
* error that can happen/show.
*/
-function textValidator(ev, textInput, textLimitMin, textLimitMax, clearErr, tinyMCE) {
+function textValidator(ev, textInput, textLimitMin, textLimitMax, clearErr, tinyMCEInput) {
clearErr = _.isUndefined(clearErr) ? false : clearErr;
- if(tinyMCE){
- var text = textInput.length;
+ if(tinyMCEInput){
+ var text = tinyMCEInput;
} else {
var text = $(textInput).val().trim();
$(textInput).val(text);
diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss
index e5a41a988..91a296e07 100644
--- a/app/assets/stylesheets/themes/scinote.scss
+++ b/app/assets/stylesheets/themes/scinote.scss
@@ -1288,7 +1288,8 @@ ul.content-module-activities {
padding-top: 30px;
}
-.dnd-error {
+.dnd-error,
+.dnd-total-error {
color: $color-milano-red;
}
diff --git a/app/controllers/canvas_controller.rb b/app/controllers/canvas_controller.rb
index 4f0291a64..14728cc92 100644
--- a/app/controllers/canvas_controller.rb
+++ b/app/controllers/canvas_controller.rb
@@ -29,58 +29,41 @@ class CanvasController < ApplicationController
end
def update
- error = false
-
# Make sure that remove parameter is valid
to_archive = []
- if can_archive_modules(@experiment) and
- update_params[:remove].present? then
- to_archive = update_params[:remove].split(",")
- unless to_archive.all? { |id| is_int? id }
- error = true
+ if can_archive_modules(@experiment) && update_params[:remove].present?
+ to_archive = update_params[:remove].split(',')
+ if to_archive.all? { |id| is_int? id }
+ to_archive.collect!(&:to_i)
else
- to_archive.collect! { |id| id.to_i }
+ return render_403
end
end
- if error then
- render_403 and return
- end
-
# Make sure connections parameter is valid
connections = []
- if can_edit_connections(@experiment) and
- update_params[:connections].present? then
- conns = update_params[:connections].split(",")
- unless conns.length % 2 == 0 and
- conns.all? { |c| c.is_a? String } then
- error = true
- else
+ if can_edit_connections(@experiment) && update_params[:connections].present?
+ conns = update_params[:connections].split(',')
+ if conns.length.even? && conns.all? { |c| c.is_a? String }
conns.each_slice(2).each do |c|
connections << [c[0], c[1]]
end
+ else
+ return render_403
end
end
- if error then
- render_403 and return
- end
-
# Make sure positions parameter is valid
- positions = Hash.new
- if can_reposition_modules(@experiment) and
- update_params[:positions].present? then
- poss = update_params[:positions].split(";")
- center = ""
- (poss.collect { |pos| pos.split(",") }).each_with_index do |pos, index|
- unless pos.length == 3 &&
- pos[0].is_a?(String) &&
- float?(pos[1]) &&
- float?(pos[2])
- error = true
- break
+ positions = {}
+ if can_reposition_modules(@experiment) && update_params[:positions].present?
+ poss = update_params[:positions].split(';')
+ center = ''
+ (poss.collect { |pos| pos.split(',') }).each_with_index do |pos, index|
+ unless pos.length == 3 && pos[0].is_a?(String) &&
+ float?(pos[1]) && float?(pos[2])
+ return render_403
end
- if index == 0
+ if index.zero?
center = pos
x = 0
y = 0
@@ -89,86 +72,59 @@ class CanvasController < ApplicationController
y = pos[2].to_i - center[2].to_i
end
# Multiple modules cannot have same position
- if positions.any? { |k,v| v[:x] == x and v[:y] == y} then
- error = true
- break
- end
+ return render_403 if positions.any? { |_, v| v[:x] == x && v[:y] == y }
positions[pos[0]] = { x: x, y: y }
end
end
- if error then
- render_403 and return
- end
-
# Make sure that to_add is an array of strings,
# as well as that positions for newly added modules exist
to_add = []
- if can_create_modules(@experiment) and
- update_params[:add].present? and
- update_params["add-names"].present? then
- ids = update_params[:add].split(",")
- names = update_params["add-names"].split("|")
- unless ids.length == names.length and
- ids.all? { |id| id.is_a? String and positions.include? id } and
- names.all? { |name| name.is_a? String }
- error = true
- else
+ if can_create_modules(@experiment) && update_params[:add].present? &&
+ update_params['add-names'].present?
+ ids = update_params[:add].split(',')
+ names = update_params['add-names'].split('|')
+ if ids.length == names.length &&
+ ids.all? { |id| id.is_a?(String) && positions.include?(id) } &&
+ names.all? { |name| name.is_a? String }
ids.each_with_index do |id, i|
- to_add << {
- id: id,
- name: names[i],
- x: positions[id][:x],
- y: positions[id][:y]
- }
+ to_add << { id: id, name: names[i],
+ x: positions[id][:x], y: positions[id][:y] }
end
+ else
+ return render_403
end
end
- if error then
- render_403 and return
- end
-
# Make sure rename parameter is valid
- to_rename = Hash.new
- if can_edit_modules(@experiment) and
- update_params[:rename].present? then
+ to_rename = {}
+ if can_edit_modules(@experiment) && update_params[:rename].present?
begin
to_rename = JSON.parse(update_params[:rename])
-
# Okay, JSON parsed!
- unless (
- to_rename.is_a? Hash and
- to_rename.keys.all? { |k| k.is_a? String } and
- to_rename.values.all? { |k| k.is_a? String }
- )
- error = true
+ unless to_rename.is_a?(Hash) &&
+ to_rename.keys.all? { |k| k.is_a? String } &&
+ to_rename.values.all? { |k| k.is_a? String }
+ return render_403
end
rescue
- error = true
+ return render_403
end
end
- if error then
- render_403 and return
- end
-
# Make sure move parameter is valid
to_move = {}
if can_move_modules(@experiment) && update_params[:move].present?
begin
to_move = JSON.parse(update_params[:move])
-
# Okay, JSON parsed!
- unless (
- to_move.is_a? Hash and
- to_move.keys.all? { |k| k.is_a? String } &&
- to_move.values.all? { |k| k.is_a? String }
- )
- error = true
+ unless to_move.is_a?(Hash) &&
+ to_move.keys.all? { |k| k.is_a? String } &&
+ to_move.values.all? { |k| k.is_a? String }
+ return render_403
end
rescue
- error = true
+ return render_403
end
end
@@ -181,54 +137,21 @@ class CanvasController < ApplicationController
end
end
- render_403 and return if error
-
# Make sure that to_clone is an array of pairs,
# as well as that all IDs exist
- to_clone = Hash.new
- if can_clone_modules(@experiment) and
- update_params[:cloned].present? then
- clones = update_params[:cloned].split(";")
- (clones.collect { |v| v.split(",") }).each do |val|
- unless (val.length == 2 and
- is_int? val[0] and
- val[1].is_a? String and
- to_add.any? { |m| m[:id] == val[1] })
- error = true
- break
- else
+ to_clone = {}
+ if can_clone_modules(@experiment) && update_params[:cloned].present?
+ clones = update_params[:cloned].split(';')
+ (clones.collect { |v| v.split(',') }).each do |val|
+ if val.length == 2 && is_int?(val[0]) && val[1].is_a?(String) &&
+ to_add.any? { |m| m[:id] == val[1] }
to_clone[val[1]] = val[0]
+ else
+ return render_403
end
end
end
- if error then
- render_403 and return
- end
-
- module_groups = Hash.new
- if can_edit_module_groups(@experiment) and
- update_params["module-groups"].present? then
- begin
- module_groups = JSON.parse(update_params["module-groups"])
-
- # Okay, JSON parsed!
- unless (
- module_groups.is_a? Hash and
- module_groups.keys.all? { |k| k.is_a? String } and
- module_groups.values.all? { |k| k.is_a? String }
- )
- error = true
- end
- rescue
- error = true
- end
- end
-
- if error then
- render_403 and return
- end
-
# Call the "master" function to do all the updating for us
unless @experiment.update_canvas(
to_archive,
@@ -239,36 +162,29 @@ class CanvasController < ApplicationController
to_clone,
connections,
positions,
- current_user,
- module_groups
+ current_user
)
- render_403 and return
+ return render_403
end
- #Save activities that modules were archived
+ # Save activities that modules were archived
to_archive.each do |module_id|
my_module = MyModule.find_by_id(module_id)
- unless my_module.blank?
- Activity.create(
- type_of: :archive_module,
- project: my_module.experiment.project,
- experiment: my_module.experiment,
- my_module: my_module,
- user: current_user,
- message: t(
- 'activities.archive_module',
- user: current_user.full_name,
- module: my_module.name
- )
- )
- end
+ next if my_module.blank?
+ Activity.create(type_of: :archive_module,
+ project: my_module.experiment.project,
+ experiment: my_module.experiment,
+ my_module: my_module,
+ user: current_user,
+ message: t('activities.archive_module',
+ user: current_user.full_name,
+ module: my_module.name))
end
# Create workflow image
@experiment.delay.generate_workflow_img
- flash[:success] = t(
- "experiments.canvas.update.success_flash")
+ flash[:success] = t('experiments.canvas.update.success_flash')
redirect_to canvas_experiment_path(@experiment)
end
diff --git a/app/controllers/experiments_controller.rb b/app/controllers/experiments_controller.rb
index 513229aee..af1ae1e8b 100644
--- a/app/controllers/experiments_controller.rb
+++ b/app/controllers/experiments_controller.rb
@@ -77,6 +77,8 @@ class ExperimentsController < ApplicationController
def canvas
@project = @experiment.project
+ @active_modules = @experiment.active_modules
+ .includes(:tags, :inputs, :outputs)
current_team_switch(@project.team)
end
diff --git a/app/controllers/my_modules_controller.rb b/app/controllers/my_modules_controller.rb
index be9eec021..c0457c635 100644
--- a/app/controllers/my_modules_controller.rb
+++ b/app/controllers/my_modules_controller.rb
@@ -291,7 +291,7 @@ class MyModulesController < ApplicationController
task_names = []
new_samples = []
- @my_module.get_downstream_modules.each do |my_module|
+ @my_module.downstream_modules.each do |my_module|
new_samples = samples.select { |el| my_module.samples.exclude?(el) }
my_module.samples.push(*new_samples)
task_names << my_module.name
@@ -330,7 +330,7 @@ class MyModulesController < ApplicationController
end
task_names = []
- @my_module.get_downstream_modules.each do |my_module|
+ @my_module.downstream_modules.each do |my_module|
task_names << my_module.name
my_module.samples.destroy(samples & my_module.samples)
end
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
index 524123e90..7a544e972 100644
--- a/app/controllers/reports_controller.rb
+++ b/app/controllers/reports_controller.rb
@@ -182,7 +182,8 @@ class ReportsController < ApplicationController
@html = '
No content
' if @html.blank?
render pdf: 'report',
header: { right: '[page] of [topage]' },
- template: 'reports/report.pdf.erb'
+ template: 'reports/report.pdf.erb',
+ disable_javascript: true
end
end
end
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index bb56e7938..e356471e8 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -198,12 +198,14 @@ class RepositoriesController < ApplicationController
if parsed_file.too_large?
repository_response(t('general.file.size_exceeded',
file_size: Constants::FILE_MAX_SIZE_MB))
- elsif parsed_file.empty?
- 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?
+
+ if @import_data.header.empty? || @import_data.columns.empty?
+ return repository_response(t('teams.parse_sheet.errors.empty_file'))
+ end
+
+ if (@temp_file = parsed_file.generate_temp_file)
respond_to do |format|
format.json do
render json: {
diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb
index faeb069e2..e966bd731 100644
--- a/app/controllers/result_assets_controller.rb
+++ b/app/controllers/result_assets_controller.rb
@@ -76,23 +76,23 @@ class ResultAssetsController < ApplicationController
@result.last_modified_by = current_user
@result.assign_attributes(update_params)
- success_flash = t("result_assets.update.success_flash",
- module: @my_module.name)
+ success_flash = t('result_assets.update.success_flash',
+ module: @my_module.name)
if @result.archived_changed?(from: false, to: true)
if previous_asset.locked?
respond_to do |format|
- format.html {
+ format.html do
flash[:error] = t('result_assets.archive.error_flash')
redirect_to results_my_module_path(@my_module)
return
- }
+ end
end
end
saved = @result.archive(current_user)
- success_flash = t("result_assets.archive.success_flash",
- module: @my_module.name)
+ success_flash = t('result_assets.archive.success_flash',
+ module: @my_module.name)
if saved
Activity.create(
type_of: :archive_result,
@@ -126,7 +126,7 @@ class ResultAssetsController < ApplicationController
# Asset (file) and/or name has been changed
saved = @result.save
- if saved then
+ if saved
# Release team's space taken due to
# previous asset being removed
team = @result.my_module.experiment.project.team
@@ -134,9 +134,7 @@ class ResultAssetsController < ApplicationController
team.save
# Post process new file if neccesary
- if @result.asset.present?
- @result.asset.post_process_file(team)
- end
+ @result.asset.post_process_file(team) if @result.asset.present?
Activity.create(
type_of: :edit_result,
@@ -144,32 +142,33 @@ class ResultAssetsController < ApplicationController
project: @my_module.experiment.project,
experiment: @my_module.experiment,
my_module: @my_module,
- message: t(
- "activities.edit_asset_result",
- user: current_user.full_name,
- result: @result.name
- )
+ message: t('activities.edit_asset_result',
+ user: current_user.full_name,
+ result: @result.name)
)
end
end
respond_to do |format|
if saved
- format.html {
+ format.html do
flash[:success] = success_flash
redirect_to results_my_module_path(@my_module)
- }
- format.json {
+ end
+ format.json do
render json: {
- html: render_to_string({
- partial: "my_modules/result.html.erb", locals: {result: @result}
- })
+ html: render_to_string(
+ partial: 'my_modules/result.html.erb', locals: { result: @result }
+ )
}, status: :ok
- }
+ end
else
- format.json {
- render json: @result.errors, status: :bad_request
- }
+ format.json do
+ render json: {
+ status: :error,
+ errors: @result.errors
+ }, status: :bad_request
+ end
end
end
end
@@ -189,22 +188,15 @@ class ResultAssetsController < ApplicationController
def load_vars_nested
@my_module = MyModule.find_by_id(params[:my_module_id])
-
- unless @my_module
- render_404
- end
+ render_404 unless @my_module
end
def check_create_permissions
- unless can_create_result_asset_in_module(@my_module)
- render_403
- end
+ render_403 unless can_create_result_asset_in_module(@my_module)
end
def check_edit_permissions
- unless can_edit_result_asset_in_module(@my_module)
- render_403
- end
+ render_403 unless can_edit_result_asset_in_module(@my_module)
end
def check_archive_permissions
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index de98b6347..5cfe0bcfe 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -8,7 +8,6 @@ class SearchController < ApplicationController
search_projects if @search_category == :projects
search_experiments if @search_category == :experiments
- search_workflows if @search_category == :workflows
search_modules if @search_category == :modules
search_results if @search_category == :results
search_tags if @search_category == :tags
@@ -145,7 +144,6 @@ class SearchController < ApplicationController
def count_search_results
@project_search_count = count_by_name Project
@experiment_search_count = count_by_name Experiment
- @workflow_search_count = count_by_name MyModuleGroup
@module_search_count = count_by_name MyModule
@result_search_count = count_by_name Result
@tag_search_count = count_by_name Tag
@@ -161,7 +159,6 @@ class SearchController < ApplicationController
@search_results_count = @project_search_count
@search_results_count += @experiment_search_count
- @search_results_count += @workflow_search_count
@search_results_count += @module_search_count
@search_results_count += @result_search_count
@search_results_count += @tag_search_count
@@ -190,14 +187,6 @@ class SearchController < ApplicationController
@search_count = @experiment_search_count
end
- def search_workflows
- @workflow_results = []
- if @workflow_search_count > 0
- @workflow_results = search_by_name(MyModuleGroup)
- end
- @search_count = @workflow_search_count
- end
-
def search_modules
@module_results = []
@module_results = search_by_name(MyModule) if @module_search_count > 0
diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb
index 20ee1b81b..9b12b30b6 100644
--- a/app/controllers/steps_controller.rb
+++ b/app/controllers/steps_controller.rb
@@ -290,10 +290,12 @@ class StepsController < ApplicationController
"activities.uncheck_step_checklist_item"
completed_items = chkItem.checklist.checklist_items.where(checked: true).count
all_items = chkItem.checklist.checklist_items.count
+ text_activity = smart_annotation_parser(chkItem.text)
+ .gsub(/\s+/, ' ')
message = t(
str,
user: current_user.full_name,
- checkbox: smart_annotation_parser(simple_format(chkItem.text)),
+ checkbox: text_activity,
step: chkItem.checklist.step.position + 1,
step_name: chkItem.checklist.step.name,
completed: completed_items,
diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb
index 622cff038..982b34d11 100644
--- a/app/controllers/teams_controller.rb
+++ b/app/controllers/teams_controller.rb
@@ -7,106 +7,57 @@ class TeamsController < ApplicationController
def parse_sheet
session[:return_to] ||= request.referer
- respond_to do |format|
- if params[:file]
- begin
+ unless params[:file]
+ return parse_sheet_error(t('teams.parse_sheet.errors.no_file_selected'))
+ end
+ if params[:file].size > Constants::FILE_MAX_SIZE_MB.megabytes
+ error = t('general.file.size_exceeded',
+ file_size: Constants::FILE_MAX_SIZE_MB)
+ return parse_sheet_error(error)
+ end
- if params[:file].size > Constants::FILE_MAX_SIZE_MB.megabytes
- error = t 'general.file.size_exceeded',
- file_size: Constants::FILE_MAX_SIZE_MB
+ begin
+ sheet = SpreadsheetParser.open_spreadsheet(params[:file])
+ @header, @columns = SpreadsheetParser.first_two_rows(sheet)
- format.html {
- flash[:alert] = error
- redirect_to session.delete(:return_to)
+ if @header.empty? || @columns.empty?
+ return parse_sheet_error(t('teams.parse_sheet.errors.empty_file'))
+ end
+
+ # 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
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: render_to_string(
+ partial: 'samples/parse_samples_modal.html.erb'
+ )
}
- 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)
- @columns = sheet.row(2)
-
- # 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
- }
+ return parse_sheet_error(
+ t('teams.parse_sheet.errors.temp_file_failure')
+ )
end
+ rescue ArgumentError, CSV::MalformedCSVError
+ return parse_sheet_error(t('teams.parse_sheet.errors.invalid_file',
+ encoding: ''.encoding))
+ rescue TypeError
+ return parse_sheet_error(t('teams.parse_sheet.errors.invalid_extension'))
end
end
@@ -122,7 +73,7 @@ class TeamsController < ApplicationController
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)
+ @sheet = SpreadsheetParser.open_spreadsheet(@temp_file.file)
# Check for duplicated values
h1 = params[:mappings].clone.delete_if { |k, v| v.empty? }
@@ -275,6 +226,20 @@ class TeamsController < ApplicationController
private
+ def parse_sheet_error(error)
+ 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 load_vars
@team = Team.find_by_id(params[:id])
diff --git a/app/helpers/activity_helper.rb b/app/helpers/activity_helper.rb
index 83cf57023..feb13115a 100644
--- a/app/helpers/activity_helper.rb
+++ b/app/helpers/activity_helper.rb
@@ -1,17 +1,52 @@
module ActivityHelper
+ # constants for correct truncation length
+ TAGS_LENGTH = 4
+ TRUNCATE_OFFSET = 3
def activity_truncate(message, len = Constants::NAME_TRUNCATION_LENGTH)
activity_titles = message.scan(/
(.*?)<\/strong>/)
activity_titles.each do |activity_title|
activity_title = activity_title[0]
- if activity_title.length > Constants::NAME_TRUNCATION_LENGTH
+ # find first closing tag of smart annotation
+ closing_tag_sa = activity_title.index('')
+ unless closing_tag_sa.nil?
+ opening_tag_sa = activity_title.index('
opening_temp
+ end
+ len_temp = len
+ # check until we run out of smart annotations in message
+ while !opening_tag_sa.nil? && !closing_tag_sa.nil? &&
+ opening_tag_sa < len_temp
+ stripped = strip_tags(activity_title[opening_tag_sa...closing_tag_sa])
+ .length
+ len_temp += (activity_title[opening_tag_sa...closing_tag_sa +
+ TAGS_LENGTH]).length - stripped
+ len = len_temp + TRUNCATE_OFFSET + TAGS_LENGTH if len <= closing_tag_sa
+ closing_temp = closing_tag_sa + 1
+ closing_tag_sa = activity_title.index('', closing_temp)
+ unless closing_tag_sa.nil?
+ # find next smart annotation
+ opening_tag_sa = activity_title.index('
opening_temp
+ end
+ end
+ # adjust truncation length according to smart annotations length
+ len = activity_title.length if len > activity_title.length &&
+ len != Constants::NAME_TRUNCATION_LENGTH
+ if activity_title.length > len
title = "
- #{truncate(activity_title, length: len)}
+ #{truncate(activity_title, length: len, escape: false)}
#{activity_title}
"
else
- title = truncate(activity_title, length: len)
+ title = truncate(activity_title, length: len, escape: false)
end
message = message.gsub(/#{Regexp.escape(activity_title)}/, title)
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 1713b706c..7a5222ee0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -126,12 +126,12 @@ module ApplicationHelper
next unless project
if project.archived?
"" \
- "#{sanitize_input(match[2])} " \
+ "#{sanitize_input(match[2])}" \
"#{link_to project.name,
projects_archive_path} #{I18n.t('atwho.res.archived')}"
else
"" \
- "#{sanitize_input(match[2])} " \
+ "#{sanitize_input(match[2])}" \
"#{link_to project.name,
project_path(project)}"
end
@@ -140,13 +140,13 @@ module ApplicationHelper
next unless experiment
if experiment.archived?
"" \
- "#{sanitize_input(match[2])} " \
+ "#{sanitize_input(match[2])}" \
"#{link_to experiment.name,
experiment_archive_project_path(experiment.project)} " \
"#{I18n.t('atwho.res.archived')}"
else
""\
- "#{sanitize_input(match[2])} " \
+ "#{sanitize_input(match[2])}" \
"#{link_to experiment.name,
canvas_experiment_path(experiment)}"
end
@@ -155,25 +155,25 @@ module ApplicationHelper
next unless my_module
if my_module.archived?
"" \
- "#{sanitize_input(match[2])} " \
+ "#{sanitize_input(match[2])}" \
"#{link_to my_module.name,
module_archive_experiment_path(my_module.experiment)} " \
"#{I18n.t('atwho.res.archived')}"
else
"" \
- "#{sanitize_input(match[2])} " \
+ "#{sanitize_input(match[2])}" \
"#{link_to my_module.name,
protocols_my_module_path(my_module)}"
end
when 'sam'
sample = Sample.find_by_id(match[3].base62_decode)
if sample
- " " \
+ "" \
"#{link_to(sample.name,
sample_path(sample.id),
class: 'sample-info-link')}"
else
- " " \
+ "" \
"#{match[1]} #{I18n.t('atwho.res.deleted')}"
end
end
diff --git a/app/helpers/permission_helper.rb b/app/helpers/permission_helper.rb
index 907cb2ac8..ef403766f 100644
--- a/app/helpers/permission_helper.rb
+++ b/app/helpers/permission_helper.rb
@@ -64,7 +64,6 @@ module PermissionHelper
:can_edit_connections,
:can_create_modules,
:can_edit_modules,
- :can_edit_module_groups,
:can_clone_modules,
:can_archive_modules,
:can_view_reports,
@@ -149,7 +148,6 @@ module PermissionHelper
:can_edit_connections,
:can_create_modules,
:can_edit_modules,
- :can_edit_module_groups,
:can_clone_modules,
:can_archive_modules
] do |proxy, *args, &block|
@@ -421,10 +419,6 @@ module PermissionHelper
is_user_or_higher_of_project(experiment.project)
end
- def can_edit_module_groups(experiment)
- is_user_or_higher_of_project(experiment.project)
- end
-
def can_clone_modules(experiment)
is_user_or_higher_of_project(experiment.project)
end
diff --git a/app/models/experiment.rb b/app/models/experiment.rb
index 334a2b626..8920484b4 100644
--- a/app/models/experiment.rb
+++ b/app/models/experiment.rb
@@ -96,26 +96,27 @@ class Experiment < ApplicationRecord
end
def modules_without_group
- MyModule.where(experiment_id: id).where(my_module_group: nil)
- .where(archived: false)
+ MyModule.where(experiment_id: id)
+ .where(my_module_group: nil)
+ .where(archived: false)
end
def active_module_groups
- self.my_module_groups.joins(:my_modules)
- .where('my_modules.archived = ?', false)
- .distinct
+ my_module_groups.joins(:my_modules)
+ .where('my_modules.archived = ?', false)
+ .distinct
end
def active_modules
- my_modules.where(:archived => false)
+ my_modules.where(archived: false)
end
def archived_modules
- my_modules.where(:archived => true)
+ my_modules.where(archived: true)
end
def assigned_samples
- Sample.joins(:my_modules).where(my_modules: {id: my_modules} )
+ Sample.joins(:my_modules).where(my_modules: { id: my_modules })
end
def unassigned_samples(assigned_samples)
@@ -131,15 +132,15 @@ class Experiment < ApplicationRecord
to_clone,
connections,
positions,
- current_user,
- module_groups
+ current_user
)
cloned_modules = []
begin
- Experiment.transaction do
+ with_lock do
# First, add new modules
new_ids, cloned_pairs, originals = add_modules(
- to_add, to_clone, current_user)
+ to_add, to_clone, current_user
+ )
cloned_modules = cloned_pairs.collect { |mn, _| mn }
# Rename modules
@@ -147,39 +148,31 @@ class Experiment < ApplicationRecord
# Add activities that modules were created
originals.each do |m|
- Activity.create(
- type_of: :create_module,
+ Activity.create(type_of: :create_module,
user: current_user,
- project: self.project,
+ project: project,
experiment: m.experiment,
my_module: m,
- message: I18n.t(
- "activities.create_module",
- user: current_user.full_name,
- module: m.name
- )
- )
+ message: I18n.t('activities.create_module',
+ user: current_user.full_name,
+ module: m.name))
end
# Add activities that modules were cloned
cloned_pairs.each do |mn, mo|
- Activity.create(
- type_of: :clone_module,
+ Activity.create(type_of: :clone_module,
project: mn.experiment.project,
experiment: mn.experiment,
my_module: mn,
user: current_user,
- message: I18n.t(
- "activities.clone_module",
- user: current_user.full_name,
- module_new: mn.name,
- module_original: mo.name
- )
- )
+ message: I18n.t('activities.clone_module',
+ user: current_user.full_name,
+ module_new: mn.name,
+ module_original: mo.name))
end
# Then, archive modules that need to be archived
- archive_modules(to_archive, current_user)
+ archive_modules(to_archive, current_user) if to_archive.any?
# Update connections, positions & module group variables
# with actual IDs retrieved from the new modules creation
@@ -196,17 +189,13 @@ class Experiment < ApplicationRecord
updated_to_move_groups[mapped] = value
end
updated_connections = []
- connections.each do |a,b|
+ connections.each do |a, b|
updated_connections << [new_ids.fetch(a, a), new_ids.fetch(b, b)]
end
- updated_positions = Hash.new
+ updated_positions = {}
positions.each do |id, pos|
updated_positions[new_ids.fetch(id, id)] = pos
end
- updated_module_groups = {}
- module_groups.each do |id, name|
- updated_module_groups[new_ids.fetch(id, id)] = name
- end
# Update connections
update_module_connections(updated_connections)
@@ -218,7 +207,7 @@ class Experiment < ApplicationRecord
normalize_module_positions
# Finally, update module groups
- update_module_groups(updated_module_groups, current_user)
+ update_module_groups(current_user)
# Finally move any modules to another experiment
move_modules(updated_to_move)
@@ -226,11 +215,13 @@ class Experiment < ApplicationRecord
# Everyhing is set, now we can move any module groups
move_module_groups(updated_to_move_groups)
end
- rescue ActiveRecord::ActiveRecordError, ArgumentError, ActiveRecord::RecordNotSaved
+ rescue ActiveRecord::ActiveRecordError,
+ ArgumentError,
+ ActiveRecord::RecordNotSaved => ex
+ logger.error ex.message
return false
end
-
- return true
+ true
end
# This method generate the workflow image and saves it as
@@ -431,22 +422,15 @@ class Experiment < ApplicationRecord
# Archive all modules. Receives an array of module integer IDs.
def archive_modules(module_ids)
- module_ids.each do |m_id|
- my_module = self.my_modules.find_by_id(m_id)
- unless my_module.blank?
- my_module.archive!
- end
- end
- modules.reload
+ my_modules.where(id: module_ids).each(&:archive!)
+ my_modules.reload
end
- # Archive all modules. Receives an array of module integer IDs and current user.
+ # Archive all modules. Receives an array of module integer IDs
+ # and current user.
def archive_modules(module_ids, current_user)
- module_ids.each do |m_id|
- my_module = self.my_modules.find_by_id(m_id)
- unless my_module.blank?
- my_module.archive!(current_user)
- end
+ my_modules.where(id: module_ids).each do |m|
+ m.archive!(current_user)
end
my_modules.reload
end
@@ -460,15 +444,14 @@ class Experiment < ApplicationRecord
def add_modules(to_add, to_clone, current_user)
originals = []
cloned_pairs = {}
- ids_map = Hash.new
+ ids_map = {}
to_add.each do |m|
original = MyModule.find_by_id(to_clone.fetch(m[:id], nil))
- if original.present? then
+ if original.present?
my_module = original.deep_clone(current_user)
cloned_pairs[my_module] = original
else
- my_module = MyModule.new(
- experiment: self)
+ my_module = MyModule.new(experiment: self)
originals << my_module
end
@@ -537,7 +520,7 @@ class Experiment < ApplicationRecord
# to bottom left corner.
def move_module_groups(to_move)
to_move.each do |ids, experiment_id|
- modules = my_modules.where(id: ids)
+ modules = my_modules.find(ids)
groups = Set.new(modules.map(&:my_module_group))
experiment = project.experiments.find_by_id(experiment_id)
@@ -577,10 +560,8 @@ class Experiment < ApplicationRecord
# Generates workflow img when the workflow or module is moved
# to other experiment
def generate_workflow_img_for_moved_modules(to_move)
- to_move.values.uniq.each do |id|
- experiment = Experiment.find_by_id(id)
- next unless experiment
- experiment.delay.generate_workflow_img
+ Experiment.where(id: to_move.values.uniq).each do |exp|
+ exp.delay.generate_workflow_img
end
end
@@ -592,42 +573,39 @@ class Experiment < ApplicationRecord
require 'rgl/base'
require 'rgl/adjacency'
require 'rgl/topsort'
-
dg = RGL::DirectedAdjacencyGraph.new
- connections.each do |a,b|
+ connections.each do |a, b|
# Check if both vertices exist
- if (my_modules.find_all {|m| [a.to_i, b.to_i].include? m.id }).count == 2
+ if (my_modules.find_all { |m| [a.to_i, b.to_i].include? m.id }).count == 2
dg.add_edge(a, b)
end
end
# Check if cycles exist!
topsort = dg.topsort_iterator.to_a
- if topsort.length == 0 and dg.edges.size > 1
- raise ArgumentError, "Cycles exist."
+ if topsort.length.zero? && dg.edges.size > 1
+ raise ArgumentError, 'Cycles exist.'
end
# First, delete existing connections
# but keep a copy of previous state
previous_sources = {}
previous_sources.default = []
- my_modules.each do |m|
+
+ my_modules.includes(inputs: { from: [:inputs, outputs: :to] }).each do |m|
previous_sources[m.id] = []
m.inputs.each do |c|
previous_sources[m.id] << c.from
end
end
- self.my_modules.each do |m|
- unless m.outputs.destroy_all
- raise ActiveRecord::ActiveRecordError
- end
- end
+ # There are no callbacks in Connection, so delete_all should be safe
+ Connection.delete_all(output_id: my_modules)
# Add new connections
filtered_edges = dg.edges.collect { |e| [e.source, e.target] }
filtered_edges.each do |a, b|
- Connection.create!(:input_id => b, :output_id => a)
+ Connection.create!(input_id: b, output_id: a)
end
# Unassign samples from former downstream modules
@@ -637,47 +615,49 @@ class Experiment < ApplicationRecord
visited = []
# Assign samples to all new downstream modules
filtered_edges.each do |a, b|
- source = self.my_modules.find(a.to_i)
- target = self.my_modules.find(b.to_i)
+ source = my_modules.includes({ inputs: :from }, :samples).find(a.to_i)
+ target = my_modules.find(b.to_i)
# Do this only for new edges
- if previous_sources[target.id].exclude?(source)
- # Go as high upstream as new edges take us
- # and then assign samples to all downsteam samples
- assign_samples_to_new_downstream_modules(previous_sources, visited, source)
- end
+ next unless previous_sources[target.id].exclude?(source)
+ # Go as high upstream as new edges take us
+ # and then assign samples to all downsteam samples
+ assign_samples_to_new_downstream_modules(previous_sources,
+ visited,
+ source)
end
# Save topological order of modules (for modules without workflow,
# leave them unordered)
- self.my_modules.each do |m|
- if topsort.include? m.id.to_s
- m.workflow_order = topsort.find_index(m.id.to_s)
- else
- m.workflow_order = -1
- end
+ my_modules.includes(:my_module_group).each do |m|
+ m.workflow_order =
+ if topsort.include? m.id.to_s
+ topsort.find_index(m.id.to_s)
+ else
+ -1
+ end
m.save!
end
- # Make sure to reload my modules, which now have updated connections and samples
- self.my_modules.reload
+ # Make sure to reload my modules, which now have updated connections
+ # and samples
+ my_modules.reload
true
end
# When connections are deleted, unassign samples that
# are not inherited anymore
def unassign_samples_from_old_downstream_modules(sources)
- self.my_modules.each do |my_module|
- sources[my_module.id].each do |s|
+ my_modules.each do |my_module|
+ sources[my_module.id].each do |src|
# Only do this for newly deleted connections
- if s.outputs.map{|i| i.to}.exclude? my_module
- my_module.get_downstream_modules.each do |dm|
- # Get unique samples for all upstream modules
- um = dm.get_upstream_modules
- um.shift # remove current module
- ums = um.map{|m| m.samples}.flatten.uniq
- s.samples.each do |sample|
- dm.samples.destroy(sample) if ums.exclude? sample
- end
+ next unless src.outputs.map(&:to).exclude? my_module
+ my_module.downstream_modules.each do |dm|
+ # Get unique samples for all upstream modules
+ um = dm.upstream_modules
+ um.shift # remove current module
+ ums = um.map(&:samples).flatten.uniq
+ src.samples.find_each do |sample|
+ dm.samples.destroy(sample) if ums.exclude? sample
end
end
end
@@ -687,24 +667,21 @@ class Experiment < ApplicationRecord
# Assign samples to new connections recursively
def assign_samples_to_new_downstream_modules(sources, visited, my_module)
# If samples are already assigned for this module, stop going upstream
- if visited.include? (my_module)
- return
- end
+ return if visited.include?(my_module)
visited << my_module
- # Edge case, when module is source or it doesn't have any new input connections
- if my_module.inputs.blank? or (
- my_module.inputs.map{|c| c.from} -
- sources[my_module.id]
- ).empty?
- my_module.get_downstream_modules.each do |dm|
- new_samples = my_module.samples.select { |el| dm.samples.exclude?(el) }
- dm.samples.push(*new_samples)
+ # Edge case, when module is source or it doesn't have any new input
+ # connections
+ if my_module.inputs.blank? ||
+ (my_module.inputs.map(&:from) - sources[my_module.id]).empty?
+ my_module.downstream_modules.each do |dm|
+ new_samples = my_module.samples.where.not(id: dm.samples)
+ dm.samples << new_samples
end
else
my_module.inputs.each do |input|
# Go upstream for new in connections
if sources[my_module.id].exclude?(input.from)
- assign_samples_to_new_downstream_modules(input.from)
+ assign_samples_to_new_downstream_modules(sources, visited, input.from)
end
end
end
@@ -714,102 +691,58 @@ class Experiment < ApplicationRecord
# Input is a map where keys are module IDs, and values are
# hashes like { x: , y: }.
def update_module_positions(positions)
- positions.each do |id, pos|
- unless MyModule.update(id, x: pos[:x], y: pos[:y])
- raise ActiveRecord::ActiveRecordError
- end
+ modules = my_modules.where(id: positions.keys)
+ modules.each do |m|
+ m.update_columns(x: positions[m.id.to_s][:x], y: positions[m.id.to_s][:y])
end
- self.my_modules.reload
+ my_modules.reload
end
# Normalize module positions in this project.
def normalize_module_positions
# This method normalizes module positions so x-s and y-s
# are all positive
- x_diff = (self.my_modules.collect { |m| m.x }).min
- y_diff = (self.my_modules.collect { |m| m.y }).min
+ x_diff = my_modules.pluck(:x).min
+ y_diff = my_modules.pluck(:y).min
- self.my_modules.each do |m|
- unless
- m.update_attribute(:x, m.x - x_diff) and
- m.update_attribute(:y, m.y - y_diff)
- raise ActiveRecord::ActiveRecordError
- end
+ my_modules.each do |m|
+ m.update_columns(x: m.x - x_diff, y: m.y - y_diff)
end
end
# Recalculate module groups in this project. Input is
# a hash of module ids and their corresponding module names.
- def update_module_groups(module_groups, current_user)
+ def update_module_groups(current_user)
require 'rgl/base'
require 'rgl/adjacency'
require 'rgl/connected_components'
dg = RGL::DirectedAdjacencyGraph[]
group_ids = Set.new
- active_modules.each do |m|
- unless m.my_module_group.blank?
- group_ids << m.my_module_group.id
- end
- unless dg.has_vertex? m.id
- dg.add_vertex m.id
- end
+ active_modules.includes(:my_module_group, outputs: :to).each do |m|
+ group_ids << m.my_module_group.id unless m.my_module_group.blank?
+ dg.add_vertex m.id unless dg.has_vertex? m.id
m.outputs.each do |o|
dg.add_edge m.id, o.to.id
end
end
workflows = []
- dg.to_undirected.each_connected_component { |w| workflows << w }
-
- # For each workflow, generate new names
- new_index = 1
- wf_names = []
- suffix = I18n.t("my_module_groups.new.suffix")
- cut_index = -(suffix.length + 1)
- workflows.each do |w|
- modules = MyModule.find(w)
-
- # Get an array of module names
- names = []
- modules.each do |m|
- names << module_groups.fetch(m.id.to_s, "")
- end
- names = names.uniq
- name = (names.select { |v| v != "" }).join(", ")
-
- if w.length <= 1
- name = nil
- elsif name.blank?
- name = I18n.t("my_module_groups.new.name", index: new_index)
- new_index += 1
- while MyModuleGroup.find_by(name: name).present?
- name = I18n.t("my_module_groups.new.name", index: new_index)
- new_index += 1
- end
- elsif name.length > Constants::NAME_MAX_LENGTH
- # If length is too long, shorten it
- name = name[0..(Constants::NAME_MAX_LENGTH + cut_index)] + suffix
- end
-
- wf_names << name
+ dg.to_undirected.each_connected_component do |w|
+ workflows << my_modules.find(w)
end
# Remove any existing module groups from modules
- unless MyModuleGroup.where(id: group_ids.to_a).destroy_all
+ unless MyModuleGroup.destroy_all(id: group_ids.to_a)
raise ActiveRecord::ActiveRecordError
end
# Second, create new groups
- workflows.each_with_index do |w, i|
+ workflows.each do |modules|
# Single modules are not considered part of any workflow
- if w.length > 1
- group = MyModuleGroup.new(
- name: wf_names[i],
- experiment: self,
- my_modules: MyModule.find(w))
- group.created_by = current_user
- group.save!
- end
+ next unless modules.length > 1
+ MyModuleGroup.create!(experiment: self,
+ my_modules: modules,
+ created_by: current_user)
end
my_module_groups.reload
diff --git a/app/models/my_module.rb b/app/models/my_module.rb
index 90df13b09..e4677a97f 100644
--- a/app/models/my_module.rb
+++ b/app/models/my_module.rb
@@ -278,28 +278,24 @@ class MyModule < ApplicationRecord
end
# Treat this module as root, get all modules of that subtree
- def get_downstream_modules
+ def downstream_modules
final = []
modules = [self]
- while !modules.empty?
+ until modules.empty?
my_module = modules.shift
- if !final.include?(my_module)
- final << my_module
- end
+ final << my_module unless final.include?(my_module)
modules.push(*my_module.my_modules)
end
final
end
# Treat this module as inversed root, get all modules of that inversed subtree
- def get_upstream_modules
+ def upstream_modules
final = []
modules = [self]
- while !modules.empty?
+ until modules.empty?
my_module = modules.shift
- if !final.include?(my_module)
- final << my_module
- end
+ final << my_module unless final.include?(my_module)
modules.push(*my_module.my_module_antecessors)
end
final
diff --git a/app/models/my_module_group.rb b/app/models/my_module_group.rb
index 251229da9..bfe4493af 100644
--- a/app/models/my_module_group.rb
+++ b/app/models/my_module_group.rb
@@ -1,10 +1,6 @@
class MyModuleGroup < ApplicationRecord
include SearchableModel
- auto_strip_attributes :name, nullify: false
- validates :name,
- presence: true,
- length: { maximum: Constants::NAME_MAX_LENGTH }
validates :experiment, presence: true
belongs_to :experiment, inverse_of: :my_module_groups, optional: true
@@ -14,39 +10,12 @@ class MyModuleGroup < ApplicationRecord
optional: true
has_many :my_modules, inverse_of: :my_module_group, dependent: :nullify
- def self.search(user,
- include_archived,
- query = nil,
- page = 1,
- _current_team = nil,
- options = {})
- exp_ids =
- Experiment
- .search(user, include_archived, nil, Constants::SEARCH_NO_LIMIT)
- .pluck(:id)
-
- new_query = MyModuleGroup
- .distinct
- .where('my_module_groups.experiment_id IN (?)', exp_ids)
- .where_attributes_like('my_module_groups.name', query, options)
-
- # Show all results if needed
- if page == Constants::SEARCH_NO_LIMIT
- new_query
- else
- new_query
- .limit(Constants::SEARCH_LIMIT)
- .offset((page - 1) * Constants::SEARCH_LIMIT)
- end
- end
-
def ordered_modules
my_modules.order(workflow_order: :asc)
end
def deep_clone_to_experiment(current_user, experiment)
clone = MyModuleGroup.new(
- name: name,
created_by: created_by,
experiment: experiment
)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e9ec2fc51..b82a8688e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -60,17 +60,6 @@ class Repository < ApplicationRecord
end
end
- 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 record name
@@ -116,6 +105,7 @@ class Repository < ApplicationRecord
name_index = -1
total_nr = 0
nr_of_added = 0
+ header_skipped = false
mappings.each.with_index do |(_k, value), index|
if value == '-1'
@@ -132,54 +122,63 @@ class Repository < ApplicationRecord
unless col_compact.map(&:id).uniq.length == col_compact.length
return { status: :error, nr_of_added: nr_of_added, total_nr: total_nr }
end
+ rows = SpreadsheetParser.spreadsheet_enumerator(sheet)
# Now we can iterate through record data and save stuff into db
- transaction do
- (2..sheet.last_row).each do |i|
- total_nr += 1
- record_row = RepositoryRow.new(name: sheet.row(i)[name_index],
- repository: self,
- created_by: user,
- last_modified_by: user)
- record_row.transaction(requires_new: true) do
- unless record_row.save
- errors = true
- raise ActiveRecord::Rollback
- end
+ rows.each do |row|
+ # Skip empty rows
+ next if row.empty?
+ unless header_skipped
+ header_skipped = true
+ next
+ end
+ total_nr += 1
- row_cell_values = []
+ # Creek XLSX parser returns Hash of the row, Roo - Array
+ row = row.is_a?(Hash) ? row.values.map(&:to_s) : row.map(&:to_s)
- 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
+ record_row = RepositoryRow.new(name: row[name_index],
+ repository: self,
+ created_by: user,
+ last_modified_by: user)
+ record_row.transaction do
+ unless record_row.save
+ errors = true
+ raise ActiveRecord::Rollback
end
+
+ row_cell_values = []
+
+ row.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
@@ -188,22 +187,4 @@ class Repository < ApplicationRecord
end
{ status: :ok, nr_of_added: nr_of_added, total_nr: total_nr }
end
-
- private
-
- def generate_file(filename, file_path)
- case File.extname(filename)
- when '.csv'
- Roo::CSV.new(file_path, extension: :csv)
- when '.tsv'
- Roo::CSV.new(file_path, 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 '.xlsx'
- Roo::Excelx.new(file_path)
- else
- raise TypeError
- end
- end
end
diff --git a/app/models/team.rb b/app/models/team.rb
index e8fdfca64..05f139d0d 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -32,30 +32,6 @@ class Team < ApplicationRecord
has_many :protocol_keywords, inverse_of: :team, dependent: :destroy
has_many :tiny_mce_assets, inverse_of: :team, dependent: :destroy
has_many :repositories, dependent: :destroy
- # Based on file's extension opens file (used for importing)
- def self.open_spreadsheet(file)
- filename = file.original_filename
- file_path = file.path
-
- if file.class == Paperclip::Attachment and file.is_stored_on_s3?
- fa = file.fetch
- file_path = fa.path
- end
-
- case File.extname(filename)
- when '.csv' then
- Roo::CSV.new(file_path, extension: :csv)
- when '.tsv' then
- Roo::CSV.new(file_path, csv_options: { col_sep: "\t" })
- when '.txt' then
- # This assumption is based purely on biologist's habits
- Roo::CSV.new(file_path, csv_options: { col_sep: "\t" })
- when '.xlsx' then
- Roo::Excelx.new(file_path)
- else
- raise TypeError
- end
- end
def search_users(query = nil)
a_query = "%#{query}%"
@@ -72,6 +48,7 @@ class Team < ApplicationRecord
errors = false
nr_of_added = 0
total_nr = 0
+ header_skipped = false
# First let's query for all custom_fields we're refering to
custom_fields = []
@@ -97,10 +74,22 @@ class Team < ApplicationRecord
custom_fields << cf
end
end
+
+ rows = SpreadsheetParser.spreadsheet_enumerator(sheet)
+
# Now we can iterate through sample data and save stuff into db
- (2..sheet.last_row).each do |i|
+ rows.each do |row|
+ # Skip empty rows
+ next if row.empty?
+ unless header_skipped
+ header_skipped = true
+ next
+ end
total_nr += 1
- sample = Sample.new(name: sheet.row(i)[sname_index],
+ # Creek XLSX parser returns Hash of the row, Roo - Array
+ row = row.is_a?(Hash) ? row.values.map(&:to_s) : row.map(&:to_s)
+
+ sample = Sample.new(name: row[sname_index],
team: self,
user: user)
@@ -110,12 +99,14 @@ class Team < ApplicationRecord
raise ActiveRecord::Rollback
end
- sheet.row(i).each.with_index do |value, index|
+ row.each.with_index do |value, index|
if index == stype_index
- stype = SampleType.where(name: value.strip, team: self).take
+ stype = SampleType.where(team: self)
+ .where('name ILIKE ?', value.strip)
+ .take
unless stype
- stype = SampleType.new(name: value, team: self)
+ stype = SampleType.new(name: value.strip, team: self)
unless stype.save
errors = true
raise ActiveRecord::Rollback
@@ -123,10 +114,12 @@ class Team < ApplicationRecord
end
sample.sample_type = stype
elsif index == sgroup_index
- sgroup = SampleGroup.where(name: value.strip, team: self).take
+ sgroup = SampleGroup.where(team: self)
+ .where('name ILIKE ?', value.strip)
+ .take
unless sgroup
- sgroup = SampleGroup.new(name: value, team: self)
+ sgroup = SampleGroup.new(name: value.strip, team: self)
unless sgroup.save
errors = true
raise ActiveRecord::Rollback
diff --git a/app/services/import_repository/import_records.rb b/app/services/import_repository/import_records.rb
index e9deea8b4..ba80a4b0a 100644
--- a/app/services/import_repository/import_records.rb
+++ b/app/services/import_repository/import_records.rb
@@ -17,9 +17,11 @@ module ImportRepository
private
def run_import_actions
- @repository.import_records(@repository.open_spreadsheet(@temp_file.file),
- @mappings,
- @user)
+ @repository.import_records(
+ SpreadsheetParser.open_spreadsheet(@temp_file.file),
+ @mappings,
+ @user
+ )
end
def run_checks
diff --git a/app/services/import_repository/parse_repository.rb b/app/services/import_repository/parse_repository.rb
index 192f4b61e..222b71274 100644
--- a/app/services/import_repository/parse_repository.rb
+++ b/app/services/import_repository/parse_repository.rb
@@ -5,48 +5,40 @@ module ImportRepository
@file = options.fetch(:file)
@repository = options.fetch(:repository)
@session = options.fetch(:session)
- @sheet = @repository.open_spreadsheet(@file)
+ @sheet = SpreadsheetParser.open_spreadsheet(@file)
end
def data
- # Get data (it will trigger any errors as well)
- header = @sheet.row(1)
- columns = @sheet.row(2)
+ header, columns = SpreadsheetParser.first_two_rows(@sheet)
# Fill in fields for dropdown
@repository.available_repository_fields.transform_values! do |name|
truncate(name, length: Constants::NAME_TRUNCATION_LENGTH_DROPDOWN)
end
- @temp_file = TempFile.create(session_id: @session.id, file: @file)
Data.new(header,
columns,
@repository.available_repository_fields,
- @repository,
- @temp_file)
+ @repository)
end
def too_large?
@file.size > Constants::FILE_MAX_SIZE_MB.megabytes
end
- def empty?
- @sheet.last_row.between?(0, 1)
- end
-
- def generated_temp_file?
+ def generate_temp_file
# Save file for next step (importing)
- @temp_file = TempFile.new(
+ temp_file = TempFile.new(
session_id: @session.id,
file: @file
)
- if @temp_file.save
- @temp_file.destroy_obsolete
- return true
+ if temp_file.save
+ temp_file.destroy_obsolete
+ return temp_file
end
end
Data = Struct.new(
- :header, :columns, :available_fields, :repository, :temp_file
+ :header, :columns, :available_fields, :repository
)
end
end
diff --git a/app/services/spreadsheet_parser.rb b/app/services/spreadsheet_parser.rb
new file mode 100644
index 000000000..3d5a64774
--- /dev/null
+++ b/app/services/spreadsheet_parser.rb
@@ -0,0 +1,55 @@
+class SpreadsheetParser
+ # Based on file's extension opens file (used for importing)
+ def self.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
+
+ case File.extname(filename)
+ when '.csv'
+ Roo::CSV.new(file_path, extension: :csv)
+ when '.tsv'
+ Roo::CSV.new(file_path, 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 '.xlsx'
+ # Roo Excel parcel was replaced with Creek, but it can be enabled back,
+ # just swap lines below. But only one can be enabled at the same time.
+ # Roo::Excelx.new(file_path)
+ Creek::Book.new(file_path).sheets[0]
+ else
+ raise TypeError
+ end
+ end
+
+ def self.spreadsheet_enumerator(sheet)
+ if sheet.is_a?(Roo::CSV)
+ sheet
+ elsif sheet.is_a?(Roo::Excelx)
+ sheet.each_row_streaming
+ else
+ sheet.rows
+ end
+ end
+
+ def self.first_two_rows(sheet)
+ rows = spreadsheet_enumerator(sheet)
+ header = []
+ columns = []
+ i = 1
+ rows.each do |row|
+ # Creek XLSX parser returns Hash of the row, Roo - Array
+ row = row.is_a?(Hash) ? row.values.map(&:to_s) : row.map(&:to_s)
+ header = row if i == 1 && row
+ columns = row if i == 2 && row
+ i += 1
+ break if i > 2
+ end
+ return header, columns
+ end
+end
diff --git a/app/utilities/delayed_uploader_tutorial.rb b/app/utilities/delayed_uploader_tutorial.rb
index 993e519cb..87e349f1a 100644
--- a/app/utilities/delayed_uploader_tutorial.rb
+++ b/app/utilities/delayed_uploader_tutorial.rb
@@ -41,6 +41,7 @@ module DelayedUploaderTutorial
my_module: my_module,
user: current_user,
created_at: temp_result.created_at,
+ updated_at: temp_result.created_at,
message: I18n.t(
'activities.add_asset_result',
user: current_user.full_name,
diff --git a/app/utilities/first_time_data_generator.rb b/app/utilities/first_time_data_generator.rb
index c77cf8ca0..c3a7933d2 100644
--- a/app/utilities/first_time_data_generator.rb
+++ b/app/utilities/first_time_data_generator.rb
@@ -150,7 +150,6 @@ module FirstTimeDataGenerator
# Create a module group
my_module_group = MyModuleGroup.create(
- name: 'Potato qPCR workflow',
experiment: experiment
)
@@ -317,7 +316,7 @@ module FirstTimeDataGenerator
samples_to_assign << sample
end
- my_modules[1].get_downstream_modules.each do |mm|
+ my_modules[1].downstream_modules.each do |mm|
samples_to_assign.each do |s|
SampleMyModule.create(
sample: s,
@@ -881,7 +880,7 @@ module FirstTimeDataGenerator
).sneaky_save
# create thumbnail
- experiment.generate_workflow_img
+ experiment.delay.generate_workflow_img
# Lastly, create cookie with according ids
# so tutorial steps can be properly positioned
diff --git a/app/views/canvas/_edit.html.erb b/app/views/canvas/_edit.html.erb
index 45b44cb37..a362c5066 100644
--- a/app/views/canvas/_edit.html.erb
+++ b/app/views/canvas/_edit.html.erb
@@ -1,7 +1,6 @@
"
data-can-edit-modules="<%= can_edit_modules(@experiment) ? "yes" : "no" %>"
- data-can-edit-module-groups="<%= can_edit_module_groups(@experiment) ? "yes" : "no" %>"
data-can-clone-modules="<%= can_clone_modules(@experiment) ? "yes" : "no" %>"
data-can-move-modules="<%= can_move_modules(@experiment) ? "yes" : "no" %>"
data-can-delete-modules="<%= can_archive_modules(@experiment) ? "yes" : "no" %>"
@@ -49,9 +48,6 @@
<%=t "experiments.canvas.edit.edit_module" %>
-
- <%=t "experiments.canvas.edit.edit_module_group" %>
-
<%=t "experiments.canvas.edit.clone_module" %>
@@ -87,9 +83,6 @@
<% if can_edit_modules(@experiment) %>
<%= render partial: "canvas/edit/modal/edit_module", locals: {experiment: @experiment } %>
<% end %>
-<% if can_edit_module_groups(@experiment) %>
- <%= render partial: "canvas/edit/modal/edit_module_group", locals: {experiment: @experiment } %>
-<% end %>
<% if can_move_modules(@experiment) %>
<%= render partial: "canvas/edit/modal/move_module", locals: {experiment: @experiment } %>
<%= render partial: "canvas/edit/modal/move_module_group", locals: {experiment: @experiment } %>
diff --git a/app/views/canvas/edit/_my_module.html.erb b/app/views/canvas/edit/_my_module.html.erb
index 54ee90c4e..d0a876290 100644
--- a/app/views/canvas/edit/_my_module.html.erb
+++ b/app/views/canvas/edit/_my_module.html.erb
@@ -2,11 +2,6 @@
id="<%= my_module.id %>"
data-module-id="<%= my_module.id %>"
data-module-name="<%= my_module.name %>"
- <% if my_module.my_module_group.present? %>
- data-module-group-name="<%= my_module.my_module_group.name %>"
- <% else %>
- data-module-group-name=""
- <% end %>
data-module-x="<%= my_module.x %>"
data-module-y="<%= my_module.y %>"
data-module-conns="<%= construct_module_connections(my_module) %>">
@@ -26,11 +21,6 @@
<%=t "experiments.canvas.edit.edit_module" %>
<% end %>
- <% if can_edit_module_groups(my_module.experiment) %>
-
>
- <%=t "experiments.canvas.edit.edit_module_group" %>
-
- <% end %>
<% if can_clone_modules(my_module.experiment) %>
<%=t "experiments.canvas.edit.clone_module" %>
diff --git a/app/views/canvas/edit/modal/_edit_module_group.html.erb b/app/views/canvas/edit/modal/_edit_module_group.html.erb
deleted file mode 100644
index d2e14a628..000000000
--- a/app/views/canvas/edit/modal/_edit_module_group.html.erb
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
- <%= bootstrap_form_tag do |f| %>
- <%= f.text_field t("experiments.canvas.edit.modal_edit_module_group.name"), id: "edit-module-group-name-input" %>
- <% end %>
-
-
-
-
-
diff --git a/app/views/experiments/canvas.html.erb b/app/views/experiments/canvas.html.erb
index 798e1d845..ef6a09b04 100644
--- a/app/views/experiments/canvas.html.erb
+++ b/app/views/experiments/canvas.html.erb
@@ -58,7 +58,7 @@
- <%= render partial: 'canvas/full_zoom', locals: { experiment: @experiment, my_modules: @experiment.active_modules } %>
+ <%= render partial: 'canvas/full_zoom', locals: { experiment: @experiment, my_modules: @active_modules } %>
diff --git a/app/views/protocols/header/_edit_keywords_modal_body.html.erb b/app/views/protocols/header/_edit_keywords_modal_body.html.erb
index 51250f321..021e33ad1 100644
--- a/app/views/protocols/header/_edit_keywords_modal_body.html.erb
+++ b/app/views/protocols/header/_edit_keywords_modal_body.html.erb
@@ -4,4 +4,5 @@
<% end %>
-<% end %>
\ No newline at end of file
+ <%=t 'protocols.header.keywords_modal' %>
+<% end %>
diff --git a/app/views/reports/report.pdf.erb b/app/views/reports/report.pdf.erb
index aaa96c774..6e96f962d 100644
--- a/app/views/reports/report.pdf.erb
+++ b/app/views/reports/report.pdf.erb
@@ -10,9 +10,7 @@
<% # Also whitelist
![]()
and
tags %>
- <%= sanitize_input(fix_smart_annotation_image(@html),
- ['img', 'input'],
- ['type', 'disabled', 'checked']) %>
+ <%= @html.html_safe %>