Merge branch 'rails-5.1' into decoupling-settings-page

Conflicts:
	Gemfile
	Gemfile.lock
This commit is contained in:
Luka Murn 2017-10-23 10:59:10 +02:00
commit 82f982e983
46 changed files with 692 additions and 1001 deletions

View file

@ -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',

View file

@ -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)

View file

@ -1 +1 @@
1.12.3
1.12.5

View file

@ -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');

View file

@ -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();

View file

@ -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 &&

View file

@ -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 "<p><%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %></p>";
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 "<p class='dnd-error'><%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %></p>";
}
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("<p class='dnd-error'><%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %></p>");
if(!$(this).find('p').hasClass('dnd-total-error')) {
$(this)
.find('.panel-body')
.append("<p class='dnd-total-error'><%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %></p>");
}
});
} 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 = '<div class="panel panel-default panel-step-attachment-new">';
html += '<div class="panel-heading">';
html += '<span class="glyphicon glyphicon-file"></span>';
@ -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 "<p><%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %></p>";
var fileSize = file.size;
totalSize += parseInt(fileSize);
if(fileSize > <%= Constants::FILE_MAX_SIZE_MB.megabyte %>) {
file.isValid = false;
_disableSubmitButton();
return "<p class='dnd-error'><%= I18n.t 'general.file.size_exceeded', file_size: Constants::FILE_MAX_SIZE_MB %></p>";
}
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("<p class='dnd-error'><%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %></p>");
if(!$(this).find('p').hasClass('dnd-total-error')) {
$(this)
.find('.panel-body')
.append("<p class='dnd-total-error'><%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %></p>");
}
});
} 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 = '<div class="panel panel-default panel-result-attachment-new">';
html += '<div class="panel-heading">';
html += '<span class="glyphicon glyphicon-file"></span>';
@ -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();
});
}

View file

@ -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);

View file

@ -1288,7 +1288,8 @@ ul.content-module-activities {
padding-top: 30px;
}
.dnd-error {
.dnd-error,
.dnd-total-error {
color: $color-milano-red;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -182,7 +182,8 @@ class ReportsController < ApplicationController
@html = '<h1>No content</h1>' 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

View file

@ -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: {

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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])

View file

@ -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>(.*?)<\/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('</a>')
unless closing_tag_sa.nil?
opening_tag_sa = activity_title.index('<img')
opening_temp = activity_title.index('<span')
# depending on user/experiment set the first opening tag
opening_tag_sa = opening_temp if opening_tag_sa.nil? ||
opening_tag_sa > 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('</a>', closing_temp)
unless closing_tag_sa.nil?
# find next smart annotation
opening_tag_sa = activity_title.index('<img', closing_temp)
opening_temp = activity_title.index('<span', closing_temp)
# depending on user/experiment set the next opening tag
opening_tag_sa = opening_temp if opening_tag_sa.nil? ||
opening_tag_sa > 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 = "<div class='modal-tooltip'>
#{truncate(activity_title, length: len)}
#{truncate(activity_title, length: len, escape: false)}
<span class='modal-tooltiptext'>
#{activity_title}
</span>
</div>"
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

View file

@ -126,12 +126,12 @@ module ApplicationHelper
next unless project
if project.archived?
"<span class='sa-type'>" \
"#{sanitize_input(match[2])}</span> " \
"#{sanitize_input(match[2])}</span>" \
"#{link_to project.name,
projects_archive_path} #{I18n.t('atwho.res.archived')}"
else
"<span class='sa-type'>" \
"#{sanitize_input(match[2])}</span> " \
"#{sanitize_input(match[2])}</span>" \
"#{link_to project.name,
project_path(project)}"
end
@ -140,13 +140,13 @@ module ApplicationHelper
next unless experiment
if experiment.archived?
"<span class='sa-type'>" \
"#{sanitize_input(match[2])}</span> " \
"#{sanitize_input(match[2])}</span>" \
"#{link_to experiment.name,
experiment_archive_project_path(experiment.project)} " \
"#{I18n.t('atwho.res.archived')}"
else
"<span class='sa-type'>"\
"#{sanitize_input(match[2])}</span> " \
"#{sanitize_input(match[2])}</span>" \
"#{link_to experiment.name,
canvas_experiment_path(experiment)}"
end
@ -155,25 +155,25 @@ module ApplicationHelper
next unless my_module
if my_module.archived?
"<span class='sa-type'>" \
"#{sanitize_input(match[2])}</span> " \
"#{sanitize_input(match[2])}</span>" \
"#{link_to my_module.name,
module_archive_experiment_path(my_module.experiment)} " \
"#{I18n.t('atwho.res.archived')}"
else
"<span class='sa-type'>" \
"#{sanitize_input(match[2])}</span> " \
"#{sanitize_input(match[2])}</span>" \
"#{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
"<span class='glyphicon glyphicon-tint'></span> " \
"<span class='glyphicon glyphicon-tint'></span>" \
"#{link_to(sample.name,
sample_path(sample.id),
class: 'sample-info-link')}"
else
"<span class='glyphicon glyphicon-tint'></span> " \
"<span class='glyphicon glyphicon-tint'></span>" \
"#{match[1]} #{I18n.t('atwho.res.deleted')}"
end
end

