mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-04 12:14:37 +08:00
Merge branch 'master' of https://github.com/biosistemika/scinote-web into decoupling
This commit is contained in:
commit
f8a3fca2db
84 changed files with 2546 additions and 485 deletions
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Root EditorConfig preference file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.rb]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{js,jsx}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
18
Gemfile
18
Gemfile
|
@ -16,13 +16,17 @@ gem 'yomu'
|
|||
gem 'font-awesome-rails', '~> 4.7.0.2'
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
gem 'sanitize', '~> 4.4'
|
||||
gem 'omniauth'
|
||||
|
||||
# Gems for API implementation
|
||||
gem 'jwt'
|
||||
|
||||
# JS datetime library, requirement of datetime picker
|
||||
gem 'momentjs-rails', '~> 2.17.1'
|
||||
# JS datetime picker
|
||||
gem 'bootstrap3-datetimepicker-rails', '~> 4.15.35'
|
||||
# Select elements for Bootstrap
|
||||
gem 'bootstrap-select-rails'
|
||||
gem 'bootstrap-select-rails', '~> 1.6.3'
|
||||
gem 'uglifier', '>= 1.3.0'
|
||||
# jQuery & plugins
|
||||
gem 'jquery-turbolinks'
|
||||
|
@ -37,7 +41,7 @@ gem 'spinjs-rails'
|
|||
gem 'autosize-rails' # jQuery autosize plugin
|
||||
|
||||
gem 'underscore-rails'
|
||||
gem 'turbolinks'
|
||||
gem 'turbolinks', '~> 2.0'
|
||||
gem 'sdoc', '~> 0.4.0', group: :doc
|
||||
gem 'bcrypt', '~> 3.1.10'
|
||||
gem 'logging', '~> 2.0.0'
|
||||
|
@ -47,17 +51,16 @@ gem 'nested_form_fields'
|
|||
gem 'ajax-datatables-rails', '~> 0.3.1'
|
||||
gem 'commit_param_routing' # Enables different submit actions in the same form to route to different actions in controller
|
||||
gem 'kaminari'
|
||||
gem "i18n-js", ">= 3.0.0.rc11" # Localization in javascript files
|
||||
gem 'i18n-js', '~> 3.0' # Localization in javascript files
|
||||
gem 'roo', '~> 2.7.1' # Spreadsheet parser
|
||||
gem 'creek'
|
||||
gem 'wicked_pdf'
|
||||
gem 'wicked_pdf', '~> 1.0.6'
|
||||
gem 'silencer' # Silence certain Rails logs
|
||||
gem 'wkhtmltopdf-heroku'
|
||||
gem 'remotipart', '~> 1.2' # Async file uploads
|
||||
gem 'faker' # Generate fake data
|
||||
gem 'auto_strip_attributes', '~> 2.1' # Removes unnecessary whitespaces from ActiveRecord or ActiveModel attributes
|
||||
gem 'deface', '~> 1.0'
|
||||
gem 'nokogiri' # HTML/XML parser
|
||||
gem 'nokogiri', '~> 1.8.1' # HTML/XML parser
|
||||
gem 'sneaky-save', git: 'https://github.com/einzige/sneaky-save'
|
||||
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
|
||||
gem 'delayed_paperclip',
|
||||
|
@ -122,7 +125,8 @@ end
|
|||
group :production do
|
||||
gem 'puma'
|
||||
gem 'rails_12factor'
|
||||
gem 'whacamole'
|
||||
end
|
||||
|
||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||
gem 'tzinfo-data', platforms: %i(mingw mswin x64_mingw jruby)
|
||||
|
|
|
@ -516,7 +516,7 @@ DEPENDENCIES
|
|||
better_errors
|
||||
binding_of_caller
|
||||
bootstrap-sass (~> 3.3.5)
|
||||
bootstrap-select-rails
|
||||
bootstrap-select-rails (~> 1.6.3)
|
||||
bootstrap3-datetimepicker-rails (~> 4.15.35)
|
||||
bootstrap_form
|
||||
bullet
|
||||
|
@ -540,13 +540,13 @@ DEPENDENCIES
|
|||
figaro
|
||||
font-awesome-rails (~> 4.7.0.2)
|
||||
hammerjs-rails
|
||||
i18n-js (>= 3.0.0.rc11)
|
||||
i18n-js (~> 3.0)
|
||||
introjs-rails
|
||||
jbuilder
|
||||
jquery-rails
|
||||
jquery-scrollto-rails!
|
||||
jquery-turbolinks
|
||||
jquery-ui-rails
|
||||
jquery-ui-rails (~> 5.0)
|
||||
js_cookie_rails
|
||||
json_matchers
|
||||
kaminari
|
||||
|
|
1
Procfile
1
Procfile
|
@ -1,2 +1,3 @@
|
|||
web: bundle exec puma -C config/puma.rb
|
||||
worker: bundle exec rake jobs:work
|
||||
whacamole: bundle exec whacamole -c ./config/whacamole.rb
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.12.5
|
||||
1.12.9
|
||||
|
|
|
@ -219,6 +219,9 @@ var HelperModule = (function(){
|
|||
} else if (type === 'info') {
|
||||
alertType = ' alert-info ';
|
||||
glyphSign = ' glyphicon-exclamation-sign ';
|
||||
} else if (type === 'warning') {
|
||||
alertType = ' alert-warning ';
|
||||
glyphSign = ' glyphicon-exclamation-sign ';
|
||||
}
|
||||
var htmlSnippet = '<div class="alert alert' + alertType +
|
||||
'alert-dismissable alert-floating">' +
|
||||
|
|
|
@ -240,6 +240,7 @@ function initializeEdit() {
|
|||
// Restore draggable position
|
||||
restoreDraggablePosition($("#diagram"), $("#canvas-container"));
|
||||
|
||||
preventCanvasReloadOnSave();
|
||||
$("#canvas-container").submit(function (){
|
||||
animateSpinner(
|
||||
this,
|
||||
|
@ -3108,3 +3109,39 @@ function tutorialAfterCb() {
|
|||
.css({'pointer-events': 'auto'});
|
||||
});
|
||||
}
|
||||
|
||||
/** prevent reload page */
|
||||
var preventCanvasReloadOnSave = (function() {
|
||||
'use strict';
|
||||
|
||||
function confirmReload() {
|
||||
if( confirm(I18n.t('experiments.canvas.reload_on_submit')) ) {
|
||||
return true;
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function preventCanvasReload() {
|
||||
document.onkeydown = function(){
|
||||
switch (event.keyCode){
|
||||
case 116:
|
||||
event.returnValue = false;
|
||||
return confirmReload();
|
||||
case 82:
|
||||
if (event.ctrlKey){
|
||||
event.returnValue = false;
|
||||
return confirmReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindToCanvasSave(fun) {
|
||||
$('#canvas-save').on('click', function() {
|
||||
fun();
|
||||
})
|
||||
}
|
||||
|
||||
return function() { bindToCanvasSave(preventCanvasReload) };
|
||||
})();
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
function init(){
|
||||
$("[data-id]").each(function(){
|
||||
var that = $(this);
|
||||
that.find(".workflowimg-container").hide();
|
||||
initProjectExperiment(that);
|
||||
});
|
||||
|
||||
|
@ -36,7 +35,9 @@
|
|||
},
|
||||
error: function (ev) {
|
||||
if (ev.status == 404) {
|
||||
setTimeout(checkUpdatedImg(img_url, url, timestamp, container), 500);
|
||||
setTimeout(function () {
|
||||
checkUpdatedImg(img_url, url, timestamp, container)
|
||||
}, 5000);
|
||||
} else {
|
||||
animateSpinner(container, false);
|
||||
}
|
||||
|
|
|
@ -134,8 +134,11 @@ function importProtocolFromFile(
|
|||
var stepGuid = node.attr("guid");
|
||||
var stepPosition = String(Number.parseInt(node.attr("position")) + 1);
|
||||
var stepName = node.children("name").text();
|
||||
var stepDescription = $(node.children("description")).html();
|
||||
|
||||
var stepDescription = displayTinyMceAssetInDescription(
|
||||
node,
|
||||
protocolFolders[position],
|
||||
stepGuid
|
||||
);
|
||||
// Generate step element
|
||||
var stepEl = newPreviewElement(
|
||||
"step",
|
||||
|
@ -225,6 +228,37 @@ function importProtocolFromFile(
|
|||
});
|
||||
}
|
||||
|
||||
// display tiny_mce_assets in step description
|
||||
function displayTinyMceAssetInDescription(node, folder, stepGuid) {
|
||||
if (node.children('descriptionAssets').length === 0) {
|
||||
var description = node.children('description').html();
|
||||
return $('<div></div>').html(
|
||||
description.replace(/\[~tiny_mce_id:([0-9a-zA-Z]+)\]/,
|
||||
'<strong>Can\'t import image</strong>')
|
||||
.replace('<!--[CDATA[ ', '')
|
||||
.replace(' ]]-->', '')
|
||||
.replace(' ]]-->','')
|
||||
.replace(' ]]>', '')
|
||||
).html();
|
||||
}
|
||||
var description = node.children('description').html();
|
||||
node.find('descriptionAssets > tinyMceAsset').each(function(i, element) {
|
||||
var match = '[~tiny_mce_id:' + element.getAttribute('tokenId') + ']';
|
||||
var assetBytes = getAssetBytes(folder,
|
||||
stepGuid,
|
||||
element.getAttribute('fileref'));
|
||||
var image_tag = "<img style='max-width:300px; max-height:300px;' src='data:" + element.children[1].innerHTML + ";base64," + assetBytes + "' />"
|
||||
description = description.replace(match, image_tag); // replace the token with image
|
||||
}).bind(this);
|
||||
// I know is crazy but is the only way I found to pass valid HTML
|
||||
return $('<div></div>').html(
|
||||
description.replace('<!--[CDATA[ ', '')
|
||||
.replace(' ]]-->', '')
|
||||
.replace(' ]]-->','')
|
||||
.replace(' ]]>', '')
|
||||
).html();
|
||||
}
|
||||
|
||||
/* Navigation functions */
|
||||
|
||||
function navigationJumpToFirstProtocol() {
|
||||
|
@ -435,7 +469,28 @@ function importProtocolFromFile(
|
|||
stepJson.id = stepId;
|
||||
stepJson.position = $(this).attr("position");
|
||||
stepJson.name = $(this).children("name").text();
|
||||
stepJson.description = $(this).children("description").html();
|
||||
// I know is crazy but is the only way I found to pass valid HTML
|
||||
stepJson.description = $('<div></div>').html($(this)
|
||||
.children("description")
|
||||
.html()
|
||||
.replace('<!--[CDATA[', '')
|
||||
.replace(']]>', ''))
|
||||
.html();
|
||||
// Iterage throug tiny_mce_assets
|
||||
var descriptionAssetsJson = [];
|
||||
$(this).find("descriptionAssets > tinyMceAsset").each(function() {
|
||||
var tinyMceAsset = {};
|
||||
var fileRef = $(this).attr("fileRef");
|
||||
tinyMceAsset.tokenId = $(this).attr('tokenId');
|
||||
tinyMceAsset.fileType= $(this).children("fileType").text();
|
||||
tinyMceAsset.bytes = getAssetBytes(
|
||||
protocolFolders[index],
|
||||
stepGuid,
|
||||
fileRef
|
||||
)
|
||||
descriptionAssetsJson.push(tinyMceAsset);
|
||||
});
|
||||
stepJson.descriptionAssets = descriptionAssetsJson;
|
||||
|
||||
// Iterate through assets
|
||||
var stepAssetsJson = [];
|
||||
|
|
|
@ -123,6 +123,7 @@
|
|||
initPreviewModal();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
TinyMCE.destroyAll();
|
||||
DragNDropSteps.clearFiles();
|
||||
}, 1000);
|
||||
|
||||
})
|
||||
|
@ -148,8 +149,8 @@
|
|||
initializeCheckboxSorting();
|
||||
animateSpinner(null, false);
|
||||
initPreviewModal();
|
||||
|
||||
TinyMCE.refresh()
|
||||
DragNDropSteps.clearFiles();
|
||||
TinyMCE.refresh();
|
||||
$("#new-step-checklists fieldset.nested_step_checklists ul").each(function () {
|
||||
enableCheckboxSorting(this);
|
||||
});
|
||||
|
@ -371,6 +372,7 @@
|
|||
newStepHandler();
|
||||
});
|
||||
toggleButtons(true);
|
||||
DragNDropSteps.clearFiles();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -592,22 +594,17 @@
|
|||
});
|
||||
animateSpinner(null, false);
|
||||
setupAssetsLoading();
|
||||
DragNDropSteps.clearFiles();
|
||||
},
|
||||
error: function(e) {
|
||||
$form.after(xhr.responseJSON.html);
|
||||
var $new_form = $form.next();
|
||||
$form.remove();
|
||||
|
||||
$errInput = $new_form.find(".form-group.has-error")
|
||||
.first()
|
||||
.find("input");
|
||||
renderFormError(e, $errInput);
|
||||
|
||||
formCallback($form);
|
||||
applyCancelOnNew();
|
||||
error: function(xhr) {
|
||||
if (xhr.responseJSON['assets.file']) {
|
||||
$('#new-step-assets-group').addClass('has-error');
|
||||
$('#new-step-assets-tab').addClass('has-error');
|
||||
$.each(xhr.responseJSON['assets.file'], function(_, value) {
|
||||
$('#new-step-assets-group').prepend('<span class="help-block">' + value + '</span>');
|
||||
});
|
||||
}
|
||||
animateSpinner(null, false);
|
||||
|
||||
TinyMCE.destroyAll();
|
||||
SmartAnnotation.preventPropagation('.atwho-user-popover');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -118,6 +118,7 @@ function dataTableInit() {
|
|||
$('#selected_info').html(' ('+rowsSelected.length+' entries selected)');
|
||||
},
|
||||
preDrawCallback: function() {
|
||||
rowsSelected = [];
|
||||
animateSpinner(this);
|
||||
$('.sample-info-link').off('click');
|
||||
},
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
return filesValid;
|
||||
}
|
||||
|
||||
function clearFiles() {
|
||||
droppedFiles = [];
|
||||
}
|
||||
|
||||
// loops through a list of files and display each file in a separate panel
|
||||
function listItems() {
|
||||
totalSize = 0;
|
||||
|
@ -26,7 +30,7 @@
|
|||
$('.panel-step-attachment-new').remove();
|
||||
_dragNdropAssetsOff();
|
||||
for(var i = 0; i < droppedFiles.length; i++) {
|
||||
$('#new-step-assets')
|
||||
$('#new-step-assets-group')
|
||||
.append(_uploadedAssetPreview(droppedFiles[i], i))
|
||||
.promise()
|
||||
.done(function() {
|
||||
|
@ -49,7 +53,6 @@
|
|||
var name = 'step[assets_attributes][' + index + '][file]';
|
||||
fd.append(name, droppedFiles[i]);
|
||||
}
|
||||
droppedFiles = [];
|
||||
filesValid = true;
|
||||
totalSize = 0;
|
||||
_dragNdropAssetsOff();
|
||||
|
@ -144,7 +147,8 @@
|
|||
init: init,
|
||||
appendFilesToForm: appendFilesToForm,
|
||||
listItems: listItems,
|
||||
filesStatus: filesStatus
|
||||
filesStatus: filesStatus,
|
||||
clearFiles: clearFiles
|
||||
});
|
||||
})();
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ function filesSizeValidator(ev, fileInputs, fileTypeEnum) {
|
|||
case FileTypeEnum.FILE:
|
||||
return "<%= I18n.t('general.file.total_size', size: Constants::FILE_MAX_SIZE_MB) %>".strToErrorFormat();
|
||||
case FileTypeEnum.AVATAR:
|
||||
return "<%= I18n.t('general.file.total_size', size: Constants::AVATAR_MAX_SIZE_MB) %>".strToErrorFormat();
|
||||
return "<%= I18n.t('users.registrations.edit.avatar_total_size', size: Constants::AVATAR_MAX_SIZE_MB) %>".strToErrorFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,9 +29,7 @@
|
|||
// This cannot be scoped outside this function
|
||||
// because it is generated via JS
|
||||
teamSelectorDropdown2 =
|
||||
teamSelectorDropdown
|
||||
.next('.btn-group.bootstrap-select.form-control')
|
||||
.find('button.dropdown-toggle, li');
|
||||
teamSelectorDropdown.parent().find('button.dropdown-toggle, li');
|
||||
|
||||
// Show/hide correct step
|
||||
stepForm.show();
|
||||
|
|
|
@ -88,8 +88,10 @@ var TinyMCE = (function() {
|
|||
},
|
||||
destroyAll: function() {
|
||||
_.each(tinymce.editors, function(editor) {
|
||||
editor.destroy();
|
||||
initHighlightjs();
|
||||
if(editor) {
|
||||
editor.destroy();
|
||||
initHighlightjs();
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function() {
|
||||
|
|
|
@ -337,6 +337,19 @@ a {
|
|||
border-color: darken($color-theme-secondary, 10%);
|
||||
}
|
||||
}
|
||||
.btn-link-alt {
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.btn-invis-file {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.btn-open-file {
|
||||
position: relative;
|
||||
|
@ -1259,8 +1272,50 @@ ul.content-module-activities {
|
|||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
}
|
||||
// Protocolsio Import protocol modal
|
||||
|
||||
.import-protocols-modal-preview-container-json {
|
||||
height: 300px;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
|
||||
.eln-table {
|
||||
height: 21px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.badge-preview {
|
||||
background-color: $color-silver;
|
||||
border-radius: 2em;
|
||||
float: left;
|
||||
font-size: 20px;
|
||||
padding: 5px 14px;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
}
|
||||
// Protocolsio Preview protocol modal
|
||||
@media (min-width: 768px) {
|
||||
#modal-import-json-protocol-preview .modal-dialog {
|
||||
width: 70%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#modal-import-json-protocol-preview .modal-dialog {
|
||||
.modal-body {
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
|
||||
.ql-editor {
|
||||
min-height: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Steps and Results */
|
||||
#steps {
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAABCAYAAACsXeyTAAAAIUlEQVQImWNgoD5gZGBgMILSjKRo/P//vwiSGQwMDAwMAEnaA0jgHoquAAAAAElFTkSuQmCC");
|
||||
|
@ -1796,6 +1851,15 @@ textarea.textarea-sm {
|
|||
-webkit-padding-before: 0;
|
||||
-webkit-padding-after: 0;
|
||||
}
|
||||
.btn.btn-default-link {
|
||||
border: 0;
|
||||
height: 100%;
|
||||
min-height: 34px;
|
||||
padding: 0;
|
||||
-webkit-padding-after: 0;
|
||||
-webkit-padding-before: 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2002,6 +2066,7 @@ th.custom-field .modal-tooltiptext {
|
|||
padding: 3px;
|
||||
}
|
||||
|
||||
|
||||
.btn:last-child {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
|
90
app/controllers/api/api_controller.rb
Normal file
90
app/controllers/api/api_controller.rb
Normal file
|
@ -0,0 +1,90 @@
|
|||
module Api
|
||||
class ApiController < ActionController::Base
|
||||
attr_reader :iss
|
||||
attr_reader :token
|
||||
attr_reader :current_user
|
||||
|
||||
before_action :load_token, except: %i(authenticate status)
|
||||
before_action :load_iss, except: %i(authenticate status)
|
||||
before_action :authenticate_request!, except: %i(authenticate status)
|
||||
|
||||
rescue_from StandardError do |e|
|
||||
logger.error e.message
|
||||
render json: {}, status: :bad_request
|
||||
end
|
||||
|
||||
rescue_from JWT::InvalidPayload, JWT::DecodeError do |e|
|
||||
logger.error e.message
|
||||
render json: { message: I18n.t('api.core.invalid_token') },
|
||||
status: :unauthorized
|
||||
end
|
||||
|
||||
def initialize
|
||||
super
|
||||
@iss = nil
|
||||
end
|
||||
|
||||
def status
|
||||
response = {}
|
||||
response[:message] = I18n.t('api.core.status_ok')
|
||||
response[:versions] = []
|
||||
Extends::API_VERSIONS.each do |ver|
|
||||
response[:versions] << { version: ver, baseUrl: "/api/#{ver}/" }
|
||||
end
|
||||
render json: response, status: :ok
|
||||
end
|
||||
|
||||
def authenticate
|
||||
if auth_params[:grant_type] == 'password'
|
||||
user = User.find_by_email(auth_params[:email])
|
||||
unless user && user.valid_password?(auth_params[:password])
|
||||
raise StandardError, 'Wrong user password'
|
||||
end
|
||||
payload = { user_id: user.id }
|
||||
token = CoreJwt.encode(payload)
|
||||
render json: { token_type: 'bearer', access_token: token }
|
||||
else
|
||||
raise StandardError, 'Wrong grant type in request'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_token
|
||||
if request.headers['Authorization']
|
||||
@token =
|
||||
request.headers['Authorization'].scan(/Bearer (.*)$/).flatten.last
|
||||
end
|
||||
raise StandardError, 'No token in the header' unless @token
|
||||
end
|
||||
|
||||
def authenticate_request!
|
||||
Extends::API_PLUGABLE_AUTH_METHODS.each do |auth_method|
|
||||
method(auth_method).call
|
||||
return true if current_user
|
||||
end
|
||||
# Check request header for proper auth token
|
||||
payload = CoreJwt.decode(token)
|
||||
@current_user = User.find_by_id(payload['user_id'])
|
||||
# Implement sliding sessions, i.e send new token in case of successful
|
||||
# authorization and when tokens TTL reached specific value (to avoid token
|
||||
# generation on each request)
|
||||
if CoreJwt.refresh_needed?(payload)
|
||||
new_token = CoreJwt.encode(user_id: @current_user.id)
|
||||
response.headers['X-Access-Token'] = new_token
|
||||
end
|
||||
rescue JWT::ExpiredSignature
|
||||
render json: { message: I18n.t('api.core.expired_token') },
|
||||
status: :unauthorized
|
||||
end
|
||||
|
||||
def load_iss
|
||||
@iss = CoreJwt.read_iss(token)
|
||||
raise JWT::InvalidPayload, 'Wrong ISS in the token' unless @iss
|
||||
end
|
||||
|
||||
def auth_params
|
||||
params.permit(:grant_type, :email, :password)
|
||||
end
|
||||
end
|
||||
end
|
54
app/controllers/api/v20170715/core_api_controller.rb
Normal file
54
app/controllers/api/v20170715/core_api_controller.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
module Api
|
||||
module V20170715
|
||||
class CoreApiController < ApiController
|
||||
include PermissionHelper
|
||||
|
||||
def tasks_tree
|
||||
teams_json = []
|
||||
current_user.teams.find_each do |tm|
|
||||
team = tm.as_json(only: %i(name description))
|
||||
team['team_id'] = tm.id.to_s
|
||||
projects = []
|
||||
tm.projects.find_each do |pr|
|
||||
project = pr.as_json(only: %i(name visibility archived))
|
||||
project['project_id'] = pr.id.to_s
|
||||
experiments = []
|
||||
pr.experiments.find_each do |exp|
|
||||
experiment = exp.as_json(only: %i(name description archived))
|
||||
experiment['experiment_id'] = exp.id.to_s
|
||||
tasks = []
|
||||
exp.my_modules.find_each do |tk|
|
||||
task = tk.as_json(only: %i(name description archived))
|
||||
task['task_id'] = tk.id.to_s
|
||||
task['editable'] = can_edit_module(tk)
|
||||
tasks << task
|
||||
end
|
||||
experiment['tasks'] = tasks
|
||||
experiments << experiment
|
||||
end
|
||||
project['experiments'] = experiments
|
||||
projects << project
|
||||
end
|
||||
team['projects'] = projects
|
||||
teams_json << team
|
||||
end
|
||||
render json: teams_json, status: :ok
|
||||
end
|
||||
|
||||
def task_samples
|
||||
task = MyModule.find_by_id(params[:task_id])
|
||||
return render json: {}, status: :not_found unless task
|
||||
return render json: {}, status: :forbidden unless can_view_module(task)
|
||||
samples = task.samples
|
||||
samples_json = []
|
||||
samples.find_each do |s|
|
||||
sample = {}
|
||||
sample['sample_id'] = s.id.to_s
|
||||
sample['name'] = s.name
|
||||
samples_json << sample
|
||||
end
|
||||
render json: samples_json, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,84 +1,85 @@
|
|||
|
||||
class ProtocolsController < ApplicationController
|
||||
include RenamingUtil
|
||||
include ProtocolsImporter
|
||||
include ProtocolsExporter
|
||||
include InputSanitizeHelper
|
||||
include ProtocolsIoHelper
|
||||
|
||||
before_action :check_create_permissions, only: [
|
||||
:create_new_modal,
|
||||
:create
|
||||
]
|
||||
before_action :check_clone_permissions, only: [ :clone ]
|
||||
before_action :check_view_permissions, only: [
|
||||
:protocol_status_bar,
|
||||
:updated_at_label,
|
||||
:preview,
|
||||
:linked_children,
|
||||
:linked_children_datatable
|
||||
]
|
||||
before_action :check_edit_permissions, only: [
|
||||
:edit,
|
||||
:update_metadata,
|
||||
:update_keywords,
|
||||
:edit_name_modal,
|
||||
:edit_keywords_modal,
|
||||
:edit_authors_modal,
|
||||
:edit_description_modal
|
||||
]
|
||||
before_action :check_view_all_permissions, only: [
|
||||
:index,
|
||||
:datatable
|
||||
]
|
||||
before_action :check_unlink_permissions, only: [
|
||||
:unlink,
|
||||
:unlink_modal
|
||||
]
|
||||
before_action :check_revert_permissions, only: [
|
||||
:revert,
|
||||
:revert_modal
|
||||
]
|
||||
before_action :check_update_parent_permissions, only: [
|
||||
:update_parent,
|
||||
:update_parent_modal
|
||||
]
|
||||
before_action :check_update_from_parent_permissions, only: [
|
||||
:update_from_parent,
|
||||
:update_from_parent_modal
|
||||
]
|
||||
before_action :check_load_from_repository_views_permissions, only: [
|
||||
:load_from_repository_modal,
|
||||
:load_from_repository_datatable
|
||||
]
|
||||
before_action :check_create_permissions, only: %i(
|
||||
create_new_modal
|
||||
create
|
||||
)
|
||||
before_action :check_clone_permissions, only: [:clone]
|
||||
before_action :check_view_permissions, only: %i(
|
||||
protocol_status_bar
|
||||
updated_at_label
|
||||
preview
|
||||
linked_children
|
||||
linked_children_datatable
|
||||
)
|
||||
before_action :check_edit_permissions, only: %i(
|
||||
edit
|
||||
update_metadata
|
||||
update_keywords
|
||||
edit_name_modal
|
||||
edit_keywords_modal
|
||||
edit_authors_modal
|
||||
edit_description_modal
|
||||
)
|
||||
before_action :check_view_all_permissions, only: %i(
|
||||
index
|
||||
datatable
|
||||
)
|
||||
before_action :check_unlink_permissions, only: %i(
|
||||
unlink
|
||||
unlink_modal
|
||||
)
|
||||
before_action :check_revert_permissions, only: %i(
|
||||
revert
|
||||
revert_modal
|
||||
)
|
||||
before_action :check_update_parent_permissions, only: %i(
|
||||
update_parent
|
||||
update_parent_modal
|
||||
)
|
||||
before_action :check_update_from_parent_permissions, only: %i(
|
||||
update_from_parent
|
||||
update_from_parent_modal
|
||||
)
|
||||
before_action :check_load_from_repository_views_permissions, only: %i(
|
||||
load_from_repository_modal
|
||||
load_from_repository_datatable
|
||||
)
|
||||
before_action :check_load_from_repository_permissions, only: [
|
||||
:load_from_repository
|
||||
]
|
||||
before_action :check_load_from_file_permissions, only: [
|
||||
:load_from_file
|
||||
]
|
||||
before_action :check_copy_to_repository_permissions, only: [
|
||||
:copy_to_repository,
|
||||
:copy_to_repository_modal
|
||||
]
|
||||
before_action :check_make_private_permissions, only: [ :make_private ]
|
||||
before_action :check_publish_permissions, only: [ :publish ]
|
||||
before_action :check_archive_permissions, only: [ :archive ]
|
||||
before_action :check_restore_permissions, only: [ :restore ]
|
||||
before_action :check_import_permissions, only: [ :import ]
|
||||
before_action :check_export_permissions, only: [ :export ]
|
||||
before_action :check_copy_to_repository_permissions, only: %i(
|
||||
copy_to_repository
|
||||
copy_to_repository_modal
|
||||
)
|
||||
before_action :check_make_private_permissions, only: [:make_private]
|
||||
before_action :check_publish_permissions, only: [:publish]
|
||||
before_action :check_archive_permissions, only: [:archive]
|
||||
before_action :check_restore_permissions, only: [:restore]
|
||||
before_action :check_import_permissions, only: [:import]
|
||||
before_action :check_export_permissions, only: [:export]
|
||||
|
||||
def index
|
||||
end
|
||||
def index; end
|
||||
|
||||
def datatable
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: ::ProtocolsDatatable.new(
|
||||
view_context,
|
||||
@current_team,
|
||||
@type,
|
||||
current_user
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -103,50 +104,47 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
def linked_children
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: I18n.t('protocols.index.linked_children.title',
|
||||
protocol: escape_input(@protocol.name)),
|
||||
html: render_to_string({
|
||||
partial: "protocols/index/linked_children_modal_body.html.erb",
|
||||
locals: { protocol: @protocol }
|
||||
})
|
||||
html: render_to_string(partial: 'protocols/index/linked_children_modal_body.html.erb',
|
||||
locals: { protocol: @protocol })
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def linked_children_datatable
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: ::ProtocolLinkedChildrenDatatable.new(
|
||||
view_context,
|
||||
@protocol,
|
||||
current_user,
|
||||
self
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_private
|
||||
move_protocol("make_private")
|
||||
move_protocol('make_private')
|
||||
end
|
||||
|
||||
def publish
|
||||
move_protocol("publish")
|
||||
move_protocol('publish')
|
||||
end
|
||||
|
||||
def archive
|
||||
move_protocol("archive")
|
||||
move_protocol('archive')
|
||||
end
|
||||
|
||||
def restore
|
||||
move_protocol("restore")
|
||||
move_protocol('restore')
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
def edit; end
|
||||
|
||||
def update_metadata
|
||||
@protocol.record_timestamps = false
|
||||
|
@ -154,27 +152,27 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
respond_to do |format|
|
||||
if @protocol.save
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
updated_at_label: render_to_string(
|
||||
partial: "protocols/header/updated_at_label.html.erb"
|
||||
partial: 'protocols/header/updated_at_label.html.erb'
|
||||
),
|
||||
name_label: render_to_string(
|
||||
partial: "protocols/header/name_label.html.erb"
|
||||
partial: 'protocols/header/name_label.html.erb'
|
||||
),
|
||||
authors_label: render_to_string(
|
||||
partial: "protocols/header/authors_label.html.erb"
|
||||
partial: 'protocols/header/authors_label.html.erb'
|
||||
),
|
||||
description_label: render_to_string(
|
||||
partial: "protocols/header/description_label.html.erb"
|
||||
partial: 'protocols/header/description_label.html.erb'
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
else
|
||||
format.json {
|
||||
format.json do
|
||||
render json: @protocol.errors,
|
||||
status: :unprocessable_entity
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -182,20 +180,22 @@ class ProtocolsController < ApplicationController
|
|||
def update_keywords
|
||||
respond_to do |format|
|
||||
# sanitize user input
|
||||
params[:keywords].collect! do |keyword|
|
||||
escape_input(keyword)
|
||||
if params[:keywords]
|
||||
params[:keywords].collect! do |keyword|
|
||||
escape_input(keyword)
|
||||
end
|
||||
end
|
||||
if @protocol.update_keywords(params[:keywords])
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
updated_at_label: render_to_string(
|
||||
partial: "protocols/header/updated_at_label.html.erb"
|
||||
partial: 'protocols/header/updated_at_label.html.erb'
|
||||
),
|
||||
keywords_label: render_to_string(
|
||||
partial: "protocols/header/keywords_label.html.erb"
|
||||
partial: 'protocols/header/keywords_label.html.erb'
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
else
|
||||
format.json { render json: {}, status: :unprocessable_entity }
|
||||
end
|
||||
|
@ -214,13 +214,11 @@ class ProtocolsController < ApplicationController
|
|||
@protocol.record_timestamps = false
|
||||
@protocol.created_at = ts
|
||||
@protocol.updated_at = ts
|
||||
if @type == :public
|
||||
@protocol.published_on = ts
|
||||
end
|
||||
@protocol.published_on = ts if @type == :public
|
||||
|
||||
respond_to do |format|
|
||||
if @protocol.save
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
url: edit_protocol_path(
|
||||
@protocol,
|
||||
|
@ -228,12 +226,12 @@ class ProtocolsController < ApplicationController
|
|||
type: @type
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
else
|
||||
format.json {
|
||||
format.json do
|
||||
render json: @protocol.errors,
|
||||
status: :unprocessable_entity
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -248,9 +246,9 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
if cloned != nil
|
||||
if !cloned.nil?
|
||||
flash[:success] = t(
|
||||
"protocols.index.clone.success_flash",
|
||||
'protocols.index.clone.success_flash',
|
||||
original: @original.name,
|
||||
new: cloned.name
|
||||
)
|
||||
|
@ -258,7 +256,7 @@ class ProtocolsController < ApplicationController
|
|||
format.json { render json: {}, status: :ok }
|
||||
else
|
||||
flash[:error] = t(
|
||||
"protocols.index.clone.error_flash",
|
||||
'protocols.index.clone.error_flash',
|
||||
original: @original.name
|
||||
)
|
||||
flash.keep(:error)
|
||||
|
@ -287,18 +285,18 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
message: t("my_modules.protocols.copy_to_repository_modal.error_400")
|
||||
message: t('my_modules.protocols.copy_to_repository_modal.error_400')
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
end
|
||||
elsif @new.invalid?
|
||||
# Render errors
|
||||
format.json {
|
||||
format.json do
|
||||
render json: @new.errors,
|
||||
status: :unprocessable_entity
|
||||
}
|
||||
end
|
||||
else
|
||||
# Everything good, render 200
|
||||
format.json { render json: { refresh: link_protocols }, status: :ok }
|
||||
|
@ -320,16 +318,16 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
message: t("my_modules.protocols.unlink_error")
|
||||
message: t('my_modules.protocols.unlink_error')
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
end
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.unlink_flash",
|
||||
'my_modules.protocols.unlink_flash'
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
|
@ -392,12 +390,12 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
message: t("my_modules.protocols.update_parent_error")
|
||||
message: t('my_modules.protocols.update_parent_error')
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
end
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
|
@ -413,7 +411,7 @@ class ProtocolsController < ApplicationController
|
|||
)
|
||||
)
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.update_parent_flash",
|
||||
'my_modules.protocols.update_parent_flash'
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
|
@ -443,16 +441,16 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
if transaction_error
|
||||
# Bad request error
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
message: t("my_modules.protocols.update_from_parent_error")
|
||||
message: t('my_modules.protocols.update_from_parent_error')
|
||||
},
|
||||
status: :bad_request
|
||||
}
|
||||
end
|
||||
else
|
||||
# Everything good, display flash & render 200
|
||||
flash[:success] = t(
|
||||
"my_modules.protocols.update_from_parent_flash",
|
||||
'my_modules.protocols.update_from_parent_flash'
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json { render json: {}, status: :ok }
|
||||
|
@ -533,9 +531,9 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
if transaction_error
|
||||
format.json {
|
||||
format.json do
|
||||
render json: { status: :error }, status: :bad_request
|
||||
}
|
||||
end
|
||||
else
|
||||
# Everything good, record activity, display flash & render 200
|
||||
Activity.create(
|
||||
|
@ -551,12 +549,12 @@ class ProtocolsController < ApplicationController
|
|||
)
|
||||
)
|
||||
flash[:success] = t(
|
||||
'my_modules.protocols.load_from_file_flash',
|
||||
'my_modules.protocols.load_from_file_flash'
|
||||
)
|
||||
flash.keep(:success)
|
||||
format.json {
|
||||
format.json do
|
||||
render json: { status: :ok }, status: :ok
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
|
@ -586,20 +584,123 @@ class ProtocolsController < ApplicationController
|
|||
t('protocols.index.no_protocol_name')
|
||||
end
|
||||
if transaction_error
|
||||
format.json {
|
||||
format.json do
|
||||
render json: { name: p_name, status: :bad_request }, status: :bad_request
|
||||
}
|
||||
end
|
||||
else
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
name: p_name, new_name: protocol.name, status: :ok
|
||||
},
|
||||
status: :ok
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def protocolsio_import_create
|
||||
@protocolsio_too_big = false
|
||||
@protocolsio_invalid_file = false
|
||||
extension = File.extname(params[:json_file].path)
|
||||
file_size = File.size(params[:json_file].path)
|
||||
if extension != '.txt' && extension != '.json'
|
||||
@protocolsio_invalid_file = true
|
||||
respond_to do |format|
|
||||
format.js {}
|
||||
end
|
||||
return 0 # return 0 stops the rest of the controller code from executing
|
||||
end
|
||||
if file_size > Constants::FILE_MAX_SIZE_MB.megabytes
|
||||
@protocolsio_too_big = true
|
||||
respond_to do |format|
|
||||
format.js {}
|
||||
# if file is too big, default to the js.erb file,
|
||||
# named the same as this controller
|
||||
# where a javascript alert is called
|
||||
end
|
||||
return 0 # return 0 stops the rest of the controller code from executing
|
||||
end
|
||||
json_file_contents = File.read(params[:json_file].path)
|
||||
json_file_contents.gsub! '\"', "'"
|
||||
# escaped double quotes too stressfull, html works with single quotes too
|
||||
# json double quotes dont get escaped since they dont match \"
|
||||
unless valid_protocol_json(json_file_contents)
|
||||
@protocolsio_invalid_file = true
|
||||
respond_to do |format|
|
||||
format.js {}
|
||||
end
|
||||
return 0 # return 0 stops the rest of the controller code from executing
|
||||
end
|
||||
@json_object = JSON.parse(json_file_contents)
|
||||
@protocol = Protocol.new
|
||||
respond_to do |format|
|
||||
format.js {} # go to the js.erb file named the same as this controller,
|
||||
# where a preview modal is rendered,
|
||||
# and some modals get closed and opened
|
||||
end
|
||||
end
|
||||
|
||||
def protocolsio_import_save
|
||||
@json_object = JSON.parse(params['json_object'])
|
||||
@db_json = {}
|
||||
@toolong = false
|
||||
@db_json['name'] = pio_eval_title_len(
|
||||
sanitize_input(params['protocol']['name'])
|
||||
)
|
||||
# since scinote only has description field, and protocols.io has many others
|
||||
# ,here i am putting everything important from protocols.io into description
|
||||
@db_json['authors'] = pio_eval_title_len(
|
||||
sanitize_input(params['protocol']['authors'])
|
||||
)
|
||||
@db_json['created_at'] = pio_eval_title_len(
|
||||
sanitize_input(params['protocol']['created_at'])
|
||||
)
|
||||
@db_json['updated_at'] = pio_eval_title_len(
|
||||
sanitize_input(params['protocol']['last_modified'])
|
||||
)
|
||||
@db_json['steps'] = {}
|
||||
@db_json['steps'] = protocols_io_fill_step(
|
||||
@json_object, @db_json['steps']
|
||||
)
|
||||
protocol = nil
|
||||
respond_to do |format|
|
||||
transaction_error = false
|
||||
@protocolsio_general_error = false
|
||||
Protocol.transaction do
|
||||
begin
|
||||
protocol = import_new_protocol(
|
||||
@db_json, current_team, params[:type].to_sym, current_user
|
||||
)
|
||||
rescue Exception
|
||||
transaction_error = true
|
||||
raise ActiveRecord:: Rollback
|
||||
end
|
||||
end
|
||||
p_name =
|
||||
if @db_json['name'].present?
|
||||
escape_input(@db_json['name'])
|
||||
else
|
||||
t('protocols.index.no_protocol_name')
|
||||
end
|
||||
if transaction_error
|
||||
@protocolsio_general_error = true
|
||||
# General something went wrong, upload to db failed error
|
||||
# format.json {
|
||||
# render json: { name: p_name, status: :bad_request },
|
||||
# status: :bad_request
|
||||
# }
|
||||
else
|
||||
@protocolsio_general_error = false
|
||||
format.json do
|
||||
render json:
|
||||
{ name: @db_json['name'], new_name: @db_json['name'], status: :ok },
|
||||
status: :ok
|
||||
end
|
||||
end
|
||||
format.js {}
|
||||
end
|
||||
end
|
||||
|
||||
def export
|
||||
# Make a zip output stream and send it to the client
|
||||
respond_to do |format|
|
||||
|
@ -622,15 +723,29 @@ class ProtocolsController < ApplicationController
|
|||
protocol.steps.order(:id).each do |step|
|
||||
step_guid = get_guid(step.id)
|
||||
step_dir = "#{protocol_dir}/#{step_guid}"
|
||||
next if step.assets.count <= 0
|
||||
step.assets.order(:id).each do |asset|
|
||||
asset_guid = get_guid(asset.id)
|
||||
asset_file_name = asset_guid.to_s +
|
||||
File.extname(asset.file_file_name).to_s
|
||||
ostream.put_next_entry("#{step_dir}/#{asset_file_name}")
|
||||
input_file = asset.open
|
||||
ostream.print(input_file.read)
|
||||
input_file.close
|
||||
if step.assets.exists?
|
||||
step.assets.order(:id).each do |asset|
|
||||
asset_guid = get_guid(asset.id)
|
||||
asset_file_name = asset_guid.to_s +
|
||||
File.extname(asset.file_file_name).to_s
|
||||
ostream.put_next_entry("#{step_dir}/#{asset_file_name}")
|
||||
input_file = asset.open
|
||||
ostream.print(input_file.read)
|
||||
input_file.close
|
||||
end
|
||||
end
|
||||
if step.tiny_mce_assets.exists?
|
||||
step.tiny_mce_assets.order(:id).each do |tiny_mce_asset|
|
||||
asset_guid = get_guid(tiny_mce_asset.id)
|
||||
asset_file_name =
|
||||
"rte-#{asset_guid.to_s +
|
||||
File.extname(tiny_mce_asset.image_file_name).to_s}"
|
||||
|
||||
ostream.put_next_entry("#{step_dir}/#{asset_file_name}")
|
||||
input_file = tiny_mce_asset.open
|
||||
ostream.print(input_file.read)
|
||||
input_file.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -658,81 +773,81 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
def unlink_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: t("my_modules.protocols.confirm_link_update_modal.unlink_title"),
|
||||
message: t("my_modules.protocols.confirm_link_update_modal.unlink_message"),
|
||||
btn_text: t("my_modules.protocols.confirm_link_update_modal.unlink_btn_text"),
|
||||
title: t('my_modules.protocols.confirm_link_update_modal.unlink_title'),
|
||||
message: t('my_modules.protocols.confirm_link_update_modal.unlink_message'),
|
||||
btn_text: t('my_modules.protocols.confirm_link_update_modal.unlink_btn_text'),
|
||||
url: unlink_protocol_path(@protocol)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def revert_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: t("my_modules.protocols.confirm_link_update_modal.revert_title"),
|
||||
message: t("my_modules.protocols.confirm_link_update_modal.revert_message"),
|
||||
btn_text: t("my_modules.protocols.confirm_link_update_modal.revert_btn_text"),
|
||||
title: t('my_modules.protocols.confirm_link_update_modal.revert_title'),
|
||||
message: t('my_modules.protocols.confirm_link_update_modal.revert_message'),
|
||||
btn_text: t('my_modules.protocols.confirm_link_update_modal.revert_btn_text'),
|
||||
url: revert_protocol_path(@protocol)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_parent_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: t("my_modules.protocols.confirm_link_update_modal.update_parent_title"),
|
||||
message: t("my_modules.protocols.confirm_link_update_modal.update_parent_message"),
|
||||
btn_text: t("general.update"),
|
||||
title: t('my_modules.protocols.confirm_link_update_modal.update_parent_title'),
|
||||
message: t('my_modules.protocols.confirm_link_update_modal.update_parent_message'),
|
||||
btn_text: t('general.update'),
|
||||
url: update_parent_protocol_path(@protocol)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_from_parent_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: t("my_modules.protocols.confirm_link_update_modal.update_self_title"),
|
||||
message: t("my_modules.protocols.confirm_link_update_modal.update_self_message"),
|
||||
btn_text: t("general.update"),
|
||||
title: t('my_modules.protocols.confirm_link_update_modal.update_self_title'),
|
||||
message: t('my_modules.protocols.confirm_link_update_modal.update_self_message'),
|
||||
btn_text: t('general.update'),
|
||||
url: update_from_parent_protocol_path(@protocol)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_from_repository_datatable
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
@type = (params[:type] || "public").to_sym
|
||||
@type = (params[:type] || 'public').to_sym
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: ::LoadFromRepositoryProtocolsDatatable.new(
|
||||
view_context,
|
||||
@protocol.team,
|
||||
@type,
|
||||
current_user
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_from_repository_modal
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "my_modules/protocols/load_from_repository_modal_body.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -740,99 +855,99 @@ class ProtocolsController < ApplicationController
|
|||
@new = Protocol.new
|
||||
@original = Protocol.find(params[:id])
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "my_modules/protocols/copy_to_repository_modal_body.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def protocol_status_bar
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "my_modules/protocols/protocol_status_bar.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def updated_at_label
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "protocols/header/updated_at_label.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_modal
|
||||
@new_protocol = Protocol.new
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "protocols/index/create_new_modal_body.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit_name_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: I18n.t('protocols.header.edit_name_modal.title',
|
||||
protocol: escape_input(@protocol.name)),
|
||||
html: render_to_string({
|
||||
partial: "protocols/header/edit_name_modal_body.html.erb"
|
||||
})
|
||||
html: render_to_string({
|
||||
partial: "protocols/header/edit_name_modal_body.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit_keywords_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: I18n.t('protocols.header.edit_keywords_modal.title',
|
||||
protocol: escape_input(@protocol.name)),
|
||||
html: render_to_string({
|
||||
partial: "protocols/header/edit_keywords_modal_body.html.erb"
|
||||
}),
|
||||
html: render_to_string({
|
||||
partial: "protocols/header/edit_keywords_modal_body.html.erb"
|
||||
}),
|
||||
keywords: @protocol.team.protocol_keywords_list
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit_authors_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: I18n.t('protocols.header.edit_authors_modal.title',
|
||||
protocol: escape_input(@protocol.name)),
|
||||
html: render_to_string({
|
||||
partial: "protocols/header/edit_authors_modal_body.html.erb"
|
||||
})
|
||||
html: render_to_string({
|
||||
partial: "protocols/header/edit_authors_modal_body.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit_description_modal
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
format.json do
|
||||
render json: {
|
||||
title: I18n.t('protocols.header.edit_description_modal.title',
|
||||
protocol: escape_input(@protocol.name)),
|
||||
|
@ -840,12 +955,19 @@ class ProtocolsController < ApplicationController
|
|||
partial: "protocols/header/edit_description_modal_body.html.erb"
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_protocol_json(json)
|
||||
JSON.parse(json)
|
||||
return true
|
||||
rescue JSON::ParserError => e
|
||||
return false
|
||||
end
|
||||
|
||||
def move_protocol(action)
|
||||
rollbacked = false
|
||||
results = []
|
||||
|
@ -875,19 +997,19 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
respond_to do |format|
|
||||
unless rollbacked
|
||||
format.json {
|
||||
if rollbacked
|
||||
format.json do
|
||||
render json: {}, status: :bad_request
|
||||
end
|
||||
else
|
||||
format.json do
|
||||
render json: {
|
||||
html: render_to_string({
|
||||
partial: "protocols/index/results_modal_body.html.erb",
|
||||
locals: { results: results, en_action: "#{action}_results" }
|
||||
})
|
||||
}
|
||||
}
|
||||
else
|
||||
format.json {
|
||||
render json: {}, status: :bad_request
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -895,7 +1017,7 @@ class ProtocolsController < ApplicationController
|
|||
def load_team_and_type
|
||||
@current_team = current_team
|
||||
# :public, :private or :archive
|
||||
@type = (params[:type] || "public").to_sym
|
||||
@type = (params[:type] || 'public').to_sym
|
||||
end
|
||||
|
||||
def check_view_all_permissions
|
||||
|
@ -943,25 +1065,19 @@ class ProtocolsController < ApplicationController
|
|||
def check_unlink_permissions
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
|
||||
if @protocol.blank? || !can_unlink_protocol(@protocol)
|
||||
render_403
|
||||
end
|
||||
render_403 if @protocol.blank? || !can_unlink_protocol(@protocol)
|
||||
end
|
||||
|
||||
def check_revert_permissions
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
|
||||
if @protocol.blank? || !can_revert_protocol(@protocol)
|
||||
render_403
|
||||
end
|
||||
render_403 if @protocol.blank? || !can_revert_protocol(@protocol)
|
||||
end
|
||||
|
||||
def check_update_parent_permissions
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
|
||||
if @protocol.blank? || !can_update_parent_protocol(@protocol)
|
||||
render_403
|
||||
end
|
||||
render_403 if @protocol.blank? || !can_update_parent_protocol(@protocol)
|
||||
end
|
||||
|
||||
def check_update_from_parent_permissions
|
||||
|
@ -975,9 +1091,7 @@ class ProtocolsController < ApplicationController
|
|||
def check_load_from_repository_views_permissions
|
||||
@protocol = Protocol.find_by_id(params[:id])
|
||||
|
||||
if @protocol.blank? || !can_view_protocol(@protocol)
|
||||
render_403
|
||||
end
|
||||
render_403 if @protocol.blank? || !can_view_protocol(@protocol)
|
||||
end
|
||||
|
||||
def check_load_from_repository_permissions
|
||||
|
@ -995,9 +1109,9 @@ class ProtocolsController < ApplicationController
|
|||
@my_module = @protocol.my_module
|
||||
|
||||
if @protocol_json.blank? ||
|
||||
@protocol.blank? ||
|
||||
@my_module.blank? ||
|
||||
!can_load_protocol_into_module(@my_module)
|
||||
@protocol.blank? ||
|
||||
@my_module.blank? ||
|
||||
!can_load_protocol_into_module(@my_module)
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
@ -1006,7 +1120,7 @@ class ProtocolsController < ApplicationController
|
|||
@protocol = Protocol.find_by_id(params[:id])
|
||||
@my_module = @protocol.my_module
|
||||
|
||||
if @my_module.blank? or !can_copy_protocol_to_repository(@my_module)
|
||||
if @my_module.blank? || !can_copy_protocol_to_repository(@my_module)
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
@ -1059,12 +1173,9 @@ class ProtocolsController < ApplicationController
|
|||
@protocol_json = params[:protocol]
|
||||
@team = Team.find(params[:team_id])
|
||||
@type = params[:type] ? params[:type].to_sym : nil
|
||||
if !(
|
||||
@protocol_json.present? &&
|
||||
@team.present? &&
|
||||
(@type == :public || @type == :private) &&
|
||||
can_create_protocols_in_repository?(@team)
|
||||
)
|
||||
unless @protocol_json.present? && @team.present? &&
|
||||
(@type == :public || @type == :private) &&
|
||||
can_create_protocols_in_repository?(@team)
|
||||
render_403
|
||||
end
|
||||
end
|
||||
|
@ -1091,5 +1202,4 @@ class ProtocolsController < ApplicationController
|
|||
def metadata_params
|
||||
params.require(:protocol).permit(:name, :authors, :description)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -34,7 +34,8 @@ class ResultTextsController < ApplicationController
|
|||
def create
|
||||
@result_text = ResultText.new(result_params[:result_text_attributes])
|
||||
# gerate a tag that replaces img tag in database
|
||||
@result_text.text = parse_tiny_mce_asset_to_token(@result_text.text)
|
||||
@result_text.text = parse_tiny_mce_asset_to_token(@result_text.text,
|
||||
@result_text)
|
||||
@result = Result.new(
|
||||
user: current_user,
|
||||
my_module: @my_module,
|
||||
|
@ -88,7 +89,8 @@ class ResultTextsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
@result_text.text = generate_image_tag_from_token(@result_text.text)
|
||||
@result_text.text = generate_image_tag_from_token(@result_text.text,
|
||||
@result_text)
|
||||
respond_to do |format|
|
||||
format.json {
|
||||
render json: {
|
||||
|
|
|
@ -30,7 +30,7 @@ class StepsController < ApplicationController
|
|||
def create
|
||||
@step = Step.new(step_params)
|
||||
# gerate a tag that replaces img tag in database
|
||||
@step.description = parse_tiny_mce_asset_to_token(@step.description)
|
||||
@step.description = parse_tiny_mce_asset_to_token(@step.description, @step)
|
||||
@step.completed = false
|
||||
@step.position = @protocol.number_of_steps
|
||||
@step.protocol = @protocol
|
||||
|
@ -96,11 +96,9 @@ class StepsController < ApplicationController
|
|||
status: :ok
|
||||
end
|
||||
else
|
||||
format.json {
|
||||
render json: {
|
||||
html: render_to_string(partial: 'new.html.erb')
|
||||
}, status: :bad_request
|
||||
}
|
||||
format.json do
|
||||
render json: @step.errors.to_json, status: :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -120,7 +118,7 @@ class StepsController < ApplicationController
|
|||
end
|
||||
|
||||
def edit
|
||||
@step.description = generate_image_tag_from_token(@step.description)
|
||||
@step.description = generate_image_tag_from_token(@step.description, @step)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
|
|
|
@ -10,6 +10,7 @@ module Users
|
|||
before_action :update_sanitized_params, only: :update
|
||||
|
||||
def update
|
||||
return super unless Rails.configuration.x.new_team_on_signup
|
||||
# Instantialize a new team with the provided name
|
||||
@team = Team.new
|
||||
@team.name = params[:team][:name]
|
||||
|
@ -29,6 +30,8 @@ module Users
|
|||
end
|
||||
|
||||
def accept_resource
|
||||
return super unless Rails.configuration.x.new_team_on_signup
|
||||
|
||||
unless @team.valid?
|
||||
# Find the user being invited
|
||||
resource = User.find_by_invitation_token(
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
# You should configure your model like this:
|
||||
# devise :omniauthable, omniauth_providers: [:twitter]
|
||||
module Users
|
||||
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
# You should also create an action method in this controller like this:
|
||||
# def twitter
|
||||
# end
|
||||
# You should configure your model like this:
|
||||
# devise :omniauthable, omniauth_providers: [:twitter]
|
||||
|
||||
# More info at:
|
||||
# https://github.com/plataformatec/devise#omniauth
|
||||
# You should also create an action method in this controller like this:
|
||||
# def twitter
|
||||
# end
|
||||
|
||||
# GET|POST /resource/auth/twitter
|
||||
# def passthru
|
||||
# super
|
||||
# end
|
||||
# More info at:
|
||||
# https://github.com/plataformatec/devise#omniauth
|
||||
|
||||
# GET|POST /users/auth/twitter/callback
|
||||
# def failure
|
||||
# super
|
||||
# end
|
||||
# GET|POST /resource/auth/twitter
|
||||
# def passthru
|
||||
# super
|
||||
# end
|
||||
|
||||
# protected
|
||||
# GET|POST /users/auth/twitter/callback
|
||||
# def failure
|
||||
# super
|
||||
# end
|
||||
|
||||
# The path used when OmniAuth fails
|
||||
# def after_omniauth_failure_path_for(scope)
|
||||
# super(scope)
|
||||
# end
|
||||
# protected
|
||||
|
||||
# The path used when OmniAuth fails
|
||||
# def after_omniauth_failure_path_for(scope)
|
||||
# super(scope)
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -128,38 +128,44 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
def create
|
||||
render_403 && return unless Rails.configuration.x.enable_user_registration
|
||||
|
||||
build_resource(sign_up_params)
|
||||
valid_resource = resource.valid?
|
||||
# ugly checking if new team on sign up is enabled :(
|
||||
if Rails.configuration.x.new_team_on_signup
|
||||
# Create new team for the new user
|
||||
@team = Team.new
|
||||
@team.name = params[:team][:name]
|
||||
valid_team = @team.valid?
|
||||
|
||||
# Create new team for the new user
|
||||
@team = Team.new
|
||||
@team.name = params[:team][:name]
|
||||
valid_team = @team.valid?
|
||||
if valid_team && valid_resource
|
||||
# this must be called after @team variable is defined. Otherwise this
|
||||
# variable won't be accessable in view.
|
||||
super do |resource|
|
||||
# Set the confirmed_at == created_at IF not using email confirmations
|
||||
unless Rails.configuration.x.enable_email_confirmations
|
||||
resource.update(confirmed_at: resource.created_at)
|
||||
end
|
||||
|
||||
if valid_team && valid_resource
|
||||
if resource.valid? && resource.persisted?
|
||||
@team.created_by = resource # set created_by for oraganization
|
||||
@team.save
|
||||
|
||||
# this must be called after @team variable is defined. Otherwise this
|
||||
# variable won't be accessable in view.
|
||||
# Add this user to the team as owner
|
||||
UserTeam.create(user: resource, team: @team, role: :admin)
|
||||
|
||||
# set current team to new user
|
||||
resource.current_team_id = @team.id
|
||||
resource.save
|
||||
end
|
||||
end
|
||||
else
|
||||
render :new
|
||||
end
|
||||
elsif valid_resource
|
||||
super do |resource|
|
||||
# Set the confirmed_at == created_at IF not using email confirmations
|
||||
unless Rails.configuration.x.enable_email_confirmations
|
||||
resource.update(confirmed_at: resource.created_at)
|
||||
end
|
||||
|
||||
if resource.valid? && resource.persisted?
|
||||
@team.created_by = resource # set created_by for oraganization
|
||||
@team.save
|
||||
|
||||
# Add this user to the team as owner
|
||||
UserTeam.create(
|
||||
user: resource,
|
||||
team: @team,
|
||||
role: :admin
|
||||
)
|
||||
|
||||
# set current team to new user
|
||||
resource.current_team_id = @team.id
|
||||
resource.save
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
module Users
|
||||
module Settings
|
||||
module Account
|
||||
class AddonsController < ApplicationController
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
375
app/helpers/protocols_io_helper.rb
Normal file
375
app/helpers/protocols_io_helper.rb
Normal file
|
@ -0,0 +1,375 @@
|
|||
module ProtocolsIoHelper
|
||||
#=============================================================================
|
||||
# Protocols.io limits
|
||||
#=============================================================================
|
||||
TEXT_MAX_LENGTH = Constants::TEXT_MAX_LENGTH
|
||||
|
||||
PIO_ELEMENT_RESERVED_LENGTH_BIG = TEXT_MAX_LENGTH * 0.015
|
||||
PIO_ELEMENT_RESERVED_LENGTH_MEDIUM = TEXT_MAX_LENGTH * 0.01
|
||||
PIO_ELEMENT_RESERVED_LENGTH_SMALL = TEXT_MAX_LENGTH * 0.005
|
||||
|
||||
# PROTOCOLS.IO PROTOCOL ATTRIBUTES
|
||||
PIO_P_AVAILABLE_LENGTH =
|
||||
TEXT_MAX_LENGTH -
|
||||
(PIO_ELEMENT_RESERVED_LENGTH_SMALL * 2 +
|
||||
PIO_ELEMENT_RESERVED_LENGTH_MEDIUM * 8 +
|
||||
PIO_ELEMENT_RESERVED_LENGTH_BIG * 2)
|
||||
# -- 2 small = created at , publish date PROTOCOL ATTRIBUTES
|
||||
# -- 8 medium = description,tags,before_start,warning,guidelines,
|
||||
# manuscript_citation,keywords,vendor_name PROTOCOL ATTRIBUTES
|
||||
# -- 2 big = vendor_link, link PROTOCOL ATTRIBUTES
|
||||
|
||||
# PROTOCOLS.IO STEP ATTRIBUTES
|
||||
PIO_S_AVAILABLE_LENGTH =
|
||||
TEXT_MAX_LENGTH -
|
||||
(PIO_ELEMENT_RESERVED_LENGTH_SMALL * 20)
|
||||
# -- 20 small = description,expected_result,safety_information
|
||||
# software_package version, software_package os_name,
|
||||
# software_package os_version,software_package link,
|
||||
# software_package repository,software_package developer,software_package name
|
||||
# commands os_version,commands os_name, commands name,commands description,
|
||||
# sub protocol full name (author), sub protocol name, sub protocol link,
|
||||
# dataset link,dataset name, safety_information link,
|
||||
# -- 0 medium =
|
||||
# -- 0 big =
|
||||
|
||||
PIO_TITLE_TOOLONG_LEN =
|
||||
I18n.t('protocols.protocols_io_import.title_too_long').length + 2
|
||||
PIO_STEP_TOOLONG_LEN =
|
||||
I18n.t('protocols.protocols_io_import.too_long').length
|
||||
# The + 2 above (in title) is there because if the length was at the limit,
|
||||
# the cutter method had issues, this gives it some space
|
||||
def protocolsio_string_to_table_element(description_string)
|
||||
string_without_tables = string_html_table_remove(description_string)
|
||||
table_regex = %r{<table\b[^>]*>(.*?)<\/table>}m
|
||||
tr_regex = %r{<tr\b[^>]*>(.*?)<\/tr>}m
|
||||
td_regex = %r{<td\b[^>]*>(.*?)<\/td>}m
|
||||
tables = {}
|
||||
table_strings = description_string.scan(table_regex)
|
||||
table_strings.each_with_index do |table, table_counter|
|
||||
tables[table_counter.to_s] = {}
|
||||
tr_strings = table[0].scan(tr_regex)
|
||||
contents = {}
|
||||
contents['data'] = []
|
||||
tr_strings.each_with_index do |tr, tr_counter|
|
||||
td_strings = tr[0].scan(td_regex)
|
||||
contents['data'][tr_counter] = []
|
||||
td_strings.each do |td|
|
||||
td_stripped = ActionController::Base.helpers.strip_tags(td[0])
|
||||
contents['data'][tr_counter].push(td_stripped)
|
||||
end
|
||||
end
|
||||
tables[table_counter.to_s]['contents'] = Base64.encode64(
|
||||
contents.to_s.sub('=>', ':')
|
||||
)
|
||||
tables[table_counter.to_s]['name'] = nil
|
||||
end
|
||||
# return string_without_tables, tables
|
||||
return tables, string_without_tables
|
||||
end
|
||||
|
||||
def string_html_table_remove(description_string)
|
||||
description_string.remove!("\n", "\t", "\r", "\f")
|
||||
table_whole_regex = %r{(<table\b[^>]*>.*?<\/table>)}m
|
||||
table_pattern_array = description_string.scan(table_whole_regex)
|
||||
string_without_tables = description_string
|
||||
table_pattern_array.each do |table_pattern|
|
||||
string_without_tables = string_without_tables.gsub(
|
||||
table_pattern[0],
|
||||
t('protocols.protocols_io_import.comp_append.table_moved').html_safe
|
||||
)
|
||||
end
|
||||
string_without_tables
|
||||
end
|
||||
|
||||
def pio_eval_prot_desc(text, attribute_name)
|
||||
case attribute_name
|
||||
when 'publish_date'
|
||||
pio_eval_len(text, ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL)
|
||||
when 'vendor_link', 'link'
|
||||
pio_eval_len(text, ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_BIG)
|
||||
else
|
||||
pio_eval_len(text, ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM)
|
||||
end
|
||||
end
|
||||
|
||||
def pio_eval_title_len(text)
|
||||
if text
|
||||
text += ' ' if text.length < Constants::NAME_MIN_LENGTH
|
||||
if text.length > Constants::NAME_MAX_LENGTH
|
||||
text =
|
||||
text[0..(Constants::NAME_MAX_LENGTH - PIO_TITLE_TOOLONG_LEN)] +
|
||||
t('protocols.protocols_io_import.title_too_long')
|
||||
@toolong = true
|
||||
end
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
def pio_eval_len(text, reserved)
|
||||
if text
|
||||
text_end = reserved + @remaining - PIO_STEP_TOOLONG_LEN
|
||||
text_end = 2 if text_end < 2
|
||||
# Since steps have very low reserved values now (below 100),
|
||||
# the above sets their index to 1 if its negative
|
||||
# (length of toolong text is about 90 chars, and if remaining is 0,
|
||||
# then the negative index just gets set to 1. this is a workaround
|
||||
|
||||
# it would also be possible to not count the length of the "too long" text
|
||||
# or setting the import reserved value to 95,but then available characters
|
||||
# will be like before (around 7600)
|
||||
if text.length - reserved > @remaining
|
||||
text =
|
||||
close_open_html_tags(
|
||||
text[0..text_end] + t('protocols.protocols_io_import.too_long')
|
||||
)
|
||||
@toolong = true
|
||||
@remaining = 0
|
||||
elsif (text.length - reserved) > 0
|
||||
@remaining -= text.length - reserved
|
||||
end
|
||||
text
|
||||
end
|
||||
end
|
||||
|
||||
# Checks so that null values are returned as zero length strings
|
||||
# Did this so views arent as ugly (i avoid using if present statements)
|
||||
def not_null(attribute)
|
||||
if attribute
|
||||
attribute
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def close_open_html_tags(text)
|
||||
Nokogiri::HTML::DocumentFragment.parse(text).to_html
|
||||
end
|
||||
|
||||
def prepare_for_view(attribute_text1, size, table = 'no_table')
|
||||
if table == 'no_table'
|
||||
attribute_text = sanitize_input(not_null(attribute_text1))
|
||||
elsif table == 'table'
|
||||
attribute_text = sanitize_input(
|
||||
string_html_table_remove(not_null(attribute_text1))
|
||||
)
|
||||
end
|
||||
pio_eval_len(
|
||||
attribute_text,
|
||||
size
|
||||
)
|
||||
end
|
||||
|
||||
def fill_attributes(attribute_name, attribute_text, step_component)
|
||||
output_string = ''
|
||||
trans_string = step_component
|
||||
trans_string +=
|
||||
if attribute_name != 'os_name' && attribute_name != 'os_version'
|
||||
attribute_name
|
||||
else
|
||||
'os'
|
||||
end
|
||||
output_string +=
|
||||
if attribute_name != 'os_version'
|
||||
t(trans_string)
|
||||
else
|
||||
' , '
|
||||
end
|
||||
output_string += prepare_for_view(
|
||||
attribute_text, ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL
|
||||
)
|
||||
output_string
|
||||
end
|
||||
|
||||
# pio_stp_x means protocols io step (id of component) parser
|
||||
def pio_stp_1(iterating_key) # protocols io description parser
|
||||
br = '<br>'
|
||||
append =
|
||||
if iterating_key.present?
|
||||
br +
|
||||
prepare_for_view(
|
||||
iterating_key,
|
||||
ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL
|
||||
) +
|
||||
br
|
||||
else
|
||||
t('protocols.protocols_io_import.comp_append.missing_desc')
|
||||
end
|
||||
append
|
||||
end
|
||||
|
||||
def pio_stp_6(iterating_key) # protocols io section(title) parser
|
||||
return pio_eval_title_len(sanitize_input(iterating_key)) if iterating_key.present?
|
||||
t('protocols.protocols_io_import.comp_append.missing_step')
|
||||
end
|
||||
|
||||
def pio_stp_17(iterating_key) # protocols io expected result parser
|
||||
if iterating_key.present?
|
||||
append =
|
||||
t('protocols.protocols_io_import.comp_append.expected_result') +
|
||||
prepare_for_view(
|
||||
iterating_key, ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL
|
||||
) +
|
||||
'<br>'
|
||||
return append
|
||||
end
|
||||
''
|
||||
end
|
||||
# protocols io software package,dataset,commands,sub_protocol and safety_information parser
|
||||
|
||||
def pio_stp(iterating_key, parse_elements_array, en_local_text)
|
||||
append = ''
|
||||
parse_elements_array.each do |element|
|
||||
return '' unless iterating_key[element]
|
||||
append += fill_attributes(
|
||||
element,
|
||||
iterating_key[element],
|
||||
en_local_text
|
||||
)
|
||||
end
|
||||
append
|
||||
end
|
||||
|
||||
def protocols_io_fill_desc(json_hash)
|
||||
description_array = %w[
|
||||
( before_start warning guidelines manuscript_citation publish_date
|
||||
vendor_name vendor_link keywords tags link created_on )
|
||||
]
|
||||
description_string =
|
||||
if json_hash['description'].present?
|
||||
'<strong>' + t('protocols.protocols_io_import.preview.description') +
|
||||
'</strong>' +
|
||||
prepare_for_view(
|
||||
json_hash['description'],
|
||||
ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM
|
||||
).html_safe
|
||||
else
|
||||
'<strong>' + t('protocols.protocols_io_import.preview.description') +
|
||||
'</strong>' + t('protocols.protocols_io_import.comp_append.missing_desc')
|
||||
end
|
||||
description_string += '<br>'
|
||||
description_array.each do |e|
|
||||
if e == 'created_on' && json_hash[e].present?
|
||||
new_e = '<strong>' + e.humanize + '</strong>'
|
||||
description_string +=
|
||||
new_e.to_s + ': ' +
|
||||
prepare_for_view(
|
||||
params['protocol']['created_at'].to_s,
|
||||
ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL
|
||||
) +
|
||||
+ '<br>'
|
||||
elsif e == 'tags' && json_hash[e].any? && json_hash[e] != ''
|
||||
new_e = '<strong>' + e.humanize + '</strong>'
|
||||
description_string +=
|
||||
new_e.to_s + ': '
|
||||
tags_length_checker = ''
|
||||
json_hash[e].each do |tag|
|
||||
tags_length_checker +=
|
||||
sanitize_input(tag['tag_name']) + ' , '
|
||||
end
|
||||
description_string += prepare_for_view(
|
||||
tags_length_checker,
|
||||
ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM
|
||||
)
|
||||
description_string += '<br>'
|
||||
elsif json_hash[e].present?
|
||||
new_e = '<strong>' + e.humanize + '</strong>'
|
||||
description_string +=
|
||||
new_e.to_s + ': ' +
|
||||
pio_eval_prot_desc(
|
||||
sanitize_input(json_hash[e]),
|
||||
e
|
||||
).html_safe + '<br>'
|
||||
end
|
||||
end
|
||||
description_string
|
||||
end
|
||||
|
||||
def protocols_io_fill_step(original_json, newj)
|
||||
# newj = new json
|
||||
# (simple to map) id 1= step description, id 6= section (title),
|
||||
# id 17= expected result
|
||||
# (complex mapping with nested hashes) id 8 = software package,
|
||||
# id 9 = dataset, id 15 = command, id 18 = attached sub protocol
|
||||
# id 19= safety information ,
|
||||
# id 20= regents (materials, like scinote samples kind of)
|
||||
newj['0'] = {}
|
||||
newj['0']['position'] = 0
|
||||
newj['0']['name'] = 'Protocol info'
|
||||
@remaining = ProtocolsIoHelper::PIO_P_AVAILABLE_LENGTH
|
||||
newj['0']['tables'], table_str = protocolsio_string_to_table_element(
|
||||
sanitize_input(protocols_io_fill_desc(original_json).html_safe)
|
||||
)
|
||||
newj['0']['description'] = table_str
|
||||
original_json['steps'].each_with_index do |step, pos_orig| # loop over steps
|
||||
i = pos_orig + 1
|
||||
@remaining = ProtocolsIoHelper::PIO_S_AVAILABLE_LENGTH
|
||||
# position of step (first, second.... etc),
|
||||
newj[i.to_s] = {} # the json we will insert into db
|
||||
newj[i.to_s]['position'] = i
|
||||
newj[i.to_s]['description'] = '' unless newj[i.to_s].key?('description')
|
||||
newj[i.to_s]['name'] = '' unless newj[i.to_s].key?('name')
|
||||
step['components'].each do |key, value|
|
||||
# sometimes there are random index values as keys
|
||||
# instead of hashes, this is a workaround to that buggy json format
|
||||
key = value if value.class == Hash
|
||||
# append is the string that we append values into for description
|
||||
# pio_stp_x means protocols io step (id of component) parser
|
||||
case key['component_type_id']
|
||||
when '1'
|
||||
newj[i.to_s]['description'] += pio_stp_1(key['data'])
|
||||
when '6'
|
||||
newj[i.to_s]['name'] = pio_stp_6(key['data'])
|
||||
when '17'
|
||||
newj[i.to_s]['description'] += pio_stp_17(key['data'])
|
||||
when '8'
|
||||
pe_array = %w(
|
||||
name developer version link repository os_name os_version
|
||||
)
|
||||
trans_text = 'protocols.protocols_io_import.comp_append.soft_packg.'
|
||||
newj[i.to_s]['description'] += pio_stp(
|
||||
key['source_data'], pe_array, trans_text
|
||||
)
|
||||
when '9'
|
||||
pe_array = %w(
|
||||
name link
|
||||
)
|
||||
trans_text = 'protocols.protocols_io_import.comp_append.dataset.'
|
||||
newj[i.to_s]['description'] += pio_stp(
|
||||
key['source_data'], pe_array, trans_text
|
||||
)
|
||||
when '15'
|
||||
pe_array = %w(
|
||||
name description os_name os_version
|
||||
)
|
||||
trans_text = 'protocols.protocols_io_import.comp_append.command.'
|
||||
newj[i.to_s]['description'] += pio_stp(
|
||||
key['source_data'], pe_array, trans_text
|
||||
)
|
||||
when '18'
|
||||
pe_array = %w(
|
||||
protocol_name full_name link
|
||||
)
|
||||
trans_text = 'protocols.protocols_io_import.comp_append.sub_protocol.'
|
||||
newj[i.to_s]['description'] += pio_stp(
|
||||
key['source_data'], pe_array, trans_text
|
||||
)
|
||||
when '19'
|
||||
pe_array = %w(
|
||||
body link
|
||||
)
|
||||
trans_text = 'protocols.protocols_io_import.comp_append.safety_infor.'
|
||||
newj[i.to_s]['description'] += pio_stp(
|
||||
key['source_data'], pe_array, trans_text
|
||||
)
|
||||
end # case end
|
||||
end # finished looping over step components
|
||||
newj[i.to_s]['tables'], table_str = protocolsio_string_to_table_element(
|
||||
newj[i.to_s]['description']
|
||||
)
|
||||
newj[i.to_s]['description'] = table_str
|
||||
end # steps
|
||||
newj
|
||||
end
|
||||
|
||||
end
|
|
@ -1,29 +1,30 @@
|
|||
module TinyMceHelper
|
||||
def parse_tiny_mce_asset_to_token(text, ref = nil)
|
||||
def parse_tiny_mce_asset_to_token(text, obj)
|
||||
ids = []
|
||||
html = Nokogiri::HTML(text)
|
||||
html = Nokogiri::HTML(remove_pasted_tokens(text))
|
||||
html.search('img').each do |img|
|
||||
next unless img['data-token']
|
||||
img_id = Base62.decode(img['data-token'])
|
||||
ids << img_id
|
||||
token = "[~tiny_mce_id:#{img_id}]"
|
||||
img.replace(token)
|
||||
next unless ref
|
||||
next unless obj
|
||||
tiny_img = TinyMceAsset.find_by_id(img_id)
|
||||
tiny_img.reference = ref unless tiny_img.step || tiny_img.result_text
|
||||
tiny_img.reference = obj unless tiny_img.step || tiny_img.result_text
|
||||
tiny_img.save
|
||||
end
|
||||
destroy_removed_tiny_mce_assets(ids, ref) if ref
|
||||
destroy_removed_tiny_mce_assets(ids, obj) if obj
|
||||
html
|
||||
end
|
||||
|
||||
def generate_image_tag_from_token(text)
|
||||
def generate_image_tag_from_token(text, obj)
|
||||
return unless text
|
||||
regex = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
|
||||
regex = Constants::TINY_MCE_ASSET_REGEX
|
||||
text.gsub(regex) do |el|
|
||||
match = el.match(regex)
|
||||
img = TinyMceAsset.find_by_id(match[1])
|
||||
next unless img
|
||||
next unless img && img.team == current_team
|
||||
next unless check_image_permissions(obj, img)
|
||||
image_tag img.url,
|
||||
class: 'img-responsive',
|
||||
data: { token: Base62.encode(img.id) }
|
||||
|
@ -32,7 +33,7 @@ module TinyMceHelper
|
|||
|
||||
def link_tiny_mce_assets(text, ref)
|
||||
ids = []
|
||||
regex = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
|
||||
regex = Constants::TINY_MCE_ASSET_REGEX
|
||||
text.gsub(regex) do |img|
|
||||
match = img.match(regex)
|
||||
tiny_img = TinyMceAsset.find_by_id(match[1])
|
||||
|
@ -44,7 +45,35 @@ module TinyMceHelper
|
|||
destroy_removed_tiny_mce_assets(ids, ref)
|
||||
end
|
||||
|
||||
def replace_tiny_mce_assets(text, img_ids)
|
||||
img_ids.each do |src_id, dest_id|
|
||||
regex = /\[~tiny_mce_id:#{src_id}\]/
|
||||
new_token = "[~tiny_mce_id:#{dest_id}]"
|
||||
text.sub!(regex, new_token)
|
||||
end
|
||||
text
|
||||
end
|
||||
|
||||
def destroy_removed_tiny_mce_assets(ids, ref)
|
||||
ref.tiny_mce_assets.where.not('id IN (?)', ids).destroy_all
|
||||
# need to check if the array is empty because if we pass the empty array
|
||||
# in the SQL query it will not work properly
|
||||
if ids.empty?
|
||||
ref.tiny_mce_assets.destroy_all
|
||||
else
|
||||
ref.tiny_mce_assets.where.not('id IN (?)', ids).destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
def check_image_permissions(obj, img)
|
||||
if obj.class == Step
|
||||
img.step == obj
|
||||
elsif obj.class == ResultText
|
||||
img.result_text == obj
|
||||
end
|
||||
end
|
||||
|
||||
def remove_pasted_tokens(text)
|
||||
regex = Constants::TINY_MCE_ASSET_REGEX
|
||||
text.gsub(regex, ' ')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
module UserSettingsHelper
|
||||
def on_settings_account_page?
|
||||
controller_name == 'registrations' && action_name == 'edit' ||
|
||||
controller_name == 'preferences' && action_name == 'index'
|
||||
controller_name == 'preferences' && action_name == 'index' ||
|
||||
controller_name == 'addons' && action_name == 'index'
|
||||
end
|
||||
|
||||
def on_settings_account_profile_page?
|
||||
|
@ -12,6 +13,10 @@ module UserSettingsHelper
|
|||
controller_name == 'preferences'
|
||||
end
|
||||
|
||||
def on_settings_account_addons_page?
|
||||
controller_name == 'addons'
|
||||
end
|
||||
|
||||
def on_settings_team_page?
|
||||
controller_name.in?(%w(teams audits)) &&
|
||||
action_name.in?(%w(index new create show audits_index))
|
||||
|
|
|
@ -12,8 +12,10 @@ class Asset < ApplicationRecord
|
|||
has_attached_file :file,
|
||||
styles: { large: [Constants::LARGE_PIC_FORMAT, :jpg],
|
||||
medium: [Constants::MEDIUM_PIC_FORMAT, :jpg] },
|
||||
convert_options: { medium: '-quality 70 -strip' }
|
||||
|
||||
convert_options: {
|
||||
medium: '-quality 70 -strip',
|
||||
all: '-background "#d2d2d2" -flatten +matte'
|
||||
}
|
||||
validates_attachment :file,
|
||||
presence: true,
|
||||
size: {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class Protocol < ApplicationRecord
|
||||
include SearchableModel
|
||||
include RenamingUtil
|
||||
extend TinyMceHelper
|
||||
|
||||
after_save :update_linked_children
|
||||
after_destroy :decrement_linked_children
|
||||
|
@ -329,6 +330,24 @@ class Protocol < ApplicationRecord
|
|||
table2.team = dest.team
|
||||
step2.tables << table2
|
||||
end
|
||||
|
||||
# Copy tinyMce assets
|
||||
cloned_img_ids = []
|
||||
step.tiny_mce_assets.each do |tiny_img|
|
||||
tiny_img2 = TinyMceAsset.new(
|
||||
image: tiny_img.image,
|
||||
estimated_size: tiny_img.estimated_size,
|
||||
step: step2,
|
||||
team: dest.team
|
||||
)
|
||||
tiny_img2.save
|
||||
|
||||
step2.tiny_mce_assets << tiny_img2
|
||||
cloned_img_ids << [tiny_img.id, tiny_img2.id]
|
||||
end
|
||||
step2.update(
|
||||
description: replace_tiny_mce_assets(step2.description, cloned_img_ids)
|
||||
)
|
||||
end
|
||||
|
||||
# Call clone helper
|
||||
|
|
|
@ -135,8 +135,7 @@ class Repository < ApplicationRecord
|
|||
end
|
||||
total_nr += 1
|
||||
|
||||
# Creek XLSX parser returns Hash of the row, Roo - Array
|
||||
row = row.is_a?(Hash) ? row.values.map(&:to_s) : row.map(&:to_s)
|
||||
row = SpreadsheetParser.parse_row(row, sheet)
|
||||
|
||||
record_row = RepositoryRow.new(name: row[name_index],
|
||||
repository: self,
|
||||
|
|
|
@ -86,8 +86,7 @@ class Team < ApplicationRecord
|
|||
next
|
||||
end
|
||||
total_nr += 1
|
||||
# Creek XLSX parser returns Hash of the row, Roo - Array
|
||||
row = row.is_a?(Hash) ? row.values.map(&:to_s) : row.map(&:to_s)
|
||||
row = SpreadsheetParser.parse_row(row, sheet)
|
||||
|
||||
sample = Sample.new(name: row[sname_index],
|
||||
team: self,
|
||||
|
@ -100,6 +99,7 @@ class Team < ApplicationRecord
|
|||
end
|
||||
|
||||
row.each.with_index do |value, index|
|
||||
next unless value.present?
|
||||
if index == stype_index
|
||||
stype = SampleType.where(team: self)
|
||||
.where('name ILIKE ?', value.strip)
|
||||
|
|
|
@ -54,6 +54,14 @@ class TinyMceAsset < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def open
|
||||
if image.is_stored_on_s3?
|
||||
Kernel.open(presigned_url, 'rb')
|
||||
else
|
||||
File.open(image.path, 'rb')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_estimated_size
|
||||
|
|
|
@ -5,6 +5,7 @@ class User < ApplicationRecord
|
|||
acts_as_token_authenticatable
|
||||
devise :invitable, :confirmable, :database_authenticatable, :registerable,
|
||||
:async, :recoverable, :rememberable, :trackable, :validatable,
|
||||
:omniauthable, omniauth_providers: Extends::OMNIAUTH_PROVIDERS,
|
||||
stretches: Constants::PASSWORD_STRETCH_FACTOR
|
||||
has_attached_file :avatar,
|
||||
styles: {
|
||||
|
@ -51,6 +52,7 @@ class User < ApplicationRecord
|
|||
)
|
||||
|
||||
# Relations
|
||||
has_many :user_identities, inverse_of: :user
|
||||
has_many :user_teams, inverse_of: :user
|
||||
has_many :teams, through: :user_teams
|
||||
has_many :user_projects, inverse_of: :user
|
||||
|
@ -219,6 +221,17 @@ class User < ApplicationRecord
|
|||
Team.find_by_id(self.current_team_id)
|
||||
end
|
||||
|
||||
def self.from_omniauth(auth)
|
||||
includes(:user_identities)
|
||||
.where(
|
||||
'user_identities.provider=? AND user_identities.uid=?',
|
||||
auth.provider,
|
||||
auth.uid
|
||||
)
|
||||
.references(:user_identities)
|
||||
.take
|
||||
end
|
||||
|
||||
# Search all active users for username & email. Can
|
||||
# also specify which team to ignore.
|
||||
def self.search(
|
||||
|
|
5
app/models/user_identity.rb
Normal file
5
app/models/user_identity.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class UserIdentity < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
validates :provider, uniqueness: { scope: :user_id }
|
||||
validates :uid, uniqueness: { scope: :provider }
|
||||
end
|
25
app/services/api.rb
Normal file
25
app/services/api.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
module Api
|
||||
class << self
|
||||
attr_accessor :configuration
|
||||
end
|
||||
|
||||
def self.configuration
|
||||
@configuration ||= Configuration.new
|
||||
end
|
||||
|
||||
def self.configure
|
||||
yield(configuration)
|
||||
end
|
||||
|
||||
class Configuration
|
||||
attr_accessor :core_api_sign_alg
|
||||
attr_accessor :core_api_token_ttl
|
||||
attr_accessor :core_api_token_iss
|
||||
|
||||
def initialize
|
||||
@core_api_sign_alg = 'HS256'
|
||||
@core_api_token_ttl = 30.minutes
|
||||
@core_api_token_iss = 'SciNote'
|
||||
end
|
||||
end
|
||||
end
|
34
app/services/api/core_jwt.rb
Normal file
34
app/services/api/core_jwt.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module Api
|
||||
class CoreJwt
|
||||
require 'jwt'
|
||||
KEY_SECRET = Rails.application.secrets.secret_key_base
|
||||
|
||||
def self.encode(payload, expires_at = nil)
|
||||
if expires_at
|
||||
payload[:exp] = expires_at
|
||||
else
|
||||
payload[:exp] = Api.configuration.core_api_token_ttl.from_now.to_i
|
||||
end
|
||||
payload[:iss] = Api.configuration.core_api_token_iss
|
||||
JWT.encode(payload, KEY_SECRET, Api.configuration.core_api_sign_alg)
|
||||
end
|
||||
|
||||
def self.decode(token)
|
||||
HashWithIndifferentAccess.new(
|
||||
JWT.decode(token, KEY_SECRET, Api.configuration.core_api_sign_alg)[0]
|
||||
)
|
||||
end
|
||||
|
||||
def self.read_iss(token)
|
||||
HashWithIndifferentAccess.new(
|
||||
JWT.decode(token, nil, false)[0]
|
||||
)[:iss].to_s
|
||||
end
|
||||
|
||||
def self.refresh_needed?(payload)
|
||||
time_left = payload[:exp].to_i - Time.now.to_i
|
||||
return true if time_left < (Api.configuration.core_api_token_ttl.to_i / 2)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,8 +20,8 @@ class SpreadsheetParser
|
|||
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]
|
||||
Roo::Excelx.new(file_path)
|
||||
# Creek::Book.new(file_path).sheets[0]
|
||||
else
|
||||
raise TypeError
|
||||
end
|
||||
|
@ -52,4 +52,15 @@ class SpreadsheetParser
|
|||
end
|
||||
return header, columns
|
||||
end
|
||||
|
||||
def self.parse_row(row, sheet)
|
||||
# Creek XLSX parser returns Hash of the row, Roo - Array
|
||||
if row.is_a?(Hash)
|
||||
row.values.map(&:to_s)
|
||||
elsif sheet.is_a?(Roo::Excelx)
|
||||
row.map { |cell| cell.value.to_s }
|
||||
else
|
||||
row.map(&:to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,32 @@ module ProtocolsExporter
|
|||
envelope_xml
|
||||
end
|
||||
|
||||
def tiny_mce_asset_present?(step)
|
||||
step.tiny_mce_assets.exists?
|
||||
end
|
||||
|
||||
def get_tiny_mce_assets(text)
|
||||
return unless text
|
||||
regex = Constants::TINY_MCE_ASSET_REGEX
|
||||
tiny_assets_xml = "<descriptionAssets>\n"
|
||||
text.gsub(regex) do |el|
|
||||
match = el.match(regex)
|
||||
img = TinyMceAsset.find_by_id(match[1])
|
||||
next unless img
|
||||
img_guid = get_guid(img.id)
|
||||
asset_file_name = "rte-#{img_guid}" \
|
||||
"#{File.extname(img.image_file_name)}"
|
||||
asset_xml = "<tinyMceAsset tokenId=\"#{match[1]}\" id=\"#{img.id}\" guid=\"#{img_guid}\" " \
|
||||
"fileRef=\"#{asset_file_name}\">\n"
|
||||
asset_xml << "<fileName>#{img.image_file_name}</fileName>\n"
|
||||
asset_xml << "<fileType>#{img.image_content_type}</fileType>\n"
|
||||
asset_xml << "</tinyMceAsset>\n"
|
||||
tiny_assets_xml << asset_xml
|
||||
end
|
||||
tiny_assets_xml << "</descriptionAssets>\n"
|
||||
tiny_assets_xml
|
||||
end
|
||||
|
||||
def generate_protocol_xml(protocol)
|
||||
protocol_name = get_protocol_name(protocol)
|
||||
protocol_xml = "<eln xmlns=\"http://www.scinote.net\" version=\"1.0\">\n"
|
||||
|
@ -54,8 +80,14 @@ module ProtocolsExporter
|
|||
step_xml = "<step id=\"#{step.id}\" guid=\"#{step_guid}\" " \
|
||||
"position=\"#{step.position}\">\n"
|
||||
step_xml << "<name>#{step.name}</name>\n"
|
||||
step_xml << "<description>#{step.description}</description>\n"
|
||||
# uses 2 spaces to make more difficult to remove user data on import
|
||||
step_xml << "<description><!--[CDATA[ #{
|
||||
Nokogiri::HTML::DocumentFragment.parse(step.description).to_s
|
||||
} ]]--></description>\n"
|
||||
|
||||
if tiny_mce_asset_present?(step)
|
||||
step_xml << get_tiny_mce_assets(step.description)
|
||||
end
|
||||
# Assets
|
||||
if step.assets.count > 0
|
||||
step_xml << "<assets>\n"
|
||||
|
@ -217,6 +249,30 @@ module ProtocolsExporter
|
|||
eln_xsd << "</xs:sequence>\n"
|
||||
eln_xsd << "</xs:complexType>\n"
|
||||
eln_xsd << "</xs:element>\n"
|
||||
eln_xsd << "<xs:element name=\"descriptionAssets\" minOccurs=\"0\">\n"
|
||||
eln_xsd << "<xs:complexType>\n"
|
||||
eln_xsd << "<xs:sequence>\n"
|
||||
eln_xsd << "<xs:element name=\"tinyMceAsset\" maxOccurs=\"unbounded\">\n"
|
||||
eln_xsd << "<xs:complexType>\n"
|
||||
eln_xsd << "<xs:all>\n"
|
||||
eln_xsd << "<xs:element name=\"fileName\" " \
|
||||
"type=\"xs:string\"></xs:element>\n"
|
||||
eln_xsd << "<xs:element name=\"fileType\" " \
|
||||
"type=\"xs:string\"></xs:element>\n"
|
||||
eln_xsd << "</xs:all>\n"
|
||||
eln_xsd << "<xs:attribute name=\"id\" type=\"xs:int\" " \
|
||||
"use=\"required\"></xs:attribute>\n"
|
||||
eln_xsd << "<xs:attribute name=\"tokenId\" type=\"xs:string\" " \
|
||||
"use=\"required\"></xs:attribute>\n"
|
||||
eln_xsd << "<xs:attribute name=\"guid\" type=\"xs:string\" " \
|
||||
"use=\"required\"></xs:attribute>\n"
|
||||
eln_xsd << "<xs:attribute name=\"fileRef\" type=\"xs:string\" " \
|
||||
"use=\"required\"></xs:attribute>\n"
|
||||
eln_xsd << "</xs:complexType>\n"
|
||||
eln_xsd << "</xs:element>\n"
|
||||
eln_xsd << "</xs:sequence>\n"
|
||||
eln_xsd << "</xs:complexType>\n"
|
||||
eln_xsd << "</xs:element>\n"
|
||||
eln_xsd << "<xs:element name=\"assets\" minOccurs=\"0\">\n"
|
||||
eln_xsd << "<xs:complexType>\n"
|
||||
eln_xsd << "<xs:sequence>\n"
|
||||
|
|
|
@ -45,86 +45,87 @@ module ProtocolsImporter
|
|||
|
||||
def populate_protocol(protocol, protocol_json, user, team)
|
||||
protocol.reload
|
||||
|
||||
asset_ids = []
|
||||
step_pos = 0
|
||||
# Check if protocol has steps
|
||||
if protocol_json['steps']
|
||||
protocol_json['steps'].values.each do |step_json|
|
||||
step = Step.create!(
|
||||
name: step_json['name'],
|
||||
description: step_json['description'],
|
||||
position: step_pos,
|
||||
completed: false,
|
||||
user: user,
|
||||
last_modified_by: user,
|
||||
protocol: protocol
|
||||
)
|
||||
step_pos += 1
|
||||
step = Step.create!(
|
||||
name: step_json['name'],
|
||||
position: step_pos,
|
||||
completed: false,
|
||||
user: user,
|
||||
last_modified_by: user,
|
||||
protocol: protocol
|
||||
)
|
||||
# need step id to link image to step
|
||||
step.description = populate_rte(step_json, step, team)
|
||||
step.save!
|
||||
step_pos += 1
|
||||
|
||||
if step_json["checklists"]
|
||||
step_json["checklists"].values.each do |checklist_json|
|
||||
checklist = Checklist.create!(
|
||||
name: checklist_json["name"],
|
||||
step: step,
|
||||
created_by: user,
|
||||
last_modified_by: user
|
||||
)
|
||||
if checklist_json["items"]
|
||||
item_pos = 0
|
||||
checklist_json["items"].values.each do |item_json|
|
||||
item = ChecklistItem.create!(
|
||||
text: item_json["text"],
|
||||
checked: false,
|
||||
position: item_pos,
|
||||
created_by: user,
|
||||
last_modified_by: user,
|
||||
checklist: checklist
|
||||
)
|
||||
item_pos += 1
|
||||
if step_json["checklists"]
|
||||
step_json["checklists"].values.each do |checklist_json|
|
||||
checklist = Checklist.create!(
|
||||
name: checklist_json["name"],
|
||||
step: step,
|
||||
created_by: user,
|
||||
last_modified_by: user
|
||||
)
|
||||
if checklist_json["items"]
|
||||
item_pos = 0
|
||||
checklist_json["items"].values.each do |item_json|
|
||||
item = ChecklistItem.create!(
|
||||
text: item_json["text"],
|
||||
checked: false,
|
||||
position: item_pos,
|
||||
created_by: user,
|
||||
last_modified_by: user,
|
||||
checklist: checklist
|
||||
)
|
||||
item_pos += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if step_json['tables']
|
||||
step_json['tables'].values.each do |table_json|
|
||||
table = Table.create!(
|
||||
name: table_json['name'],
|
||||
contents: Base64.decode64(table_json['contents']),
|
||||
created_by: user,
|
||||
last_modified_by: user,
|
||||
team: team
|
||||
)
|
||||
StepTable.create!(
|
||||
step: step,
|
||||
table: table
|
||||
)
|
||||
if step_json['tables']
|
||||
step_json['tables'].values.each do |table_json|
|
||||
table = Table.create!(
|
||||
name: table_json['name'],
|
||||
contents: Base64.decode64(table_json['contents']),
|
||||
created_by: user,
|
||||
last_modified_by: user,
|
||||
team: team
|
||||
)
|
||||
StepTable.create!(
|
||||
step: step,
|
||||
table: table
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if step_json["assets"]
|
||||
step_json["assets"].values.each do |asset_json|
|
||||
asset = Asset.new(
|
||||
created_by: user,
|
||||
last_modified_by: user,
|
||||
team: team
|
||||
)
|
||||
|
||||
# Decode the file bytes
|
||||
asset.file = StringIO.new(Base64.decode64(asset_json["bytes"]))
|
||||
asset.file_file_name = asset_json["fileName"]
|
||||
asset.file_content_type = asset_json["fileType"]
|
||||
asset.save!
|
||||
asset_ids << asset.id
|
||||
|
||||
StepAsset.create!(
|
||||
step: step,
|
||||
asset: asset
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if step_json["assets"]
|
||||
step_json["assets"].values.each do |asset_json|
|
||||
asset = Asset.new(
|
||||
created_by: user,
|
||||
last_modified_by: user,
|
||||
team: team
|
||||
)
|
||||
|
||||
# Decode the file bytes
|
||||
asset.file = StringIO.new(Base64.decode64(asset_json["bytes"]))
|
||||
asset.file_file_name = asset_json["fileName"]
|
||||
asset.file_content_type = asset_json["fileType"]
|
||||
asset.save!
|
||||
asset_ids << asset.id
|
||||
|
||||
StepAsset.create!(
|
||||
step: step,
|
||||
asset: asset
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Post process assets
|
||||
|
@ -144,4 +145,33 @@ module ProtocolsImporter
|
|||
end
|
||||
end
|
||||
|
||||
# create tiny_mce assets and change the inport tokens
|
||||
def populate_rte(step_json, step, team)
|
||||
return populate_rte_legacy(step_json) unless step_json['descriptionAssets']
|
||||
description = step_json['description']
|
||||
step_json['descriptionAssets'].values.each do |tiny_mce_img_json|
|
||||
tiny_mce_img = TinyMceAsset.new(
|
||||
reference: step,
|
||||
team_id: team.id
|
||||
)
|
||||
# Decode the file bytes
|
||||
tiny_mce_img.image = StringIO.new(
|
||||
Base64.decode64(tiny_mce_img_json['bytes'])
|
||||
)
|
||||
tiny_mce_img.image_content_type = tiny_mce_img_json['fileType']
|
||||
tiny_mce_img.save!
|
||||
description.gsub!("[~tiny_mce_id:#{tiny_mce_img_json['tokenId']}]",
|
||||
"[~tiny_mce_id:#{tiny_mce_img.id}]")
|
||||
.gsub!(' ]]-->', '')
|
||||
|
||||
end
|
||||
description
|
||||
end
|
||||
|
||||
# handle import from legacy exports
|
||||
def populate_rte_legacy(step_json)
|
||||
return unless step_json['description'] && step_json['description'].present?
|
||||
step_json['description'].gsub(Constants::TINY_MCE_ASSET_REGEX, '')
|
||||
.gsub(' ]]-->', '')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% if experiment.workflowimg? && experiment.active_modules.length > 0 %>
|
||||
<% if experiment.active_modules.length > 0 %>
|
||||
<div class="workflowimg-container"
|
||||
data-check-img="<%= updated_img_experiment_url(experiment) %>"
|
||||
data-updated-img="<%= fetch_workflow_img_experiment_url(experiment) %>"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<div
|
||||
class="modal fade"
|
||||
id="modal-import-json-protocol"
|
||||
tabindex="-1"
|
||||
role="dialog">
|
||||
<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">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title"><%= t('protocols.index.modal_import_json_title') %></h4>
|
||||
<%= t("protocols.index.modal_import_json_notice") %>
|
||||
</div>
|
||||
<%= form_tag({ action: "protocolsio_import_create"}, id:"protocols_io_form",
|
||||
format: :json, multipart: true,remote: true,:method => "post") do %>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<%= file_field_tag 'json_file', accept: '.txt,.json' %>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t('general.cancel')%></button>
|
||||
<%= submit_tag t('protocols.index.modal_import_json_upload'), class: "btn
|
||||
btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,91 @@
|
|||
<div style="display: block;">
|
||||
<hr>
|
||||
<td>
|
||||
<div class="badge-num">
|
||||
<span class="badge-preview bg-primary size-digit-1">
|
||||
<b data-val="position"><%= 1 %></b>
|
||||
</span>
|
||||
|
||||
<span class="step-panel-collapse-link" data-toggle="collapse">
|
||||
<span class="glyphicon collapse-step-icon glyphicon-collapse-up"></span>
|
||||
<strong data-val="name"><%= 'Protocol info' %></strong>
|
||||
</span>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" role="tabpanel">
|
||||
<div data-val="description" class="ql-editor">
|
||||
<br>
|
||||
<% prot_info_string = '' %>
|
||||
<% @remaining = ProtocolsIoHelper::PIO_P_AVAILABLE_LENGTH %>
|
||||
<% protocol_table_elements_array = [] %>
|
||||
<% add_to_info_string_elements = ['description','before_start','warning','guidelines','manuscript_citation']%>
|
||||
<% protocol_attr_len_hash = {
|
||||
description: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
before_start: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
warning: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
guidelines: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
manuscript_citation: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
vendor_name: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
keywords: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
tags: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,
|
||||
publish_date: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,
|
||||
link: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_BIG,
|
||||
vendor_link: ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_BIG } %>
|
||||
<% protocol_attr_len_hash.each do |element, value|%>
|
||||
<% element = element.to_s %>
|
||||
<% if(json_object[element].present?) %>
|
||||
<% if element == 'tags' %>
|
||||
<% translation_string = 'protocols.protocols_io_import.preview.' + element %>
|
||||
<strong><%= t(translation_string) %></strong>
|
||||
<% string_of_tags='' %>
|
||||
<% json_object['tags'].each do |tag| %>
|
||||
<% string_of_tags += tag['tag_name']+' , ' %>
|
||||
<% end %>
|
||||
<%= prepare_for_view(string_of_tags, ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_MEDIUM,'table').html_safe %>
|
||||
<% else %>
|
||||
<% prot_info_string += not_null(json_object[element]) if add_to_info_string_elements.include? element %>
|
||||
<% translation_string = 'protocols.protocols_io_import.preview.' + element %>
|
||||
<strong><%= t(translation_string) %></strong>
|
||||
<%= prepare_for_view(json_object[element],protocol_attr_len_hash[element.to_sym],'table').html_safe %>
|
||||
<% end %>
|
||||
<br>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% tables, garbage = protocolsio_string_to_table_element(prot_info_string) %>
|
||||
<% if tables.present? %>
|
||||
<br><hr><br>
|
||||
<% end %>
|
||||
<% table_count = 0 %>
|
||||
<% tables.each do |index, table| %>
|
||||
<% table_hash = JSON.parse((Base64.decode64(table['contents']))) %>
|
||||
<% pio_table_id = "pio_table_prot_info_"+table_count.to_s %>
|
||||
<% protocol_table_elements_array.push([pio_table_id,table_hash['data']]) %>
|
||||
<div id="<%=pio_table_id%>" ></div>
|
||||
<% table_count += 1 %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$('#modal-import-json-protocol-preview').on('shown.bs.modal', function (e) {
|
||||
var javascript_table_elements_p = <%=raw sanitize_input(protocol_table_elements_array.to_json) %>
|
||||
for(var j=0;j<javascript_table_elements_p.length;j++)
|
||||
{
|
||||
|
||||
var target = document.getElementById(javascript_table_elements_p[j][0]);
|
||||
var hot = new Handsontable(target, {
|
||||
data: javascript_table_elements_p[j][1],
|
||||
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
|
||||
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
fillHandle: false,
|
||||
formulas: true,
|
||||
readOnly: true
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
|
@ -0,0 +1,96 @@
|
|||
<%= form_for(@protocol, :url => url_for(:controller => 'protocols',
|
||||
:action=>'protocolsio_import_save'),method: :post,format: :javascript,
|
||||
remote: true, :html=> { :id => "protocolsio-import-form" }) do |f| %>
|
||||
|
||||
<%= hidden_field_tag :json_object, JSON.generate(@json_object) %>
|
||||
|
||||
<% if URI.parse(request.referrer).query %>
|
||||
<%= hidden_field_tag :type,CGI.parse(URI.parse(request.referrer).query).fetch('type') %>
|
||||
<% else %>
|
||||
<%= hidden_field_tag :type,'public' %>
|
||||
<% end %>
|
||||
<!--Get the type of protocol to import (private, public) from the url -->
|
||||
<div
|
||||
id="modal-import-json-protocol-preview"
|
||||
class="modal fade"
|
||||
tabindex="-1"
|
||||
role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title" data-role="header-import-into-protocol_protocols_io"><%=
|
||||
t('protocols.import_export.import_modal.title_import_into_protocol_protocols_io')
|
||||
%></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<!-- General protocol info -->
|
||||
<div class="form-group">
|
||||
|
||||
<label><%= t('protocols.import_export.import_modal.name_label') %></label>
|
||||
|
||||
<%= f.text_field :name, :value => pio_eval_title_len(sanitize_input(@json_object['protocol_name'])), class:
|
||||
"form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<span class="glyphicon glyphicon-user">
|
||||
</span> <%= t('protocols.import_export.import_modal.authors_label') %>
|
||||
</label>
|
||||
<%= f.text_field :authors, :value => pio_eval_title_len(sanitize_input(@json_object['full_name'])), class:
|
||||
"form-control" %>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><%= t('protocols.import_export.import_modal.description_label') %></label>
|
||||
<%= f.text_area :description_notice, :value => t('protocols.protocols_io_import.import_description_notice'), class:
|
||||
"form-control",readonly: true %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<label><%= t('protocols.import_export.import_modal.created_at_label') %></label>
|
||||
|
||||
<% display_created_at=Time.at(@json_object['created_on'].to_i) %>
|
||||
<%= f.text_field :created_at, :value => display_created_at.to_s,
|
||||
readonly: true, class: "form-control" %>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<label><%= t('protocols.import_export.import_modal.updated_at_label') %></label>
|
||||
|
||||
<% display_last_modified=Time.at(@json_object['last_modified'].to_i) %>
|
||||
<%= f.text_field :last_modified, :value =>
|
||||
display_last_modified.to_s,readonly: true, class:
|
||||
"form-control" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview title -->
|
||||
<div>
|
||||
<h2 style="display: inline;"><%= t('protocols.import_export.import_modal.preview_title') %></h2>
|
||||
<h3 style="display: none;" data-role="title-position"></h3>
|
||||
</div>
|
||||
|
||||
<!-- Preview scroller -->
|
||||
<div
|
||||
class="import-protocols-modal-preview-container-json"
|
||||
data-role="preview-container">
|
||||
|
||||
<!-- Partial -->
|
||||
<%= render partial: "protocols/import_export/import_json_protocol_p_desc.html.erb", locals: {json_object: @json_object} %>
|
||||
<%= render partial: "protocols/import_export/import_json_protocol_s_desc.html.erb", locals: {json_object: @json_object} %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= t('general.cancel') %></button>
|
||||
|
||||
<%= f.submit t('protocols.import_export.import_modal.import'),class: "btn
|
||||
btn-primary" %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,161 @@
|
|||
<% whitelist_simple=['1','6','17'] %>
|
||||
<% whitelist_complex=['8','9','15','18','19','20']%>
|
||||
<% step_table_elements_array = [] %>
|
||||
<% json_object['steps'].each_with_index do |step,counter_orig| %>
|
||||
<% @remaining = ProtocolsIoHelper::PIO_S_AVAILABLE_LENGTH %>
|
||||
<% counter = counter_orig + 2 %>
|
||||
<div style="display: block;">
|
||||
<hr>
|
||||
<td>
|
||||
<div class="badge-num">
|
||||
<span class="badge-preview bg-primary size-digit-1">
|
||||
<b data-val="position"><%= (counter) %></b>
|
||||
</span>
|
||||
|
||||
<span class="step-panel-collapse-link" data-toggle="collapse">
|
||||
<span class="glyphicon collapse-step-icon glyphicon-collapse-up"></span>
|
||||
<% title = nil %>
|
||||
<% step['components'].each do |key1,value1| #finding section (title of step) %>
|
||||
<% key1 = value1 if value1.class == Hash %>
|
||||
<% if(key1['component_type_id']=='6') %>
|
||||
<% title = pio_eval_title_len(sanitize_input(key1['data'])) if key1['data'].present? %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% title ||=t('protocols.protocols_io_import.comp_append.missing_step') %>
|
||||
<strong data-val="name"><%= title %></strong>
|
||||
</span>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" role="tabpanel">
|
||||
<div data-val="description" class="ql-editor">
|
||||
<% step_info_string = '' %>
|
||||
<% step['components'].each do |key,value| %>
|
||||
<%#here i made an if to distinguish the first step from the
|
||||
others, because the first step #sometimes has index values as keys instead of
|
||||
hashes, you might see this elsewhere %>
|
||||
<% key = value if value.class == Hash %>
|
||||
<% if whitelist_simple.include?(key['component_type_id']) &&
|
||||
key['data'].present? %>
|
||||
<br>
|
||||
<% case key['component_type_id']
|
||||
when '1' %>
|
||||
<% step_info_string += (key['data']) %>
|
||||
<br>
|
||||
<strong><%= t('protocols.protocols_io_import.preview.strng_s_desc') %></strong>
|
||||
<%= prepare_for_view(key['data'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<% when '17' %>
|
||||
<% step_info_string += (key['data']) %>
|
||||
<br>
|
||||
<strong><%= t('protocols.protocols_io_import.preview.s_exp_res') %></strong>
|
||||
<%= prepare_for_view(key['data'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<% end %>
|
||||
<% elsif key && whitelist_complex.include?(key['component_type_id']) %>
|
||||
<% case key['component_type_id']
|
||||
when '8'%>
|
||||
<% step_info_string += not_null(key['source_data']['name']) %>
|
||||
<br>
|
||||
<strong><%= not_null(key['name'])+': ' %></strong>
|
||||
<%= prepare_for_view(key['source_data']['name'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.dev') %>
|
||||
<%= prepare_for_view(key['source_data']['developer'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.vers') %>
|
||||
<%= prepare_for_view(key['source_data']['version'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.s_link') %>
|
||||
<%= prepare_for_view(key['source_data']['link'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.repo') %>
|
||||
<%= prepare_for_view(key['source_data']['repository'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.os') %>
|
||||
<%= prepare_for_view(key['source_data']['os_name'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe + ' , ' +
|
||||
prepare_for_view(key['source_data']['os_version'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<% when '9'%>
|
||||
<% step_info_string += not_null(key['source_data']['name']) %>
|
||||
<br>
|
||||
<strong><%= not_null(key['name'])+': ' %></strong>
|
||||
<%= prepare_for_view(key['source_data']['name'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.s_link') %>
|
||||
<%= prepare_for_view(key['source_data']['link'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<% when '15'%>
|
||||
<% step_info_string += not_null(key['source_data']['description']) %>
|
||||
<br>
|
||||
<strong><%= key['name']+': ' %></strong>
|
||||
<%= prepare_for_view(key['source_data']['name'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.s_desc') %>
|
||||
<%= prepare_for_view(key['source_data']['description'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.os') %>
|
||||
<%= prepare_for_view(key['source_data']['os_name'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe + ' , ' +
|
||||
prepare_for_view(key['source_data']['os_version'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<% when '18'%>
|
||||
<br>
|
||||
<strong><%= t('protocols.protocols_io_import.preview.sub_prot') %></strong>
|
||||
<%= prepare_for_view(key['source_data']['protocol_name'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.auth') %>
|
||||
<%= prepare_for_view(key['source_data']['full_name'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<% if key['source_data']['link'].present? %>
|
||||
<%= t('protocols.protocols_io_import.preview.s_nobr_link') %>
|
||||
<%= prepare_for_view(key['source_data']['link'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<% end %>
|
||||
<% when '19'%>
|
||||
<% step_info_string += not_null(key['source_data']['body']) %>
|
||||
<br>
|
||||
<strong><%= not_null(key['name'])+': ' %></strong>
|
||||
<%= prepare_for_view(key['source_data']['body'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<br>
|
||||
<%= t('protocols.protocols_io_import.preview.s_link') %>
|
||||
<%= prepare_for_view(key['source_data']['link'],ProtocolsIoHelper::PIO_ELEMENT_RESERVED_LENGTH_SMALL,'table').html_safe %>
|
||||
<% else %>
|
||||
<% end #case if%>
|
||||
<% end #inner if%>
|
||||
<% end #step component loop %>
|
||||
<% tables, garbage = protocolsio_string_to_table_element(step_info_string) %>
|
||||
<% if tables.present? %>
|
||||
<br><hr><br>
|
||||
<% end %>
|
||||
<% table_count = 0 %>
|
||||
<% tables.each do |index, table| %>
|
||||
<% table_hash = JSON.parse((Base64.decode64(table['contents']))) %>
|
||||
<% pio_table_id = "pio_table_step_"+counter.to_s+"_info_"+table_count.to_s %>
|
||||
<% step_table_elements_array.push([pio_table_id,table_hash['data']]) %>
|
||||
<div id="<%=pio_table_id%>" ></div>
|
||||
<% table_count += 1 %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end #step loop%>
|
||||
<br>
|
||||
<script type="text/javascript">
|
||||
$('#modal-import-json-protocol-preview').on('shown.bs.modal', function (e) {
|
||||
var javascript_table_elements_s = <%=raw sanitize_input(step_table_elements_array.to_json) %>
|
||||
|
||||
for(var j=0;j<javascript_table_elements_s.length;j++)
|
||||
{
|
||||
|
||||
var target = document.getElementById(javascript_table_elements_s[j][0]);
|
||||
var hot = new Handsontable(target, {
|
||||
data: javascript_table_elements_s[j][1],
|
||||
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
|
||||
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
|
||||
rowHeaders: true,
|
||||
colHeaders: true,
|
||||
fillHandle: false,
|
||||
formulas: true,
|
||||
readOnly: true
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
<% provide(:head_title, t("protocols.index.head_title")) %>
|
||||
|
||||
<% if current_team %>
|
||||
<%= render partial: "protocols/breadcrumbs.html.erb", locals: { teams: @teams, current_team: @current_team, type: @type } %>
|
||||
|
||||
<ul class="nav nav-tabs nav-settings">
|
||||
<li role="presentation" class="<%= "active" if @type == :public %>">
|
||||
<%= link_to t("protocols.index.navigation.public"), protocols_path(team: @current_team, type: :public) %>
|
||||
|
@ -48,16 +48,29 @@
|
|||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="import-export-protocols" class="btn-group" role="group">
|
||||
<a class="btn btn-default btn-open-file" <%= can_create_protocols_in_repository?(@current_team) ? 'data-action="import"' : 'disabled="disabled"' %>>
|
||||
<span class="glyphicon glyphicon-import"></span>
|
||||
<span class="hidden-xs"> <%= t("protocols.index.import") %></span>
|
||||
<input type="file" value="" accept=".eln" data-role="import-file-input"
|
||||
data-team-id="<%= @current_team.id %>"
|
||||
data-type="<%= @type %>" data-import-url="<%= import_protocols_path %>"
|
||||
<%= 'disabled="disabled"' unless can_create_protocols_in_repository?(@current_team) %>>
|
||||
</a>
|
||||
<a class="btn btn-default btn-open-file" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#">
|
||||
<span class="glyphicon glyphicon-import"></span><span class="hidden-xs"> <%= "Import protocol" %></span></a>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="btn btn-default btn-open-file" <%= can_create_protocols_in_repository?(@current_team) ? 'data-action="import"' : 'disabled="disabled"' %>>
|
||||
<span class="glyphicon glyphicon-import"></span>
|
||||
<span class="hidden-xs"> <%= t("protocols.index.import") %></span>
|
||||
<input type="file" value="" accept=".eln" data-role="import-file-input"
|
||||
data-team-id="<%= @current_team.id %>"
|
||||
data-type="<%= @type %>" data-import-url="<%= import_protocols_path %>"
|
||||
<%= 'disabled="disabled"' unless can_create_protocols_in_repository?(@current_team) %>>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "#modal-import-json-protocol", "data-toggle" => "modal" do %>
|
||||
<span class="glyphicon glyphicon-file"></span>
|
||||
<span class="hidden-xs"><%= t("protocols.index.import_json") %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<a class="btn btn-default" data-action="export" data-export-url="<%= export_protocols_path() %>">
|
||||
<span class="glyphicon glyphicon-export"></span>
|
||||
<span class="hidden-xs"> <%= t("protocols.index.export") %></span>
|
||||
|
@ -121,6 +134,8 @@
|
|||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<div id="protocolsio-preview-modal-target"></div>
|
||||
<%= render partial: "protocols/import_export/import_json_protocol_modal.html.erb" %>
|
||||
|
||||
<%= render partial: "protocols/index/create_new_modal.html.erb" %>
|
||||
<%= render partial: "protocols/index/make_private_results_modal.html.erb" %>
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
<em><%= t("protocols.steps.no_description") %></em>
|
||||
<% else %>
|
||||
<div class="ql-editor">
|
||||
<%= sanitize_input(step.description) %>
|
||||
<%= sanitize_input(generate_image_tag_from_token(step.description, step), ['img']) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
28
app/views/protocols/protocolsio_import_create.js.erb
Normal file
28
app/views/protocols/protocolsio_import_create.js.erb
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
<% if @protocolsio_too_big %>
|
||||
$('#modal-import-json-protocol').modal('hide');
|
||||
HelperModule.flashAlertMsg(' <%= t('my_modules.protocols.load_from_file_size_error',
|
||||
size: Constants::FILE_MAX_SIZE_MB ) %>','danger');
|
||||
<% elsif @protocolsio_invalid_file %>
|
||||
$('#modal-import-json-protocol').modal('hide');
|
||||
HelperModule.flashAlertMsg(' <%= t('my_modules.protocols.load_from_file_invalid_error') %>','danger');
|
||||
<% else %>
|
||||
$('#modal-import-json-protocol').modal('hide');
|
||||
<% if remotipart_submitted? %> <%# a workaround to a bug with remotipart, that caused alot of headache, courtesy of github.com/dhampik %>
|
||||
$('#protocolsio-preview-modal-target').html(
|
||||
"<%= j "#{render(:partial => 'protocols/import_export/import_json_protocol_preview_modal')}" %>"
|
||||
);
|
||||
<% else %>
|
||||
$('#protocolsio-preview-modal-target').html(
|
||||
"<%= j render(:partial => 'protocols/import_export/import_json_protocol_preview_modal') %>"
|
||||
);
|
||||
<% end %>
|
||||
$('#modal-import-json-protocol-preview').modal('show');
|
||||
$('.modal').on('hidden.bs.modal', function (e) {
|
||||
if($('.modal').hasClass('in')) {
|
||||
$('body').addClass('modal-open');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
<% end %>
|
15
app/views/protocols/protocolsio_import_save.js.erb
Normal file
15
app/views/protocols/protocolsio_import_save.js.erb
Normal file
|
@ -0,0 +1,15 @@
|
|||
<% if @protocolsio_general_error %>
|
||||
$('#modal-import-json-protocol-preview').modal('hide');
|
||||
alert(' <%= t('my_modules.protocols.load_from_file_protocol_general_error',
|
||||
max: Constants::NAME_MAX_LENGTH, min: Constants::NAME_MIN_LENGTH) %>');
|
||||
<% elsif @toolong%>
|
||||
$('#modal-import-json-protocol-preview').modal('hide');
|
||||
$('#protocols_io_form').trigger("reset");
|
||||
protocolsDatatable.ajax.reload();
|
||||
HelperModule.flashAlertMsg(' <%= t('protocols.index.import_results.message_warn_truncated')%>', 'warning');
|
||||
<% else %>
|
||||
$('#modal-import-json-protocol-preview').modal('hide');
|
||||
$('#protocols_io_form').trigger("reset");
|
||||
protocolsDatatable.ajax.reload();
|
||||
HelperModule.flashAlertMsg(' <%= t('protocols.index.import_results.message_ok_pio')%>', 'success');
|
||||
<% end %>
|
|
@ -23,7 +23,7 @@
|
|||
<div class="report-element-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-container ql-editor">
|
||||
<%= custom_auto_link(generate_image_tag_from_token(result_text.text),
|
||||
<%= custom_auto_link(generate_image_tag_from_token(result_text.text, result_text),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
</div>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-12 ql-editor">
|
||||
<% if strip_tags(step.description).present? %>
|
||||
<%= custom_auto_link(generate_image_tag_from_token(step.description),
|
||||
<%= custom_auto_link(generate_image_tag_from_token(step.description, step),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
<% else %>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<div class="form-group">
|
||||
<%= f.text_field :name,
|
||||
label: t("repositories.index.modal_copy.name"),
|
||||
help: t("repositories.index.modal_copy.description"),
|
||||
autofocus: true,
|
||||
placeholder: t("repositories.index.modal_copy.name_placeholder") %>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="ql-editor">
|
||||
<%= custom_auto_link(generate_image_tag_from_token(result.result_text.text),
|
||||
<%= custom_auto_link(generate_image_tag_from_token(result.result_text.text, result.result_text),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<ul class="nav nav-tabs">
|
||||
<li role="presentation" id="new-step-main-tab" class="active">
|
||||
<a href="#new-step-main" data-toggle="tab">
|
||||
<a href="#new-step-main" data-toggle="tab" data-no-turbolink="true">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" id="new-step-checklists-tab">
|
||||
<a href="#new-step-checklists" data-toggle="tab">
|
||||
<a href="#new-step-checklists" data-toggle="tab" data-no-turbolink="true">
|
||||
<span class="glyphicon glyphicon-list"></span>
|
||||
<%= t("protocols.steps.new.tab_checklists") %>
|
||||
</a>
|
||||
|
@ -13,13 +13,13 @@
|
|||
<li role="presentation"
|
||||
id="new-step-assets-tab"
|
||||
onClick="dragNdropAssetsInit('steps')">
|
||||
<a href="#new-step-assets" data-toggle="tab">
|
||||
<a href="#new-step-assets" data-toggle="tab" data-no-turbolink="true">
|
||||
<span class="glyphicon glyphicon-file"></span>
|
||||
<%= t("protocols.steps.new.tab_assets") %>
|
||||
</a>
|
||||
</li>
|
||||
<li role="presentation" id="new-step-tables-tab">
|
||||
<a href="#new-step-tables" data-toggle="tab">
|
||||
<a href="#new-step-tables" data-toggle="tab" data-no-turbolink="true">
|
||||
<span class="glyphicon glyphicon-th"></span>
|
||||
<%= t("protocols.steps.new.tab_tables") %>
|
||||
</a>
|
||||
|
@ -50,9 +50,11 @@
|
|||
style="display: none" multiple>
|
||||
</label>
|
||||
</div>
|
||||
<%= f.nested_fields_for :assets do |ff| %>
|
||||
<%= render "form_assets.html.erb", ff: ff, step: step %>
|
||||
<% end %>
|
||||
<div id="new-step-assets-group" class="form-group">
|
||||
<%= f.nested_fields_for :assets do |ff| %>
|
||||
<%= render "form_assets.html.erb", ff: ff, step: step %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" role="tabpanel" id="new-step-tables">
|
||||
<%= f.nested_fields_for :tables do |ff| %>
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<em><%= t('protocols.steps.no_description') %></em>
|
||||
<% else %>
|
||||
<div class="ql-editor">
|
||||
<%= custom_auto_link(generate_image_tag_from_token(step.description),
|
||||
<%= custom_auto_link(generate_image_tag_from_token(step.description, step),
|
||||
simple_format: false,
|
||||
tags: %w(img)) %>
|
||||
</div>
|
||||
|
|
|
@ -3,26 +3,9 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-3 col-md-1">
|
||||
<span style="display: none;" data-hook="user-notification-list-item"></span>
|
||||
<% if notification.type_of == 'recent_changes' %>
|
||||
<div class="text-center">
|
||||
<%= image_tag avatar_path(notification.generator_user, :icon_small), class: 'avatar img-circle' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if notification.type_of == 'assignment' %>
|
||||
<div class="text-center">
|
||||
<span class="assignment"><%= fa_icon 'newspaper-o' %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if notification.type_of == 'system_message' %>
|
||||
<div class="text-center">
|
||||
<span class="system-message"><i class="glyphicon glyphicon-tower" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if notification.type_of == 'deliver' %>
|
||||
<div class="text-center">
|
||||
<span class="deliver"><%= fa_icon 'truck' %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render partial: "user_notifications/icons/#{notification.type_of}",
|
||||
locals: {notification: notification},
|
||||
formats: [:html] %>
|
||||
</div>
|
||||
<div class="col-xs-9 col-md-11">
|
||||
<strong><%= sanitize_input(notification.title) %></strong> <br>
|
||||
|
|
|
@ -2,27 +2,9 @@
|
|||
<li class="notification <%= 'unseen' unless notification.already_seen(current_user) %>">
|
||||
<div class="row">
|
||||
<div class="col-xs-2">
|
||||
<span style="display: none;" data-hook="user-notification-recent-list-item"></span>
|
||||
<% if notification.type_of == 'recent_changes' %>
|
||||
<div class="text-center">
|
||||
<%= image_tag avatar_path(notification.generator_user, :icon_small), class: 'avatar' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if notification.type_of == 'assignment' %>
|
||||
<div class="text-center">
|
||||
<span class="assignment"><%= fa_icon 'newspaper-o' %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if notification.type_of == 'system_message' %>
|
||||
<div class="text-center">
|
||||
<span class="system-message"><i class="glyphicon glyphicon-tower" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if notification.type_of == 'deliver' %>
|
||||
<div class="text-center">
|
||||
<span class="deliver"><%= fa_icon 'truck' %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render partial: "user_notifications/icons/#{notification.type_of}",
|
||||
locals: {notification: notification},
|
||||
formats: [:html] %>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-10">
|
||||
|
|
3
app/views/user_notifications/icons/_assignment.html.erb
Normal file
3
app/views/user_notifications/icons/_assignment.html.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="text-center">
|
||||
<span class="assignment"><%= fa_icon 'newspaper-o' %></span>
|
||||
</div>
|
3
app/views/user_notifications/icons/_deliver.html.erb
Normal file
3
app/views/user_notifications/icons/_deliver.html.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="text-center">
|
||||
<span class="deliver"><%= fa_icon 'truck' %></span>
|
||||
</div>
|
|
@ -0,0 +1,3 @@
|
|||
<div class="text-center">
|
||||
<%= image_tag avatar_path(notification.generator_user, :icon_small), class: 'avatar img-circle' %>
|
||||
</div>
|
|
@ -0,0 +1,5 @@
|
|||
<div class="text-center">
|
||||
<span class="system-message">
|
||||
<i class="glyphicon glyphicon-tower" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
|
@ -29,6 +29,7 @@
|
|||
<%= f.password_field :password_confirmation, autocomplete: "off", class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<% if Rails.configuration.x.new_team_on_signup %>
|
||||
<div class="form-group"
|
||||
data-hook="confirm-invitation-team-name">
|
||||
<%= label :team, :name, t('users.registrations.new.team_name_label') %>
|
||||
|
@ -42,7 +43,7 @@
|
|||
<% end %>
|
||||
<span><small><%= t 'users.registrations.new.team_name_help' %></small></span>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
<%= recaptcha_input_tag %>
|
||||
|
||||
<div class="form-group" data-hook="confirm-invitation-form-submit">
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<%= f.label :password_confirmation %>
|
||||
<%= f.password_field :password_confirmation, autocomplete: 'off', class: 'form-control' %>
|
||||
</div>
|
||||
|
||||
<% if Rails.configuration.x.new_team_on_signup %>
|
||||
<div class="form-group" id="team_name_form">
|
||||
<%= label :team, :name, t('users.registrations.new.team_name_label') %>
|
||||
<% if @team %>
|
||||
|
@ -37,7 +37,7 @@
|
|||
<% end %>
|
||||
<span><small><%= t 'users.registrations.new.team_name_help' %></small></span>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
<%= recaptcha_input_tag %>
|
||||
|
||||
<div class="form-group" data-hook="sign-up-form-submit">
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<ul class="nav nav-pills nav-stacked nav-stacked-arrow">
|
||||
<ul class="nav nav-pills nav-stacked nav-stacked-arrow" data-hook="user-settings-account-navigation-html">
|
||||
<li role="presentation" class="<%= 'active' if on_settings_account_profile_page? %>">
|
||||
<%= link_to t('users.settings.account.navigation.profile'), edit_user_registration_path %>
|
||||
</li>
|
||||
<li role="presentation" class="<%= 'active' if on_settings_account_preferences_page? %>">
|
||||
<%= link_to t('users.settings.account.navigation.preferences'), preferences_path %>
|
||||
</li>
|
||||
</ul>
|
||||
<li role="presentation" class="<%= 'active' if on_settings_account_addons_page? %>">
|
||||
<%= link_to t('users.settings.account.navigation.addons'), addons_path %>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
22
app/views/users/settings/account/addons/index.html.erb
Normal file
22
app/views/users/settings/account/addons/index.html.erb
Normal file
|
@ -0,0 +1,22 @@
|
|||
<% provide(:head_title, t('users.settings.account.addons.head_title')) %>
|
||||
|
||||
<%= render partial: 'users/settings/navigation.html.erb' %>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane tab-pane-settings active" role="tabpanel">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-3">
|
||||
<%= render partial: 'users/settings/account/navigation.html.erb' %>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-9">
|
||||
<h4><%= t('users.settings.account.addons.title') %></h4>
|
||||
<div data-hook="settings-addons-container">
|
||||
<em data-hook="settings-addons-no-addons">
|
||||
<%= t('users.settings.account.addons.no_addons') %>
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane tab-pane-settings" role="tabpanel"></div>
|
||||
</div>
|
|
@ -18,8 +18,6 @@
|
|||
<%= link_to t("devise.links.not_receive_unlock"), new_unlock_path(resource_name) %><br />
|
||||
<% end -%>
|
||||
|
||||
<%- if devise_mapping.omniauthable? %>
|
||||
<%- resource_class.omniauth_providers.each do |provider| %>
|
||||
<%= link_to t("devise.links.sign_in_provider", provider: "#{provider.to_s.titleize}"), omniauth_authorize_path(resource_name, provider) %><br />
|
||||
<% end -%>
|
||||
<%- if devise_mapping.omniauthable? && resource_class.omniauth_providers.any? %>
|
||||
<div data-hook="omniauth-sign-in-links"></div>
|
||||
<% end -%>
|
||||
|
|
|
@ -123,4 +123,12 @@ Rails.application.configure do
|
|||
|
||||
config.cache_store = :null_store
|
||||
end
|
||||
# Enable new team on sign up
|
||||
new_team_on_signup = ENV['NEW_TEAM_ON_SIGNUP'] || 'true'
|
||||
if new_team_on_signup == 'true'
|
||||
config.x.new_team_on_signup = true
|
||||
else
|
||||
config.x.new_team_on_signup = false
|
||||
config.x.enable_tutorial = false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -156,4 +156,13 @@ Rails.application.configure do
|
|||
# Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
|
||||
# `config/secrets.yml.key`.
|
||||
config.read_encrypted_secrets = true
|
||||
|
||||
# Enable new team on sign up
|
||||
new_team_on_signup = ENV['NEW_TEAM_ON_SIGNUP'] || 'true'
|
||||
if new_team_on_signup == 'true'
|
||||
config.x.new_team_on_signup = true
|
||||
else
|
||||
config.x.new_team_on_signup = false
|
||||
config.x.enable_tutorial = false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -864,6 +864,8 @@ class Constants
|
|||
# Very basic regex to check for validity of emails
|
||||
BASIC_EMAIL_REGEX = URI::MailTo::EMAIL_REGEXP
|
||||
|
||||
TINY_MCE_ASSET_REGEX = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
|
||||
|
||||
# Team name for default admin user
|
||||
DEFAULT_PRIVATE_TEAM_NAME = 'My projects'.freeze
|
||||
|
||||
|
|
11
config/initializers/core_api.rb
Normal file
11
config/initializers/core_api.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
Api.configure do |config|
|
||||
if ENV['CORE_API_SIGN_ALG']
|
||||
config.core_api_sign_alg = ENV['CORE_API_SIGN_ALG']
|
||||
end
|
||||
if ENV['CORE_API_TOKEN_TTL']
|
||||
config.core_api_token_ttl = ENV['CORE_API_TOKEN_TTL']
|
||||
end
|
||||
if ENV['CORE_API_TOKEN_ISS']
|
||||
config.core_api_token_iss = ENV['CORE_API_TOKEN_ISS']
|
||||
end
|
||||
end
|
65
config/initializers/delayed_job_config.rb
Normal file
65
config/initializers/delayed_job_config.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
# DelayedWorkerConfig is a configuration object that parses and sanitize
|
||||
# environmental variables. @note Used only for Delayed::Worker settings
|
||||
module DelayedWorkerConfig
|
||||
module_function
|
||||
|
||||
# If you want to keep failed jobs, set DELAYED_WORKER_DESTROY_FAILED_JOBS to
|
||||
# false. The failed jobs will be marked with non-null failed_at.
|
||||
def destroy_failed_jobs
|
||||
value = ENV['DELAYED_WORKER_DESTROY_FAILED_JOBS']
|
||||
return false unless value
|
||||
return false unless value.in? %w(true 1)
|
||||
true
|
||||
end
|
||||
|
||||
# If no jobs are found, the worker sleeps for the amount of time specified by
|
||||
# the sleep delay option. Default to 60 second.
|
||||
def sleep_delay
|
||||
value = ENV['DELAYED_WORKER_SLEEP_DELAY'].to_i
|
||||
return 60 if value.zero?
|
||||
value
|
||||
end
|
||||
|
||||
# The default is 6 attempts. After this, the job either deleted
|
||||
# or left in the database with "failed_at" set dempends on the
|
||||
# DESTROY_FAILED_JOBS value
|
||||
def max_attempts
|
||||
value = ENV['DELAYED_WORKER_MAX_ATTEMPTS'].to_i
|
||||
return 6 if value.zero?
|
||||
value
|
||||
end
|
||||
|
||||
# The default DELAYED_WORKER_MAX_RUN_TIME is 30.minutes.
|
||||
# If your job takes longer than that, another computer could pick it up.
|
||||
# It's up to you to make sure your job doesn't exceed this time.
|
||||
# You should set this to the longest time you think the job could take.
|
||||
def max_run_time
|
||||
value = ENV['DELAYED_WORKER_MAX_RUN_TIME'].to_i
|
||||
return 30.minutes if value.zero?
|
||||
value.minutes
|
||||
end
|
||||
|
||||
# The default behavior is to read 10 jobs from the queue when finding an
|
||||
# available job. You can configure this by setting.
|
||||
def read_ahead
|
||||
value = ENV['DELAYED_WORKER_READ_AHEAD'].to_i
|
||||
return 10 if value.zero?
|
||||
value
|
||||
end
|
||||
|
||||
# By default all jobs will be queued without a named queue.
|
||||
# A default named queue can be specified by using
|
||||
def default_queue_name
|
||||
value = ENV['DELAYED_WORKER_DEFAULT_QUEUE_NAME']
|
||||
return 'default' unless value
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
# Delayed::Worker configuration
|
||||
Delayed::Worker.destroy_failed_jobs = DelayedWorkerConfig.destroy_failed_jobs
|
||||
Delayed::Worker.sleep_delay = DelayedWorkerConfig.sleep_delay
|
||||
Delayed::Worker.max_attempts = DelayedWorkerConfig.max_attempts
|
||||
Delayed::Worker.max_run_time = DelayedWorkerConfig.max_run_time
|
||||
Delayed::Worker.read_ahead = DelayedWorkerConfig.read_ahead
|
||||
Delayed::Worker.default_queue_name = DelayedWorkerConfig.default_queue_name
|
|
@ -42,4 +42,12 @@ class Extends
|
|||
# Data type name should match corresponding model's name
|
||||
REPOSITORY_DATA_TYPES = { RepositoryTextValue: 0,
|
||||
RepositoryDateValue: 1 }
|
||||
|
||||
# List of implemented core API versions
|
||||
API_VERSIONS = ['20170715']
|
||||
|
||||
# Array used for injecting names of additional authentication methods for API
|
||||
API_PLUGABLE_AUTH_METHODS = []
|
||||
|
||||
OMNIAUTH_PROVIDERS = []
|
||||
end
|
||||
|
|
|
@ -4,12 +4,12 @@ if ENV['PAPERCLIP_HASH_SECRET'].nil?
|
|||
exit 1
|
||||
end
|
||||
|
||||
Paperclip::Attachment.default_options.merge!({
|
||||
Paperclip::Attachment.default_options.merge!(
|
||||
hash_data: ':class/:attachment/:id/:style',
|
||||
hash_secret: ENV['PAPERCLIP_HASH_SECRET'],
|
||||
preserve_files: false,
|
||||
preserve_files: true,
|
||||
url: '/system/:class/:attachment/:id_partition/:hash/:style/:filename'
|
||||
})
|
||||
)
|
||||
|
||||
if ENV['PAPERCLIP_STORAGE'] == "s3"
|
||||
|
||||
|
@ -183,6 +183,7 @@ module Paperclip
|
|||
)) ||
|
||||
# Word processor application
|
||||
(Set[content_type, content_types_from_name].subset? Set.new %w(
|
||||
application/zip
|
||||
application/vnd.ms-office
|
||||
application/msword
|
||||
application/msword-template
|
||||
|
@ -218,9 +219,11 @@ module Paperclip
|
|||
application/vnd.sun.xml.calc.template
|
||||
application/vnd.stardivision.calc
|
||||
application/x-starcalc
|
||||
application/CDFV2-encrypted
|
||||
)) ||
|
||||
# Presentation application
|
||||
(Set[content_type, content_types_from_name].subset? Set.new %w(
|
||||
application/zip
|
||||
application/vnd.ms-office
|
||||
application/vnd.ms-powerpoint
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
|
|
|
@ -605,6 +605,8 @@ en:
|
|||
load_from_file_error: "Failed to load the protocol from file."
|
||||
load_from_file_error_locked: "Failed to load the protocol from file. One or more files are currently being edited."
|
||||
load_from_file_size_error: "Failed to load the protocol from file. Limit is %{size}Mb."
|
||||
load_from_file_invalid_error: "The file you provided is invalid or has an invalid extension."
|
||||
load_from_file_protocol_general_error: "Failed to load the protocol from file. It is likely that certain fields (protocol and individual step titles and names) contain too many or too few characters.(max is %{max} and min is %{min})"
|
||||
results:
|
||||
head_title: "%{project} | %{module} | Results"
|
||||
add_label: "Add new result:"
|
||||
|
@ -684,6 +686,7 @@ en:
|
|||
head_title: "%{project} | Overview"
|
||||
canvas_edit: "Edit experiment"
|
||||
zoom: "Zoom: "
|
||||
reload_on_submit: "Save action is running. Reloading this page may cause unexpected behavior."
|
||||
modal_manage_tags:
|
||||
head_title: "Manage tags for"
|
||||
subtitle: "Showing tags of task %{module}"
|
||||
|
@ -777,11 +780,6 @@ en:
|
|||
archived_on: "Archived on"
|
||||
archived_on_title: "Task archived on %{date} at %{time}."
|
||||
|
||||
my_module_groups:
|
||||
new:
|
||||
name: "Workflow %{index}"
|
||||
suffix: "..."
|
||||
|
||||
tags:
|
||||
new:
|
||||
head_title: "Create tag"
|
||||
|
@ -889,6 +887,7 @@ en:
|
|||
modal_copy:
|
||||
title_html: "Copy repository <em>%{name}</em>"
|
||||
name: "New repository name"
|
||||
description: "Only the structure of the repository is going to be copied."
|
||||
name_placeholder: "My repository"
|
||||
copy: "Copy repository"
|
||||
modal_create:
|
||||
|
@ -1254,6 +1253,7 @@ en:
|
|||
avatar_title: "Change avatar"
|
||||
avatar_edit_label: "Upload new avatar file"
|
||||
avatar_submit: "Upload"
|
||||
avatar_total_size: "Your avatar file cannot be larger than 0.2 MB. (Please try again with a smaller file.)"
|
||||
name_label: "Full name"
|
||||
name_title: "Change name"
|
||||
initials_label: "Initials"
|
||||
|
@ -1289,6 +1289,7 @@ en:
|
|||
navigation:
|
||||
profile: "Profile"
|
||||
preferences: "Preferences"
|
||||
addons: "Add-ons"
|
||||
preferences:
|
||||
head_title: "Settings | My preferences"
|
||||
edit:
|
||||
|
@ -1304,6 +1305,10 @@ en:
|
|||
select_team: "Select team"
|
||||
tutorial_reset_flash: "Tutorial can now be repeated."
|
||||
tutorial_reset_error: "Tutorial could not be repeated."
|
||||
addons:
|
||||
head_title: "Settings | Add-ons"
|
||||
title: "Add-ons"
|
||||
no_addons: "You have no sciNote Add-ons."
|
||||
teams:
|
||||
head_title: "Settings | Teams"
|
||||
breadcrumbs:
|
||||
|
@ -1381,11 +1386,66 @@ en:
|
|||
breadcrumbs:
|
||||
manager: "Protocol management"
|
||||
edit: "Edit protocol"
|
||||
protocols_io_import:
|
||||
title_too_long: "... Text is too long so we had to cut it off."
|
||||
too_long: "... <span class='label label-warning'>Text is too long so we had to cut it off.</span>"
|
||||
import_description_notice: "The protocols description is listed below under \"Protocol info\"."
|
||||
preview:
|
||||
description: "Protocol Description: "
|
||||
before_start: "Before starting protocol information:"
|
||||
warning: "Protocol warning:"
|
||||
guidelines: "Guidelines:"
|
||||
link: "Link:"
|
||||
s_nobr_link: "Link:"
|
||||
s_link: "Link:"
|
||||
s_desc: "Description:"
|
||||
strng_s_desc: "Description:"
|
||||
s_exp_res: "Expected result:"
|
||||
dev: "Developer:"
|
||||
vers: "Version:"
|
||||
repo: "Repository:"
|
||||
os: "OS name , OS version:"
|
||||
sub_prot: "This protocol also contains an attached sub-protocol:"
|
||||
auth: "Author:"
|
||||
manuscript_citation: "Manuscript citation:"
|
||||
publish_date: "Publish date:"
|
||||
vendor_name: "Vendor name:"
|
||||
vendor_link: "Vendor link:"
|
||||
keywords: "Keywords:"
|
||||
tags: "Tags:"
|
||||
comp_append:
|
||||
table_moved: "<br><strong><i>There was a table here, it was moved to the end of this step. </i></strong>"
|
||||
missing_step: "Step"
|
||||
missing_desc: "Description missing"
|
||||
general_link: "<br>Link: "
|
||||
expected_result: "<br><strong>Expected result: </strong>"
|
||||
soft_packg:
|
||||
name: "<br><strong>Software package: </strong>"
|
||||
developer: "<br>Developer: "
|
||||
version: "<br>Version: "
|
||||
link: "<br>Link: "
|
||||
repository: "<br>Repository: "
|
||||
os: "<br>OS name , OS version: "
|
||||
dataset:
|
||||
name: "<br><strong>Dataset: </strong>"
|
||||
link: "<br>Link: "
|
||||
command:
|
||||
name: "<br><strong>Command: </strong>"
|
||||
description: "<br>Description: "
|
||||
os: "<br>OS name , OS version: "
|
||||
sub_protocol:
|
||||
protocol_name: "<br><strong>This step also contains an attached sub-protocol: </strong>"
|
||||
full_name: "<br>Author: "
|
||||
link: "<br>Link: "
|
||||
safety_infor:
|
||||
body: "<br><strong>Safety information: </strong>"
|
||||
link: "<br>Link: "
|
||||
import_export:
|
||||
load_file_error: "Failed to load the protocol file."
|
||||
import_modal:
|
||||
title_import: "Import protocol/s"
|
||||
title_import_into_protocol: "Load protocol from file"
|
||||
title_import_into_protocol_protocols_io: "Load protocol from protocols.io file"
|
||||
import_into_protocol_message: "This will overwrite the current protocol!"
|
||||
import_into_protocol_confirm: "Are you sure you wish to load protocol from file? This action will overwrite the current protocol."
|
||||
import_to_linked_task_file: "Are you sure you wish to load protocol from file? This action will overwrite the current protocol in the task and unlink it from repository. The current protocol will remain unchanged in repository."
|
||||
|
@ -1441,6 +1501,11 @@ en:
|
|||
edit: "Edit"
|
||||
clone_btn: "Copy"
|
||||
import: "Import"
|
||||
import_alt: "from SciNote protocol file(.eln)"
|
||||
import_json: "from protocols.io file"
|
||||
modal_import_json_upload: "Upload file"
|
||||
modal_import_json_title: "Import protocols.io file"
|
||||
modal_import_json_notice: "Upload your protocols.io protocol file"
|
||||
export: "Export"
|
||||
make_private: "Make private"
|
||||
publish: "Publish"
|
||||
|
@ -1506,6 +1571,8 @@ en:
|
|||
title: "Import results"
|
||||
message_failed: "Failed to import %{nr} protocol/s."
|
||||
message_ok: "Successfully imported %{nr} protocol/s."
|
||||
message_ok_pio: "Successfully imported protocol from protocols.io file."
|
||||
message_warn_truncated: "Successfully imported protocol from protocols.io file. However, text in some fields was too long so we had to cut it off."
|
||||
row_success: "Imported"
|
||||
row_renamed: "Imported & renamed"
|
||||
row_failed: "Failed"
|
||||
|
@ -1735,8 +1802,8 @@ en:
|
|||
zip_export:
|
||||
modal_label: 'Export repository'
|
||||
notification_title: 'Your package is ready to be exported!'
|
||||
expired_title: 'The required file was expired!'
|
||||
expired_description: 'The downloadable file expires in 7 days after its creation.'
|
||||
expired_title: 'Looks like your link has expired.'
|
||||
expired_description: 'Please export the data again in order to receive a new link.'
|
||||
modal_label: 'Export request received'
|
||||
modal_html: "<p>Your export request is being processed.</p><p>When completed we will <strong>send an email to %{email}</strong> inbox with a link to your exported samples. Note that the link will expire in 7 days.</p>"
|
||||
repository_html: '<p>You are about to export selected items in repository %{repository}</p> <br> Repository will be exported in a .csv file format. You will receive <strong>email with a link</strong> where you can download it.'
|
||||
|
@ -1784,6 +1851,12 @@ en:
|
|||
busy: "The server is still processing your request. If you leave this page, the changes will be lost! Are you sure you want to continue?"
|
||||
no_name: "(no name)"
|
||||
|
||||
api:
|
||||
core:
|
||||
status_ok: "Ok"
|
||||
expired_token: "Token is expired"
|
||||
invalid_token: "Token is invalid"
|
||||
|
||||
Add: "Add"
|
||||
Asset: "File"
|
||||
Assets: "Files"
|
||||
|
|
|
@ -6,10 +6,12 @@ Rails.application.routes.draw do
|
|||
end
|
||||
|
||||
constraints UserSubdomain do
|
||||
devise_for :users, controllers: { registrations: 'users/registrations',
|
||||
sessions: 'users/sessions',
|
||||
invitations: 'users/invitations',
|
||||
confirmations: 'users/confirmations' }
|
||||
devise_for :users,
|
||||
controllers: { registrations: 'users/registrations',
|
||||
sessions: 'users/sessions',
|
||||
invitations: 'users/invitations',
|
||||
confirmations: 'users/confirmations',
|
||||
omniauth_callbacks: 'users/omniauth_callbacks' }
|
||||
|
||||
root 'projects#index'
|
||||
|
||||
|
@ -48,6 +50,9 @@ Rails.application.routes.draw do
|
|||
get 'users/settings/account/preferences',
|
||||
to: 'users/settings/account/preferences#index',
|
||||
as: 'preferences'
|
||||
get 'users/settings/account/addons',
|
||||
to: 'users/settings/account/addons#index',
|
||||
as: 'addons'
|
||||
put 'users/settings/account/preferences',
|
||||
to: 'users/settings/account/preferences#update',
|
||||
as: 'update_preferences'
|
||||
|
@ -418,6 +423,7 @@ Rails.application.routes.draw do
|
|||
to: 'protocols#load_from_repository_modal'
|
||||
post 'load_from_repository', to: 'protocols#load_from_repository'
|
||||
post 'load_from_file', to: 'protocols#load_from_file'
|
||||
|
||||
get 'copy_to_repository_modal', to: 'protocols#copy_to_repository_modal'
|
||||
post 'copy_to_repository', to: 'protocols#copy_to_repository'
|
||||
get 'protocol_status_bar', to: 'protocols#protocol_status_bar'
|
||||
|
@ -435,6 +441,8 @@ Rails.application.routes.draw do
|
|||
post 'archive', to: 'protocols#archive'
|
||||
post 'restore', to: 'protocols#restore'
|
||||
post 'import', to: 'protocols#import'
|
||||
post 'protocolsio_import_create', to: 'protocols#protocolsio_import_create'
|
||||
post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save'
|
||||
get 'export', to: 'protocols#export'
|
||||
end
|
||||
end
|
||||
|
@ -491,6 +499,15 @@ Rails.application.routes.draw do
|
|||
post 'avatar_signature' => 'users/registrations#signature'
|
||||
get 'users/auth_token_sign_in' => 'users/sessions#auth_token_create'
|
||||
end
|
||||
|
||||
namespace :api, defaults: { format: 'json' } do
|
||||
get 'status', to: 'api#status'
|
||||
post 'auth/token', to: 'api#authenticate'
|
||||
scope '20170715', module: 'v20170715' do
|
||||
get 'tasks/tree', to: 'core_api#tasks_tree'
|
||||
get 'tasks/:task_id/samples', to: 'core_api#task_samples'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
constraints WopiSubdomain do
|
||||
|
|
14
config/whacamole.rb
Normal file
14
config/whacamole.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# https://github.com/arches/whacamole
|
||||
#
|
||||
# Steps to setup this on your heroku instance, this will affect only the worker
|
||||
#
|
||||
# 1) set the env variables (api token: > heroku auth:token)
|
||||
# 2) > heroku labs:enable log-runtime-metrics --app YOUR_APP_NAME
|
||||
# 3) > heroku ps:scale whacamole=1 --app YOUR_APP_NAME
|
||||
|
||||
Whacamole.configure(ENV['HEROKU_APP_NAME']) do |config|
|
||||
config.api_token = ENV['HEROKU_API_TOKEN']
|
||||
config.dynos = %w{worker}
|
||||
config.restart_threshold = ENV['WHACAMOLE_DYNO_RESTART_THRESHOLD'].to_i
|
||||
config.restart_window = ENV['WHACAMOLE_DYNO_RESTART_TIME_IN_SEC'].to_i
|
||||
end
|
13
db/migrate/20170803153030_add_user_identity_table.rb
Normal file
13
db/migrate/20170803153030_add_user_identity_table.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
class AddUserIdentityTable < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :user_identities do |t|
|
||||
t.belongs_to :user, index: true
|
||||
t.string :provider, null: false
|
||||
t.string :uid, null: false
|
||||
t.timestamps null: true
|
||||
end
|
||||
|
||||
add_index :user_identities, %i(provider uid), unique: true
|
||||
add_index :user_identities, %i(user_id provider), unique: true
|
||||
end
|
||||
end
|
|
@ -1,9 +1,117 @@
|
|||
namespace :tiny_mce_asset do
|
||||
REGEX = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
|
||||
desc 'Remove obsolete images that were created on new steps or '\
|
||||
'results and the step/result didn\'t get saved.'
|
||||
task remove_obsolete_images: :environment do
|
||||
TinyMceAsset.where('step_id IS ? AND ' \
|
||||
'result_text_id IS ? AND created_at < ?',
|
||||
nil, nil, 7.days.ago)
|
||||
nil, nil, 7.days.ago).destroy_all
|
||||
end
|
||||
|
||||
desc 'Generate new tiny_mce_assets and replace old assets in RTE for ' \
|
||||
'steps. Assign the last printed id if the script crashes or ' \
|
||||
'id + 1 if there is a problematic asset'
|
||||
task :regenerate_step_images, [:last_id] => :environment do |_, args|
|
||||
replaced_images = 0
|
||||
failed_attempts = 0
|
||||
all_images = TinyMceAsset.where.not(step: nil).count
|
||||
failed_attempts_ids = []
|
||||
puts 'Start processing steps...'
|
||||
params = { batch_size: 100 }
|
||||
if args.present? && args[:last_id].present?
|
||||
# fetch all steps and sort them asc
|
||||
params[:start] = args[:last_id].to_i
|
||||
end
|
||||
Step.find_each(params) do |step|
|
||||
next unless step.description && step.description.match(REGEX)
|
||||
team = step.protocol.team
|
||||
puts "******************************* \n\n\n\n"
|
||||
puts "Processing step id => [#{step.id}] \n\n\n\n"
|
||||
puts '*******************************'
|
||||
step.description.gsub!(REGEX) do |el|
|
||||
match = el.match(REGEX)
|
||||
old_img = TinyMceAsset.find_by_id(match[1])
|
||||
# skip other processing and deletes tiny_mce tag
|
||||
# if image is not in database
|
||||
next unless old_img
|
||||
new_img = TinyMceAsset.create(image: old_img.image,
|
||||
team: team,
|
||||
reference: step)
|
||||
if new_img
|
||||
# This image will be removed by `remove_obsolete_images` rake task
|
||||
# until all the steps are not updated we still need this image
|
||||
# in case it appears on some other step
|
||||
old_img.update_attributes(result_text_id: nil, step_id: nil)
|
||||
replaced_images += 1
|
||||
"[~tiny_mce_id:#{new_img.id}]"
|
||||
else
|
||||
failed_attempts += 1
|
||||
failed_attempts_ids << old_img.id
|
||||
"[~tiny_mce_id:#{old_img.id}]" # return the old img
|
||||
end
|
||||
end
|
||||
step.save
|
||||
end
|
||||
puts 'Completed processing steps...'
|
||||
|
||||
puts '----------- TASK REPORT -----------------'
|
||||
puts "All images: #{all_images}"
|
||||
puts "Recreated images: #{replaced_images}"
|
||||
puts "Failed attempts: #{failed_attempts}"
|
||||
puts "TinyMceAsset ids of failed attempts: #{failed_attempts_ids}"
|
||||
puts '-----------------------------------------'
|
||||
end
|
||||
|
||||
desc 'Generate new tiny_mce_assets and replace old assets in RTE ' \
|
||||
'for results. Assign the last printed id if the script crashes or ' \
|
||||
'id + 1 if there is a problematic asset'
|
||||
task :regenerate_results_images, [:last_id] => :environment do |_, args|
|
||||
replaced_images = 0
|
||||
failed_attempts = 0
|
||||
all_images = TinyMceAsset.where.not(result_text: nil).count
|
||||
failed_attempts_ids = []
|
||||
params = { batch_size: 100 }
|
||||
if args.present? && args[:last_id].present?
|
||||
params[:start] = args[:last_id].to_i
|
||||
end
|
||||
|
||||
puts 'Start processing result_texts...'
|
||||
ResultText.find_each(params) do |result_text|
|
||||
next unless result_text.text && result_text.text.match(REGEX)
|
||||
team = result_text.result.my_module.protocol.team
|
||||
puts "******************************************* \n\n\n\n"
|
||||
puts "Processing result_text id => [#{result_text.id}] \n\n\n\n"
|
||||
puts '*******************************************'
|
||||
result_text.text.gsub!(REGEX) do |el|
|
||||
match = el.match(REGEX)
|
||||
old_img = TinyMceAsset.find_by_id(match[1])
|
||||
# skip other processing and deletes tiny_mce tag
|
||||
# if image is not in database
|
||||
next unless old_img
|
||||
new_img = TinyMceAsset.create(image: old_img.image,
|
||||
team: team,
|
||||
reference: result_text)
|
||||
if new_img
|
||||
# This image will be removed by `remove_obsolete_images` rake task
|
||||
# until all the steps are not updated we still need this image
|
||||
# in case it appears on some other step
|
||||
old_img.update_attributes(result_text_id: nil, step_id: nil)
|
||||
replaced_images += 1
|
||||
"[~tiny_mce_id:#{new_img.id}]"
|
||||
else
|
||||
failed_attempts += 1
|
||||
failed_attempts_ids << old_img.id
|
||||
"[~tiny_mce_id:#{old_img.id}]" # return the old img
|
||||
end
|
||||
end
|
||||
result_text.save
|
||||
end
|
||||
puts 'Completed processing result_texts...'
|
||||
puts '----------- TASK REPORT -----------------'
|
||||
puts "All images: #{all_images}"
|
||||
puts "Recreated images: #{replaced_images}"
|
||||
puts "Failed attempts: #{failed_attempts}"
|
||||
puts "TinyMceAsset ids of failed attempts: #{failed_attempts_ids}"
|
||||
puts '-----------------------------------------'
|
||||
end
|
||||
end
|
||||
|
|
65
spec/controllers/api/api_controller_spec.rb
Normal file
65
spec/controllers/api/api_controller_spec.rb
Normal file
|
@ -0,0 +1,65 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Api::ApiController, type: :controller do
|
||||
describe 'GET #status' do
|
||||
before do
|
||||
get :status
|
||||
end
|
||||
|
||||
it 'Returns HTTP success' do
|
||||
expect(response).to be_success
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'Response with correct JSON status structure' do
|
||||
hash_body = nil
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body).to match(
|
||||
'message' => I18n.t('api.core.status_ok'),
|
||||
'versions' => [{ 'version' => '20170715',
|
||||
'baseUrl' => '/api/20170715/' }]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Post #authenticate' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'When valid request' do
|
||||
before do
|
||||
post :authenticate, email: user.email,
|
||||
password: user.password,
|
||||
grant_type: 'password'
|
||||
end
|
||||
|
||||
it 'Returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'Returns valid JWT token' do
|
||||
token = nil
|
||||
expect { token = json['access_token'] }.not_to raise_exception
|
||||
user_id = nil
|
||||
expect { user_id = decode_token(token) }.not_to raise_exception
|
||||
expect(user_id).to eq(user.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'When invalid password in request' do
|
||||
it 'Returns HTTP error' do
|
||||
post :authenticate, email: user.email,
|
||||
password: 'wrong_password',
|
||||
grant_type: 'password'
|
||||
expect(response).to have_http_status(400)
|
||||
end
|
||||
end
|
||||
|
||||
context 'When no grant_type in request' do
|
||||
it 'Returns HTTP error' do
|
||||
post :authenticate, email: user.email,
|
||||
password: user.password
|
||||
expect(response).to have_http_status(400)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
129
spec/controllers/api/v20170715/core_api_controller_spec.rb
Normal file
129
spec/controllers/api/v20170715/core_api_controller_spec.rb
Normal file
|
@ -0,0 +1,129 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Api::V20170715::CoreApiController, type: :controller do
|
||||
let(:user) { create(:user) }
|
||||
let(:task) { create(:my_module) }
|
||||
let(:sample1) { create(:sample) }
|
||||
let(:sample2) { create(:sample) }
|
||||
let(:sample3) { create(:sample) }
|
||||
before do
|
||||
task.samples << [sample1, sample2, sample3]
|
||||
UserProject.create!(user: user, project: task.experiment.project, role: 0)
|
||||
end
|
||||
|
||||
describe 'GET #task_samples' do
|
||||
context 'When valid request' do
|
||||
before do
|
||||
request.headers['HTTP_ACCEPT'] = 'application/json'
|
||||
request.headers['Authorization'] = 'Bearer ' + generate_token(user.id)
|
||||
get :task_samples, task_id: task.id
|
||||
end
|
||||
|
||||
it 'Returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'Returns JSON body containing expected Samples' do
|
||||
hash_body = nil
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body).to match(
|
||||
[{ 'sample_id' => sample1.id.to_s, 'name' => sample1.name },
|
||||
{ 'sample_id' => sample2.id.to_s, 'name' => sample2.name },
|
||||
{ 'sample_id' => sample3.id.to_s, 'name' => sample3.name }]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'When invalid request' do
|
||||
context 'When invalid token' do
|
||||
before do
|
||||
request.headers['HTTP_ACCEPT'] = 'application/json'
|
||||
request.headers['Authorization'] = 'Bearer WroNgToken'
|
||||
get :task_samples, task_id: task.id
|
||||
end
|
||||
|
||||
it 'Returns HTTP unauthorized' do
|
||||
expect(response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
|
||||
context 'When valid token, but invalid request' do
|
||||
before do
|
||||
request.headers['HTTP_ACCEPT'] = 'application/json'
|
||||
request.headers['Authorization'] = 'Bearer ' + generate_token(user.id)
|
||||
end
|
||||
|
||||
it 'Returns HTTP not found' do
|
||||
get :task_samples, task_id: 1000
|
||||
expect(response).to have_http_status(404)
|
||||
expect(json).to match({})
|
||||
end
|
||||
|
||||
it 'Returns HTTP access denied' do
|
||||
UserProject.where(user: user, project: task.experiment.project)
|
||||
.first
|
||||
.destroy!
|
||||
get :task_samples, task_id: task.id
|
||||
expect(response).to have_http_status(403)
|
||||
expect(json).to match({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #tasks_tree' do
|
||||
context 'When valid request' do
|
||||
before do
|
||||
request.headers['HTTP_ACCEPT'] = 'application/json'
|
||||
request.headers['Authorization'] = 'Bearer ' + generate_token(user.id)
|
||||
get :tasks_tree
|
||||
end
|
||||
|
||||
it 'Returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'Returns JSON body containing expected Task tree' do
|
||||
team = user.teams.first
|
||||
experiment = task.experiment
|
||||
project = experiment.project
|
||||
hash_body = nil
|
||||
expect { hash_body = json }.not_to raise_exception
|
||||
expect(hash_body).to match(
|
||||
['team_id' => team.id.to_s, 'name' => team.name,
|
||||
'description' => team.description,
|
||||
'projects' => [{
|
||||
'project_id' => project.id.to_s, 'name' => project.name,
|
||||
'visibility' => project.visibility,
|
||||
'archived' => project.archived,
|
||||
'experiments' => [{
|
||||
'experiment_id' => experiment.id.to_s,
|
||||
'name' => experiment.name,
|
||||
'description' => experiment.description,
|
||||
'archived' => experiment.archived,
|
||||
'tasks' => [{
|
||||
'task_id' => task.id.to_s, 'name' => task.name,
|
||||
'description' => task.description,
|
||||
'archived' => task.archived
|
||||
}]
|
||||
}]
|
||||
}]]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'When invalid request' do
|
||||
context 'When invalid token' do
|
||||
before do
|
||||
request.headers['HTTP_ACCEPT'] = 'application/json'
|
||||
request.headers['Authorization'] = 'Bearer WroNgToken'
|
||||
get :tasks_tree
|
||||
end
|
||||
|
||||
it 'Returns HTTP unauthorized' do
|
||||
expect(response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,5 +5,8 @@ FactoryGirl.define do
|
|||
email 'admin_test@scinote.net'
|
||||
password 'asdf1243'
|
||||
password_confirmation 'asdf1243'
|
||||
after(:create) do |user|
|
||||
user.teams << (Team.first || FactoryGirl.create(:team))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ describe MyModuleGroup, type: :model do
|
|||
describe 'Relations' do
|
||||
it { should belong_to :experiment }
|
||||
it { should belong_to(:created_by).class_name('User') }
|
||||
it { should have_many(:my_modules)}
|
||||
it { should have_many(:my_modules) }
|
||||
end
|
||||
|
||||
describe 'Should be a valid object' do
|
||||
|
|
|
@ -10,7 +10,7 @@ require File.expand_path('../../config/environment', __FILE__)
|
|||
abort('The Rails environment is running in production mode!') if Rails.env.production?
|
||||
require 'rspec/rails'
|
||||
# Add additional requires below this line. Rails is not loaded until this point!
|
||||
|
||||
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
|
||||
# Requires supporting ruby files with custom matchers and macros, etc, in
|
||||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
||||
# run as spec files by default. This means that files in spec/support that end
|
||||
|
@ -82,6 +82,7 @@ RSpec.configure do |config|
|
|||
config.include FactoryGirl::Syntax::Methods
|
||||
# Devise
|
||||
config.include Devise::Test::ControllerHelpers, type: :controller
|
||||
config.include ApiHelper, type: :controller
|
||||
config.extend ControllerMacros, type: :controller
|
||||
end
|
||||
|
||||
|
|
17
spec/support/api_helper.rb
Normal file
17
spec/support/api_helper.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module ApiHelper
|
||||
def generate_token(user_id)
|
||||
Api::CoreJwt.encode(user_id: user_id)
|
||||
end
|
||||
|
||||
def generate_expired_token(user_id)
|
||||
Api::CoreJwt.encode({ user_id: user_id }, (Time.now.to_i - 300))
|
||||
end
|
||||
|
||||
def decode_token(token)
|
||||
Api::CoreJwt.decode(token)['user_id'].to_i
|
||||
end
|
||||
|
||||
def json
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue