diff --git a/Gemfile b/Gemfile
index 9b77f0893..d545da3ae 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,6 +14,7 @@ gem 'bootstrap_form'
gem 'yomu'
gem 'font-awesome-rails', '~> 4.6'
gem 'recaptcha', require: 'recaptcha/rails'
+gem 'sanitize', '~> 4.4'
# JS datetime library, requirement of datetime picker
gem 'momentjs-rails', '>= 2.9.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 080015b5c..f0a7e09df 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -103,6 +103,7 @@ GEM
colorize (0.8.1)
commit_param_routing (0.0.1)
concurrent-ruby (1.0.0)
+ crass (1.0.2)
debug_inspector (0.0.2)
deface (1.0.2)
colorize (>= 0.5.8)
@@ -192,6 +193,8 @@ GEM
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
+ nokogumbo (1.4.10)
+ nokogiri
oj (2.17.4)
orm_adapter (0.5.0)
paperclip (4.3.2)
@@ -266,6 +269,10 @@ GEM
ruby-graphviz (1.2.2)
ruby-progressbar (1.8.1)
rubyzip (1.1.7)
+ sanitize (4.4.0)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.4.4)
+ nokogumbo (~> 1.4.1)
sass (3.4.23)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
@@ -387,6 +394,7 @@ DEPENDENCIES
rubocop
ruby-graphviz (~> 1.2)
rubyzip
+ sanitize (~> 4.4)
sass-rails (~> 5.0)
scss_lint
sdoc (~> 0.4.0)
diff --git a/app/assets/javascripts/protocols/steps.js.erb b/app/assets/javascripts/protocols/steps.js.erb
index ffcd6e459..a536e33be 100644
--- a/app/assets/javascripts/protocols/steps.js.erb
+++ b/app/assets/javascripts/protocols/steps.js.erb
@@ -544,14 +544,12 @@ $("[data-action='new-step']").on("ajax:success", function(e, data) {
toggleButtons(false);
initializeCheckboxSorting();
-
$("#step_name").focus();
$("#new-step-main-tab a").on("shown.bs.tab", function() {
$("#step_name").focus();
});
TinyMCE.refresh();
-
});
// Needed because server-side validation failure clears locations of
diff --git a/app/assets/javascripts/results/result_assets.js b/app/assets/javascripts/results/result_assets.js
index cf9f0e456..93493a8a2 100644
--- a/app/assets/javascripts/results/result_assets.js
+++ b/app/assets/javascripts/results/result_assets.js
@@ -25,7 +25,8 @@
Results.toggleResultEditButtons(false);
$('#result_name').focus();
},
- error: function() {
+ error: function(xhr, status, e) {
+ $(this).renderFormErrors('result', xhr.responseJSON, true, e);
animateSpinner(null, false);
initNewResultAsset();
}
@@ -74,15 +75,8 @@
initPreviewModal();
Comments.initialize();
initNewResultAsset();
- }).on('ajax:error', function(e, data) {
- // This check is here only because of remotipart bug, which returns
- // HTML instead of JSON, go figure
- var errors = '';
- if (data.errors)
- errors = data.errors;
- else
- errors = data.responseJSON.errors;
- $form.renderFormErrors('result', errors, true, e);
+ }).on('ajax:error', function(xhr, status, e) {
+ $form.renderFormErrors('result', xhr.responseJSON, true, e);
animateSpinner(null, false);
});
}
diff --git a/app/assets/javascripts/samples/sample_datatable.js.erb b/app/assets/javascripts/samples/sample_datatable.js.erb
index c0bbff776..a53a1ccad 100644
--- a/app/assets/javascripts/samples/sample_datatable.js.erb
+++ b/app/assets/javascripts/samples/sample_datatable.js.erb
@@ -154,7 +154,10 @@ function dataTableInit() {
fnInitComplete: function(oSettings, json) {
// Reload correct column order and visibility (if you refresh page)
for (var i = 0; i < table.columns()[0].length; i++) {
- var visibility = myData.columns[i].visible;
+ var visibility = false;
+ if (myData.columns[i]) {
+ visibility = myData.columns[i].visible;
+ }
if (typeof (visibility) === 'string') {
visibility = (visibility === 'true');
}
@@ -976,7 +979,6 @@ function changeToEditMode() {
// Add number of columns
$('#samples').data('num-columns',
$('#samples').data('num-columns') + 1);
-
// Add column to table (=table header)
originalHeader.append(
'
| ');
+ var $errSpan = "' + errorText + '';
+ $formGroup.append($errSpan);
+ }
+
+ var $parent;
+ var $tab = $(input).closest('.tab-pane');
+ if ($tab.length) {
+ // Mark error tab
+ tabsPropagateErrorClass($form);
+ $parent = $tab;
+ } else {
+ $parent = $form;
+ }
+
+ // Focus and scroll to the error if it is the first (most upper) one
+ if ($parent.find('.form-group.has-error').length === 1 ||
+ _.isUndefined(errMsgs)) {
+ goToFormElement(input);
+ }
+
+ if (!_.isUndefined(ev)) {
+ // Don't submit form
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+};
+
/*
* Render errors specified in JSON format for many form elements.
*/
-$.fn.renderFormErrors = function (modelName, errors, clear, ev) {
- clear = (typeof clear !== 'undefined') ? clear : true;
+$.fn.renderFormErrors = function(modelName, errors, clear, ev) {
+ clear = ((typeof clear) === 'undefined') ? true : clear;
if (clear || _.isUndefined(clear)) {
this.clearFormErrors();
}
-
var form = $(this);
- $.each(errors, function (field, messages) {
- $input = $(_.filter(form.find('input, select, textarea'), function (el) {
+ $.each(errors, function(field, messages) {
+ // Special exception for file uploads in steps and results
+ if (field === 'assets.file') {
+ field = 'assets_attributes';
+ } else if (field === 'asset.file') {
+ field = 'asset_attribute';
+ }
+ var types = 'input, file, select, textarea';
+ var $input = $(_.filter(form.find(types), function(el) {
var name = $(el).attr('name');
if (name) {
return name.match(new RegExp(modelName + '\\[' + field + '\\(?'));
@@ -25,80 +92,22 @@ $.fn.renderFormErrors = function (modelName, errors, clear, ev) {
});
};
-/*
- * Render errors specified in array of strings format (or string if
- * just one error) for a single form element.
- *
- * Show error message/s and mark error input (if errMsgs is defined)
- * and, if present, mark and show the tab where the error occured and
- * focus/scroll to the error input, if it is the first one to be
- * specified or if errMsgs is undefined.
- *
- * @param {string} errAttributes Span element (error) attributes
- * @param {boolean} clearErr Set clearErr to true if this is the only
- * error that can happen/show.
- */
-var renderFormError = function (ev, input, errMsgs, clearErr, errAttributes) {
- clearErr = _.isUndefined(clearErr) ? false : clearErr;
- errAttributes = _.isUndefined(errAttributes) ? "" : " " + errAttributes;
- $form = $(input).closest("form");
-
- if (!_.isUndefined(errMsgs)) {
- if (clearErr) {
- $form.clearFormErrors();
- }
-
- // Mark error form group
- $formGroup = $(input).closest(".form-group");
- if (!$formGroup.hasClass("has-error")) {
- $formGroup.addClass("has-error");
- }
-
- // Add error message/s
- error_text = ($.makeArray(errMsgs).map(function (m) {
- return m.strToErrorFormat();
- })).join("
");
- $errSpan = "" + error_text + "";
- $formGroup.append($errSpan);
- }
-
- $tab = $(input).closest(".tab-pane");
- if ($tab.length) {
- // Mark error tab
- tabsPropagateErrorClass($form);
- $parent = $tab;
- } else {
- $parent = $form;
- }
-
- // Focus and scroll to the error if it is the first (most upper) one
- if ($parent.find(".form-group.has-error").length === 1 || _.isUndefined(errMsgs)) {
- goToFormElement(input);
- }
-
- if(!_.isUndefined(ev)) {
- // Don't submit form
- ev.preventDefault();
- ev.stopPropagation();
- }
-}
-
/*
* If any of form tabs (if exist) has errors, mark it and
* and show the first erroneous tab.
*/
function tabsPropagateErrorClass($form) {
- var $contents = $form.find("div.tab-pane");
- _.each($contents, function (tab) {
+ var $contents = $form.find('div.tab-pane');
+ _.each($contents, function(tab) {
var $tab = $(tab);
- var $errorFields = $tab.find(".has-error");
+ var $errorFields = $tab.find('.has-error');
if ($errorFields.length) {
- var id = $tab.attr("id");
+ var id = $tab.attr('id');
var navLink = $form.find("a[href='#" + id + "'][data-toggle='tab']");
if (navLink.parent().length) {
- navLink.parent().addClass("has-error");
+ navLink.parent().addClass('has-error');
}
}
});
- $form.find(".nav-tabs .has-error:first > a", $form).tab("show");
+ $form.find('.nav-tabs .has-error:first > a', $form).tab('show');
}
diff --git a/app/assets/stylesheets/projects.scss b/app/assets/stylesheets/projects.scss
index e7899e24d..fa92a85e4 100644
--- a/app/assets/stylesheets/projects.scss
+++ b/app/assets/stylesheets/projects.scss
@@ -200,7 +200,7 @@ path, ._jsPlumb_endpoint {
cursor: pointer;
display: block;
position: absolute;
- width: 294px;
+ width: 300px;
z-index: 5;
.panel-body .due-date-link {
diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss
index 06a2feca9..057e3cd70 100644
--- a/app/assets/stylesheets/themes/scinote.scss
+++ b/app/assets/stylesheets/themes/scinote.scss
@@ -282,7 +282,9 @@ a {
.badge-indicator,
.btn .badge-indicator {
+ font-size: 9px;
margin-left: -8px;
+ padding: 3px 5px;
top: 3px;
}
diff --git a/app/controllers/concerns/report_actions.rb b/app/controllers/concerns/report_actions.rb
index 50deeeedc..6b0653738 100644
--- a/app/controllers/concerns/report_actions.rb
+++ b/app/controllers/concerns/report_actions.rb
@@ -52,7 +52,7 @@ module ReportActions
def generate_experiment_contents_json(experiment, selected_modules)
res = []
- experiment.my_modules.each do |my_module|
+ experiment.my_modules.order(:workflow_order).each do |my_module|
next unless selected_modules.include?(my_module.id)
res << generate_new_el(false)
@@ -72,9 +72,9 @@ module ReportActions
ReportExtends::MODULE_CONTENTS.each do |contents|
protocol = contents.element == :step ? my_module.protocol.present? : true
next unless in_params?("module_#{contents.element}".to_sym) && protocol
- res << generate_new_el(false)
if contents.children
contents.collection(my_module).each do |report_el|
+ res << generate_new_el(false)
el = generate_el(
"reports/elements/my_module_#{contents
.element
@@ -82,9 +82,9 @@ module ReportActions
.singularize}_element.html.erb",
contents.parse_locals([report_el])
)
- if contents.element == :step
+ if contents.locals.first == :step
el[:children] = generate_step_contents_json(report_el)
- elsif contents.element == :result
+ elsif contents.locals.first == :result
el[:children] = generate_result_contents_json(report_el)
end
res << el
@@ -92,6 +92,7 @@ module ReportActions
else
file_name = contents.file_name
file_name = contents.element if contents.element == :samples
+ res << generate_new_el(false)
res << generate_el(
"reports/elements/my_module_#{file_name}_element.html.erb",
contents.parse_locals([my_module, :asc])
@@ -105,7 +106,7 @@ module ReportActions
def generate_step_contents_json(step)
res = []
if in_params? :step_checklists
- step.checklists.each do |checklist|
+ step.checklists.asc.each do |checklist|
res << generate_new_el(false)
res << generate_el(
'reports/elements/step_checklist_element.html.erb',
diff --git a/app/controllers/experiments_controller.rb b/app/controllers/experiments_controller.rb
index 41388f209..513229aee 100644
--- a/app/controllers/experiments_controller.rb
+++ b/app/controllers/experiments_controller.rb
@@ -185,6 +185,8 @@ class ExperimentsController < ApplicationController
cloned_experiment = @experiment.deep_clone_to_project(current_user,
project)
success = cloned_experiment.valid?
+ # Create workflow image
+ cloned_experiment.delay.generate_workflow_img if success
else
success = false
end
diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb
index 75247bb05..7bae4741b 100644
--- a/app/controllers/result_assets_controller.rb
+++ b/app/controllers/result_assets_controller.rb
@@ -72,11 +72,7 @@ class ResultAssetsController < ApplicationController
}, status: :ok
end
else
- # This response is sent as 200 OK due to IE security error when
- # accessing iframe content.
- format.json do
- render json: { status: 'error', errors: @result.errors }
- end
+ format.json { render json: @result.errors, status: :bad_request }
end
end
end
diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb
index 8b1a340b0..a9cbf6409 100644
--- a/app/controllers/steps_controller.rb
+++ b/app/controllers/steps_controller.rb
@@ -97,11 +97,6 @@ class StepsController < ApplicationController
status: :ok
end
else
- # On error, delete the newly added files from S3, as they were
- # uploaded on client-side (in case of client-side hacking of
- # asset's signature response)
- Asset.destroy_all(new_assets)
-
format.json {
render json: {
html: render_to_string(partial: 'new.html.erb')
diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb
index b0fbdb42a..3472610cb 100644
--- a/app/controllers/teams_controller.rb
+++ b/app/controllers/teams_controller.rb
@@ -53,6 +53,7 @@ class TeamsController < ApplicationController
)
if @temp_file.save
+ @temp_file.destroy_obsolete
# format.html
format.json {
render :json => {
diff --git a/app/controllers/users/invitations_controller.rb b/app/controllers/users/invitations_controller.rb
index bac1b5707..21cf02977 100644
--- a/app/controllers/users/invitations_controller.rb
+++ b/app/controllers/users/invitations_controller.rb
@@ -1,5 +1,6 @@
module Users
class InvitationsController < Devise::InvitationsController
+ include InputSanitizeHelper
include UsersGenerator
prepend_before_action :check_captcha, only: [:update]
@@ -175,8 +176,8 @@ module Users
message = "#{I18n.t('search.index.team')} #{team.name}"
notification = Notification.create(
type_of: :assignment,
- title: ActionController::Base.helpers.sanitize(title),
- message: ActionController::Base.helpers.sanitize(message)
+ title: sanitize_input(title),
+ message: sanitize_input(message)
)
if target_user.assignments_notification
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index cad28e101..0b6c0b199 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -92,10 +92,8 @@ module ApplicationHelper
def generate_annotation_notification(target_user, title, message)
notification = Notification.create(
type_of: :assignment,
- title:
- ActionController::Base.helpers.sanitize(title),
- message:
- ActionController::Base.helpers.sanitize(message)
+ title: sanitize_input(title),
+ message: sanitize_input(message)
)
if target_user.assignments_notification
UserNotification.create(notification: notification, user: target_user)
@@ -122,11 +120,13 @@ module ApplicationHelper
project = Project.find_by_id(match[3].base62_decode)
next unless project
if project.archived?
- "#{sanitize(match[2])} " \
+ "" \
+ "#{sanitize_input(match[2])} " \
"#{link_to project.name,
projects_archive_path} #{I18n.t('atwho.res.archived')}"
else
- "#{sanitize(match[2])} " \
+ "" \
+ "#{sanitize_input(match[2])} " \
"#{link_to project.name,
project_path(project)}"
end
@@ -134,12 +134,14 @@ module ApplicationHelper
experiment = Experiment.find_by_id(match[3].base62_decode)
next unless experiment
if experiment.archived?
- "#{sanitize(match[2])} " \
+ "" \
+ "#{sanitize_input(match[2])} " \
"#{link_to experiment.name,
experiment_archive_project_path(experiment.project)} " \
"#{I18n.t('atwho.res.archived')}"
else
- "#{sanitize(match[2])} " \
+ ""\
+ "#{sanitize_input(match[2])} " \
"#{link_to experiment.name,
canvas_experiment_path(experiment)}"
end
@@ -147,12 +149,14 @@ module ApplicationHelper
my_module = MyModule.find_by_id(match[3].base62_decode)
next unless my_module
if my_module.archived?
- "#{sanitize(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(match[2])} " \
+ "" \
+ "#{sanitize_input(match[2])} " \
"#{link_to my_module.name,
protocols_my_module_path(my_module)}"
end
@@ -213,19 +217,20 @@ module ApplicationHelper
user_description += %()
end
- raw(image_tag(user_avatar_absolute_url(user, :icon_small),
- class: 'atwho-user-img-popover')) +
- raw('') + user.full_name + raw('')
+ raw("
") +
+ raw('') + user.full_name + raw('')
end
def user_avatar_absolute_url(user, style)
unless user.avatar(style) == '/images/icon_small/missing.png'
return user.avatar(style)
end
- URI.join(root_url, "/images/#{style}/missing.png").to_s
+ URI.join(Rails.application.routes.url_helpers.root_url,
+ "/images/#{style}/missing.png").to_s
end
end
diff --git a/app/helpers/input_sanitize_helper.rb b/app/helpers/input_sanitize_helper.rb
index ce58cc235..b8ba5b5d6 100644
--- a/app/helpers/input_sanitize_helper.rb
+++ b/app/helpers/input_sanitize_helper.rb
@@ -1,14 +1,17 @@
+require 'sanitize'
+
module InputSanitizeHelper
- def sanitize_input(
- text,
- tags = [],
- attributes = []
- )
- ActionController::Base.helpers.sanitize(
- text,
- tags: Constants::WHITELISTED_TAGS + tags,
- attributes: Constants::WHITELISTED_ATTRIBUTES + attributes
- )
+ # Rails default ActionController::Base.helpers.sanitize method call
+ # the ActiveRecord connecton method on the caller object which in
+ # our cases throws an error when called from not ActiveRecord objects
+ # such as SamplesDatatables
+ def sanitize_input(html, tags = [], attributes = [])
+ Sanitize.fragment(
+ html,
+ elements: Constants::WHITELISTED_TAGS + tags,
+ attributes: { all: Constants::WHITELISTED_ATTRIBUTES + attributes },
+ css: Constants::WHITELISTED_CSS_ATTRIBUTES
+ ).html_safe
end
def escape_input(text)
@@ -16,15 +19,13 @@ module InputSanitizeHelper
end
def custom_auto_link(text, options = {})
- simple_format = options.fetch(:simple_format) { true }
- team = options.fetch(:team) { nil },
+ simple_f = options.fetch(:simple_format) { true }
+ team = options.fetch(:team) { nil }
wrapper_tag = options.fetch(:wrapper_tag) { {} }
tags = options.fetch(:tags) { [] }
- text = if simple_format
- simple_format(sanitize_input(text), {}, wrapper_tag)
- else
- sanitize_input(text, tags)
- end
+ format_opt = wrapper_tag.merge(sanitize: false)
+ text = sanitize_input(text, tags)
+ text = simple_format(sanitize_input(text), {}, format_opt) if simple_f
auto_link(
smart_annotation_parser(text, team),
link: :urls,
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index e21066a54..f41eb90ac 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -40,10 +40,8 @@ module NotificationsHelper
notification = Notification.create(
type_of: :assignment,
- title:
- ActionController::Base.helpers.sanitize(title),
- message:
- ActionController::Base.helpers.sanitize(message)
+ title: sanitize_input(title),
+ message: sanitize_input(message)
)
if target_user.assignments_notification
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8c266803e..479907bbb 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -11,10 +11,11 @@ module SearchHelper
if search_team != current_team
link_to text,
path,
- data: { confirm: t('users.settings.changed_team_in_search',
+ data: { no_turbolink: true,
+ confirm: t('users.settings.changed_team_in_search',
team: search_team.name) }
else
- link_to text, path
+ link_to text, path, data: { no_turbolink: true }
end
end
diff --git a/app/helpers/tiny_mce_helper.rb b/app/helpers/tiny_mce_helper.rb
index 73d76cadf..0b9da187e 100644
--- a/app/helpers/tiny_mce_helper.rb
+++ b/app/helpers/tiny_mce_helper.rb
@@ -23,7 +23,9 @@ module TinyMceHelper
match = el.match(regex)
img = TinyMceAsset.find_by_id(match[1])
next unless img
- image_tag img.url, data: { token: Base62.encode(img.id) }
+ image_tag img.url,
+ class: 'img-responsive',
+ data: { token: Base62.encode(img.id) }
end
end
diff --git a/app/models/activity.rb b/app/models/activity.rb
index 47541a78f..99dec64e0 100644
--- a/app/models/activity.rb
+++ b/app/models/activity.rb
@@ -1,4 +1,6 @@
class Activity < ActiveRecord::Base
+ include InputSanitizeHelper
+
after_create :generate_notification
enum type_of: [
@@ -104,15 +106,12 @@ class Activity < ActiveRecord::Base
notification = Notification.create(
type_of: notification_type,
- title:
- ActionController::Base.helpers.sanitize(message, tags: %w(strong a)),
- message:
- ActionController::Base
- .helpers.sanitize(
- "#{I18n.t('search.index.project')}
- #{project_m} #{experiment_m} #{task_m}",
- tags: %w(strong a)
- ),
+ title: sanitize_input(message, %w(strong a)),
+ message: sanitize_input(
+ "#{I18n.t('search.index.project')}
+ #{project_m} #{experiment_m} #{task_m}",
+ %w(strong a)
+ ),
generator_user_id: user.id
)
diff --git a/app/models/checklist.rb b/app/models/checklist.rb
index 8e4b0e021..2d06bfe61 100644
--- a/app/models/checklist.rb
+++ b/app/models/checklist.rb
@@ -22,6 +22,8 @@ class Checklist < ActiveRecord::Base
reject_if: :all_blank,
allow_destroy: true
+ scope :asc, -> { order('checklists.created_at ASC') }
+
def self.search(user,
include_archived,
query = nil,
diff --git a/app/models/experiment.rb b/app/models/experiment.rb
index cabc7d54b..3ad412694 100644
--- a/app/models/experiment.rb
+++ b/app/models/experiment.rb
@@ -1,5 +1,6 @@
class Experiment < ActiveRecord::Base
- include ArchivableModel, SearchableModel
+ include ArchivableModel
+ include SearchableModel
belongs_to :project, inverse_of: :experiments
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
@@ -354,7 +355,7 @@ class Experiment < ActiveRecord::Base
i += 1 while experiment_names.include?(format(format, i, name))
clone = Experiment.new(
- name: format(format, i, name),
+ name: format(format, i, name).truncate(Constants::NAME_MAX_LENGTH),
description: description,
created_by: current_user,
last_modified_by: current_user,
@@ -371,10 +372,6 @@ class Experiment < ActiveRecord::Base
m.deep_clone_to_experiment(current_user, clone)
end
clone.save
-
- # Create workflow image
- clone.delay.generate_workflow_img
-
clone
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 0ba2f943c..0baab5d64 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -189,4 +189,12 @@ class Project < ActiveRecord::Base
.distinct
end
end
+
+ def notifications_count(user)
+ res = 0
+ assigned_modules(user).find_each do |t|
+ res += 1 if t.is_overdue? || t.is_one_day_prior?
+ end
+ res
+ end
end
diff --git a/app/models/protocol.rb b/app/models/protocol.rb
index dc8270bef..8f4049561 100644
--- a/app/models/protocol.rb
+++ b/app/models/protocol.rb
@@ -268,7 +268,7 @@ class Protocol < ActiveRecord::Base
step2.save
# Copy checklists
- step.checklists.each do |checklist|
+ step.checklists.asc.each do |checklist|
checklist2 = Checklist.new(
name: checklist.name,
step: step2
@@ -372,7 +372,7 @@ class Protocol < ActiveRecord::Base
end
def completed_steps
- steps.select(&:completed)
+ steps.where(completed: true)
end
def space_taken
diff --git a/app/models/temp_file.rb b/app/models/temp_file.rb
index 90fbbcc3d..b351f5f2a 100644
--- a/app/models/temp_file.rb
+++ b/app/models/temp_file.rb
@@ -3,4 +3,11 @@ class TempFile < ActiveRecord::Base
has_attached_file :file
do_not_validate_attachment_file_type :file
+
+ def destroy_obsolete
+ destroy! if self
+ end
+
+ handle_asynchronously :destroy_obsolete,
+ run_at: proc { 7.days.from_now }
end
diff --git a/app/models/tiny_mce_asset.rb b/app/models/tiny_mce_asset.rb
index 6898c1ea5..b38004a11 100644
--- a/app/models/tiny_mce_asset.rb
+++ b/app/models/tiny_mce_asset.rb
@@ -25,7 +25,7 @@ class TinyMceAsset < ActiveRecord::Base
# When using S3 file upload, we can limit file accessibility with url signing
def presigned_url(style = :large,
download: false,
- timeout: Constants::URL_SHORT_EXPIRE_TIME)
+ timeout: Constants::URL_LONG_EXPIRE_TIME)
if stored_on_s3?
if download
download_arg = 'attachment; filename=' + URI.escape(image_file_name)
@@ -46,7 +46,7 @@ class TinyMceAsset < ActiveRecord::Base
image.options[:storage].to_sym == :s3
end
- def url(style = :large, timeout: Constants::URL_SHORT_EXPIRE_TIME)
+ def url(style = :large, timeout: Constants::URL_LONG_EXPIRE_TIME)
if image.is_stored_on_s3?
presigned_url(style, timeout: timeout)
else
diff --git a/app/views/canvas/full_zoom/_my_module.html.erb b/app/views/canvas/full_zoom/_my_module.html.erb
index 5a80aabc9..3a3fcbdc0 100644
--- a/app/views/canvas/full_zoom/_my_module.html.erb
+++ b/app/views/canvas/full_zoom/_my_module.html.erb
@@ -44,14 +44,14 @@