View file

@ -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

View file

@ -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: <x>, y: <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

View file

@ -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

View file

@ -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
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -1,7 +1,6 @@
<div id="update-canvas"
data-can-create-modules="<%= can_create_modules(@experiment) ? "yes" : "no" %>"
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 @@
<span style="display: none;" id="edit-link-placeholder">
<%=t "experiments.canvas.edit.edit_module" %>
</span>
<span style="display: none;" id="edit-group-link-placeholder">
<%=t "experiments.canvas.edit.edit_module_group" %>
</span>
<span style="display: none;" id="clone-link-placeholder">
<%=t "experiments.canvas.edit.clone_module" %>
</span>
@ -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 } %>

View file

@ -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 @@
<a class="edit-module" href="" data-module-id="<%= my_module.id %>"><%=t "experiments.canvas.edit.edit_module" %></a>
</li>
<% end %>
<% if can_edit_module_groups(my_module.experiment) %>
<li <%= 'style=display:none;' if my_module.my_module_group.blank? %>>
<a class="edit-module-group" href="" data-module-id="<%= my_module.id %>"><%=t "experiments.canvas.edit.edit_module_group" %></a>
</li>
<% end %>
<% if can_clone_modules(my_module.experiment) %>
<li>
<a class ="clone-module" href="" data-module-id="<%= my_module.id %>"><%=t "experiments.canvas.edit.clone_module" %></a>

View file

@ -1,19 +0,0 @@
<div class="modal fade" id="modal-edit-module-group" tabindex="-1" role="dialog" aria-labelledby="modal-edit-module-group-label">
<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" id="modal-edit-module-group-label"><%=t "experiments.canvas.edit.modal_edit_module_group.title" %></h4>
</div>
<div class="modal-body">
<%= bootstrap_form_tag do |f| %>
<%= f.text_field t("experiments.canvas.edit.modal_edit_module_group.name"), id: "edit-module-group-name-input" %>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-action="confirm"><%=t "experiments.canvas.edit.modal_edit_module_group.confirm" %></button>
<button type="button" class="btn btn-default" data-dismiss="modal"><%=t "general.cancel" %></button>
</div>
</div>
</div>
</div>

View file

@ -58,7 +58,7 @@
</div>
<div id="canvas-container" data-project-id="<%= @project.id %>">
<%= 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 } %>
</div>
<!-- Manage tags modal -->

View file

@ -4,4 +4,5 @@
<option value="<%= kw.name %>"><%= kw.name %></option>
<% end %>
</select>
<% end %>
<i><%=t 'protocols.header.keywords_modal' %></i>
<% end %>

View file

@ -10,9 +10,7 @@
<body class="print-report-body">
<div class="print-report">
<% # Also whitelist <img> and <input type="checkbox"> tags %>
<%= sanitize_input(fix_smart_annotation_image(@html),
['img', 'input'],
['type', 'disabled', 'checked']) %>
<%= @html.html_safe %>
</div>
</body>
</html>

View file

@ -58,7 +58,7 @@
</tbody>
</table>
</div>
<%= hidden_field_tag 'file_id', @import_data.temp_file.id %>
<%= hidden_field_tag 'file_id', @temp_file.id %>
<div id="import-errors-container">
</div>

View file

@ -11,6 +11,7 @@
<br />
<button type="button"
class="btn btn-primary save-result"
disabled="true"
data-href="<%= my_module_result_assets_path(format: :json) %>"
onClick="DragNDropResults.processResult(this)"><%=t 'result_assets.new.create' %></button>
<button type="button"
@ -18,5 +19,4 @@
onClick="DragNDropResults.destroyAll()">
<%= t("general.cancel")%>
</button>
<%# end %>
</div>

View file

@ -70,19 +70,6 @@
<%= t'Experiments' %>
</a>
</li>
<li role="presentation"
class="
<%= "active" if @search_category.present? and @search_category == :workflows %>
<%= "disabled" if @workflow_search_count == 0 %>"
>
<a href="?<%= {category: 'workflows', q: @search_query,
whole_word: @search_whole_word, whole_phrase: @search_whole_phrase,
match_case: @search_case, utf8: '✓'}.to_query %>">
<span class="badge pull-right"><%= @workflow_search_count %></span>
<span class="glyphicon glyphicon-random"></span>
<%= t'Workflows' %>
</a>
</li>
<li role="presentation"
class="
<%= "active" if @search_category.present? and @search_category == :modules %>
@ -268,9 +255,6 @@
<% if @search_category == :experiments and @experiment_search_count > 0 %>
<%= render 'search/results/experiments', search_query: @search_query, results: @experiment_results %>
<% end %>
<% if @search_category == :workflows and @workflow_search_count > 0 %>
<%= render 'search/results/workflows', search_query: @search_query, results: @workflow_results %>
<% end %>
<% if @search_category == :modules and @module_search_count > 0 %>
<%= render 'search/results/modules', search_query: @search_query, results: @module_results %>
<% end %>

View file

@ -1,33 +0,0 @@
<% results.each do |workflow| %>
<h5>
<span class="glyphicon glyphicon-random"></span>
<%= highlight workflow.name, search_query.strip.split(/\s+/) %>
</h5>
<p>
<span>
<%=t 'search.index.created_at' %>
<%=l workflow.created_at, format: :full %>
</span>
<br>
<span>
<%=t 'search.index.experiment' %>
<%= render partial: 'search/results/partials/experiment_text.html.erb',
locals: { experiment: workflow.experiment } %>
</span>
<br>
<span>
<%=t 'search.index.project' %>
<%= render partial: 'search/results/partials/project_text.html.erb',
locals: { project: workflow.experiment.project, link_to_page: :show } %>
</span>
<br>
<span>
<%=t 'search.index.team' %>
<%= render partial: 'search/results/partials/team_text.html.erb',
locals: { team: workflow.experiment.project.team } %>
</span>
</p>
<hr>
<% end %>

View file

@ -1,4 +1,4 @@
Devise::Async.enabled = true
Devise::Async.backend = :delayed_job
Devise::Async.queue = :devise_email
# Devise::Async.priority = 10
# Devise::Async.priority = 10

View file

@ -705,7 +705,6 @@ en:
drag_connections: "Drag connection/s from here"
options_header: "Options"
edit_module: "Rename task"
edit_module_group: "Rename workflow"
clone_module: "Copy task as template (only Protocols steps copied)"
clone_module_group: "Copy workflow as template (only Protocols steps copied)"
move_module: "Move task to another experiment"
@ -721,10 +720,6 @@ en:
title: "Rename task"
name: "Task name"
confirm: "Rename task"
modal_edit_module_group:
title: "Rename workflow"
name: "Workflow name"
confirm: "Rename workflow"
modal_move_module:
title: "Move task to experiment"
confirm: "Move task"
@ -1421,6 +1416,7 @@ en:
no_authors: "No authors"
description: "Description"
no_description: "No description"
keywords_modal: "Input one or multiple keywords, confirm each keyword with ENTER key"
edit_name_modal:
title: "Edit name of protocol %{protocol}"
label: "Name"

View file

@ -0,0 +1,7 @@
class AddConnectionsAndSampleTasksIndexes < ActiveRecord::Migration[4.2]
def change
add_index :connections, :input_id
add_index :connections, :output_id
add_index :sample_my_modules, :my_module_id
end
end

View file

@ -0,0 +1,5 @@
class UpdateMyModuleGroups < ActiveRecord::Migration[4.2]
def change
remove_column :my_module_groups, :name, :string
end
end

View file

@ -8,15 +8,7 @@ class MyModuleGroupTest < ActiveSupport::TestCase
@module_group = my_module_groups(:wf1)
end
should validate_presence_of(:name)
should validate_length_of(:name)
.is_at_most(Constants::NAME_MAX_LENGTH)
test "should validate with valid data" do
assert @module_group.valid?
end
test "where_attributes_like should work" do
attributes_like_test(MyModuleGroup, :name, "expression")
end
end