diff --git a/.gitignore b/.gitignore
index c0f9f219b..f50dc2373 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,3 +77,8 @@ spec/addons
# RVM/rbenv ruby version for local development
.ruby-version
+
+#ignore marvinJs
+
+public/marvinjs
+public/marvin4js-license.cxl
diff --git a/Gemfile b/Gemfile
index b523846fa..0acba81b4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,9 +4,7 @@ source 'http://rubygems.org'
ruby '2.6.3'
-
gem 'bootsnap', require: false
-gem 'webpacker', '~> 3.5'
gem 'bootstrap-sass', '~> 3.3.7'
gem 'bootstrap_form', '~> 2.7.0'
gem 'devise', '~> 4.6.2'
@@ -19,6 +17,7 @@ gem 'recaptcha', require: 'recaptcha/rails'
gem 'sanitize', '~> 4.4'
gem 'sassc-rails'
gem 'simple_token_authentication', '~> 1.15.1' # Token authentication for Devise
+gem 'webpacker', '~> 3.5'
gem 'yomu'
# Gems for OAuth2 subsystem
@@ -29,6 +28,7 @@ gem 'omniauth-linkedin-oauth2'
# Gems for API implementation
gem 'active_model_serializers', '~> 0.10.7'
gem 'json-jwt'
+gem 'jsonapi-renderer', '= 0.2.0'
gem 'jwt', '~> 1.5'
gem 'kaminari'
gem 'rack-attack'
@@ -78,16 +78,16 @@ gem 'sneaky-save', git: 'https://github.com/einzige/sneaky-save'
gem 'turbolinks', '~> 5.1.1'
gem 'underscore-rails'
gem 'wicked_pdf', '~> 1.1.0'
-gem 'wkhtmltopdf-heroku'
+gem 'wkhtmltopdf-heroku', '2.12.4'
gem 'aws-sdk-rails'
gem 'aws-sdk-s3'
-gem 'mini_magick'
-gem 'paperclip', '~> 6.1' # File attachment, image attachment library
gem 'delayed_job_active_record'
gem 'devise-async',
git: 'https://github.com/mhfs/devise-async.git',
branch: 'devise-4.x'
+gem 'mini_magick'
+gem 'paperclip', '~> 6.1' # File attachment, image attachment library
gem 'rufus-scheduler', '~> 3.5'
gem 'discard', '~> 1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index c2297e6d7..a006d3b98 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -283,7 +283,7 @@ GEM
json_matchers (0.11.0)
json_schema
json_schema (0.20.6)
- jsonapi-renderer (0.2.1)
+ jsonapi-renderer (0.2.0)
jwt (1.5.6)
kaminari (1.1.1)
activesupport (>= 4.1.0)
@@ -558,7 +558,7 @@ GEM
websocket-extensions (0.1.4)
whacamole (1.2.0)
wicked_pdf (1.1.0)
- wkhtmltopdf-heroku (2.12.5.0)
+ wkhtmltopdf-heroku (2.12.4.0)
xpath (3.2.0)
nokogiri (~> 1.8)
yomu (0.2.4)
@@ -616,6 +616,7 @@ DEPENDENCIES
js_cookie_rails
json-jwt
json_matchers
+ jsonapi-renderer (= 0.2.0)
jwt (~> 1.5)
kaminari
listen (~> 3.0)
@@ -671,7 +672,7 @@ DEPENDENCIES
webpacker (~> 3.5)
whacamole
wicked_pdf (~> 1.1.0)
- wkhtmltopdf-heroku
+ wkhtmltopdf-heroku (= 2.12.4)
yomu
RUBY VERSION
diff --git a/app/assets/images/icon_small/marvinjs.svg b/app/assets/images/icon_small/marvinjs.svg
new file mode 100644
index 000000000..bec9ab199
--- /dev/null
+++ b/app/assets/images/icon_small/marvinjs.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb
index 324a2408c..ee00589a1 100644
--- a/app/assets/javascripts/application.js.erb
+++ b/app/assets/javascripts/application.js.erb
@@ -42,6 +42,7 @@
//= require shared/inline_editing
//= require activestorage
//= require turbolinks
+//= require marvinjslauncher
// Initialize links for submitting forms. This is useful for submitting
diff --git a/app/assets/javascripts/my_modules/protocols.js b/app/assets/javascripts/my_modules/protocols.js
new file mode 100644
index 000000000..fbaa091dc
--- /dev/null
+++ b/app/assets/javascripts/my_modules/protocols.js
@@ -0,0 +1,485 @@
+/* global TinyMCE I18n animateSpinner importProtocolFromFile truncateLongString globalConstants */
+/* global HelperModule */
+/* eslint-disable no-use-before-define, no-alert, no-restricted-globals, no-underscore-dangle */
+
+//= require my_modules
+//= require protocols/import_export/import
+
+// Currently selected row in "load from protocol" modal
+var selectedRow = null;
+
+
+function initEditMyModuleDescription() {
+ $('#my_module_description_view').on('click', function() {
+ TinyMCE.init('#my_module_description_textarea');
+ });
+}
+
+function initEditProtocolDescription() {
+ $('#protocol_description_view').on('click', function() {
+ TinyMCE.init('#protocol_description_textarea', refreshProtocolStatusBar);
+ });
+}
+
+// Initialize edit description modal window
+function initEditDescription() {
+ var editDescriptionModal = $('#manage-module-description-modal');
+ var editDescriptionModalBody = editDescriptionModal.find('.modal-body');
+ $('.description-link')
+ .on('ajax:success', function(ev, data) {
+ var descriptionLink = $('.description-refresh');
+
+ // Set modal body & title
+ editDescriptionModalBody.html(data.html);
+ editDescriptionModal
+ .find('#manage-module-description-modal-label')
+ .text(data.title);
+
+ editDescriptionModalBody.find('form')
+ .on('ajax:success', function(ev2, data2) {
+ // Update module's description in the tab
+ descriptionLink.html(data2.description_label);
+
+ // Close modal
+ editDescriptionModal.modal('hide');
+ })
+ .on('ajax:error', function(ev2, data2) {
+ // Display errors if needed
+ $(this).renderFormErrors('my_module', data2.responseJSON);
+ });
+
+ // Show modal
+ editDescriptionModal.modal('show');
+ });
+
+
+ editDescriptionModal.on('hidden.bs.modal', function() {
+ editDescriptionModalBody.find('form').off('ajax:success ajax:error');
+ editDescriptionModalBody.html('');
+ });
+}
+
+function initCopyToRepository() {
+ var link = $("[data-action='copy-to-repository']");
+ var modal = $('#copy-to-repository-modal');
+ var modalBody = modal.find('.modal-body');
+ var submitBtn = modal.find(".modal-footer [data-action='submit']");
+
+ link
+ .on('ajax:success', function(e, data) {
+ modalBody.html(data.html);
+
+ modalBody.find("[data-role='copy-to-repository']")
+ .on('ajax:success', function(e2, data2) {
+ if (data2.refresh !== null) {
+ // Reload page
+ location.reload();
+ } else {
+ // Simply hide the modal
+ modal.modal('hide');
+ }
+ })
+ .on('ajax:error', function(e2, data2) {
+ // Display errors in form
+ submitBtn[0].disabled = false;
+ if (data2.status === 422) {
+ $(this).renderFormErrors('protocol', data2.responseJSON);
+ } else {
+ // Simply display global error
+ alert(data2.responseJSON.message);
+ }
+ });
+
+ modal.modal('show');
+ submitBtn[0].disabled = false;
+ })
+ .on('ajax:error', function() {});
+
+ submitBtn.on('click', function() {
+ // Submit the embedded form
+ submitBtn[0].disabled = true;
+ modalBody.find('form').submit();
+ });
+
+ modalBody.on('click', "[data-role='link-check']", function() {
+ var text = $(this).closest('.modal-body').find("[data-role='link-text']");
+ if ($(this).prop('checked')) {
+ text.show();
+ } else {
+ text.hide();
+ }
+ });
+
+ modal.on('hidden.bs.modal', function() {
+ modalBody.find("[data-role='copy-to-repository']")
+ .off('ajax:success ajax:error');
+
+ modalBody.html('');
+ });
+}
+
+function initLinkUpdate() {
+ var modal = $('#confirm-link-update-modal');
+ var modalTitle = modal.find('.modal-title');
+ var modalBody = modal.find('.modal-body');
+ var updateBtn = modal.find(".modal-footer [data-action='submit']");
+ $("[data-action='unlink'], [data-action='revert'], [data-action='update-parent'], [data-action='update-self']")
+ .on('ajax:success', function(e, data) {
+ modalTitle.html(data.title);
+ modalBody.html(data.message);
+ updateBtn.text(data.btn_text);
+ modal.attr('data-url', data.url);
+ modal.modal('show');
+ });
+ modal.on('hidden.bs.modal', function() {
+ modalBody.html('');
+ });
+
+ if (!$._data(updateBtn[0], 'events')) {
+ updateBtn.on('click', function() {
+ // POST via ajax
+ $.ajax({
+ url: modal.attr('data-url'),
+ type: 'POST',
+ dataType: 'json',
+ success: function() {
+ // Simply reload page
+ location.reload();
+ },
+ error: function(ev) {
+ // Display error message in alert()
+ alert(ev.responseJSON.message);
+
+ // Hide modal
+ modal.modal('hide');
+ }
+ });
+ });
+ }
+
+ $('[data-role="protocol-status-bar"] .preview-protocol').click(function(e) {
+ e.preventDefault();
+ });
+}
+
+function initLoadFromRepository() {
+ var modal = $('#load-from-repository-modal');
+ var modalBody = modal.find('.modal-body');
+ var loadBtn = modal.find(".modal-footer [data-action='submit']");
+
+ $("[data-action='load-from-repository']")
+ .on('ajax:success', function(e, data) {
+ modalBody.html(data.html);
+
+ // Disable load btn
+ loadBtn.attr('disabled', 'disabled');
+
+ modal.modal('show');
+
+ // Init Datatable on public tab
+ initLoadFromRepositoryTable(modalBody.find('#public-tab'));
+
+ modalBody.find("a[data-toggle='tab']")
+ .on('hide.bs.tab', function(el) {
+ // Destroy Handsontable in to-be-hidden tab
+ var content = $($(el.target).attr('href'));
+ destroyLoadFromRepositoryTable(content);
+ })
+ .on('shown.bs.tab', function(el) {
+ // Initialize Handsontable in to-be-shown tab
+ var content = $($(el.target).attr('href'));
+ initLoadFromRepositoryTable(content);
+ });
+
+ loadBtn.on('click', function() {
+ loadFromRepository();
+ });
+ });
+ modal.on('hidden.bs.modal', function() {
+ // Destroy the current Datatable
+ destroyLoadFromRepositoryTable(modalBody.find('.tab-pane.active'));
+
+ modalBody.find("a[data-toggle='tab']")
+ .off('hide.bs.tab shown.bs.tab');
+
+ loadBtn.off('click');
+
+ modalBody.html('');
+ });
+}
+
+function initLoadFromRepositoryTable(content) {
+ var tableEl = content.find("[data-role='datatable']");
+
+ var datatable = tableEl.DataTable({
+ order: [[1, 'asc']],
+ dom: 'RBfltpi',
+ buttons: [],
+ processing: true,
+ serverSide: true,
+ responsive: true,
+ ajax: {
+ url: tableEl.data('source'),
+ type: 'POST'
+ },
+ colReorder: {
+ fixedColumnsLeft: 1000000 // Disable reordering
+ },
+ columnDefs: [{
+ targets: 0,
+ searchable: false,
+ orderable: false,
+ sWidth: '1%',
+ render: function() {
+ return "";
+ }
+ }, {
+ targets: [1, 2, 3, 4, 5, 6],
+ searchable: true,
+ orderable: true
+ }],
+ columns: [
+ { data: '0' },
+ { data: '1' },
+ { data: '2' },
+ { data: '3' },
+ { data: '4' },
+ { data: '5' },
+ { data: '6' }
+ ],
+ oLanguage: {
+ sSearch: I18n.t('general.filter')
+ },
+ rowCallback: function(row, data) {
+ // Get row ID
+ var rowId = data.DT_RowId;
+
+ $(row).attr('data-row-id', rowId);
+
+ // If row ID is in the list of selected row IDs
+ if (rowId === selectedRow) {
+ $(row).find("input[type='radio']").prop('checked', true);
+ $(row).addClass('selected');
+ }
+ },
+ fnDrawCallback: function() {
+ animateSpinner(this, false);
+ },
+ preDrawCallback: function() {
+ animateSpinner(this);
+ }
+ });
+
+ // Handle click on table cells with radio buttons
+ tableEl.find('tbody').on('click', 'td', function() {
+ $(this).parent().find("input[type='radio']").trigger('click');
+ });
+
+ // Handle clicks on radio buttons
+ tableEl.find('tbody').on('click', "input[type='radio']", function(e) {
+ // Get row ID
+ var row = $(this).closest('tr');
+ var data = datatable.row(row).data();
+ var rowId = data.DT_RowId;
+
+ // Uncheck all radio buttons
+ tableEl.find("tbody input[type='radio']")
+ .prop('checked', false)
+ .closest('tr')
+ .removeClass('selected');
+
+ // Select the current row
+ row.find("input[type='radio']").prop('checked', true);
+ selectedRow = rowId;
+ row.addClass('selected');
+
+ // Enable load btn
+ content.closest('.modal')
+ .find(".modal-footer [data-action='submit']")
+ .removeAttr('disabled');
+
+ e.stopPropagation();
+ });
+
+ tableEl.find('tbody').on('click', "a[data-action='filter']", function(e) {
+ var link = $(this);
+ var query = link.attr('data-param');
+
+ // Re-search data
+ datatable.search(query).draw();
+
+ // Don't propagate this further up
+ e.stopPropagation();
+ return false;
+ });
+}
+
+function destroyLoadFromRepositoryTable(content) {
+ var tableEl = content.find("[data-role='datatable']");
+
+ // Unbind event listeners
+ tableEl.find('tbody').off('click', "a[data-action='filter']");
+ tableEl.find('tbody').off('click', "input[type='radio']");
+ tableEl.find('tbody').off('click', 'td');
+
+ // Destroy datatable
+ tableEl.DataTable().destroy();
+ tableEl.find('tbody').html('');
+
+ selectedRow = null;
+
+ // Disable load btn
+ content.closest('.modal')
+ .find(".modal-footer [data-action='submit']")
+ .attr('disabled', 'disabled');
+}
+
+function loadFromRepository() {
+ var modal = $('#load-from-repository-modal');
+
+ var checkLinked = $("[data-role='protocol-status-bar']")
+ .text();
+
+ var confirmMessage = '';
+ if (checkLinked.trim() !== '(unlinked)') {
+ confirmMessage = I18n.t('my_modules.protocols.load_from_repository_modal.import_to_linked_task_rep');
+ } else {
+ confirmMessage = I18n.t('my_modules.protocols.load_from_repository_modal.confirm_message');
+ }
+
+ if (selectedRow !== null && confirm(confirmMessage)) {
+ // POST via ajax
+ $.ajax({
+ url: modal.attr('data-url'),
+ type: 'POST',
+ dataType: 'json',
+ data: { source_id: selectedRow },
+ success: function() {
+ // Simply reload page
+ location.reload();
+ },
+ error: function(ev) {
+ // Display error message in alert()
+ alert(ev.responseJSON.message);
+
+ // Hide modal
+ modal.modal('hide');
+ }
+ });
+ }
+}
+
+function refreshProtocolStatusBar() {
+ // Get the status bar URL
+ var url = $("[data-role='protocol-status-bar-url']").attr('data-url');
+
+ // Fetch new updated at label
+ $.ajax({
+ url: url,
+ type: 'GET',
+ dataType: 'json',
+ success: function(data) {
+ $("[data-role='protocol-status-bar']").html(data.html);
+ initLinkUpdate();
+ }
+ });
+}
+
+function initImport() {
+ var fileInput = $("[data-action='load-from-file']");
+
+ // Make sure multiple selections of same file
+ // always prompt new modal
+ fileInput.find("input[type='file']").on('click', function() {
+ this.value = null;
+ });
+
+ // Hack to hide "No file chosen" tooltip
+ fileInput.attr('title', window.URL ? ' ' : '');
+
+ fileInput.on('change', function(ev) {
+ var importUrl = fileInput.attr('data-import-url');
+ importProtocolFromFile(
+ ev.target.files[0],
+ importUrl,
+ null,
+ true,
+ function(datas) {
+ var data = datas[0];
+ if (data.status === 'ok') {
+ // Simply reload page
+ location.reload();
+ } else if (data.status === 'locked') {
+ alert(I18n.t('my_modules.protocols.load_from_file_error_locked'));
+ } else {
+ if (data.status === 'size_too_large') {
+ alert(I18n.t('my_modules.protocols.load_from_file_size_error',
+ { size: $(document.body).data('file-max-size-mb') }));
+ } else {
+ alert(I18n.t('my_modules.protocols.load_from_file_error'));
+ }
+ animateSpinner(null, false);
+ }
+ }
+ );
+ // Clear input on self
+ $(this).val('');
+ });
+}
+
+function initRecentProtocols() {
+ var recentProtocolContainer = $('.my-module-recent-protocols');
+ var dropDownList = recentProtocolContainer.find('.dropdown-menu');
+ recentProtocolContainer.find('.dropdown-button').click(function() {
+ dropDownList.find('.protocol').remove();
+ $.get('/protocols/recent_protocols', result => {
+ $.each(result, (i, protocol) => {
+ $('
'
+ + truncateLongString(protocol.name, globalConstants.name_truncation_length)
+ + '
').appendTo(dropDownList)
+ .click(() => {
+ $.post(recentProtocolContainer.data('updateUrl'), { source_id: protocol.id })
+ .success(() => {
+ location.reload();
+ })
+ .error(ev => {
+ HelperModule.flashAlertMsg(ev.responseJSON.message, 'warning');
+ });
+ });
+ });
+ });
+ });
+
+ // We use here ajax:success, because we want to check any change on this page
+ $(document).on('ajax:success', () => {
+ updateRecentProtocolsStatus();
+ });
+}
+
+function updateRecentProtocolsStatus() {
+ var recentProtocolContainer = $('.my-module-recent-protocols');
+ var steps = $('.step');
+ var protocolDescription = $('#protocol_description_view').html();
+ if (steps.length === 0 && protocolDescription.length === 0) {
+ recentProtocolContainer.css('display', '');
+ } else {
+ recentProtocolContainer.css('display', 'none');
+ }
+}
+
+/**
+ * Initializes page
+ */
+function init() {
+ initEditMyModuleDescription();
+ initEditProtocolDescription();
+ initEditDescription();
+ initCopyToRepository();
+ initLinkUpdate();
+ initLoadFromRepository();
+ refreshProtocolStatusBar();
+ initImport();
+ initRecentProtocols();
+}
+
+init();
diff --git a/app/assets/javascripts/protocols/import_export/import.js b/app/assets/javascripts/protocols/import_export/import.js
index 7091baa75..218976d1c 100644
--- a/app/assets/javascripts/protocols/import_export/import.js
+++ b/app/assets/javascripts/protocols/import_export/import.js
@@ -500,7 +500,14 @@ function importProtocolFromFile(
var tinyMceAsset = {};
var fileRef = $(this).attr('fileRef');
tinyMceAsset.tokenId = $(this).attr('tokenId');
+ tinyMceAsset.fileName = $(this).children('fileName').text();
tinyMceAsset.fileType = $(this).children('fileType').text();
+ if ($(this).children('fileMetadata').html() !== undefined) {
+ tinyMceAsset.fileMetadata = $(this).children('fileMetadata').html()
+ .replace('', '')
+ .replace(']]>', '');
+ }
tinyMceAsset.bytes = getAssetBytes(
protocolFolders[index],
stepGuid,
@@ -579,6 +586,12 @@ function importProtocolFromFile(
stepAssetJson.id = assetId;
stepAssetJson.fileName = fileName;
stepAssetJson.fileType = $(this).children('fileType').text();
+ if ($(this).children('fileMetadata').html() !== undefined) {
+ stepAssetJson.fileMetadata = $(this).children('fileMetadata').html()
+ .replace('', '')
+ .replace(']]>', '');
+ }
stepAssetJson.bytes = getAssetBytes(
protocolFolders[index],
stepGuid,
diff --git a/app/assets/javascripts/protocols/steps.js.erb b/app/assets/javascripts/protocols/steps.js.erb
index 7f798f779..93cf48e16 100644
--- a/app/assets/javascripts/protocols/steps.js.erb
+++ b/app/assets/javascripts/protocols/steps.js.erb
@@ -122,6 +122,7 @@
SmartAnnotation.preventPropagation('.atwho-user-popover');
TinyMCE.destroyAll();
DragNDropSteps.clearFiles();
+ MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
}, 1000);
})
@@ -374,7 +375,7 @@
}
function initCallBacks() {
- applyCreateWopiFileCallback();
+ applyCreateWopiFileCallback()
applyCheckboxCallBack();
applyStepCompletedCallBack();
applyEditCallBack();
@@ -502,10 +503,9 @@
$('#new-step-main-tab a')
.on('shown.bs.tab', function() {
$('#step_name').focus();
- TinyMCE.init('#step_description_textarea');
- }).on('hidden.bs.tab', function() {
tinyMCE.editors.step_description_textarea.remove();
- });
+ TinyMCE.init('#step_description_textarea');
+ })
TinyMCE.init('#step_description_textarea');
});
@@ -536,7 +536,7 @@
var nameValid = textValidator(ev, $nameInput, 1,
<%= Constants::NAME_MAX_LENGTH %>);
var $descrTextarea = $form.find("#step_description_textarea");
- var $tinyMCEInput = TinyMCE.getContent();
+ var $tinyMCEInput = tinyMCE.editors.step_description_textarea.getContent();
var descriptionValid = textValidator(ev, $descrTextarea, 0,
<%= Constants::RICH_TEXT_MAX_LENGTH %>, false, $tinyMCEInput);
var tableNamesValidArray = [];
@@ -601,6 +601,7 @@
SmartAnnotation.preventPropagation('.atwho-user-popover');
tinyMCE.editors.step_description_textarea.remove();
+ MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
//Rerender tables
$new_step.find("div.step-result-hot-table").each(function() {
@@ -611,6 +612,7 @@
FilePreviewModal.init();
$.initTooltips();
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
+ if (typeof updateRecentProtocolsStatus === 'function') updateRecentProtocolsStatus();
},
error: function(xhr) {
if (xhr.responseJSON['assets.file']) {
@@ -665,13 +667,20 @@
}
// Reorder attachments
- global.reorderAttachments = function reorderAtt(stepId, sortType) {
+ global.reorderAttachments = function reorderAtt(elem, stepId, sortType) {
var label_value = $("#dd-att-step-" + stepId + "> .dropdown-menu > li > a[data-order=" + sortType + "]").html();
$("#dd-att-step-" + stepId + "-label").html(label_value);
$('#att-' + stepId + ' a.file-preview-link').each(function(){
var elm = $(this)
elm.parent().css('order', elm.attr('data-order-' + sortType));
- })
+ });
+
+ $.post(
+ $(elem).closest('.dropdown-menu').data('stateSavePath'),
+ { assets: { order: sortType } },
+ null,
+ 'json',
+ );
}
// On init
diff --git a/app/assets/javascripts/reports/new.js b/app/assets/javascripts/reports/new.js
index ee5e9b8a9..8dcfb9326 100644
--- a/app/assets/javascripts/reports/new.js
+++ b/app/assets/javascripts/reports/new.js
@@ -45,7 +45,7 @@ function initializeHandsonTable(el) {
formulas: true
});
el.handsontable("getInstance").loadData(data);
- el.handsontable("getInstance").sort(3, order);
+ el.handsontable('getInstance').getPlugin('columnSorting').sort(3, order);
// "Hack" to disable user sorting rows by clicking on
// header elements
diff --git a/app/assets/javascripts/shared/inline_editing.js b/app/assets/javascripts/shared/inline_editing.js
index 009a38910..650ac64cc 100644
--- a/app/assets/javascripts/shared/inline_editing.js
+++ b/app/assets/javascripts/shared/inline_editing.js
@@ -85,6 +85,7 @@ function initInlineEditing(title) {
if (inputString.disabled) {
saveAllEditFields();
editBlock.dataset.editMode = 1;
+ $editBlock.closest('.inline_scroll_block').scrollTop(editBlock.offsetTop);
inputString.disabled = false;
$inputString.removeClass('hidden');
$editBlock.find('.view-mode').addClass('hidden');
diff --git a/app/assets/javascripts/sitewide/comments.js b/app/assets/javascripts/sitewide/comments.js
index 2127b94a1..ecd1f0a83 100644
--- a/app/assets/javascripts/sitewide/comments.js
+++ b/app/assets/javascripts/sitewide/comments.js
@@ -2,8 +2,14 @@
/* eslint-disable no-restricted-globals, no-alert */
var Comments = (function() {
function changeCounter(comment, value) {
- var currnetCount = $('#comment-counter-' + comment.closest('.comments-container').attr('data-object-id'));
- currnetCount.html(parseInt(currnetCount.html(), 10) + value);
+ var currentCount = $('#comment-counter-' + comment.closest('.comments-container').attr('data-object-id'));
+ var newValue = parseInt(currentCount.html(), 10) + value;
+ currentCount.html(newValue);
+ if (newValue === 0) {
+ currentCount.addClass('hidden');
+ } else {
+ currentCount.removeClass('hidden');
+ }
}
function scrollBottom(container) {
@@ -76,6 +82,7 @@ var Comments = (function() {
$el.find('#message').val('');
$el.find('.new-comment-button').removeClass('show');
newButton.disable = false;
+ $el.find('textarea').focus().blur();
})
.error((error) => {
errorField.html(error.responseJSON.errors.message);
diff --git a/app/assets/javascripts/sitewide/file_preview.js b/app/assets/javascripts/sitewide/file_preview.js
index da16d0c5b..f71203a63 100644
--- a/app/assets/javascripts/sitewide/file_preview.js
+++ b/app/assets/javascripts/sitewide/file_preview.js
@@ -1,12 +1,17 @@
/* eslint no-underscore-dangle: ["error", { "allowAfterThis": true }]*/
/* eslint no-use-before-define: ["error", { "functions": false }]*/
/* eslint-disable no-underscore-dangle */
-/* global Uint8Array fabric tui animateSpinner I18n PerfectScrollbar*/
+/* global Uint8Array fabric tui animateSpinner Assets
+ I18n PerfectScrollbar MarvinJsEditor refreshProtocolStatusBar */
+
var FilePreviewModal = (function() {
'use strict';
var readOnly = false;
+ var CHECK_READY_DELAY = 5000;
+ var CHECK_READY_TRIES_LIMIT = 60;
+ var checkReadyCntr;
function initPreviewModal(options = {}) {
var name;
@@ -17,10 +22,11 @@ var FilePreviewModal = (function() {
$('.file-preview-link').off('click');
$('.file-preview-link').click(function(e) {
e.preventDefault();
- name = $(this).find('p').text();
+ name = $(this).find('.attachment-label').text();
url = $(this).data('preview-url');
downloadUrl = $(this).attr('href');
openPreviewModal(name, url, downloadUrl);
+ return true;
});
}
@@ -402,8 +408,33 @@ var FilePreviewModal = (function() {
imageEditor = {};
$('#tui-image-editor').html('');
$('#fileEditModal').modal('hide');
- if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
});
+
+ if (data.mode === 'tinymce') {
+ $.ajax({
+ type: 'PUT',
+ url: data.url,
+ data: dataUpload,
+ contentType: false,
+ processData: false,
+ success: function(res) {
+ data.image.src = res.url;
+ }
+ }).done(function() { closeEditor(); });
+ } else {
+ $.ajax({
+ type: 'POST',
+ url: '/files/' + data.id + '/update_image',
+ data: dataUpload,
+ contentType: false,
+ processData: false,
+ success: function(res) {
+ $('#modal_link' + data.id).parent().html(res.html);
+ Assets.setupAssetsLoading();
+ }
+ }).done(function() { closeEditor(); });
+ }
+ if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
});
window.onresize = function() {
@@ -420,8 +451,7 @@ var FilePreviewModal = (function() {
dataType: 'json',
success: function(data) {
var link = modal.find('.file-download-link');
- modal.find('.file-preview-container').empty();
- modal.find('.file-wopi-controls').empty();
+ clearPrevieModal();
if (Object.prototype.hasOwnProperty.call(data, 'wopi-controls')) {
modal.find('.file-wopi-controls').html(data['wopi-controls']);
}
@@ -443,6 +473,7 @@ var FilePreviewModal = (function() {
if (!readOnly && data.editable) {
modal.find('.file-edit-link').css('display', '');
modal.find('.file-edit-link').off().click(function(ev) {
+ $.post('/files/' + data.id + '/start_edit_image');
ev.preventDefault();
ev.stopPropagation();
modal.modal('hide');
@@ -452,6 +483,8 @@ var FilePreviewModal = (function() {
modal.find('.file-edit-link').css('display', 'none');
}
}
+ } else if (data.type === 'marvinjs') {
+ openMarvinEditModal(data, modal);
} else {
modal.find('.file-edit-link').css('display', 'none');
modal.find('.file-preview-container').html(data['preview-icon']);
@@ -460,10 +493,13 @@ var FilePreviewModal = (function() {
modal.find('#wopi_file_edit_button').remove();
}
if (data.processing) {
- checkFileReady(url, modal);
+ setTimeout(function() {
+ checkFileReady(url, modal);
+ }, CHECK_READY_DELAY);
}
modal.find('.file-name').text(name);
modal.find('.preview-close').click(function() {
+ checkReadyCntr = CHECK_READY_TRIES_LIMIT;
modal.modal('hide');
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
});
@@ -472,6 +508,7 @@ var FilePreviewModal = (function() {
ev.preventDefault();
});
$('.modal-backdrop').last().css('z-index', modal.css('z-index') - 1);
+ checkReadyCntr = 0;
},
error: function() {
// TODO
@@ -492,9 +529,11 @@ var FilePreviewModal = (function() {
ev.preventDefault();
ev.stopPropagation();
});
- setTimeout(function() {
- checkFileReady(url, modal);
- }, 10000);
+ if (checkReadyCntr < CHECK_READY_TRIES_LIMIT) {
+ setTimeout(function() {
+ checkFileReady(url, modal);
+ }, CHECK_READY_DELAY);
+ }
} else {
if (data.type === 'image') {
modal.find('.file-preview-container').empty();
@@ -518,9 +557,45 @@ var FilePreviewModal = (function() {
.off();
}
});
+
+ checkReadyCntr += 1;
+ }
+
+ function clearPrevieModal() {
+ var modal = $('#filePreviewModal');
+ modal.find('.file-preview-container').empty();
+ modal.find('.file-wopi-controls').empty();
+ modal.find('.file-edit-link').css('display', 'none');
+ }
+
+ function openMarvinEditModal(data, modal) {
+ modal.find('.file-preview-container')
+ .append($('
')
+ .attr('src', data['large-preview-url'])
+ .attr('alt', data.name)
+ .click(function(ev) {
+ ev.stopPropagation();
+ }));
+ if (!readOnly && data.editable) {
+ modal.find('.file-edit-link').css('display', '');
+ modal.find('.file-edit-link').off().click(function(ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ modal.modal('hide');
+ MarvinJsEditor.open({
+ mode: 'edit',
+ data: data.description,
+ name: data.name,
+ marvinUrl: data['update-url']
+ });
+ });
+ } else {
+ modal.find('.file-edit-link').css('display', 'none');
+ }
}
return Object.freeze({
- init: initPreviewModal
+ init: initPreviewModal,
+ imageEditor: initImageEditor
});
}(window));
diff --git a/app/assets/javascripts/sitewide/marvinjs_editor.js b/app/assets/javascripts/sitewide/marvinjs_editor.js
new file mode 100644
index 000000000..3fa9442e5
--- /dev/null
+++ b/app/assets/javascripts/sitewide/marvinjs_editor.js
@@ -0,0 +1,318 @@
+/* global TinyMCE, ChemicalizeMarvinJs, MarvinJSUtil, I18n, FilePreviewModal, tinymce */
+/* global Results, Comments */
+/* eslint-disable no-param-reassign */
+/* eslint-disable wrap-iife */
+/* eslint-disable no-use-before-define */
+
+
+var marvinJsRemoteLastMrv;
+var marvinJsRemoteEditor;
+var MarvinJsEditor;
+
+var MarvinJsEditorApi = (function() {
+ var marvinJsModal = $('#MarvinJsModal');
+ var marvinJsContainer = $('#marvinjs-editor');
+ var marvinJsObject = $('#marvinjs-sketch');
+ var emptySketch = '';
+ var sketchName = marvinJsModal.find('.file-name input');
+ var marvinJsMode = marvinJsContainer.data('marvinjsMode');
+
+ // Facade api actions
+ var marvinJsExportImage = (childFuction, options = {}) => {
+ if (marvinJsMode === 'remote') {
+ remoteExportImage(childFuction, options);
+ } else {
+ localExportImage(childFuction, options);
+ }
+ };
+ // ///////////////
+
+ var loadEditor = () => {
+ if (marvinJsMode === 'remote') {
+ return marvinJsRemoteEditor;
+ }
+ return MarvinJSUtil.getEditor('#marvinjs-sketch');
+ };
+ var loadPackages = () => {
+ return MarvinJSUtil.getPackage('#marvinjs-sketch');
+ };
+
+ // Local marvinJS installation
+
+ var localExportImage = (childFuction, options = {}) => {
+ loadEditor().then(function(sketcherInstance) {
+ sketcherInstance.exportStructure('mrv').then(function(source) {
+ loadPackages().then(function(sketcherPackage) {
+ sketcherPackage.onReady(function() {
+ var exporter = createExporter(sketcherPackage, 'image/jpeg');
+ exporter.render(source).then(function(image) {
+ childFuction(source, image, options);
+ });
+ });
+ });
+ });
+ });
+ };
+
+ // Web services installation
+
+ var remoteImage = (childFuction, source, options = {}) => {
+ var params = {
+ carbonLabelVisible: false,
+ implicitHydrogen: 'TERMINAL_AND_HETERO',
+ displayMode: 'WIREFRAME',
+ width: 900,
+ height: 900
+ };
+ if (typeof (marvinJsRemoteEditor) === 'undefined') {
+ setTimeout(() => { remoteImage(childFuction, source, options); }, 100);
+ return false;
+ }
+ marvinJsRemoteEditor.exportMrvToImageDataUri(source, 'image/jpeg', params).then(function(image) {
+ childFuction(source, image, options);
+ });
+ return true;
+ };
+
+ var remoteExportImage = (childFuction, options = {}) => {
+ remoteImage(childFuction, marvinJsRemoteLastMrv, options);
+ };
+
+ // Support actions
+ function preloadActions(config) {
+ if (marvinJsMode === 'remote') {
+ if (config.mode === 'new' || config.mode === 'new-tinymce') {
+ marvinJsRemoteEditor.importStructure('mrv', emptySketch);
+ sketchName.val('');
+ } else if (config.mode === 'edit') {
+ marvinJsRemoteLastMrv = config.data;
+ marvinJsRemoteEditor.importStructure('mrv', config.data);
+ sketchName.val(config.name);
+ } else if (config.mode === 'edit-tinymce') {
+ marvinJsRemoteLastMrv = config.data;
+ $.get(config.marvinUrl, { object_type: 'TinyMceAsset' }, function(result) {
+ marvinJsRemoteEditor.importStructure('mrv', result.description);
+ sketchName.val(result.name);
+ });
+ }
+ marvinJsRemoteEditor.on('molchange', () => {
+ marvinJsRemoteEditor.exportStructure('mrv').then(function(source) {
+ marvinJsRemoteLastMrv = source;
+ });
+ });
+ } else {
+ loadEditor().then(function(marvin) {
+ if (config.mode === 'new' || config.mode === 'new-tinymce') {
+ marvin.importStructure('mrv', emptySketch);
+ sketchName.val(I18n.t('marvinjs.new_sketch'));
+ } else if (config.mode === 'edit') {
+ marvin.importStructure('mrv', config.data);
+ sketchName.val(config.name);
+ } else if (config.mode === 'edit-tinymce') {
+ $.get(config.marvinUrl, function(result) {
+ marvin.importStructure('mrv', result.description);
+ sketchName.val(result.name);
+ });
+ }
+ });
+ }
+ }
+
+ function createExporter(marvin, imageType) {
+ var inputFormat = 'mrv';
+ var settings = {
+ width: 900,
+ height: 900
+ };
+
+ var params = {
+ imageType: imageType,
+ settings: settings,
+ inputFormat: inputFormat
+ };
+ return new marvin.ImageExporter(params);
+ }
+
+ function TinyMceBuildHTML(json) {
+ var imgstr = "
";
+ return imgstr;
+ }
+
+ function saveFunction(source, image, config) {
+ $.post(config.marvinUrl, {
+ description: source,
+ object_id: config.objectId,
+ object_type: config.objectType,
+ name: sketchName.val(),
+ image: image
+ }, function(result) {
+ var newAsset = $(result.html);
+ var json;
+ if (config.objectType === 'Step') {
+ newAsset.find('.file-preview-link').css('top', '-300px');
+ newAsset.addClass('new').prependTo($(config.container));
+ setTimeout(function() {
+ newAsset.find('.file-preview-link').css('top', '0px');
+ }, 200);
+ } else if (config.objectType === 'Result') {
+ newAsset.prependTo($(config.container));
+ Results.expandResult(newAsset);
+ Comments.init();
+ } else if (config.objectType === 'TinyMceAsset') {
+ json = tinymce.util.JSON.parse(result);
+ config.editor.execCommand('mceInsertContent', false, TinyMceBuildHTML(json));
+ TinyMCE.updateImages(config.editor);
+ }
+ $(marvinJsModal).modal('hide');
+ FilePreviewModal.init();
+ });
+ }
+
+ function updateFunction(source, image, config) {
+ $.ajax({
+ url: config.marvinUrl,
+ data: {
+ description: source,
+ name: sketchName.val(),
+ object_type: config.objectType,
+ image: image
+ },
+ dataType: 'json',
+ type: 'PUT',
+ success: function(json) {
+ if (config.objectType === 'TinyMceAsset') {
+ config.image[0].src = json.url;
+ config.image[0].dataset.mceSrc = json.url;
+ $(marvinJsModal).modal('hide');
+ } else {
+ $(marvinJsModal).modal('hide');
+ $('#modal_link' + json.id + ' img').attr('src', json.url);
+ $('#modal_link' + json.id + ' .attachment-label').html(json.file_name);
+ }
+ }
+ });
+ }
+
+ // MarvinJS Methods
+
+ return {
+ enabled: function() {
+ return ($('#MarvinJsModal').length > 0);
+ },
+
+ open: function(config) {
+ if (!MarvinJsEditor.enabled()) {
+ $('#MarvinJsPromoModal').modal('show');
+ return false;
+ }
+ if (marvinJsMode === 'remote' && typeof (marvinJsRemoteEditor) === 'undefined') {
+ setTimeout(() => { MarvinJsEditor.open(config); }, 100);
+ return false;
+ }
+ preloadActions(config);
+ $(marvinJsModal).modal('show');
+ $(marvinJsObject)
+ .css('width', marvinJsContainer.width() + 'px')
+ .css('height', marvinJsContainer.height() + 'px');
+ marvinJsModal.find('.file-save-link').off('click').on('click', () => {
+ if (config.mode === 'new') {
+ MarvinJsEditor.save(config);
+ } else if (config.mode === 'edit') {
+ config.objectType = 'Asset';
+ MarvinJsEditor.update(config);
+ } else if (config.mode === 'new-tinymce') {
+ config.objectType = 'TinyMceAsset';
+ MarvinJsEditor.save(config);
+ } else if (config.mode === 'edit-tinymce') {
+ config.objectType = 'TinyMceAsset';
+ MarvinJsEditor.update(config);
+ }
+ });
+ return true;
+ },
+
+ initNewButton: function(selector) {
+ $(selector).off('click').on('click', function() {
+ var objectId = this.dataset.objectId;
+ var objectType = this.dataset.objectType;
+ var marvinUrl = this.dataset.marvinUrl;
+ var container = this.dataset.sketchContainer;
+ MarvinJsEditor.open({
+ mode: 'new',
+ objectId: objectId,
+ objectType: objectType,
+ marvinUrl: marvinUrl,
+ container: container
+ });
+ });
+ },
+
+ save: function(config) {
+ marvinJsExportImage(saveFunction, config);
+ },
+
+ update: function(config) {
+ marvinJsExportImage(updateFunction, config);
+ }
+ };
+});
+
+// TinyMCE plugin
+
+(function() {
+ 'use strict';
+
+ tinymce.PluginManager.requireLangPack('MarvinJsPlugin');
+
+ tinymce.create('tinymce.plugins.MarvinJsPlugin', {
+ MarvinJsPlugin: function(ed) {
+ var editor = ed;
+
+ function openMarvinJs() {
+ MarvinJsEditor.open({
+ mode: 'new-tinymce',
+ marvinUrl: '/tiny_mce_assets/marvinjs',
+ editor: editor
+ });
+ }
+ // Add a button that opens a window
+ editor.addButton('marvinjsplugin', {
+ tooltip: I18n.t('marvinjs.new_button'),
+ icon: 'marvinjs',
+ onclick: openMarvinJs
+ });
+
+ // Adds a menu item to the tools menu
+ editor.addMenuItem('marvinjsplugin', {
+ text: I18n.t('marvinjs.new_button'),
+ icon: 'marvinjs',
+ context: 'insert',
+ onclick: openMarvinJs
+ });
+ }
+ });
+
+ tinymce.PluginManager.add(
+ 'marvinjsplugin',
+ tinymce.plugins.MarvinJsPlugin
+ );
+})();
+
+// Initialization
+
+
+$(document).on('turbolinks:load', function() {
+ MarvinJsEditor = MarvinJsEditorApi();
+ if (MarvinJsEditor.enabled()) {
+ if ($('#marvinjs-editor')[0].dataset.marvinjsMode === 'remote') {
+ ChemicalizeMarvinJs.createEditor('#marvinjs-sketch').then(function(marvin) {
+ marvinJsRemoteEditor = marvin;
+ });
+ }
+ }
+ MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
+});
diff --git a/app/assets/javascripts/sitewide/tiny_mce_file_upload_plugin.js b/app/assets/javascripts/sitewide/tiny_mce_file_upload_plugin.js
index 87d32cdb1..0f92445f6 100644
--- a/app/assets/javascripts/sitewide/tiny_mce_file_upload_plugin.js
+++ b/app/assets/javascripts/sitewide/tiny_mce_file_upload_plugin.js
@@ -88,7 +88,7 @@
form = createElement('form', {
action: editor.getParam(
'customimageuploader_form_url',
- '/tinymce_assets'
+ '/tiny_mce_assets'
),
target: iframe._id,
method: 'POST',
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 929c3e5af..0d2cf38f6 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -23,5 +23,6 @@
@import "select2.min";
@import "extend/perfect-scrollbar";
@import "my_modules/protocols/*";
+@import "my_modules/results/*";
@import "protocols/*";
@import "hooks/*";
diff --git a/app/assets/stylesheets/hooks/tinymce.scss b/app/assets/stylesheets/hooks/tinymce.scss
index cb6ad2481..1ebe183c1 100644
--- a/app/assets/stylesheets/hooks/tinymce.scss
+++ b/app/assets/stylesheets/hooks/tinymce.scss
@@ -43,3 +43,22 @@
.mce-top-part {
z-index: 5;
}
+
+.mce-widget.mce-btn[aria-label="Restore last draft"] {
+ background: $brand-primary;
+ border-radius: 4px;
+ transition: $md-transaction;
+
+ i {
+ color: $color-white;
+ transition: $md-transaction;
+ }
+
+ &.mce-disabled {
+ background: transparent;
+
+ i {
+ color: inherit;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/marvinjs.scss b/app/assets/stylesheets/marvinjs.scss
new file mode 100644
index 000000000..317061491
--- /dev/null
+++ b/app/assets/stylesheets/marvinjs.scss
@@ -0,0 +1,166 @@
+// scss-lint:disable SelectorDepth
+// scss-lint:disable NestingDepth
+// scss-lint:disable SelectorFormat
+// scss-lint:disable ImportantRule
+// scss-lint:disable IdSelector
+
+@import "constants";
+@import "mixins";
+
+// MarvinJs modal
+.modal-marvin-js {
+ background: transparent;
+ font-size: $font-size-large;
+ padding: 0 !important;
+
+ .preview-close {
+ background: transparent;
+ border: 0;
+ color: $color-white;
+ display: inline-block;
+ float: right;
+ }
+
+ .modal-dialog {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ width: auto;
+ }
+
+ .modal-content {
+ background: transparent;
+ border: 0;
+ box-shadow: none;
+ color: $color-white;
+ height: 100%;
+ width: auto;
+ }
+
+ .modal-header {
+ background: $color-black;
+ border: 0;
+ height: 60px;
+ line-height: 40px;
+ padding: 10px 15px;
+ text-align: center;
+
+ .file-save-link {
+ margin: 0 20px 0 0;
+ }
+
+ .file-name {
+ align-items: center;
+ display: flex;
+ float: left;
+
+ input {
+ border-radius: 5px;
+ box-shadow: none;
+ color: $color-black;
+ height: 40px;
+ margin-left: 5px;
+ outline: 0;
+ padding: 5px 10px;
+ position: relative;
+ width: 350px;
+ }
+ }
+ }
+
+ .modal-body {
+ height: calc(100% - 60px);
+ padding: 0;
+
+ #marvinjs-editor {
+ height: 100%;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+
+ #marvinjs-sketch {
+ border-right: 1px solid $color-gainsboro;
+ float: left;
+ min-height: 450px;
+ min-width: 500px;
+ overflow: hidden;
+ }
+ }
+
+ .sketch-container {
+ @include md-card-style;
+ cursor: pointer;
+ margin: 10px;
+ overflow: hidden;
+ padding: 10px;
+ position: relative;
+
+ .sketch-image {
+ height: 100%;
+ width: 100%;
+ }
+
+ .sketch-name {
+ color: $brand-primary;
+ font-family: Lato;
+ font-size: 16px;
+ line-height: 18px;
+ margin: 10px auto;
+ overflow: hidden;
+ text-align: center;
+ width: 160px;
+ }
+
+ .sketch-object {
+ color: $color-emperor;
+ font-size: 12px;
+ opacity: .6;
+ text-align: center;
+ }
+ }
+ }
+
+ .file-save-link {
+ color: $color-white;
+ cursor: pointer;
+ display: inline-block;
+ float: right;
+ margin-right: 20px;
+ }
+}
+
+#new-step-sketch {
+
+ .sketch-container {
+ display: grid;
+ float: left;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ width: 100%;
+ }
+}
+
+.new-marvinjs-upload-button {
+
+ .new-marvinjs-upload-icon {
+ display: inline-block;
+ height: 22px;
+ width: 22px;
+
+ img {
+ height: 100%;
+ width: 100%;
+ }
+ }
+}
+
+.mce-i-marvinjs::before {
+ background-image: url("icon_small/marvinjs.svg");
+ content: "";
+ display: block;
+ height: 22px;
+ left: -3px;
+ line-height: 16px;
+ position: relative;
+ top: -3px;
+ width: 22px;
+}
diff --git a/app/assets/stylesheets/my_modules/protocols/index.scss b/app/assets/stylesheets/my_modules/protocols/index.scss
index 5fee791f3..f9364332b 100644
--- a/app/assets/stylesheets/my_modules/protocols/index.scss
+++ b/app/assets/stylesheets/my_modules/protocols/index.scss
@@ -239,3 +239,49 @@
border-left: 0;
border-radius: 0 5px 5px 0;
}
+
+.my-module-recent-protocols {
+ flex-grow: 1;
+ margin-bottom: 5px;
+ position: relative;
+
+ .btn-group {
+ align-items: center;
+ display: flex;
+ float: right;
+ height: 33px;
+ }
+
+ .title {
+ font-size: 14px;
+ }
+
+ .dropdown-button {
+ cursor: pointer;
+ padding: 10px 5px;
+ }
+
+ .dropdown-menu {
+ left: auto;
+ padding: 0;
+ right: 0;
+ width: 402px;
+
+ .protocol {
+ cursor: pointer;
+ display: inline-block;
+ float: left;
+ padding: 5px 10px;
+ transition: $md-transaction;
+ width: 200px;
+
+ &:hover {
+ background: $color-gainsboro;
+ }
+
+ .fas {
+ margin-right: 5px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/my_modules/results/index.scss b/app/assets/stylesheets/my_modules/results/index.scss
new file mode 100644
index 000000000..9fe221dc4
--- /dev/null
+++ b/app/assets/stylesheets/my_modules/results/index.scss
@@ -0,0 +1,17 @@
+// scss-lint:disable SelectorDepth
+// scss-lint:disable NestingDepth
+// scss-lint:disable SelectorFormat
+// scss-lint:disable ImportantRule
+
+@import "constants";
+@import "mixins";
+
+#results-toolbar {
+ .help_tooltips {
+ .btn-default {
+ border: 0;
+ color: inherit;
+ margin-left: 10px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/reports.scss b/app/assets/stylesheets/reports.scss
index e03d04417..e939cc080 100644
--- a/app/assets/stylesheets/reports.scss
+++ b/app/assets/stylesheets/reports.scss
@@ -66,10 +66,6 @@ label {
height: auto !important;
width: auto !important;
}
-
- .ht_clone_top,.ht_clone_left,.ht_clone_corner {
- display: none !important;
- }
}
diff --git a/app/assets/stylesheets/shared/comments.scss b/app/assets/stylesheets/shared/comments.scss
index b0746a06b..90e837e25 100644
--- a/app/assets/stylesheets/shared/comments.scss
+++ b/app/assets/stylesheets/shared/comments.scss
@@ -144,6 +144,21 @@
}
}
+ // Looks like PDF has some specail CSS rules, here is some hack
+ &.report {
+ display: block;
+ float: left;
+ width: 100%;
+
+ .avatar-placehodler {
+ float: left;
+ }
+
+ .content-placeholder {
+ float: left;
+ }
+ }
+
&[data-edit-mode="0"]:hover,
&[data-edit-mode="1"] {
.comment-right {
@@ -224,13 +239,16 @@
.new-comment-button {
cursor: pointer;
- font-size: 18px;
+ font-size: 14px;
line-height: 18px;
- margin: 8px;
+ margin: 4px;
+ padding: 4px;
position: absolute;
right: -36px;
+ text-align: center;
top: 0;
transition: $md-transaction;
+ width: 26px;
&.show {
right: 0;
diff --git a/app/assets/stylesheets/steps.scss b/app/assets/stylesheets/steps.scss
index 17806d96b..caa76e0e6 100644
--- a/app/assets/stylesheets/steps.scss
+++ b/app/assets/stylesheets/steps.scss
@@ -124,6 +124,35 @@
.pseudo-attachment-container {
display: flex;
justify-content: center;
+ overflow: hidden;
+
+ .file-preview-link {
+ position: relative;
+ }
+
+ &.new {
+ order: 0 !important;
+
+ .file-preview-link {
+ transition: .5s;
+ }
+
+ .attachment-placeholder {
+ border: 1px solid $brand-primary;
+
+ &::before {
+ background: $brand-primary;
+ border-radius: 0 5px;
+ bottom: 16px;
+ color: $color-white;
+ content: "NEW";
+ left: 8px;
+ line-height: 20px;
+ position: absolute;
+ width: 50px;
+ }
+ }
+ }
}
}
@@ -131,10 +160,14 @@
@include md-card-style;
color: $color-silver-chalice;
height: 280px;
- margin: 8px;
+ margin: 4px 8px 16px;
text-align: center;
width: 220px;
+ .attachment-thumbnail {
+ width: calc(100% - 20px);
+ }
+
a {
color: inherit;
}
@@ -183,6 +216,7 @@
.remove-icon {
bottom: 15px;
+ cursor: pointer;
display: none;
position: relative;
right: 10px;
diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss
index 276de21df..87a233770 100644
--- a/app/assets/stylesheets/themes/scinote.scss
+++ b/app/assets/stylesheets/themes/scinote.scss
@@ -919,6 +919,7 @@ ul.content-activities {
align-items: center;
display: flex;
flex-wrap: wrap;
+ margin-bottom: 5px;
.protocol-button {
margin-bottom: 5px;
diff --git a/app/assets/stylesheets/tiny_mce.scss b/app/assets/stylesheets/tiny_mce.scss
index f69adf97c..57eee9a8f 100644
--- a/app/assets/stylesheets/tiny_mce.scss
+++ b/app/assets/stylesheets/tiny_mce.scss
@@ -5,8 +5,11 @@
border: solid 1px;
border-color: $color-white;
border-radius: 3px;
+ float: left;
+ margin-bottom: 10px;
min-height: 100px;
padding: 3px;
+ width: 100%;
&:hover {
border-color: $color-gainsboro;
@@ -63,4 +66,50 @@
.mce-toolbar {
background: $color-white !important;
}
+
+.mce-stack-layout {
+ .tinymce-active-object-handler {
+ border-top: 1px solid rgb(226, 228, 231);
+ height: 33px;
+ width: 100%;
+
+ .tool-button {
+ border: 1px solid transparent;
+ cursor: pointer;
+ display: inline-block;
+ line-height: 27px;
+ margin: 2px;
+ text-align: center;
+ width: 30px;
+
+ &:hover {
+ border: 1px solid rgb(226, 228, 231);
+ }
+ }
+
+ .mce-i-donwload::before {
+ content: "\F019";
+ font-family: "Font Awesome 5 Free";
+ font-weight: 900;
+ line-height: 16px;
+ position: absolute;
+ }
+
+ .mce-i-pencil::before {
+ content: "\F303";
+ font-family: "Font Awesome 5 Free";
+ font-weight: 900;
+ line-height: 16px;
+ position: absolute;
+ }
+
+ .mce-i-image::before {
+ content: "\F03E";
+ font-family: "Font Awesome 5 Free";
+ font-weight: 900;
+ line-height: 16px;
+ position: absolute;
+ }
+ }
+}
// scss-lint:enable ImportantRule
diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb
index f3c4c5211..da3bde03f 100644
--- a/app/controllers/assets_controller.rb
+++ b/app/controllers/assets_controller.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
class AssetsController < ApplicationController
include WopiUtil
+ include AssetsActions
# include ActionView::Helpers
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::TextHelper
@@ -10,14 +13,14 @@ class AssetsController < ApplicationController
include FileIconsHelper
before_action :load_vars, except: :create_wopi_file
+
before_action :check_read_permission
before_action :check_edit_permission, only: :edit
def file_preview
response_json = {
'id' => @asset.id,
- 'type' => (@asset.image? ? 'image' : 'file'),
-
+ 'type' => @asset.file.metadata[:asset_type] || (@asset.image? ? 'image' : 'file'),
'filename' => truncate(escape_input(@asset.file_name),
length: Constants::FILENAME_TRUNCATION_LENGTH),
'download-url' => download_asset_path(@asset, timestamp: Time.now.to_i)
@@ -30,17 +33,25 @@ class AssetsController < ApplicationController
elsif @assoc.class == RepositoryCell
can_manage_repository_rows?(@repository.team)
end
-
- if @asset.image?
- if ['image/jpeg', 'image/pjpeg'].include? @asset.content_type
+ if response_json['type'] == 'image'
+ if ['image/jpeg', 'image/pjpeg'].include? @asset.file.content_type
response_json['quality'] = @asset.file_image_quality || 90
end
response_json.merge!(
'editable' => @asset.editable_image? && can_edit,
'mime-type' => @asset.file.content_type,
- 'large-preview-url' => @asset.large_preview
+ 'large-preview-url' => rails_representation_url(@asset.large_preview)
+ )
+ elsif response_json['type'] == 'marvinjs'
+ response_json.merge!(
+ 'editable' => can_edit,
+ 'large-preview-url' => rails_representation_url(@asset.large_preview),
+ 'update-url' => marvin_js_asset_path(@asset.id),
+ 'description' => @asset.file.metadata[:description],
+ 'name' => @asset.file.metadata[:name]
)
else
+
response_json['preview-icon'] = render_to_string(partial: 'shared/file_preview_icon.html.erb',
locals: { asset: @asset })
end
@@ -115,6 +126,10 @@ class AssetsController < ApplicationController
render layout: false
end
+ def create_start_edit_image_activity
+ create_edit_image_activity(@asset, current_user, :start_editing)
+ end
+
def update_image
@asset = Asset.find(params[:id])
orig_file_size = @asset.file_size
@@ -123,6 +138,7 @@ class AssetsController < ApplicationController
@asset.file.attach(io: params.require(:image), filename: orig_file_name)
@asset.save!
+ create_edit_image_activity(@asset, current_user, :finish_editing)
# release previous image space
@asset.team.release_space(orig_file_size)
# Post process file here
diff --git a/app/controllers/concerns/assets_actions.rb b/app/controllers/concerns/assets_actions.rb
new file mode 100644
index 000000000..de32d2f6b
--- /dev/null
+++ b/app/controllers/concerns/assets_actions.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module AssetsActions
+ extend ActiveSupport::Concern
+
+ def create_edit_image_activity(asset, current_user, started_editing)
+ action = if started_editing == :start_editing
+ t('activities.file_editing.started')
+ elsif started_editing == :finish_editing
+ t('activities.file_editing.finished')
+ end
+ if asset.step.class == Step
+ protocol = asset.step.protocol
+ default_step_items =
+ { step: asset.step.id,
+ step_position: { id: asset.step.id, value_for: 'position_plus_one' },
+ asset_name: { id: asset.id, value_for: 'file_file_name' },
+ action: action }
+ if protocol.in_module?
+ project = protocol.my_module.experiment.project
+ team = project.team
+ type_of = :edit_image_on_step
+ message_items = { my_module: protocol.my_module.id }
+ else
+ type_of = :edit_image_on_step_in_repository
+ project = nil
+ team = protocol.team
+ message_items = { protocol: protocol.id }
+ end
+ message_items = default_step_items.merge(message_items)
+ Activities::CreateActivityService
+ .call(activity_type: type_of,
+ owner: current_user,
+ subject: protocol,
+ team: team,
+ project: project,
+ message_items: message_items)
+ elsif asset.result.class == Result
+ my_module = asset.result.my_module
+ Activities::CreateActivityService
+ .call(activity_type: :edit_image_on_result,
+ owner: current_user,
+ subject: asset.result,
+ team: my_module.experiment.project.team,
+ project: my_module.experiment.project,
+ message_items: {
+ result: asset.result.id,
+ asset_name: { id: asset.id, value_for: 'file_file_name' },
+ action: action
+ })
+ end
+ end
+end
diff --git a/app/controllers/marvin_js_assets_controller.rb b/app/controllers/marvin_js_assets_controller.rb
new file mode 100644
index 000000000..83a7aeb56
--- /dev/null
+++ b/app/controllers/marvin_js_assets_controller.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+class MarvinJsAssetsController < ApplicationController
+ before_action :load_vars, except: :create
+ before_action :load_create_vars, only: :create
+
+ before_action :check_read_permission
+ before_action :check_edit_permission, only: %i(update create)
+
+ def create
+ result = MarvinJsService.create_sketch(marvin_params, current_user, current_team)
+ if result[:asset] && marvin_params[:object_type] == 'Step'
+ render json: {
+ html: render_to_string(
+ partial: 'steps/attachments/item.html.erb',
+ locals: { asset: result[:asset],
+ i: 0,
+ assets_count: 0,
+ step: result[:object],
+ order_atoz: 0,
+ order_ztoa: 0 }
+ )
+ }
+ elsif result[:asset] && marvin_params[:object_type] == 'Result'
+ @my_module = result[:object].my_module
+ render json: {
+ html: render_to_string(
+ partial: 'my_modules/result.html.erb',
+ locals: { result: result[:object] }
+ )
+ }, status: :ok
+ elsif result[:asset]
+ render json: result[:asset]
+ else
+ render json: result[:asset].errors, status: :unprocessable_entity
+ end
+ end
+
+ def update
+ asset = MarvinJsService.update_sketch(marvin_params, current_user, current_team)
+ if asset
+ render json: { url: rails_representation_url(asset.medium_preview), id: asset.id, file_name: asset.file_name }
+ else
+ render json: { error: t('marvinjs.no_sketches_found') }, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def load_vars
+ @asset = current_team.assets.find_by_id(params[:id])
+ return render_404 unless @asset
+
+ @assoc ||= @asset.step
+ @assoc ||= @asset.result
+
+ if @assoc.class == Step
+ @protocol = @assoc.protocol
+ elsif @assoc.class == Result
+ @my_module = @assoc.my_module
+ end
+ end
+
+ def load_create_vars
+ @assoc = Step.find_by_id(marvin_params[:object_id]) if marvin_params[:object_type] == 'Step'
+ @assoc = MyModule.find_by_id(params[:object_id]) if marvin_params[:object_type] == 'Result'
+
+ if @assoc.class == Step
+ @protocol = @assoc.protocol
+ elsif @assoc.class == MyModule
+ @my_module = @assoc
+ end
+ end
+
+ def check_read_permission
+ if @assoc.class == Step
+ return render_403 unless can_read_protocol_in_module?(@protocol) ||
+ can_read_protocol_in_repository?(@protocol)
+ elsif @assoc.class == Result || @assoc.class == MyModule
+ return render_403 unless can_read_experiment?(@my_module.experiment)
+ else
+ render_403
+ end
+ end
+
+ def check_edit_permission
+ if @assoc.class == Step
+ return render_403 unless can_manage_protocol_in_module?(@protocol) ||
+ can_manage_protocol_in_repository?(@protocol)
+ elsif @assoc.class == Result || @assoc.class == MyModule
+ return render_403 unless can_manage_module?(@my_module)
+ else
+ render_403
+ end
+ end
+
+ def marvin_params
+ params.permit(:id, :description, :object_id, :object_type, :name, :image)
+ end
+end
diff --git a/app/controllers/my_modules_controller.rb b/app/controllers/my_modules_controller.rb
index ad876c878..63287a0da 100644
--- a/app/controllers/my_modules_controller.rb
+++ b/app/controllers/my_modules_controller.rb
@@ -278,6 +278,11 @@ class MyModulesController < ApplicationController
def protocols
@protocol = @my_module.protocol
+ @recent_protcols_positive = Protocol.recent_protocols(
+ current_user,
+ current_team,
+ Constants::RECENT_PROTOCOL_LIMIT
+ ).any?
current_team_switch(@protocol.team)
end
diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb
index 7997576d7..c35cf182b 100644
--- a/app/controllers/protocols_controller.rb
+++ b/app/controllers/protocols_controller.rb
@@ -106,6 +106,14 @@ class ProtocolsController < ApplicationController
end
end
+ def recent_protocols
+ render json: Protocol.recent_protocols(
+ current_user,
+ current_team,
+ Constants::RECENT_PROTOCOL_LIMIT
+ ).select(:id, :name)
+ end
+
def linked_children
respond_to do |format|
format.json do
diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb
index 138dbca40..d0fcd9040 100644
--- a/app/controllers/steps_controller.rb
+++ b/app/controllers/steps_controller.rb
@@ -3,12 +3,11 @@ class StepsController < ApplicationController
include ApplicationHelper
include StepsActions
- before_action :load_vars, only: %i(edit update destroy show toggle_step_state
- checklistitem_state)
+ before_action :load_vars, only: %i(edit update destroy show toggle_step_state checklistitem_state update_view_state)
before_action :load_vars_nested, only: [:new, :create]
before_action :convert_table_contents_to_utf8, only: [:create, :update]
- before_action :check_view_permissions, only: [:show]
+ before_action :check_view_permissions, only: %i(show update_view_state)
before_action :check_manage_permissions, only: %i(new create edit update
destroy)
before_action :check_complete_and_checkbox_permissions, only:
@@ -214,6 +213,17 @@ class StepsController < ApplicationController
end
end
+ def update_view_state
+ view_state = @step.current_view_state(current_user)
+ view_state.state['assets']['sort'] = params.require(:assets).require(:order)
+ view_state.save! if view_state.changed?
+ respond_to do |format|
+ format.json do
+ render json: {}, status: :ok
+ end
+ end
+ end
+
def destroy
if @step.can_destroy?
# Update position on other steps of this module
@@ -617,7 +627,11 @@ class StepsController < ApplicationController
:name,
:contents,
:_destroy
- ]
+ ],
+ marvin_js_assets_attributes: %i(
+ id
+ _destroy
+ )
)
end
diff --git a/app/controllers/tiny_mce_assets_controller.rb b/app/controllers/tiny_mce_assets_controller.rb
index 2e64a4248..2d3b0d870 100644
--- a/app/controllers/tiny_mce_assets_controller.rb
+++ b/app/controllers/tiny_mce_assets_controller.rb
@@ -1,6 +1,11 @@
# frozen_string_literal: true
class TinyMceAssetsController < ApplicationController
+ before_action :load_vars, only: %i(marvinjs_show marvinjs_update download)
+
+ before_action :check_read_permission, only: %i(marvinjs_show marvinjs_update download)
+ before_action :check_edit_permission, only: %i(marvinjs_update)
+
def create
image = params.fetch(:file) { render_404 }
tiny_img = TinyMceAsset.new(team_id: current_team.id, saved: false)
@@ -23,4 +28,91 @@ class TinyMceAssetsController < ApplicationController
}, status: :unprocessable_entity
end
end
+
+ def download
+ if @asset&.image&.attached?
+ redirect_to rails_blob_path(@asset.image, disposition: 'attachment')
+ else
+ render_404
+ end
+ end
+
+ def marvinjs_show
+ asset = current_team.tiny_mce_assets.find_by_id(Base62.decode(params[:id]))
+ return render_404 unless asset
+
+ render json: {
+ name: asset.image.metadata[:name],
+ description: asset.image.metadata[:description]
+ }
+ end
+
+ def marvinjs_create
+ result = MarvinJsService.create_sketch(marvin_params, current_user, current_team)
+ if result[:asset]
+ render json: {
+ image: {
+ url: rails_representation_url(result[:asset].preview),
+ token: Base62.encode(result[:asset].id),
+ source_type: result[:asset].image.metadata[:asset_type]
+ }
+ }, content_type: 'text/html'
+ else
+ render json: result[:asset].errors, status: :unprocessable_entity
+ end
+ end
+
+ def marvinjs_update
+ asset = MarvinJsService.update_sketch(marvin_params, current_user, current_team)
+ if asset
+ render json: { url: rails_representation_url(asset.preview), id: asset.id }
+ else
+ render json: { error: t('marvinjs.no_sketches_found') }, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def load_vars
+ @asset = current_team.tiny_mce_assets.find_by_id(Base62.decode(params[:id]))
+ return render_404 unless @asset
+
+ @assoc = @asset.object
+
+ if @assoc.class == Step
+ @protocol = @assoc.protocol
+ elsif @assoc.class == Protocol
+ @protocol = @assoc
+ elsif @assoc.class == MyModule
+ @my_module = @assoc
+ elsif @assoc.class == ResultText
+ @my_module = @assoc.result.my_module
+ end
+ end
+
+ def check_read_permission
+ if @assoc.class == Step || @assoc.class == Protocol
+ return render_403 unless can_read_protocol_in_module?(@protocol) ||
+ can_read_protocol_in_repository?(@protocol)
+ elsif @assoc.class == ResultText || @assoc.class == MyModule
+ return render_403 unless can_read_experiment?(@my_module.experiment)
+ else
+ render_403
+ end
+ end
+
+ def check_edit_permission
+ if @assoc.class == Step || @assoc.class == Protocol
+ return render_403 unless can_manage_protocol_in_module?(@protocol) ||
+ can_manage_protocol_in_repository?(@protocol)
+ elsif @assoc.class == ResultText || @assoc.class == MyModule
+ return render_403 unless can_manage_module?(@my_module)
+ else
+ render_403
+ end
+ end
+
+ def marvin_params
+ params.permit(:id, :description, :object_id, :object_type, :name, :image)
+ end
end
diff --git a/app/helpers/my_modules_helper.rb b/app/helpers/my_modules_helper.rb
index f91672d89..698015546 100644
--- a/app/helpers/my_modules_helper.rb
+++ b/app/helpers/my_modules_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MyModulesHelper
def ordered_step_of(my_module)
my_module.protocol.steps.order(:position)
@@ -8,7 +10,15 @@ module MyModulesHelper
end
def ordered_assets(step)
- step.assets.order(:file_updated_at)
+ view_state = step.current_view_state(current_user)
+ sort = case view_state.state.dig('assets', 'sort')
+ when 'old' then { created_at: :asc }
+ when 'atoz' then { file_file_name: :asc }
+ when 'ztoa' then { file_file_name: :desc }
+ else { created_at: :desc }
+ end
+
+ step.assets.order(sort)
end
def az_ordered_assets_index(step, asset_id)
@@ -39,11 +49,10 @@ module MyModulesHelper
end
def is_steps_page?
- action_name == "steps"
+ action_name == 'steps'
end
def is_results_page?
- action_name == "results"
+ action_name == 'results'
end
-
end
diff --git a/app/helpers/protocol_status_helper.rb b/app/helpers/protocol_status_helper.rb
index eb074241a..71cdb8fce 100644
--- a/app/helpers/protocol_status_helper.rb
+++ b/app/helpers/protocol_status_helper.rb
@@ -7,7 +7,7 @@ module ProtocolStatusHelper
res << 'data-trigger="focus" data-placement="bottom" title="'
res << protocol_status_popover_title(parent) +
'" data-content="' + protocol_status_popover_content(parent) +
- '">' + protocol_name(parent) + ''
+ '">' + protocol_name(parent).truncate(Constants::NAME_TRUNCATION_LENGTH) + ''
res.html_safe
end
diff --git a/app/models/asset.rb b/app/models/asset.rb
index 7c1faca30..7c664cfc0 100644
--- a/app/models/asset.rb
+++ b/app/models/asset.rb
@@ -241,7 +241,7 @@ class Asset < ApplicationRecord
download_blob_to_tempfile do |tmp_file|
to_asset.file.attach(io: tmp_file.open, filename: file_name)
end
- new_asset.post_process_file(new_asset.team)
+ to_asset.post_process_file(to_asset.team)
end
def extract_image_quality
@@ -257,7 +257,7 @@ class Asset < ApplicationRecord
end
def image?
- content_type == %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
+ content_type =~ %r{^image/#{Regexp.union(Constants::WHITELISTED_IMAGE_TYPES)}}
end
def text?
diff --git a/app/models/asset_text_datum.rb b/app/models/asset_text_datum.rb
index 30fa7bc6e..3b00cfe78 100644
--- a/app/models/asset_text_datum.rb
+++ b/app/models/asset_text_datum.rb
@@ -5,7 +5,7 @@ class AssetTextDatum < ApplicationRecord
validates :data, presence: true
validates :asset, presence: true, uniqueness: true
- belongs_to :asset, inverse_of: :asset_text_datum, optional: true
+ belongs_to :asset, inverse_of: :asset_text_datum
after_save :update_ts_index
diff --git a/app/models/checklist.rb b/app/models/checklist.rb
index 321ed514b..4150e1739 100644
--- a/app/models/checklist.rb
+++ b/app/models/checklist.rb
@@ -7,7 +7,7 @@ class Checklist < ApplicationRecord
length: { maximum: Constants::TEXT_MAX_LENGTH }
validates :step, presence: true
- belongs_to :step, inverse_of: :checklists, touch: true, optional: true
+ belongs_to :step, inverse_of: :checklists, touch: true
belongs_to :created_by,
foreign_key: 'created_by_id',
class_name: 'User',
diff --git a/app/models/checklist_item.rb b/app/models/checklist_item.rb
index bc9f7b4c3..d66c1d14c 100644
--- a/app/models/checklist_item.rb
+++ b/app/models/checklist_item.rb
@@ -7,8 +7,7 @@ class ChecklistItem < ApplicationRecord
validates :checked, inclusion: { in: [true, false] }
belongs_to :checklist,
- inverse_of: :checklist_items,
- optional: true
+ inverse_of: :checklist_items
belongs_to :created_by,
foreign_key: 'created_by_id',
class_name: 'User',
diff --git a/app/models/concerns/tiny_mce_images.rb b/app/models/concerns/tiny_mce_images.rb
index b5cc990f2..e469d1912 100644
--- a/app/models/concerns/tiny_mce_images.rb
+++ b/app/models/concerns/tiny_mce_images.rb
@@ -18,7 +18,7 @@ module TinyMceImages
description = TinyMceAsset.update_old_tinymce(description, self)
tiny_mce_assets.each do |tm_asset|
- tm_asset_key = tm_asset.image.preview.key
+ tm_asset_key = tm_asset.preview.key
encoded_tm_asset = Base64.strict_encode64(tm_asset.image.service.download(tm_asset_key))
new_tm_asset_src = "data:image/jpg;base64,#{encoded_tm_asset}"
html_description = Nokogiri::HTML(description)
@@ -73,7 +73,7 @@ module TinyMceImages
tiny_img_clone.transaction do
tiny_img_clone.save!
- tiny_img_clone.image.attach(io: tiny_img.image.open, filename: tiny_img.image.filename.to_s)
+ tiny_img.duplicate_file(tiny_img_clone)
end
target.tiny_mce_assets << tiny_img_clone
@@ -96,8 +96,6 @@ module TinyMceImages
next if asset && asset.object == self && asset.team_id != asset_team_id
- new_image = asset.image
- new_image_filename = new_image.file_name
else
# We need implement size and type checks here
new_image = URI.parse(image['src']).open
@@ -111,7 +109,11 @@ module TinyMceImages
new_asset.transaction do
new_asset.save!
- new_asset.image.attach(io: new_image, filename: new_image_filename)
+ if image['data-mce-token']
+ asset.duplicate_file(new_asset)
+ else
+ new_asset.image.attach(io: new_image, filename: new_image_filename)
+ end
end
image['src'] = ''
diff --git a/app/models/concerns/viewable_model.rb b/app/models/concerns/viewable_model.rb
index 5446fd198..731d7783c 100644
--- a/app/models/concerns/viewable_model.rb
+++ b/app/models/concerns/viewable_model.rb
@@ -7,6 +7,18 @@ module ViewableModel
has_many :view_states, as: :viewable, dependent: :destroy
end
+ # This module requres that the class which includes it implements these methods:
+ # => default_view_state, returning hash with default state representation
+ # => validate_view_state(view_state), custom validator for the state hash
+
+ def default_view_state
+ raise NotImplementedError, 'default_view_state should be implemented!'
+ end
+
+ def validate_view_state(_view_state)
+ raise NotImplementedError, 'validate_view_state(view_state) should be implemented!'
+ end
+
def current_view_state(user)
state = view_states.where(user: user).take
state || view_states.create!(user: user, state: default_view_state)
diff --git a/app/models/experiment.rb b/app/models/experiment.rb
index 5d5bbb40b..5410a8838 100644
--- a/app/models/experiment.rb
+++ b/app/models/experiment.rb
@@ -3,15 +3,13 @@ class Experiment < ApplicationRecord
include SearchableModel
include SearchableByNameModel
- belongs_to :project, inverse_of: :experiments, touch: true, optional: true
+ belongs_to :project, inverse_of: :experiments, touch: true
belongs_to :created_by,
foreign_key: :created_by_id,
- class_name: 'User',
- optional: true
+ class_name: 'User'
belongs_to :last_modified_by,
foreign_key: :last_modified_by_id,
- class_name: 'User',
- optional: true
+ class_name: 'User'
belongs_to :archived_by,
foreign_key: :archived_by_id, class_name: 'User', optional: true
belongs_to :restored_by,
@@ -225,6 +223,12 @@ class Experiment < ApplicationRecord
workflowimg.service.exist?(workflowimg.blob.key)
end
+ def workflowimg_file_name
+ return '' unless workflowimg.attached?
+
+ workflowimg.blob&.filename&.sanitized
+ end
+
# Get projects where user is either owner or user in the same team
# as this experiment
def projects_with_role_above_user(current_user)
diff --git a/app/models/my_module.rb b/app/models/my_module.rb
index d764eb14f..ed3da9b15 100644
--- a/app/models/my_module.rb
+++ b/app/models/my_module.rb
@@ -34,7 +34,7 @@ class MyModule < ApplicationRecord
foreign_key: 'restored_by_id',
class_name: 'User',
optional: true
- belongs_to :experiment, inverse_of: :my_modules, touch: true, optional: true
+ belongs_to :experiment, inverse_of: :my_modules, touch: true
belongs_to :my_module_group, inverse_of: :my_modules, optional: true
has_many :results, inverse_of: :my_module, dependent: :destroy
has_many :my_module_tags, inverse_of: :my_module, dependent: :destroy
diff --git a/app/models/my_module_group.rb b/app/models/my_module_group.rb
index 718c53fc0..6682ba6a0 100644
--- a/app/models/my_module_group.rb
+++ b/app/models/my_module_group.rb
@@ -3,7 +3,7 @@ class MyModuleGroup < ApplicationRecord
validates :experiment, presence: true
- belongs_to :experiment, inverse_of: :my_module_groups, optional: true
+ belongs_to :experiment, inverse_of: :my_module_groups
belongs_to :created_by,
foreign_key: 'created_by_id',
class_name: 'User',
diff --git a/app/models/my_module_repository_row.rb b/app/models/my_module_repository_row.rb
index c28ce10a0..f0d9eb674 100644
--- a/app/models/my_module_repository_row.rb
+++ b/app/models/my_module_repository_row.rb
@@ -4,10 +4,8 @@ class MyModuleRepositoryRow < ApplicationRecord
class_name: 'User',
optional: true
belongs_to :repository_row,
- optional: true,
inverse_of: :my_module_repository_rows
belongs_to :my_module,
- optional: true,
touch: true,
inverse_of: :my_module_repository_rows
diff --git a/app/models/my_module_tag.rb b/app/models/my_module_tag.rb
index 9dac83c2e..10963cec6 100644
--- a/app/models/my_module_tag.rb
+++ b/app/models/my_module_tag.rb
@@ -2,10 +2,10 @@ class MyModuleTag < ApplicationRecord
validates :my_module, :tag, presence: true
validates :tag_id, uniqueness: { scope: :my_module_id }
- belongs_to :my_module, inverse_of: :my_module_tags, optional: true
+ belongs_to :my_module, inverse_of: :my_module_tags
belongs_to :created_by,
foreign_key: 'created_by_id',
class_name: 'User',
optional: true
- belongs_to :tag, inverse_of: :my_module_tags, optional: true
+ belongs_to :tag, inverse_of: :my_module_tags
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 1ffd8983e..7b42f9d71 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -29,7 +29,7 @@ class Project < ApplicationRecord
foreign_key: 'restored_by_id',
class_name: 'User',
optional: true
- belongs_to :team, inverse_of: :projects, touch: true, optional: true
+ belongs_to :team, inverse_of: :projects, touch: true
has_many :user_projects, inverse_of: :project
has_many :users, through: :user_projects
has_many :experiments, inverse_of: :project
diff --git a/app/models/protocol.rb b/app/models/protocol.rb
index 942386d98..23542d4a4 100644
--- a/app/models/protocol.rb
+++ b/app/models/protocol.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Protocol < ApplicationRecord
include SearchableModel
include RenamingUtil
@@ -15,6 +17,12 @@ class Protocol < ApplicationRecord
in_repository_archived: 4
}
+ scope :recent_protocols, lambda { |user, team, amount|
+ where(team: team, protocol_type: :in_repository_public)
+ .or(where(team: team, protocol_type: :in_repository_private, added_by: user))
+ .order(updated_at: :desc).limit(amount)
+ }
+
auto_strip_attributes :name, :description, nullify: false
# Name is required when its actually specified (i.e. :in_repository? is true)
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
@@ -46,7 +54,7 @@ class Protocol < ApplicationRecord
protocol
.validates_uniqueness_of :name, case_sensitive: false,
scope: :team,
- conditions: -> {
+ conditions: lambda {
where(
protocol_type:
Protocol
@@ -59,8 +67,8 @@ class Protocol < ApplicationRecord
# Private protocol must have unique name inside its team & user scope
protocol
.validates_uniqueness_of :name, case_sensitive: false,
- scope: [:team, :added_by],
- conditions: -> {
+ scope: %i(team added_by),
+ conditions: lambda {
where(
protocol_type:
Protocol
@@ -72,8 +80,8 @@ class Protocol < ApplicationRecord
# Archived protocol must have unique name inside its team & user scope
protocol
.validates_uniqueness_of :name, case_sensitive: false,
- scope: [:team, :added_by],
- conditions: -> {
+ scope: %i(team added_by),
+ conditions: lambda {
where(
protocol_type:
Protocol
@@ -92,7 +100,7 @@ class Protocol < ApplicationRecord
belongs_to :my_module,
inverse_of: :protocols,
optional: true
- belongs_to :team, inverse_of: :protocols, optional: true
+ belongs_to :team, inverse_of: :protocols
belongs_to :parent,
foreign_key: 'parent_id',
class_name: 'Protocol',
@@ -244,7 +252,7 @@ class Protocol < ApplicationRecord
src.clone_tinymce_assets(dest, dest.team)
# Update keywords
- if clone_keywords then
+ if clone_keywords
src.protocol_keywords.each do |keyword|
ProtocolProtocolKeyword.create(
protocol: dest,
@@ -294,6 +302,7 @@ class Protocol < ApplicationRecord
step.assets.each do |asset|
asset2 = asset.dup
asset2.save!
+ asset.duplicate_file(asset2)
step2.assets << asset2
assets_to_clone << [asset.id, asset2.id]
end
@@ -362,7 +371,7 @@ class Protocol < ApplicationRecord
def space_taken
st = 0
- self.steps.find_each do |step|
+ steps.find_each do |step|
st += step.space_taken
end
st
@@ -437,7 +446,7 @@ class Protocol < ApplicationRecord
# Update all module protocols that had
# parent set to this protocol
- if result then
+ if result
Protocol.where(parent: self).find_each do |p|
p.update(
parent: nil,
@@ -466,7 +475,7 @@ class Protocol < ApplicationRecord
self.archived_on = nil
self.restored_by = user
self.restored_on = Time.now
- if self.published_on.present?
+ if published_on.present?
self.published_on = Time.now
self.protocol_type = Protocol.protocol_types[:in_repository_public]
else
@@ -491,15 +500,15 @@ class Protocol < ApplicationRecord
self.record_timestamps = false
# First, destroy all keywords
- self.protocol_protocol_keywords.destroy_all
+ protocol_protocol_keywords.destroy_all
if keywords.present?
keywords.each do |kw_name|
kw = ProtocolKeyword.find_or_create_by(name: kw_name, team: team)
- self.protocol_keywords << kw
+ protocol_keywords << kw
end
end
end
- rescue
+ rescue StandardError
result = false
end
result
@@ -509,7 +518,7 @@ class Protocol < ApplicationRecord
self.parent = nil
self.parent_updated_at = nil
self.protocol_type = Protocol.protocol_types[:unlinked]
- self.save!
+ save!
end
def update_parent(current_user)
@@ -518,16 +527,16 @@ class Protocol < ApplicationRecord
parent.reload
# Now, clone step contents
- Protocol.clone_contents(self, self.parent, current_user, false)
+ Protocol.clone_contents(self, parent, current_user, false)
# Lastly, update the metadata
parent.reload
parent.record_timestamps = false
- parent.updated_at = self.updated_at
+ parent.updated_at = updated_at
parent.save!
self.record_timestamps = false
- self.parent_updated_at = self.updated_at
- self.save!
+ self.parent_updated_at = updated_at
+ save!
end
def update_from_parent(current_user)
@@ -535,15 +544,15 @@ class Protocol < ApplicationRecord
destroy_contents(current_user)
# Now, clone parent's step contents
- Protocol.clone_contents(self.parent, self, current_user, false)
+ Protocol.clone_contents(parent, self, current_user, false)
# Lastly, update the metadata
- self.reload
+ reload
self.record_timestamps = false
- self.updated_at = self.parent.updated_at
- self.parent_updated_at = self.parent.updated_at
+ self.updated_at = parent.updated_at
+ self.parent_updated_at = parent.updated_at
self.added_by = current_user
- self.save!
+ save!
end
def load_from_repository(source, current_user)
@@ -554,14 +563,14 @@ class Protocol < ApplicationRecord
Protocol.clone_contents(source, self, current_user, false)
# Lastly, update the metadata
- self.reload
+ reload
self.record_timestamps = false
self.updated_at = source.updated_at
self.parent = source
self.parent_updated_at = source.updated_at
self.added_by = current_user
self.protocol_type = Protocol.protocol_types[:linked]
- self.save!
+ save!
end
def copy_to_repository(new_name, new_protocol_type, link_protocols, current_user)
@@ -570,29 +579,23 @@ class Protocol < ApplicationRecord
description: description,
protocol_type: new_protocol_type,
added_by: current_user,
- team: self.team
+ team: team
)
- if clone.in_repository_public?
- clone.published_on = Time.now
- end
+ clone.published_on = Time.now if clone.in_repository_public?
# Don't proceed further if clone is invalid
- if clone.invalid?
- return clone
- end
+ return clone if clone.invalid?
# Okay, clone seems to be valid: let's clone it
clone = deep_clone(clone, current_user)
# If the above operation went well, update published_on
# timestamp
- if clone.in_repository_public?
- clone.update(published_on: Time.now)
- end
+ clone.update(published_on: Time.now) if clone.in_repository_public?
# Link protocols if neccesary
- if link_protocols then
- self.reload
+ if link_protocols
+ reload
self.record_timestamps = false
self.added_by = current_user
self.parent = clone
@@ -600,23 +603,23 @@ class Protocol < ApplicationRecord
self.parent_updated_at = ts
self.updated_at = ts
self.protocol_type = Protocol.protocol_types[:linked]
- self.save!
+ save!
end
- return clone
+ clone
end
def deep_clone_my_module(my_module, current_user)
clone = Protocol.new_blank_for_module(my_module)
- clone.name = self.name
- clone.authors = self.authors
- clone.description = self.description
- clone.protocol_type = self.protocol_type
+ clone.name = name
+ clone.authors = authors
+ clone.description = description
+ clone.protocol_type = protocol_type
- if self.linked?
+ if linked?
clone.added_by = current_user
- clone.parent = self.parent
- clone.parent_updated_at = self.parent_updated_at
+ clone.parent = parent
+ clone.parent_updated_at = parent_updated_at
end
deep_clone(clone, current_user)
@@ -624,13 +627,13 @@ class Protocol < ApplicationRecord
def deep_clone_repository(current_user)
clone = Protocol.new(
- name: self.name,
- authors: self.authors,
- description: self.description,
+ name: name,
+ authors: authors,
+ description: description,
added_by: current_user,
- team: self.team,
- protocol_type: self.protocol_type,
- published_on: self.in_repository_public? ? Time.now : nil,
+ team: team,
+ protocol_type: protocol_type,
+ published_on: in_repository_public? ? Time.now : nil
)
cloned = deep_clone(clone, current_user)
@@ -653,19 +656,17 @@ class Protocol < ApplicationRecord
def destroy_contents(current_user)
# Calculate total space taken by the protocol
- st = self.space_taken
+ st = space_taken
steps.pluck(:id).each do |id|
- unless Step.find(id).destroy(current_user)
- raise ActiveRecord::RecordNotDestroyed
- end
+ raise ActiveRecord::RecordNotDestroyed unless Step.find(id).destroy(current_user)
end
# Release space taken by the step
- self.team.release_space(st)
- self.team.save
+ team.release_space(st)
+ team.save
# Reload protocol
- self.reload
+ reload
end
def can_destroy?
@@ -700,15 +701,14 @@ class Protocol < ApplicationRecord
p.record_timestamps = false
p.decrement!(:nr_of_linked_children)
end
- if self.parent_id != nil
- self.parent.record_timestamps = false
- self.parent.increment!(:nr_of_linked_children)
+ unless parent_id.nil?
+ parent.record_timestamps = false
+ parent.increment!(:nr_of_linked_children)
end
end
end
def decrement_linked_children
- self.parent.decrement!(:nr_of_linked_children) if self.parent.present?
+ parent.decrement!(:nr_of_linked_children) if parent.present?
end
-
end
diff --git a/app/models/report.rb b/app/models/report.rb
index ff8f9ecdb..f9f63db38 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -11,8 +11,8 @@ class Report < ApplicationRecord
validates :project, presence: true
validates :user, presence: true
- belongs_to :project, inverse_of: :reports, optional: true
- belongs_to :user, inverse_of: :reports, optional: true
+ belongs_to :project, inverse_of: :reports
+ belongs_to :user, inverse_of: :reports
belongs_to :team, inverse_of: :reports
belongs_to :last_modified_by,
foreign_key: 'last_modified_by_id',
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 618cbdc83..231ee7493 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -6,7 +6,7 @@ class Repository < ApplicationRecord
attribute :discarded_by_id, :integer
- belongs_to :team, optional: true
+ belongs_to :team
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
has_many :repository_columns, dependent: :destroy
has_many :repository_rows, dependent: :destroy
diff --git a/app/models/repository_asset_value.rb b/app/models/repository_asset_value.rb
index e1eb6e4c3..eb63be53e 100644
--- a/app/models/repository_asset_value.rb
+++ b/app/models/repository_asset_value.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryAssetValue < ApplicationRecord
belongs_to :created_by,
foreign_key: :created_by_id,
@@ -15,6 +17,9 @@ class RepositoryAssetValue < ApplicationRecord
validates :asset, :repository_cell, presence: true
+ SORTABLE_COLUMN_NAME = 'assets.file_file_name'
+ SORTABLE_VALUE_INCLUDE = { repository_asset_value: :asset }.freeze
+
def formatted
asset.file_name
end
@@ -28,8 +33,8 @@ class RepositoryAssetValue < ApplicationRecord
end
def update_data!(new_data, user)
- file.original_filename = new_data[:file_name]
- asset.file.attach(io: new_data[:file_data], filename: new_data[:file_name])
+ asset.file.attach(io: StringIO.new(Base64.decode64(new_data[:file_data].split(',')[1])),
+ filename: new_data[:file_name])
asset.last_modified_by = user
self.last_modified_by = user
asset.save! && save!
@@ -38,15 +43,15 @@ class RepositoryAssetValue < ApplicationRecord
def self.new_with_payload(payload, attributes)
value = new(attributes)
team = value.repository_cell.repository_column.repository.team
- file = Paperclip.io_adapters.for(payload[:file_data])
- file.original_filename = payload[:file_name]
value.asset = Asset.create!(
- file: file,
created_by: value.created_by,
last_modified_by: value.created_by,
team: team
)
- value.asset.post_process_file(team)
+ value.asset.file.attach(
+ io: StringIO.new(Base64.decode64(payload[:file_data].split(',')[1])),
+ filename: payload[:file_name]
+ )
value
end
end
diff --git a/app/models/repository_cell.rb b/app/models/repository_cell.rb
index 2ee710f0a..826f61e49 100644
--- a/app/models/repository_cell.rb
+++ b/app/models/repository_cell.rb
@@ -35,7 +35,7 @@ class RepositoryCell < ActiveRecord::Base
validates_inclusion_of :repository_column,
in: (lambda do |cell|
- cell.repository_row.repository.repository_columns
+ cell.repository_row&.repository&.repository_columns || []
end)
validates :repository_column, presence: true
validate :repository_column_data_type
diff --git a/app/models/repository_date_value.rb b/app/models/repository_date_value.rb
index fae44f6aa..6e6e9e692 100644
--- a/app/models/repository_date_value.rb
+++ b/app/models/repository_date_value.rb
@@ -9,6 +9,9 @@ class RepositoryDateValue < ApplicationRecord
validates :repository_cell, presence: true
validates :data, presence: true
+ SORTABLE_COLUMN_NAME = 'repository_date_values.data'
+ SORTABLE_VALUE_INCLUDE = :repository_date_value
+
def formatted
data
end
diff --git a/app/models/repository_list_value.rb b/app/models/repository_list_value.rb
index 77574d3da..1ef154bef 100644
--- a/app/models/repository_list_value.rb
+++ b/app/models/repository_list_value.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryListValue < ApplicationRecord
belongs_to :repository_list_item
belongs_to :created_by,
@@ -12,11 +14,12 @@ class RepositoryListValue < ApplicationRecord
validates :repository_cell, presence: true
validates_inclusion_of :repository_list_item,
in: (lambda do |list_value|
- list_value.repository_cell
- .repository_column
- .repository_list_items
+ list_value.repository_cell&.repository_column&.repository_list_items || []
end)
+ SORTABLE_COLUMN_NAME = 'repository_list_items.data'
+ SORTABLE_VALUE_INCLUDE = { repository_list_value: :repository_list_item }.freeze
+
def formatted
data.to_s
end
diff --git a/app/models/repository_text_value.rb b/app/models/repository_text_value.rb
index 65b5a5dc3..61eb31857 100644
--- a/app/models/repository_text_value.rb
+++ b/app/models/repository_text_value.rb
@@ -11,6 +11,9 @@ class RepositoryTextValue < ApplicationRecord
presence: true,
length: { maximum: Constants::TEXT_MAX_LENGTH }
+ SORTABLE_COLUMN_NAME = 'repository_text_values.data'
+ SORTABLE_VALUE_INCLUDE = :repository_text_value
+
def formatted
data
end
diff --git a/app/models/step.rb b/app/models/step.rb
index ee5111216..be4a269fa 100644
--- a/app/models/step.rb
+++ b/app/models/step.rb
@@ -2,6 +2,7 @@ class Step < ApplicationRecord
include SearchableModel
include SearchableByNameModel
include TinyMceImages
+ include ViewableModel
auto_strip_attributes :name, :description, nullify: false
validates :name,
@@ -65,6 +66,16 @@ class Step < ApplicationRecord
end
end
+ def default_view_state
+ { 'assets' => { 'sort' => 'new' } }
+ end
+
+ def validate_view_state(view_state)
+ unless %w(new old atoz ztoa).include?(view_state.state.dig('assets', 'sort'))
+ view_state.errors.add(:state, :wrong_state)
+ end
+ end
+
def destroy(current_user)
@current_user = current_user
diff --git a/app/models/team.rb b/app/models/team.rb
index ccd350add..9cfa780a7 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -41,6 +41,7 @@ class Team < ApplicationRecord
has_many :repositories, dependent: :destroy
has_many :reports, inverse_of: :team, dependent: :destroy
has_many :activities, inverse_of: :team, dependent: :destroy
+ has_many :assets, inverse_of: :team, dependent: :destroy
attr_accessor :without_templates
attr_accessor :without_intro_demo
@@ -57,6 +58,13 @@ class Team < ApplicationRecord
'filter' => 'active' } }
end
+ def validate_view_state(view_state)
+ unless %w(new old atoz ztoa).include?(view_state.state.dig('projects', 'cards', 'sort')) &&
+ %w(active archived).include?(view_state.state.dig('projects', 'filter'))
+ view_state.errors.add(:state, :wrong_state)
+ end
+ end
+
def search_users(query = nil)
a_query = "%#{query}%"
users.where.not(confirmed_at: nil)
diff --git a/app/models/tiny_mce_asset.rb b/app/models/tiny_mce_asset.rb
index 4178192b7..b7478ab95 100644
--- a/app/models/tiny_mce_asset.rb
+++ b/app/models/tiny_mce_asset.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class TinyMceAsset < ApplicationRecord
+ include ActiveStorage::Downloading
extend ProtocolsExporter
attr_accessor :reference
before_create :set_reference, optional: true
@@ -120,7 +121,8 @@ class TinyMceAsset < ApplicationRecord
new_format = "
"
asset = find_by_id(token)
- unless asset
+ # impor flag only for import from file cases, because we don't have image in DB
+ unless asset || import
# remove tag if asset deleted
description.sub!(old_format, '')
next
@@ -138,10 +140,9 @@ class TinyMceAsset < ApplicationRecord
if exists?
order(:id).each do |tiny_mce_asset|
asset_guid = get_guid(tiny_mce_asset.id)
- asset_file_name = "rte-#{asset_guid.to_s + tiny_mce_asset.image.blob.filename.extension}"
+ asset_file_name = "rte-#{asset_guid}.#{tiny_mce_asset.image.blob.filename.extension}"
ostream.put_next_entry("#{dir}/#{asset_file_name}")
ostream.print(tiny_mce_asset.image.download)
- input_file.close
end
end
ostream
@@ -170,7 +171,7 @@ class TinyMceAsset < ApplicationRecord
tiny_img_clone.transaction do
tiny_img_clone.save!
- tiny_img_clone.image.attach(io: image.download, filename: image.filename.sanitized)
+ duplicate_file(tiny_img_clone)
end
return false unless tiny_img_clone.persisted?
@@ -188,6 +189,17 @@ class TinyMceAsset < ApplicationRecord
obj.reassign_tiny_mce_image_references(cloned_img_ids)
end
+ def blob
+ image&.blob
+ end
+
+ def duplicate_file(to_asset)
+ download_blob_to_tempfile do |tmp_file|
+ to_asset.image.attach(io: tmp_file.open, filename: file_name)
+ end
+ TinyMceAsset.update_estimated_size(to_asset.id)
+ end
+
private
def self_destruct
diff --git a/app/models/user.rb b/app/models/user.rb
index 9698b474a..2d177f6dd 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -8,6 +8,7 @@ class User < ApplicationRecord
include User::ProjectRoles
include TeamBySubjectModel
include InputSanitizeHelper
+ include ActiveStorage::Downloading
acts_as_token_authenticatable
devise :invitable, :confirmable, :database_authenticatable, :registerable,
@@ -248,6 +249,8 @@ class User < ApplicationRecord
end
def avatar_variant(style)
+ return Constants::DEFAULT_AVATAR_URL.gsub(':style', style) unless avatar.attached?
+
format = case style.to_sym
when :medium
Constants::MEDIUM_PIC_FORMAT
@@ -563,6 +566,28 @@ class User < ApplicationRecord
.map { |i| { name: escape_input(i[:full_name]), id: i[:id] } }
end
+ def file_name
+ return '' unless avatar.attached?
+
+ avatar.blob&.filename&.sanitized
+ end
+
+ def avatar_base64(style)
+ unless avatar.present?
+ missing_link = File.open("#{Rails.root}/app/assets/images/#{style}/missing.png").to_a.join
+ return "data:image/png;base64,#{Base64.strict_encode64(missing_link)}"
+ end
+
+ avatar_uri = if avatar.options[:storage].to_sym == :s3
+ URI.parse(avatar.url(style)).open.to_a.join
+ else
+ File.open(avatar.path(style)).to_a.join
+ end
+
+ encoded_data = Base64.strict_encode64(avatar_uri)
+ "data:#{avatar_content_type};base64,#{encoded_data}"
+ end
+
protected
def confirmation_required?
diff --git a/app/models/view_state.rb b/app/models/view_state.rb
index 9d3905111..833ab538b 100644
--- a/app/models/view_state.rb
+++ b/app/models/view_state.rb
@@ -8,4 +8,13 @@ class ViewState < ApplicationRecord
scope: %i(viewable_type user_id),
message: :not_unique
}
+
+ validate :validate_state_content
+
+ private
+
+ def validate_state_content
+ return unless state.present?
+ viewable.validate_view_state(self)
+ end
end
diff --git a/app/models/zip_export.rb b/app/models/zip_export.rb
index 6706662a8..83e0f955f 100644
--- a/app/models/zip_export.rb
+++ b/app/models/zip_export.rb
@@ -49,7 +49,7 @@ class ZipExport < ApplicationRecord
end
def zip_file_name
- return '' unless file.attached?
+ return '' unless zip_file.attached?
zip_file.blob&.filename&.to_s
end
diff --git a/app/serializers/api/v1/repository_asset_value_serializer.rb b/app/serializers/api/v1/repository_asset_value_serializer.rb
index a64c49532..b55e08664 100644
--- a/app/serializers/api/v1/repository_asset_value_serializer.rb
+++ b/app/serializers/api/v1/repository_asset_value_serializer.rb
@@ -18,10 +18,10 @@ module Api
end
def url
- if !object.asset&.file&.exists?
+ if !object.asset&.file&.attached?
nil
else
- rails_blob_path(object.asset.file, disposition: 'attachment')
+ Rails.application.routes.url_helpers.rails_blob_path(object.asset.file, disposition: 'attachment')
end
end
end
diff --git a/app/serializers/api/v1/result_asset_serializer.rb b/app/serializers/api/v1/result_asset_serializer.rb
index d86e999f2..d3da45635 100644
--- a/app/serializers/api/v1/result_asset_serializer.rb
+++ b/app/serializers/api/v1/result_asset_serializer.rb
@@ -19,10 +19,10 @@ module Api
end
def url
- if !object.asset&.file&.exists?
+ if !object.asset&.file&.attached?
nil
else
- rails_blob_path(object.asset.file, disposition: 'attachment')
+ Rails.application.routes.url_helpers.rails_blob_path(object.asset.file, disposition: 'attachment')
end
end
end
diff --git a/app/serializers/api/v1/user_serializer.rb b/app/serializers/api/v1/user_serializer.rb
index 4b29121b2..22c3f262d 100644
--- a/app/serializers/api/v1/user_serializer.rb
+++ b/app/serializers/api/v1/user_serializer.rb
@@ -5,20 +5,20 @@ module Api
class UserSerializer < ActiveModel::Serializer
type :users
attributes :full_name, :initials, :email
- attribute :avatar_file_name, if: -> { object.avatar.present? }
- attribute :avatar_file_size, if: -> { object.avatar.present? }
- attribute :avatar_url, if: -> { object.avatar.present? }
+ attribute :avatar_file_name, if: -> { object.avatar.attached? }
+ attribute :avatar_file_size, if: -> { object.avatar.attached? }
+ attribute :avatar_url, if: -> { object.avatar.attached? }
def avatar_file_name
- object.avatar_file_name
+ object.avatar.blob.filename
end
def avatar_file_size
- object.avatar.size
+ object.avatar.blob.byte_size
end
def avatar_url
- object.avatar.url(:icon)
+ object.avatar_url(:icon)
end
end
end
diff --git a/app/services/marvin_js_service.rb b/app/services/marvin_js_service.rb
new file mode 100644
index 000000000..4b337ad86
--- /dev/null
+++ b/app/services/marvin_js_service.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+class MarvinJsService
+ class << self
+ def url
+ ENV['MARVINJS_URL']
+ end
+
+ def enabled?
+ !ENV['MARVINJS_URL'].nil? || !ENV['MARVINJS_API_KEY'].nil?
+ end
+
+ def create_sketch(params, current_user, current_team)
+ file = generate_image(params)
+ if params[:object_type] == 'TinyMceAsset'
+ asset = TinyMceAsset.new(team_id: current_team.id)
+ attach_file(asset.image, file, params)
+ asset.save!
+ return { asset: asset }
+ end
+
+ asset = Asset.new(created_by: current_user,
+ last_modified_by: current_user,
+ team_id: current_team.id)
+ attach_file(asset.file, file, params)
+ asset.save!
+ connect_asset(asset, params, current_user)
+ end
+
+ def update_sketch(params, _current_user, current_team)
+ if params[:object_type] == 'TinyMceAsset'
+ asset = current_team.tiny_mce_assets.find(Base62.decode(params[:id]))
+ attachment = asset&.image
+ else
+ asset = current_team.assets.find(params[:id])
+ attachment = asset&.file
+ end
+ return unless attachment
+
+ file = generate_image(params)
+ attach_file(attachment, file, params)
+ asset
+ end
+
+ private
+
+ def connect_asset(asset, params, current_user)
+ if params[:object_type] == 'Step'
+ object = params[:object_type].constantize.find(params[:object_id])
+ object.assets << asset
+ elsif params[:object_type] == 'Result'
+ my_module = MyModule.find_by_id(params[:object_id])
+ return unless my_module
+
+ object = Result.create(user: current_user,
+ my_module: my_module,
+ name: prepare_name(params[:name]),
+ asset: asset,
+ last_modified_by: current_user)
+ end
+ { asset: asset, object: object }
+ end
+
+ def generate_image(params)
+ StringIO.new(Base64.decode64(params[:image].split(',')[1]))
+ end
+
+ def attach_file(asset, file, params)
+ asset.attach(
+ io: file,
+ filename: "#{prepare_name(params[:name])}.jpg",
+ content_type: 'image/jpeg',
+ metadata: {
+ name: prepare_name(params[:name]),
+ description: params[:description],
+ asset_type: 'marvinjs'
+ }
+ )
+ end
+
+ def prepare_name(sketch_name)
+ if !sketch_name.empty?
+ sketch_name
+ else
+ I18n.t('marvinjs.new_sketch')
+ end
+ end
+ end
+end
diff --git a/app/services/model_exporters/experiment_exporter.rb b/app/services/model_exporters/experiment_exporter.rb
index 607a122a6..e5b63b298 100644
--- a/app/services/model_exporters/experiment_exporter.rb
+++ b/app/services/model_exporters/experiment_exporter.rb
@@ -67,10 +67,19 @@ module ModelExporters
{
result: result,
result_comments: result.result_comments,
- asset: result.asset,
+ asset: result_assets_data(result.asset),
table: table(result.table),
result_text: result.result_text
}
end
+
+ def result_assets_data(asset)
+ return unless asset&.file&.attached?
+
+ {
+ asset: asset,
+ asset_blob: asset.file.blob
+ }
+ end
end
end
diff --git a/app/services/model_exporters/model_exporter.rb b/app/services/model_exporters/model_exporter.rb
index 54c1fc09f..141572ce5 100644
--- a/app/services/model_exporters/model_exporter.rb
+++ b/app/services/model_exporters/model_exporter.rb
@@ -14,27 +14,21 @@ module ModelExporters
def copy_files(assets, attachment_name, dir_name)
assets.flatten.each do |a|
- next unless a.public_send(attachment_name).present?
+ next unless a.public_send(attachment_name).attached?
- unless a.public_send(attachment_name).exists?
- raise StandardError,
- "File id:#{a.id} of type #{attachment_name} is missing"
- end
yield if block_given?
dir = FileUtils.mkdir_p(File.join(dir_name, a.id.to_s)).first
- if defined?(S3_BUCKET)
- s3_asset =
- S3_BUCKET.object(a.public_send(attachment_name).path.remove(%r{^/}))
- file_name = a.public_send(attachment_name).original_filename
- File.open(File.join(dir, file_name), 'wb') do |f|
- s3_asset.get(response_target: f)
- end
- else
- FileUtils.cp(
- a.public_send(attachment_name).path,
- File.join(dir, a.public_send(attachment_name).original_filename)
- )
- end
+
+ tempfile = Tempfile.new
+ tempfile.binmode
+ a.public_send(attachment_name).blob.download { |chunk| tempfile.write(chunk) }
+ tempfile.flush
+ tempfile.rewind
+ FileUtils.cp(
+ tempfile.path,
+ File.join(dir, a.file_name)
+ )
+ tempfile.close!
end
end
@@ -57,12 +51,21 @@ module ModelExporters
checklists: step.checklists.map { |c| checklist(c) },
step_comments: step.step_comments,
step_assets: step.step_assets,
- assets: step.assets,
+ assets: step.assets.map { |a| assets_data(a) },
step_tables: step.step_tables,
tables: step.tables.map { |t| table(t) }
}
end
+ def assets_data(asset)
+ return unless asset.file.attached?
+
+ {
+ asset: asset,
+ asset_blob: asset.file.blob
+ }
+ end
+
def checklist(checklist)
{
checklist: checklist,
diff --git a/app/services/model_exporters/team_exporter.rb b/app/services/model_exporters/team_exporter.rb
index 8bab1de3b..dc09b1a72 100644
--- a/app/services/model_exporters/team_exporter.rb
+++ b/app/services/model_exporters/team_exporter.rb
@@ -39,9 +39,7 @@ module ModelExporters
private
def team(team)
- if team.tiny_mce_assets.present?
- @tiny_mce_assets_to_copy.push(team.tiny_mce_assets)
- end
+ @tiny_mce_assets_to_copy.push(team.tiny_mce_assets) if team.tiny_mce_assets.present?
{
team: team,
default_admin_id: team.user_teams.where(role: 2).first.user.id,
@@ -53,7 +51,7 @@ module ModelExporters
.map { |n| notification(n) },
custom_fields: team.custom_fields,
repositories: team.repositories.map { |r| repository(r) },
- tiny_mce_assets: team.tiny_mce_assets,
+ tiny_mce_assets: team.tiny_mce_assets.map { |tma| tiny_mce_asset_data(tma) },
protocols: team.protocols.where(my_module: nil).map do |pr|
protocol(pr)
end,
@@ -80,6 +78,7 @@ module ModelExporters
user_json['sign_in_count'] = user.sign_in_count
user_json['last_sign_in_at'] = user.last_sign_in_at
user_json['last_sign_in_ip'] = user.last_sign_in_ip
+ user_json['avatar'] = user.avatar.blob if user.avatar.attached?
copy_files([user], :avatar, File.join(@dir_to_export, 'avatars'))
{
user: user_json,
@@ -152,11 +151,21 @@ module ModelExporters
}
end
+ def tiny_mce_asset_data(asset)
+ {
+ tiny_mce_asset: asset,
+ tiny_mce_asset_blob: asset.image.blob
+ }
+ end
+
def get_cell_value_asset(cell)
return unless cell.value_type == 'RepositoryAssetValue'
@assets_to_copy.push(cell.value.asset)
- cell.value.asset
+ {
+ asset: cell.value.asset,
+ asset_blob: cell.value.asset.blob
+ }
end
end
end
diff --git a/app/services/projects_overview_service.rb b/app/services/projects_overview_service.rb
index 082c0b589..78edc42cd 100644
--- a/app/services/projects_overview_service.rb
+++ b/app/services/projects_overview_service.rb
@@ -84,7 +84,9 @@ class ProjectsOverviewService
).joins(
"LEFT OUTER JOIN (#{due_modules.to_sql}) due_modules "\
"ON due_modules.experiment_id = experiments.id"
- ).left_outer_joins(:user_projects, :project_comments)
+ ).joins(
+ 'LEFT OUTER JOIN user_projects ON user_projects.project_id = projects.id'
+ ).left_outer_joins(:project_comments)
# Only admins see all projects of the team
unless @user.is_admin_of_team?(@team)
diff --git a/app/services/repository_actions/duplicate_cell.rb b/app/services/repository_actions/duplicate_cell.rb
index e3f717fa1..6a2d51bf1 100644
--- a/app/services/repository_actions/duplicate_cell.rb
+++ b/app/services/repository_actions/duplicate_cell.rb
@@ -10,7 +10,7 @@ module RepositoryActions
end
def call
- self.send("duplicate_#{@cell.value_type.underscore}")
+ __send__("duplicate_#{@cell.value_type.split('::').last.underscore}")
end
private
diff --git a/app/services/repository_datatable_service.rb b/app/services/repository_datatable_service.rb
index 9a5e55160..1871c3ed4 100644
--- a/app/services/repository_datatable_service.rb
+++ b/app/services/repository_datatable_service.rb
@@ -119,9 +119,18 @@ class RepositoryDatatableService
end
elsif sortable_columns[column_id - 1] == 'repository_cell.value'
id = @mappings.key(column_id.to_s)
- type = RepositoryColumn.find_by_id(id)
- return records unless type
- return select_type(type.data_type, records, id, dir)
+ sorting_column = RepositoryColumn.find_by_id(id)
+ return records unless sorting_column
+
+ sorting_data_type = sorting_column.data_type.constantize
+
+ cells = RepositoryCell.joins(sorting_data_type::SORTABLE_VALUE_INCLUDE)
+ .where('repository_cells.repository_column_id': sorting_column.id)
+ .select("repository_cells.repository_row_id,
+ #{sorting_data_type::SORTABLE_COLUMN_NAME} AS value")
+
+ records.joins("LEFT OUTER JOIN (#{cells.to_sql}) AS values ON values.repository_row_id = repository_rows.id")
+ .order("values.value #{dir}")
elsif sortable_columns[column_id - 1] == 'users.full_name'
# We don't need join user table, because it already joined in fetch_row method
return records.order("users.full_name #{dir}")
@@ -151,19 +160,6 @@ class RepositoryDatatableService
records.order(order_by_index)
end
- def select_type(type, records, id, dir)
- case type
- when 'RepositoryTextValue'
- filter_by_text_value(records, id, dir)
- when 'RepositoryListValue'
- filter_by_list_value(records, id, dir)
- when 'RepositoryAssetValue'
- filter_by_asset_value(records, id, dir)
- else
- records
- end
- end
-
def sort_null_direction(val)
val == 'ASC' ? 'LAST' : 'FIRST'
end
diff --git a/app/services/repository_table_state_column_update_service.rb b/app/services/repository_table_state_column_update_service.rb
index 2598a7327..78d433f9f 100644
--- a/app/services/repository_table_state_column_update_service.rb
+++ b/app/services/repository_table_state_column_update_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryTableStateColumnUpdateService
# We're using Constants::REPOSITORY_TABLE_DEFAULT_STATE as a reference for
# default table state; this Ruby Hash makes heavy use of Ruby symbols
@@ -56,9 +58,7 @@ class RepositoryTableStateColumnUpdateService
state['order'].reject! { |_, v| v[0] == old_column_index }
state['order'].each do |k, v|
- if v[0].to_i > old_column_index.to_i
- state['order'][k] = [(v[0].to_i - 1).to_s, v[1]]
- end
+ state['order'][k] = [(v[0].to_i - 1).to_s, v[1]] if v[0].to_i > old_column_index.to_i
end
if state['order'].empty?
# Fallback to default order if user had table ordered by
@@ -73,4 +73,4 @@ class RepositoryTableStateColumnUpdateService
table_state.save
end
end
-end
\ No newline at end of file
+end
diff --git a/app/services/team_importer.rb b/app/services/team_importer.rb
index ecb3c7dc1..906243b9f 100644
--- a/app/services/team_importer.rb
+++ b/app/services/team_importer.rb
@@ -90,6 +90,7 @@ class TeamImporter
user_notification.notification_id =
@notification_mappings[user_notification.notification_id]
next if user_notification.notification_id.blank?
+
user_notification.save!
end
@@ -241,6 +242,7 @@ class TeamImporter
comment.save! if update_annotation(comment.message)
end
next unless res.result_text
+
res.save! if update_annotation(res.result_text.text)
end
end
@@ -250,6 +252,7 @@ class TeamImporter
# Returns true if text was updated
def update_annotation(text)
return false if text.nil?
+
updated = false
%w(prj exp tsk rep_item).each do |name|
text.scan(/~#{name}~\w+\]/).each do |text_match|
@@ -267,6 +270,7 @@ class TeamImporter
@repository_row_mappings[orig_id]
end
next unless new_id
+
new_id_encoded = new_id.base62_encode
text.sub!("~#{name}~#{orig_id_encoded}]", "~#{name}~#{new_id_encoded}]")
updated = true
@@ -276,6 +280,7 @@ class TeamImporter
orig_id_encoded = user_match.match(/\[@[\w+-@?! ]+~(\w+)\]/)[1]
orig_id = orig_id_encoded.base62_decode
next unless @user_mappings[orig_id]
+
new_id_encoded = @user_mappings[orig_id].base62_encode
text.sub!("~#{orig_id_encoded}]", "~#{new_id_encoded}]")
updated = true
@@ -327,11 +332,12 @@ class TeamImporter
def create_tiny_mce_assets(tmce_assets_json, team)
tmce_assets_json.each do |tiny_mce_asset_json|
- tiny_mce_asset = TinyMceAsset.new(tiny_mce_asset_json)
+ tiny_mce_asset = TinyMceAsset.new(tiny_mce_asset_json['tiny_mce_asset'])
+ tiny_mce_asset_blob = tiny_mce_asset_json['tiny_mce_asset_blob']
# Try to find and load file
File.open(
File.join(@import_dir, 'tiny_mce_assets', tiny_mce_asset.id.to_s,
- tiny_mce_asset.image_file_name)
+ tiny_mce_asset_blob['filename'])
) do |tiny_mce_file|
orig_tmce_id = tiny_mce_asset.id
tiny_mce_asset.id = nil
@@ -341,7 +347,7 @@ class TeamImporter
end
tiny_mce_asset.team = team
tiny_mce_asset.save!
- tiny_mce_asset.image.attach(io: tiny_mce_file, filename: tiny_mce_file.basename)
+ tiny_mce_asset.image.attach(io: tiny_mce_file, filename: File.basename(tiny_mce_file))
@mce_asset_counter += 1
if tiny_mce_asset.object_id.present?
object = tiny_mce_asset.object
@@ -368,12 +374,10 @@ class TeamImporter
user.password = user_json['user']['encrypted_password']
user.current_team_id = team.id
user.invited_by_id = @user_mappings[user.invited_by_id]
- if user.avatar.present?
+ if user_json['user']['avatar']
avatar_path = File.join(@import_dir, 'avatars', orig_user_id.to_s,
- user.avatar_file_name)
- if File.exist?(avatar_path)
- File.open(avatar_path) { |f| user.avatar = f }
- end
+ user_json['user']['avatar']['filename'])
+ File.open(avatar_path) { |f| user.avatar = f } if File.exist?(avatar_path)
end
user.save!
@user_counter += 1
@@ -394,6 +398,7 @@ class TeamImporter
notifications_json.each do |notification_json|
notification = Notification.new(notification_json)
next if notification.type_of.blank?
+
orig_notification_id = notification.id
notification.id = nil
notification.generator_user_id = find_user(notification.generator_user_id)
@@ -415,7 +420,6 @@ class TeamImporter
@repository_mappings[orig_repository_id] = repository.id
@repository_counter += 1
repository_json['repository_columns'].each do |repository_column_json|
-
repository_column = RepositoryColumn.new(
repository_column_json['repository_column']
)
@@ -427,6 +431,7 @@ class TeamImporter
repository_column.save!
@repository_column_mappings[orig_rep_col_id] = repository_column.id
next unless repository_column.data_type == 'RepositoryListValue'
+
repository_column_json['repository_list_items'].each do |list_item|
created_by_id = find_user(repository_column.created_by_id)
repository_list_item = RepositoryListItem.new(data: list_item['data'])
@@ -662,9 +667,7 @@ class TeamImporter
protocol.archived_by_id = find_user(protocol.archived_by_id)
protocol.restored_by_id = find_user(protocol.restored_by_id)
protocol.my_module = my_module unless protocol.my_module_id.nil?
- unless protocol.parent_id.nil?
- protocol.parent_id = @protocol_mappings[protocol.parent_id]
- end
+ protocol.parent_id = @protocol_mappings[protocol.parent_id] unless protocol.parent_id.nil?
protocol.save!
@protocol_counter += 1
@protocol_mappings[orig_protocol_id] = protocol.id
@@ -792,9 +795,10 @@ class TeamImporter
# returns asset object
def create_asset(asset_json, team, user_id = nil)
- asset = Asset.new(asset_json)
+ asset = Asset.new(asset_json['asset'])
+ asset_blob = asset_json['asset_blob']
File.open(
- "#{@import_dir}/assets/#{asset.id}/#{asset.file_name}"
+ "#{@import_dir}/assets/#{asset.id}/#{asset_blob['filename']}"
) do |file|
orig_asset_id = asset.id
asset.id = nil
@@ -804,7 +808,7 @@ class TeamImporter
asset.team = team
asset.in_template = true if @is_template
asset.save!
- asset.file.attach(io: file, filename: file.basename)
+ asset.file.attach(io: file, filename: File.basename(file))
asset.post_process_file(team)
@asset_mappings[orig_asset_id] = asset.id
@asset_counter += 1
@@ -864,22 +868,14 @@ class TeamImporter
report_element.my_module_id =
@my_module_mappings[report_element.my_module_id]
end
- if report_element.step_id
- report_element.step_id = @step_mappings[report_element.step_id]
- end
- if report_element.result_id
- report_element.result_id = @result_mappings[report_element.result_id]
- end
+ report_element.step_id = @step_mappings[report_element.step_id] if report_element.step_id
+ report_element.result_id = @result_mappings[report_element.result_id] if report_element.result_id
if report_element.checklist_id
report_element.checklist_id =
@checklist_mappings[report_element.checklist_id]
end
- if report_element.asset_id
- report_element.asset_id = @asset_mappings[report_element.asset_id]
- end
- if report_element.table_id
- report_element.table_id = @table_mappings[report_element.table_id]
- end
+ report_element.asset_id = @asset_mappings[report_element.asset_id] if report_element.asset_id
+ report_element.table_id = @table_mappings[report_element.table_id] if report_element.table_id
if report_element.experiment_id
report_element.experiment_id =
@experiment_mappings[report_element.experiment_id]
@@ -902,7 +898,8 @@ class TeamImporter
def find_user(user_id)
return nil if user_id.nil?
- @user_mappings[user_id] ? @user_mappings[user_id] : @admin_id
+
+ @user_mappings[user_id] || @admin_id
end
def find_list_item_id(list_item_id)
diff --git a/app/utilities/delayed_uploader_demo.rb b/app/utilities/delayed_uploader_demo.rb
index e8a5a9f80..998db58c0 100644
--- a/app/utilities/delayed_uploader_demo.rb
+++ b/app/utilities/delayed_uploader_demo.rb
@@ -1,14 +1,15 @@
+# frozen_string_literal: true
+
module DelayedUploaderDemo
# Get asset from demo_files folder
def self.get_asset(user, team, file_name)
- Asset.new(
- file: File.open(
- "#{Rails.root}/app/assets/demo_files/#{file_name}", 'r'
- ),
+ asset = Asset.create(
created_by: user,
team: team,
last_modified_by: user
)
+ asset.file.attach(io: File.open("#{Rails.root}/app/assets/demo_files/#{file_name}", 'r'), filename: file_name)
+ asset
end
# Generates results asset for given module, file_name assumes file is located
@@ -32,7 +33,6 @@ module DelayedUploaderDemo
)
temp_result.save
- temp_asset.save
# Generate comment if it exists
generate_result_comment(temp_result, current_user, comment) if comment
diff --git a/app/utilities/protocols_exporter.rb b/app/utilities/protocols_exporter.rb
index f63d31246..1c05dfa02 100644
--- a/app/utilities/protocols_exporter.rb
+++ b/app/utilities/protocols_exporter.rb
@@ -56,6 +56,7 @@ module ProtocolsExporter
"fileRef=\"#{asset_file_name}\">\n"
asset_xml << "#{img.file_name}\n"
asset_xml << "#{img.content_type}\n"
+ asset_xml << "\n"
asset_xml << "\n"
tiny_assets_xml << asset_xml
end
@@ -104,6 +105,7 @@ module ProtocolsExporter
"fileRef=\"#{asset_file_name}\">\n"
asset_xml << "#{asset.file_name}\n"
asset_xml << "#{asset.content_type}\n"
+ asset_xml << "\n"
asset_xml << "\n"
step_xml << asset_xml
end
diff --git a/app/utilities/protocols_importer.rb b/app/utilities/protocols_importer.rb
index 00726582d..c4debc036 100644
--- a/app/utilities/protocols_importer.rb
+++ b/app/utilities/protocols_importer.rb
@@ -111,7 +111,10 @@ module ProtocolsImporter
)
# Decode the file bytes
- asset.file.attach(io: StringIO.new(Base64.decode64(asset_json['bytes'])), filename: asset_json['fileName'])
+ asset.file.attach(io: StringIO.new(Base64.decode64(asset_json['bytes'])),
+ filename: asset_json['fileName'],
+ content_type: asset_json['fileType'],
+ metadata: JSON.parse(asset_json['fileMetadata'] || '{}'))
asset.save!
asset_ids << asset.id
@@ -143,7 +146,7 @@ module ProtocolsImporter
def populate_rte(object_json, object, team)
return populate_rte_legacy(object_json) unless object_json['descriptionAssets']
- description = TinyMceAsset.update_old_tinymce(object_json['description'])
+ description = TinyMceAsset.update_old_tinymce(object_json['description'], nil, true)
object_json['descriptionAssets'].values.each do |tiny_mce_img_json|
tiny_mce_img = TinyMceAsset.new(
object: object,
@@ -153,8 +156,11 @@ module ProtocolsImporter
tiny_mce_img.save!
# Decode the file bytes
- tiny_mce_img.image.attach(io: StringIO.new(Base64.decode64(tiny_mce_img_json['bytes'])),
- filename: tiny_mce_img_json['fileName'])
+ file = StringIO.new(Base64.decode64(tiny_mce_img_json['bytes']))
+ tiny_mce_img.image.attach(io: file,
+ filename: tiny_mce_img_json['fileName'],
+ content_type: tiny_mce_img_json['fileType'],
+ metadata: JSON.parse(tiny_mce_img_json['fileMetadata'] || '{}'))
if description.gsub!("data-mce-token=\"#{tiny_mce_img_json['tokenId']}\"",
"data-mce-token=\"#{Base62.encode(tiny_mce_img.id)}\"")
else
diff --git a/app/utilities/repository_import_parser/repository_cell_value_resolver.rb b/app/utilities/repository_import_parser/repository_cell_value_resolver.rb
index 08912f120..b312c019f 100644
--- a/app/utilities/repository_import_parser/repository_cell_value_resolver.rb
+++ b/app/utilities/repository_import_parser/repository_cell_value_resolver.rb
@@ -14,7 +14,7 @@ module RepositoryImportParser
def get_value(value, record_row)
return unless @column
- send("new_#{@column.data_type.underscore}", value, record_row)
+ __send__("new_#{@column.data_type.split('::').last.underscore}", value, record_row)
end
private
diff --git a/app/views/assets/marvinjs/_create_marvin_sketch_button.html.erb b/app/views/assets/marvinjs/_create_marvin_sketch_button.html.erb
new file mode 100644
index 000000000..d4d3c472f
--- /dev/null
+++ b/app/views/assets/marvinjs/_create_marvin_sketch_button.html.erb
@@ -0,0 +1,12 @@
+
+
+ <%= image_tag 'icon_small/marvinjs.svg' %>
+
+ <%= t('marvinjs.new_button') %>
+
\ No newline at end of file
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 534d7785a..066a5190a 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -7,6 +7,11 @@
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
+ <% if ENV['MARVINJS_API_KEY'] %>
+
+
+ <% end %>
+
<%= favicon_link_tag "favicon.ico" %>
<%= favicon_link_tag "favicon-16.png", type: "image/png", size: "16x16" %>
<%= favicon_link_tag "favicon-32.png", type: "image/png", size: "32x32" %>
@@ -43,6 +48,7 @@
<%= render "shared/about_modal" %>
<%= render "shared/file_preview_modal.html.erb" %>
<%= render "shared/file_edit_modal.html.erb" %>
+ <%= render "shared/marvinjs_modal.html.erb" %>
<%= render "shared/navigation" %>
<% if user_signed_in? && flash[:system_notification_modal] && current_user.show_login_system_notification? %>
diff --git a/app/views/my_modules/_recent_protocol_dropdown.html.erb b/app/views/my_modules/_recent_protocol_dropdown.html.erb
new file mode 100644
index 000000000..1a24d943c
--- /dev/null
+++ b/app/views/my_modules/_recent_protocol_dropdown.html.erb
@@ -0,0 +1,21 @@
+<% display_status = protocol.description.blank? && protocol.steps.count.zero? %>
+<% if @recent_protcols_positive %>
+
+
+
<%= t('my_modules.module_header.recent_protocols_from_repository') %>
+
+
+
+
+
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/my_modules/protocols.html.erb b/app/views/my_modules/protocols.html.erb
index c8f037715..1acf4ad13 100644
--- a/app/views/my_modules/protocols.html.erb
+++ b/app/views/my_modules/protocols.html.erb
@@ -18,6 +18,9 @@
<%= render partial: "my_modules/protocols/protocol_status_bar.html.erb" %>
<%= render partial: "my_modules/protocols/protocol_buttons.html.erb" %>
+ <% if can_manage_protocol_in_module?(@protocol) %>
+ <%= render partial: "my_modules/recent_protocol_dropdown.html.erb", locals: {protocol: @my_module.protocol}%>
+ <% end %>
diff --git a/app/views/my_modules/results.html.erb b/app/views/my_modules/results.html.erb
index 48f2d1b7a..3f0e9bb80 100644
--- a/app/views/my_modules/results.html.erb
+++ b/app/views/my_modules/results.html.erb
@@ -41,6 +41,8 @@
<%= t("my_modules.results.new_asset_result") %>
+ <%= render partial: '/assets/marvinjs/create_marvin_sketch_button.html.erb',
+ locals: { element_id: @my_module.id, element_type: 'Result', sketch_container: "#results[data-module-id=#{@my_module.id}]" } %>
<%= render partial: "assets/wopi/create_wopi_file_button",
locals: { element_id: @my_module.id, element_type: 'Result' } %>
diff --git a/app/views/projects/experiment_archive/_experiment.html.erb b/app/views/projects/experiment_archive/_experiment.html.erb
index fe27a09d7..da055d451 100644
--- a/app/views/projects/experiment_archive/_experiment.html.erb
+++ b/app/views/projects/experiment_archive/_experiment.html.erb
@@ -23,7 +23,7 @@
- <% if experiment.workflowimg? %>
+ <% if experiment.workflowimg.attached? %>
<%= image_tag(
experiment.workflowimg.expiring_url(
diff --git a/app/views/reports/elements/_result_comments_element.html.erb b/app/views/reports/elements/_result_comments_element.html.erb
index 68614856f..b6635caef 100644
--- a/app/views/reports/elements/_result_comments_element.html.erb
+++ b/app/views/reports/elements/_result_comments_element.html.erb
@@ -18,22 +18,13 @@
- <%= t('protocols.steps.files', count: assets.count) %> + <%= t('protocols.steps.files', count: assets.length) %>
+
<% assets.each_with_index do |asset, i| %>
+ <% order_atoz = az_ordered_assets_index(step, asset.id) %>
+ <% order_ztoa = assets.length - az_ordered_assets_index(step, asset.id) %>
<%= render partial: 'steps/attachments/item.html.erb',
- locals: { asset: asset, i: i, assets_count: assets.count, step: step } %>
+ locals: { asset: asset, i: i, assets_count: assets.length, step: step, order_atoz: order_atoz, order_ztoa: order_ztoa } %>
<% end %>
<% ['new', 'old', 'atoz', 'ztoa'].each do |sort| %>-
-
- <%= t('protocols.steps.attachments.sort_' + sort ).html_safe %>
+
+ <%= t("protocols.steps.attachments.sort.#{sort}_html") %>
+
<% end %>
@@ -36,8 +42,10 @@diff --git a/config/initializers/constants.rb b/config/initializers/constants.rb index 1bd3a771b..0c85255a2 100644 --- a/config/initializers/constants.rb +++ b/config/initializers/constants.rb @@ -64,6 +64,9 @@ class Constants # Max characters for repository name in Atwho modal ATWHO_REP_NAME_LIMIT = 16 + # Number of protocols in recent protocol dropdown + RECENT_PROTOCOL_LIMIT = 14 + #============================================================================= # File and data memory size #============================================================================= diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index 4aac4bfff..b448bbf10 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -218,19 +218,22 @@ class Extends export_protocol_from_task: 106, import_inventory_items: 107, create_tag: 108, - delete_tag: 109 + delete_tag: 109, + edit_image_on_result: 110, + edit_image_on_step: 111, + edit_image_on_step_in_repository: 112, } ACTIVITY_GROUPS = { projects: [*0..7, 32, 33, 34, 95, 108, 65, 109], - task_results: [23, 26, 25, 42, 24, 40, 41, 99], + task_results: [23, 26, 25, 42, 24, 40, 41, 99, 110], task: [8, 58, 9, 59, 10, 11, 12, 13, 14, 35, 36, 37, 53, 54, *60..64, *66..69, 106], - task_protocol: [15, 22, 16, 18, 19, 20, 21, 17, 38, 39, 100, 45, 46, 47], + task_protocol: [15, 22, 16, 18, 19, 20, 21, 17, 38, 39, 100, 111, 45, 46, 47], task_inventory: [55, 56], experiment: [*27..31, 57], reports: [48, 50, 49], inventories: [70, 71, 105, 72, 73, 74, 102, 75, 76, 77, 78, 96, 107], - protocol_repository: [80, 103, 89, 87, 79, 90, 91, 88, 85, 86, 84, 81, 82, 83, 101], + protocol_repository: [80, 103, 89, 87, 79, 90, 91, 88, 85, 86, 84, 81, 82, 83, 101, 112], team: [92, 94, 93, 97, 104] }.freeze end diff --git a/config/locales/en.yml b/config/locales/en.yml index 2116ac1e7..a2ee38803 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -75,6 +75,8 @@ en: attributes: viewable_id: not_unique: "State already exists for this user and parent object" + state: + wrong_state: "Wrong parameters" helpers: label: @@ -606,6 +608,7 @@ en: no_description: "No task description" description_label: "Description" empty_description_edit_label: "Click here to enter Task Description (optional)" + recent_protocols_from_repository: "Recent protocols from the Repository" protocols: head_title: "%{project} | %{module} | Protocols" protocol_status_bar: @@ -1331,6 +1334,9 @@ en: wupi_file_editing: started: "editing started" finished: "editing finished" + file_editing: + started: "editing started" + finished: "editing finished" protocols: my_to_team_message: 'My protocols to Team protocols' team_to_my_message: 'Team protocols to My protocols' @@ -1759,10 +1765,11 @@ en: complete_title: "Complete Step" uncomplete_title: "Uncomplete Step" attachments: - sort_new: "Newest first ↓" - sort_old: "Oldest first ↑" - sort_atoz: "Name ↓" - sort_ztoa: "Name ↑" + sort: + new_html: "Newest first ↓" + old_html: "Oldest first ↑" + atoz_html: "Name ↓" + ztoa_html: "Name ↑" new: add_step_title: "Add new step" tab_checklists: "Checklists" @@ -2147,3 +2154,9 @@ en: new: "https://support.scinote.net/hc/en-us/articles/360004627792" visibility: "https://support.scinote.net/hc/en-us/articles/360004627472" manage_columns: "https://support.scinote.net/hc/en-us/articles/360004695831" + marvinjs: + new_sketch: "New structure" + new_button: "New structure" + structure_placeholder: "Click here to enter structure name" + modal_name_title: "Structure name:" + checmical_drawing: "Chemical drawings" \ No newline at end of file diff --git a/config/locales/global_activities/en.yml b/config/locales/global_activities/en.yml index 14e5b2b4f..b2afa22be 100644 --- a/config/locales/global_activities/en.yml +++ b/config/locales/global_activities/en.yml @@ -131,6 +131,9 @@ en: edit_tag_html: "%{user} edited tag %{tag} in project %{project}." delete_tag_html: "%{user} deleted tag %{tag} in project %{project}." import_inventory_items_html: "%{user} imported %{num_of_items} inventory item(s) to %{repository}." + edit_image_on_result_html: "%{user} edited image %{asset_name} on result %{result}: %{action}." + edit_image_on_step_html: "%{user} edited image %{asset_name} on protocol's step %{step_position} %{step} on task %{my_module}: %{action}." + edit_image_on_step_in_repository_html: "%{user} edited image %{asset_name} on protocol %{protocol}'s step %{step_position} %{step} in Protocol repository: %{action}." activity_name: create_project: "Project created" @@ -233,6 +236,9 @@ en: edit_tag: "Tag edited" delete_tag: "Tag deleted" import_inventory_items: "Inventory items imported" + edit_image_on_result: "Image on result edited" + edit_image_on_step: "Image on task step edited" + edit_image_on_step_in_repository: "Image on step edited" activity_group: projects: "Projects" diff --git a/config/routes.rb b/config/routes.rb index 6f5bf3cdb..420591b80 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -433,6 +433,7 @@ Rails.application.routes.draw do post 'toggle_step_state' get 'move_down' get 'move_up' + post 'update_view_state' end end @@ -448,7 +449,16 @@ Rails.application.routes.draw do end # tinyMCE image uploader endpoint - post '/tinymce_assets', to: 'tiny_mce_assets#create', as: :tiny_mce_assets + resources :tiny_mce_assets, only: [:create] do + member do + get :download + get :marvinjs, to: 'tiny_mce_assets#marvinjs_show' + put :marvinjs, to: 'tiny_mce_assets#marvinjs_update' + end + collection do + post :marvinjs, to: 'tiny_mce_assets#marvinjs_create' + end + end resources :results, only: [:update, :destroy] do resources :result_comments, @@ -516,6 +526,7 @@ Rails.application.routes.draw do to: 'protocols#protocolsio_import_create' post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save' get 'export', to: 'protocols#export' + get 'recent_protocols' end end @@ -589,6 +600,7 @@ Rails.application.routes.draw do post 'files/create_wopi_file', to: 'assets#create_wopi_file', as: 'create_wopi_file' + post 'files/:id/start_edit_image', to: 'assets#create_start_edit_image_activity', as: 'start_edit_image' devise_scope :user do get 'avatar/:id/:style' => 'users/registrations#avatar', as: 'avatar' @@ -672,6 +684,12 @@ Rails.application.routes.draw do end end + resources :marvin_js_assets, only: %i(create update destroy show) do + collection do + get :team_sketches + end + end + post 'global_activities', to: 'global_activities#index' constraints WopiSubdomain do diff --git a/lib/paperclip_processors/custom_file_preview.rb b/lib/paperclip_processors/custom_file_preview.rb index 256c4f261..c749d9e6d 100644 --- a/lib/paperclip_processors/custom_file_preview.rb +++ b/lib/paperclip_processors/custom_file_preview.rb @@ -3,7 +3,7 @@ module Paperclip class CustomFilePreview < Processor def make - libreoffice_path = ENV['LIBREOFFICE_PATH'] || 'libreoffice' + libreoffice_path = ENV['LIBREOFFICE_PATH'] || 'soffice' directory = File.dirname(@file.path) basename = File.basename(@file.path, '.*') original_preview_file = File.join(directory, "#{basename}.png") diff --git a/spec/controllers/assets_controller_spec.rb b/spec/controllers/assets_controller_spec.rb new file mode 100644 index 000000000..a4a853bb8 --- /dev/null +++ b/spec/controllers/assets_controller_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe AssetsController, type: :controller do + login_user + + let(:user) { subject.current_user } + let!(:team) { create :team, created_by: user } + let(:user_team) { create :user_team, :admin, user: user, team: team } + let!(:user_project) { create :user_project, :owner, user: user } + let(:project) do + create :project, team: team, user_projects: [user_project] + end + let(:experiment) { create :experiment, project: project } + let(:my_module) { create :my_module, name: 'test task', experiment: experiment } + let(:protocol) do + create :protocol, my_module: my_module, team: team, added_by: user + end + let(:step) { create :step, protocol: protocol, user: user } + let(:step_asset_task) { create :step_asset, step: step } + + let(:result) do + create :result, name: 'test result', my_module: my_module, user: user + end + let(:result_asset) { create :result_asset, result: result } + + let(:protocol_in_repository) { create :protocol, :in_public_repository, team: team } + let(:step_in_repository) { create :step, protocol: protocol_in_repository, user: user } + + let!(:asset) { create :asset } + let(:step_asset_in_repository) { create :step_asset, step: step_in_repository, asset: asset } + + describe 'POST start_edit' do + before do + allow(controller).to receive(:check_edit_permission).and_return(true) + end + let(:action) { post :create_start_edit_image_activity, params: params, format: :json } + let!(:params) do + { id: nil } + end + it 'calls create activity service (start edit image on step)' do + params[:id] = step_asset_task.asset.id + expect(Activities::CreateActivityService).to receive(:call) + .with(hash_including(activity_type: :edit_image_on_step)) + action + end + + it 'calls create activity service (start edit image on result)' do + params[:id] = result_asset.asset.id + expect(Activities::CreateActivityService).to receive(:call) + .with(hash_including(activity_type: :edit_image_on_result)) + action + end + + it 'calls create activity service (start edit image on step in repository)' do + params[:id] = step_asset_in_repository.asset.id + user_team + expect(Activities::CreateActivityService).to receive(:call) + .with(hash_including(activity_type: :edit_image_on_step_in_repository)) + action + end + + it 'adds activity in DB' do + params[:id] = step_asset_task.asset.id + expect { action } + .to(change { Activity.count }) + end + end +end diff --git a/spec/controllers/wopi_controller_spec.rb b/spec/controllers/wopi_controller_spec.rb new file mode 100644 index 000000000..6fe951b74 --- /dev/null +++ b/spec/controllers/wopi_controller_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe WopiController, type: :controller do + ENV['WOPI_USER_HOST'] = 'localhost' + + login_user + let(:user) { subject.current_user } + let!(:team) { create :team, created_by: user } + let(:user_team) { create :user_team, :admin, user: user, team: team } + let!(:user_project) { create :user_project, :owner, user: user } + let(:project) do + create :project, team: team, user_projects: [user_project] + end + let(:experiment) { create :experiment, project: project } + let(:my_module) { create :my_module, name: 'test task', experiment: experiment } + let(:result) do + create :result, name: 'test result', my_module: my_module, user: user + end + let(:protocol) do + create :protocol, my_module: my_module, team: team, added_by: user + end + let(:step) { create :step, protocol: protocol, user: user } + + let(:protocol_in_repository) { create :protocol, :in_public_repository, team: team } + let(:step_in_repository) { create :step, protocol: protocol_in_repository, user: user } + + let!(:asset) { create :asset } + let(:step_asset) { create :step_asset, step: step, asset: asset } + let(:step_asset_in_repository) { create :step_asset, step: step_in_repository, asset: asset } + let(:result_asset) { create :result_asset, result: result, asset: asset } + let(:token) { Token.create(token: 'token', ttl: 0, user_id: user.id) } + + describe 'POST unlock' do + before do + token + ENV['WOPI_SUBDOMAIN'] = nil + allow(controller).to receive(:verify_proof!).and_return(true) + @request.headers['X-WOPI-Override'] = 'UNLOCK' + @request.headers['X-WOPI-Lock'] = 'lock' + asset.lock_asset('lock') + end + + let(:action) { post :post_file_endpoint, params: { id: asset.id, access_token: 'token' } } + + describe 'Result asset' do + before do + result_asset + end + + it 'calls create activity for finish wopi editing' do + expect(Activities::CreateActivityService) + .to(receive(:call) + .with(hash_including(activity_type: + :edit_wopi_file_on_result))) + + action + end + + it 'adds activity in DB' do + expect { action } + .to(change { Activity.count }) + end + end + + describe 'Step asset' do + before do + step_asset + end + + it 'calls create activity for finish wopi editing' do + expect(Activities::CreateActivityService) + .to(receive(:call) + .with(hash_including(activity_type: + :edit_wopi_file_on_step))) + + action + end + + it 'adds activity in DB' do + expect { action } + .to(change { Activity.count }) + end + end + + describe 'Step asset in repository' do + before do + step_asset_in_repository + user_team + end + + it 'calls create activity for finish wopi editing' do + expect(Activities::CreateActivityService) + .to(receive(:call) + .with(hash_including(activity_type: + :edit_wopi_file_on_step_in_repository))) + + action + end + + it 'adds activity in DB' do + expect { action } + .to(change { Activity.count }) + end + end + end +end diff --git a/spec/factories/assets.rb b/spec/factories/assets.rb index ae9088e9b..a701f048a 100644 --- a/spec/factories/assets.rb +++ b/spec/factories/assets.rb @@ -2,11 +2,8 @@ FactoryBot.define do factory :asset do - file_file_name { 'sample_file.txt' } - file_content_type { 'text/plain' } - file_file_size { 69 } - version { 1 } - estimated_size { 232 } - file_processing { false } + file do + fixture_file_upload(Rails.root.join('spec', 'fixtures', 'files', 'test.jpg'), 'image/jpg') + end end end diff --git a/spec/factories/notifications.rb b/spec/factories/notifications.rb index 5ca75eeec..9df711663 100644 --- a/spec/factories/notifications.rb +++ b/spec/factories/notifications.rb @@ -2,8 +2,10 @@ FactoryBot.define do factory :notification do - title { 'Admin was added as Owner to project ' \ - 'Demo project - qPCR by User.' } + title do + 'Admin was added as Owner to project ' \ + 'Demo project - qPCR by User.' + end message { 'Project: Demo project - qPCR' } type_of { 'assignment' } end diff --git a/spec/factories/repository_cells.rb b/spec/factories/repository_cells.rb index b6b0d16de..e06597088 100644 --- a/spec/factories/repository_cells.rb +++ b/spec/factories/repository_cells.rb @@ -7,7 +7,7 @@ FactoryBot.define do trait :text_value do repository_column { create :repository_column, :text_type, repository: repository_row.repository } after(:build) do |repository_cell| - repository_cell.value ||= build(:repository_text_value, repository_cell: repository_cell) + repository_cell.value ||= create(:repository_text_value, repository_cell: repository_cell) end end diff --git a/spec/factories/tinymce_assets.rb b/spec/factories/tinymce_assets.rb index dbd07ffc2..5de498ec7 100644 --- a/spec/factories/tinymce_assets.rb +++ b/spec/factories/tinymce_assets.rb @@ -3,8 +3,8 @@ FactoryBot.define do factory :tiny_mce_asset do association :team, factory: :team - image_file_name { 'sample_file.jpg' } - image_content_type { 'image/jpeg' } - image_file_size { 69 } + image do + fixture_file_upload(Rails.root.join('spec', 'fixtures', 'files', 'test.jpg'), 'image/jpg') + end end end diff --git a/spec/fixtures/files/test.jpg b/spec/fixtures/files/test.jpg new file mode 100644 index 000000000..ef4fcb2ad Binary files /dev/null and b/spec/fixtures/files/test.jpg differ diff --git a/spec/models/activity_spec.rb b/spec/models/activity_spec.rb index cec6f0e2b..9a4c6370f 100644 --- a/spec/models/activity_spec.rb +++ b/spec/models/activity_spec.rb @@ -31,11 +31,11 @@ describe Activity, type: :model do end describe 'Relations' do - it { should belong_to :project } - it { should belong_to :experiment } - it { should belong_to :my_module } + it { should belong_to(:project).optional } + it { should belong_to(:experiment).optional } + it { should belong_to(:my_module).optional } it { should belong_to :owner } - it { should belong_to :subject } + it { should belong_to(:subject).optional } end describe 'Validations' do diff --git a/spec/models/asset_spec.rb b/spec/models/asset_spec.rb index e42050698..f0eddc284 100644 --- a/spec/models/asset_spec.rb +++ b/spec/models/asset_spec.rb @@ -33,9 +33,9 @@ describe Asset, type: :model do end describe 'Relations' do - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } - it { should belong_to :team } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } + it { should belong_to(:team).optional } it { should have_many :report_elements } it { should have_one :step_asset } it { should have_one :step } @@ -45,14 +45,4 @@ describe Asset, type: :model do it { should have_one :repository_asset_value } it { should have_one :repository_cell } end - - describe 'Validations' do - describe '#file' do - it { is_expected.to validate_presence_of(:file) } - end - - describe '#estimated_size' do - it { expect(asset).to validate_presence_of(:estimated_size) } - end - end end diff --git a/spec/models/checklist_item_spec.rb b/spec/models/checklist_item_spec.rb index afe132b5b..8f988713e 100644 --- a/spec/models/checklist_item_spec.rb +++ b/spec/models/checklist_item_spec.rb @@ -26,9 +26,9 @@ describe ChecklistItem, type: :model do end describe 'Relations' do - it { should belong_to :checklist } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:checklist) } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } end describe 'Validations' do diff --git a/spec/models/checklist_spec.rb b/spec/models/checklist_spec.rb index dc4c8905e..9215eeddd 100644 --- a/spec/models/checklist_spec.rb +++ b/spec/models/checklist_spec.rb @@ -24,9 +24,9 @@ describe Checklist, type: :model do end describe 'Relations' do - it { should belong_to :step } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:step) } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should have_many :report_elements } it { should have_many :checklist_items } end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index acef2eeb8..356e0cc1a 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -26,7 +26,7 @@ describe Comment, type: :model do describe 'Relations' do it { should belong_to :user } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:last_modified_by).class_name('User').optional } end describe 'Validations' do diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index eb9df1628..75fff007d 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -26,7 +26,7 @@ describe CustomField, type: :model do describe 'Relations' do it { should belong_to :user } it { should belong_to :team } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should have_many :sample_custom_fields } end @@ -44,7 +44,6 @@ describe CustomField, type: :model do expect(custom_field).to validate_uniqueness_of(:name).scoped_to(:team_id) end - end describe '#user' do diff --git a/spec/models/experiment_spec.rb b/spec/models/experiment_spec.rb index 6010c1fe5..23427f757 100644 --- a/spec/models/experiment_spec.rb +++ b/spec/models/experiment_spec.rb @@ -35,11 +35,11 @@ describe Experiment, type: :model do end describe 'Relations' do - it { should belong_to :project } + it { should belong_to(:project) } it { should belong_to(:created_by).class_name('User') } it { should belong_to(:last_modified_by).class_name('User') } - it { should belong_to(:archived_by).class_name('User') } - it { should belong_to(:restored_by).class_name('User') } + it { should belong_to(:archived_by).class_name('User').optional } + it { should belong_to(:restored_by).class_name('User').optional } it { should have_many :my_modules } it { should have_many :my_module_groups } it { should have_many :report_elements } diff --git a/spec/models/my_module_group_spec.rb b/spec/models/my_module_group_spec.rb index 1732536e1..1727d4c61 100644 --- a/spec/models/my_module_group_spec.rb +++ b/spec/models/my_module_group_spec.rb @@ -22,8 +22,8 @@ describe MyModuleGroup, type: :model do end describe 'Relations' do - it { should belong_to :experiment } - it { should belong_to(:created_by).class_name('User') } + it { should belong_to(:experiment) } + it { should belong_to(:created_by).class_name('User').optional } it { should have_many(:my_modules) } end diff --git a/spec/models/my_module_repository_row_spec.rb b/spec/models/my_module_repository_row_spec.rb index 7f2409483..99317c05a 100644 --- a/spec/models/my_module_repository_row_spec.rb +++ b/spec/models/my_module_repository_row_spec.rb @@ -23,9 +23,9 @@ describe MyModuleRepositoryRow, type: :model do end describe 'Relations' do - it { should belong_to :my_module } - it { should belong_to(:assigned_by).class_name('User') } - it { should belong_to :repository_row } + it { should belong_to(:my_module) } + it { should belong_to(:assigned_by).class_name('User').optional } + it { should belong_to(:repository_row) } end describe 'Validations' do diff --git a/spec/models/my_module_spec.rb b/spec/models/my_module_spec.rb index 418937151..014e46ad6 100644 --- a/spec/models/my_module_spec.rb +++ b/spec/models/my_module_spec.rb @@ -38,12 +38,12 @@ describe MyModule, type: :model do end describe 'Relations' do - it { should belong_to :experiment } - it { should belong_to :my_module_group } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } - it { should belong_to(:archived_by).class_name('User') } - it { should belong_to(:restored_by).class_name('User') } + it { should belong_to(:experiment) } + it { should belong_to(:my_module_group).optional } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } + it { should belong_to(:archived_by).class_name('User').optional } + it { should belong_to(:restored_by).class_name('User').optional } it { should have_many :results } it { should have_many :my_module_tags } it { should have_many :tags } diff --git a/spec/models/my_module_tag_spec.rb b/spec/models/my_module_tag_spec.rb index bb4ef9590..98a3d84c7 100644 --- a/spec/models/my_module_tag_spec.rb +++ b/spec/models/my_module_tag_spec.rb @@ -20,9 +20,9 @@ describe MyModuleTag, type: :model do end describe 'Relations' do - it { should belong_to :my_module } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to :tag } + it { should belong_to(:my_module) } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:tag) } end describe 'Validations' do diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 5c6394415..a37c941ce 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -24,7 +24,7 @@ describe Notification, type: :model do end describe 'Relations' do - it { should belong_to(:generator_user).class_name('User') } + it { should belong_to(:generator_user).class_name('User').optional } it { should have_many :users } it { should have_many :user_notifications } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 13b05d061..ec56145bc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -33,11 +33,11 @@ describe Project, type: :model do end describe 'Relations' do - it { should belong_to :team } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } - it { should belong_to(:archived_by).class_name('User') } - it { should belong_to(:restored_by).class_name('User') } + it { should belong_to(:team) } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } + it { should belong_to(:archived_by).class_name('User').optional } + it { should belong_to(:restored_by).class_name('User').optional } it { should have_many :user_projects } it { should have_many :users } it { should have_many :experiments } diff --git a/spec/models/protocol_keyword_spec.rb b/spec/models/protocol_keyword_spec.rb index dfd59c2ca..2fd75363e 100644 --- a/spec/models/protocol_keyword_spec.rb +++ b/spec/models/protocol_keyword_spec.rb @@ -31,8 +31,8 @@ describe ProtocolKeyword, type: :model do it { should validate_presence_of :team } it do should validate_length_of(:name) - .is_at_least(Constants::NAME_MIN_LENGTH) - .is_at_most(Constants::NAME_MAX_LENGTH) + .is_at_least(Constants::NAME_MIN_LENGTH) + .is_at_most(Constants::NAME_MAX_LENGTH) end end end diff --git a/spec/models/protocol_spec.rb b/spec/models/protocol_spec.rb index 5882f67dd..166f7d05a 100644 --- a/spec/models/protocol_spec.rb +++ b/spec/models/protocol_spec.rb @@ -34,12 +34,11 @@ describe Protocol, type: :model do end describe 'Relations' do - it { should belong_to :my_module } - it { should belong_to :team } - it { should belong_to(:added_by).class_name('User') } - it { should belong_to(:parent).class_name('Protocol') } - it { should belong_to(:archived_by).class_name('User') } - it { should belong_to(:restored_by).class_name('User') } + it { should belong_to(:team) } + it { should belong_to(:added_by).class_name('User').optional } + it { should belong_to(:parent).class_name('Protocol').optional } + it { should belong_to(:archived_by).class_name('User').optional } + it { should belong_to(:restored_by).class_name('User').optional } it { should have_many(:linked_children).class_name('Protocol') } it { should have_many :protocol_protocol_keywords } it { should have_many :protocol_keywords } diff --git a/spec/models/report_element_spec.rb b/spec/models/report_element_spec.rb index 2942c9c07..4a04f4a16 100644 --- a/spec/models/report_element_spec.rb +++ b/spec/models/report_element_spec.rb @@ -38,16 +38,15 @@ describe ReportElement, type: :model do end describe 'Relations' do - it { should belong_to :report } - it { should belong_to :project } - it { should belong_to :experiment } - it { should belong_to :my_module } - it { should belong_to :step } - it { should belong_to :result } - it { should belong_to :checklist } - it { should belong_to :asset } - it { should belong_to :table } - it { should belong_to :repository } + it { should belong_to(:project).optional } + it { should belong_to(:experiment).optional } + it { should belong_to(:my_module).optional } + it { should belong_to(:step).optional } + it { should belong_to(:result).optional } + it { should belong_to(:checklist).optional } + it { should belong_to(:asset).optional } + it { should belong_to(:table).optional } + it { should belong_to(:repository).optional } it { should belong_to(:report) } it { should have_many(:children).class_name('ReportElement') } end diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index 0e78ebbcf..b585293c8 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -27,9 +27,8 @@ describe Report, type: :model do describe 'Relations' do it { should belong_to :project } it { should belong_to :user } - it { should belong_to :project } it { should belong_to :team } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should have_many :report_elements } end diff --git a/spec/models/repository_asset_value_spec.rb b/spec/models/repository_asset_value_spec.rb index 697b32d54..51bd50927 100644 --- a/spec/models/repository_asset_value_spec.rb +++ b/spec/models/repository_asset_value_spec.rb @@ -22,8 +22,8 @@ describe RepositoryAssetValue, type: :model do end describe 'Relations' do - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should belong_to(:asset).dependent(:destroy) } it { should have_one :repository_cell } it { should accept_nested_attributes_for(:repository_cell) } @@ -36,10 +36,10 @@ describe RepositoryAssetValue, type: :model do describe '#data' do it 'returns the asset' do - asset = create :asset, file_file_name: 'my file' + asset = create :asset repository_asset_value = create :repository_asset_value, asset: asset - expect(repository_asset_value.reload.formatted).to eq 'my file' + expect(repository_asset_value.reload.formatted).to eq 'test.jpg' end end end diff --git a/spec/models/repository_row_spec.rb b/spec/models/repository_row_spec.rb index e90419939..a1b04db6f 100644 --- a/spec/models/repository_row_spec.rb +++ b/spec/models/repository_row_spec.rb @@ -23,7 +23,7 @@ describe RepositoryRow, type: :model do end describe 'Relations' do - it { should belong_to :repository } + it { should belong_to(:repository).optional } it { should belong_to(:created_by).class_name('User') } it { should belong_to(:last_modified_by).class_name('User') } it { should have_many :repository_cells } diff --git a/spec/models/result_spec.rb b/spec/models/result_spec.rb index 729cf75cb..76afa0902 100644 --- a/spec/models/result_spec.rb +++ b/spec/models/result_spec.rb @@ -30,9 +30,9 @@ describe Result, type: :model do describe 'Relations' do it { should belong_to :user } it { should belong_to :my_module } - it { should belong_to(:archived_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } - it { should belong_to(:restored_by).class_name('User') } + it { should belong_to(:archived_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } + it { should belong_to(:restored_by).class_name('User').optional } it { should have_one :result_asset } it { should have_one :asset } it { should have_one :result_table } diff --git a/spec/models/sample_custom_field_spec.rb b/spec/models/sample_custom_field_spec.rb deleted file mode 100644 index 7a181dad7..000000000 --- a/spec/models/sample_custom_field_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe SampleCustomField, type: :model do - it 'is valid' do - # Need this records because of after_create callback - sample = create :sample - create :samples_table, user: sample.user, team: sample.team - custom_field = create :custom_field, user: sample.user, team: sample.team - sample_custom_filed = create :sample_custom_field, sample: sample, custom_field: custom_field - - expect(sample_custom_filed).to be_valid - end - - it 'should be of class SampleCustomField' do - expect(subject.class).to eq SampleCustomField - end - - describe 'Database table' do - it { should have_db_column :value } - it { should have_db_column :custom_field_id } - it { should have_db_column :sample_id } - it { should have_db_column :created_at } - it { should have_db_column :updated_at } - end - - describe 'Relations' do - it { should belong_to :custom_field } - it { should belong_to :sample } - end - - describe 'Validations' do - describe '#value' do - it { is_expected.to validate_presence_of :value } - it { is_expected.to validate_length_of(:value).is_at_most(Constants::NAME_MAX_LENGTH) } - end - - describe '#custom_field' do - it { is_expected.to validate_presence_of :custom_field } - end - - describe '#sample' do - it { is_expected.to validate_presence_of :sample } - end - end -end diff --git a/spec/models/sample_group_spec.rb b/spec/models/sample_group_spec.rb deleted file mode 100644 index 02c2adffc..000000000 --- a/spec/models/sample_group_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe SampleGroup, type: :model do - let(:sample_group) { build :sample_group } - - it 'is valid' do - expect(sample_group).to be_valid - end - - it 'should be of class SampleGroup' do - expect(subject.class).to eq SampleGroup - end - - describe 'Database table' do - it { should have_db_column :name } - it { should have_db_column :color } - it { should have_db_column :team_id } - it { should have_db_column :created_at } - it { should have_db_column :updated_at } - it { should have_db_column :created_by_id } - it { should have_db_column :last_modified_by_id } - end - - describe 'Relations' do - it { should belong_to :team } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } - - it 'have many samples' do - table = SampleGroup.reflect_on_association(:samples) - expect(table.macro).to eq(:has_many) - end - end - - describe 'Validations' do - describe '#name' do - it { is_expected.to validate_presence_of :name } - it { is_expected.to validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH) } - it { expect(sample_group).to validate_uniqueness_of(:name).scoped_to(:team_id).case_insensitive } - end - - describe '#color' do - it { is_expected.to validate_presence_of :color } - it { is_expected.to validate_length_of(:color).is_at_most(Constants::COLOR_MAX_LENGTH) } - end - - describe '#team' do - it { is_expected.to validate_presence_of :team } - end - end -end diff --git a/spec/models/sample_my_module_spec.rb b/spec/models/sample_my_module_spec.rb deleted file mode 100644 index d97e21a37..000000000 --- a/spec/models/sample_my_module_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe SampleMyModule, type: :model do - let(:sample_my_module) { build :sample_my_module } - - it 'is valid' do - expect(sample_my_module).to be_valid - end - - it 'should be of class SampleMyModule' do - expect(subject.class).to eq SampleMyModule - end - - describe 'Database table' do - it { should have_db_column :sample_id } - it { should have_db_column :my_module_id } - it { should have_db_column :assigned_by_id } - it { should have_db_column :assigned_on } - end - - describe 'Relations' do - it { should belong_to(:assigned_by).class_name('User') } - it { should belong_to :sample } - it { should belong_to :my_module } - end - - describe 'Validations' do - describe '#sample' do - it { should validate_presence_of :sample } - it { expect(sample_my_module).to validate_uniqueness_of(:sample_id).scoped_to(:my_module_id) } - end - - describe '#my_module' do - it { should validate_presence_of :my_module } - end - end -end diff --git a/spec/models/sample_spec.rb b/spec/models/sample_spec.rb deleted file mode 100644 index f4d5e501a..000000000 --- a/spec/models/sample_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Sample, type: :model do - let(:sample) { build :sample } - - it 'is valid' do - expect(sample).to be_valid - end - - it 'should be of class Sample' do - expect(subject.class).to eq Sample - end - - describe 'Database table' do - it { should have_db_column :name } - it { should have_db_column :user_id } - it { should have_db_column :team_id } - it { should have_db_column :created_at } - it { should have_db_column :updated_at } - it { should have_db_column :sample_group_id } - it { should have_db_column :sample_type_id } - it { should have_db_column :last_modified_by_id } - it { should have_db_column :nr_of_modules_assigned_to } - end - - describe 'Relations' do - it { should belong_to :user } - it { should belong_to :team } - it { should belong_to :sample_group } - it { should belong_to :sample_type } - it { should belong_to(:last_modified_by).class_name('User') } - it { should have_many :sample_my_modules } - it { should have_many :my_modules } - it { should have_many :sample_custom_fields } - it { should have_many :custom_fields } - end - - describe 'Validations' do - describe '#name' do - it { is_expected.to validate_presence_of :name } - it { is_expected.to validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH) } - end - - describe '#user' do - it { is_expected.to validate_presence_of :user } - end - - describe '#team' do - it { is_expected.to validate_presence_of :team } - end - end -end diff --git a/spec/models/sample_type_spec.rb b/spec/models/sample_type_spec.rb deleted file mode 100644 index fb5d0c8aa..000000000 --- a/spec/models/sample_type_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe SampleType, type: :model do - let(:sample_type) { build :sample_type } - - it 'is valid' do - expect(sample_type).to be_valid - end - it 'should be of class SampleType' do - expect(subject.class).to eq SampleType - end - - describe 'Database table' do - it { should have_db_column :name } - it { should have_db_column :team_id } - it { should have_db_column :created_at } - it { should have_db_column :updated_at } - it { should have_db_column :created_by_id } - it { should have_db_column :last_modified_by_id } - end - - describe 'Relations' do - it { should belong_to :team } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } - - it 'have many samples' do - table = SampleType.reflect_on_association(:samples) - expect(table.macro).to eq(:has_many) - end - end - - describe 'Validations' do - describe '#name' do - it { is_expected.to validate_presence_of :name } - it { is_expected.to validate_length_of(:name).is_at_most(Constants::NAME_MAX_LENGTH) } - it { expect(sample_type).to validate_uniqueness_of(:name).scoped_to(:team_id).case_insensitive } - end - - describe '#team' do - it { is_expected.to validate_presence_of :team } - end - end -end diff --git a/spec/models/samples_table_spec.rb b/spec/models/samples_table_spec.rb deleted file mode 100644 index 428771c88..000000000 --- a/spec/models/samples_table_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe SamplesTable, type: :model do - let(:samples_table) { build :samples_table } - - it 'is valid' do - expect(samples_table).to be_valid - end - - it 'should be of class SamplesTable' do - expect(subject.class).to eq SamplesTable - end - - describe 'Database table' do - it { should have_db_column :status } - it { should have_db_column :user_id } - it { should have_db_column :team_id } - it { should have_db_column :created_at } - it { should have_db_column :updated_at } - end - - describe 'Relations' do - it { should belong_to :user } - it { should belong_to :team } - end - - describe 'Validations' do - it { should validate_presence_of :team } - it { should validate_presence_of :user } - end -end diff --git a/spec/models/step_spec.rb b/spec/models/step_spec.rb index 385cb24ab..634f4b7a2 100644 --- a/spec/models/step_spec.rb +++ b/spec/models/step_spec.rb @@ -29,7 +29,7 @@ describe Step, type: :model do describe 'Relations' do it { should belong_to :user } it { should belong_to :protocol } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should have_many :checklists } it { should have_many :step_comments } it { should have_many :step_assets } @@ -50,7 +50,7 @@ describe Step, type: :model do end it do should validate_length_of(:description) - .is_at_most(Constants::RICH_TEXT_MAX_LENGTH) + .is_at_most(Constants::RICH_TEXT_MAX_LENGTH) end it { should validate_inclusion_of(:completed).in_array([true, false]) } end diff --git a/spec/models/table_spec.rb b/spec/models/table_spec.rb index 6fa844103..608da62f7 100644 --- a/spec/models/table_spec.rb +++ b/spec/models/table_spec.rb @@ -25,9 +25,9 @@ describe Table, type: :model do end describe 'Relations' do - it { should belong_to :team } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:team).optional } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should have_one :step_table } it { should have_one :step } it { should have_one :result_table } diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 7e6e86c55..2690f1fdf 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -25,8 +25,8 @@ describe Tag, type: :model do describe 'Relations' do it { should belong_to :project } - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should have_many :my_module_tags } it { should have_many :my_modules } end diff --git a/spec/models/team_spec.rb b/spec/models/team_spec.rb index a56e036ef..a0b5e30c6 100644 --- a/spec/models/team_spec.rb +++ b/spec/models/team_spec.rb @@ -24,8 +24,8 @@ describe Team, type: :model do end describe 'Relations' do - it { should belong_to(:created_by).class_name('User') } - it { should belong_to(:last_modified_by).class_name('User') } + it { should belong_to(:created_by).class_name('User').optional } + it { should belong_to(:last_modified_by).class_name('User').optional } it { should have_many :user_teams } it { should have_many :users } it { should have_many :samples } @@ -45,12 +45,12 @@ describe Team, type: :model do it { should validate_presence_of :space_taken } it do should validate_length_of(:name) - .is_at_least(Constants::NAME_MIN_LENGTH) - .is_at_most(Constants::NAME_MAX_LENGTH) + .is_at_least(Constants::NAME_MIN_LENGTH) + .is_at_most(Constants::NAME_MAX_LENGTH) end it do should validate_length_of(:description) - .is_at_most(Constants::TEXT_MAX_LENGTH) + .is_at_most(Constants::TEXT_MAX_LENGTH) end end end diff --git a/spec/models/tiny_mce_asset_spec.rb b/spec/models/tiny_mce_asset_spec.rb index 331e41b3b..645401941 100644 --- a/spec/models/tiny_mce_asset_spec.rb +++ b/spec/models/tiny_mce_asset_spec.rb @@ -21,8 +21,8 @@ describe TinyMceAsset, type: :model do end describe 'Relations' do - it { should belong_to :team } - it { should belong_to :object } + it { should belong_to(:team).optional } + it { should belong_to(:object).optional } end describe 'Should be a valid object' do @@ -49,8 +49,8 @@ describe TinyMceAsset, type: :model do describe '#generate_url' do it 'create new url' do - image - expect(TinyMceAsset.generate_url(result_text.text)).to include 'sample_file.jpg' + image.update(object: result_text) + expect(TinyMceAsset.generate_url(result_text.text, result_text)).to include 'test.jpg' end end diff --git a/spec/models/user_my_module_spec.rb b/spec/models/user_my_module_spec.rb index 466a74b92..102ae7efa 100644 --- a/spec/models/user_my_module_spec.rb +++ b/spec/models/user_my_module_spec.rb @@ -24,7 +24,7 @@ describe UserMyModule, type: :model do describe 'Relations' do it { should belong_to :user } it { should belong_to :my_module } - it { should belong_to(:assigned_by).class_name('User') } + it { should belong_to(:assigned_by).class_name('User').optional } end describe 'Validations' do diff --git a/spec/models/user_notification_spec.rb b/spec/models/user_notification_spec.rb index c59645ee3..ab3cec155 100644 --- a/spec/models/user_notification_spec.rb +++ b/spec/models/user_notification_spec.rb @@ -23,8 +23,8 @@ describe UserNotification, type: :model do end describe 'Relations' do - it { should belong_to :user } - it { should belong_to :notification } + it { should belong_to(:user).optional } + it { should belong_to(:notification).optional } end describe '#unseen_notification_count ' do @@ -54,9 +54,9 @@ describe UserNotification, type: :model do end it 'set the check status to false' do - expect { + expect do UserNotification.seen_by_user(user) - }.to change { user_notification_one.reload.checked }.from(false).to(true) + end.to change { user_notification_one.reload.checked }.from(false).to(true) end end end diff --git a/spec/models/user_project_spec.rb b/spec/models/user_project_spec.rb index 3c8a29c1f..5c35aadf9 100644 --- a/spec/models/user_project_spec.rb +++ b/spec/models/user_project_spec.rb @@ -30,7 +30,7 @@ describe UserProject, type: :model do describe 'Relations' do it { should belong_to :user } it { should belong_to :project } - it { should belong_to(:assigned_by).class_name('User') } + it { should belong_to(:assigned_by).class_name('User').optional } end describe 'Should be a valid object' do diff --git a/spec/models/user_system_notification_spec.rb b/spec/models/user_system_notification_spec.rb index 1a0da2600..199b4d052 100644 --- a/spec/models/user_system_notification_spec.rb +++ b/spec/models/user_system_notification_spec.rb @@ -10,8 +10,8 @@ describe UserSystemNotification do end describe 'Associations' do - it { is_expected.to belong_to(:user) } - it { is_expected.to belong_to(:system_notification) } + it { should belong_to :user } + it { should belong_to :system_notification } end describe 'Methods' do diff --git a/spec/models/user_team_spec.rb b/spec/models/user_team_spec.rb index 1f98c469d..18fa61ead 100644 --- a/spec/models/user_team_spec.rb +++ b/spec/models/user_team_spec.rb @@ -25,7 +25,7 @@ describe UserTeam, type: :model do describe 'Relations' do it { should belong_to :user } it { should belong_to :team } - it { should belong_to(:assigned_by).class_name('User') } + it { should belong_to(:assigned_by).class_name('User').optional } end describe 'Validations' do diff --git a/spec/models/views/datatables/teams_spec.rb b/spec/models/views/datatables/teams_spec.rb index cc4095b8d..0e6fabdde 100644 --- a/spec/models/views/datatables/teams_spec.rb +++ b/spec/models/views/datatables/teams_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Views::Datatables::DatatablesTeam, type: :model do @@ -14,10 +16,10 @@ RSpec.describe Views::Datatables::DatatablesTeam, type: :model do describe 'is readonly' do let(:user) { create :user } it do - expect { + expect do Views::Datatables::DatatablesTeam.create!(user_id: user.id) - }.to raise_error(ActiveRecord::ReadOnlyRecord, - 'Views::Datatables::DatatablesTeam is marked as readonly') + end.to raise_error(ActiveRecord::ReadOnlyRecord, + 'Views::Datatables::DatatablesTeam is marked as readonly') end end end diff --git a/spec/models/zip_export_spec.rb b/spec/models/zip_export_spec.rb index d1cf9b619..8972e30d6 100644 --- a/spec/models/zip_export_spec.rb +++ b/spec/models/zip_export_spec.rb @@ -24,6 +24,6 @@ describe ZipExport, type: :model do end describe 'Relations' do - it { should belong_to :user } + it { should belong_to(:user).optional } end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index fa9acd82e..8ff668e7f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -103,6 +103,9 @@ RSpec.configure do |config| # includes FactoryBot in rspec config.include FactoryBot::Syntax::Methods + FactoryBot::SyntaxRunner.class_eval do + include ActionDispatch::TestProcess + end # Devise config.include Devise::Test::ControllerHelpers, type: :controller config.include ApiHelper, type: :controller diff --git a/spec/requests/api/v1/inventories_controller_spec.rb b/spec/requests/api/v1/inventories_controller_spec.rb index 612b1c1b8..22f4e736b 100644 --- a/spec/requests/api/v1/inventories_controller_spec.rb +++ b/spec/requests/api/v1/inventories_controller_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe "Api::V1::InventoriesController", type: :request do +RSpec.describe 'Api::V1::InventoriesController', type: :request do before :all do @user = create(:user) @teams = create_list(:team, 2, created_by: @user) @@ -55,6 +55,7 @@ RSpec.describe "Api::V1::InventoriesController", type: :request do id: @teams.first.repositories.first.id), headers: @valid_headers expect { hash_body = json }.not_to raise_exception + expect(hash_body[:data]).to match( ActiveModelSerializers::SerializableResource .new(@teams.first.repositories.first, diff --git a/spec/services/client_api/invitations_service_spec.rb b/spec/services/client_api/invitations_service_spec.rb index ddeea4c9c..b919f240d 100644 --- a/spec/services/client_api/invitations_service_spec.rb +++ b/spec/services/client_api/invitations_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ClientApi::InvitationsService do @@ -7,40 +9,40 @@ describe ClientApi::InvitationsService do it 'raises an ClientApi::CustomInvitationsError if ' \ 'role is not assigned' do - expect { + expect do ClientApi::InvitationsService.new(user: user_one, team: team_one, emails: emails_one) - }.to raise_error(ClientApi::CustomInvitationsError) + end.to raise_error(ClientApi::CustomInvitationsError) end it 'raises an ClientApi::CustomInvitationsError if ' \ 'emails are not assigned' do - expect { + expect do ClientApi::InvitationsService.new(user: user_one, team: team_one, role: 'normal_user') - }.to raise_error(ClientApi::CustomInvitationsError) + end.to raise_error(ClientApi::CustomInvitationsError) end it 'raises an ClientApi::CustomInvitationsError if ' \ 'emails are not present' do - expect { + expect do ClientApi::InvitationsService.new(user: user_one, team: team_one, role: 'normal_user', emails: []) - }.to raise_error(ClientApi::CustomInvitationsError) + end.to raise_error(ClientApi::CustomInvitationsError) end it 'raises an ClientApi::CustomInvitationsError if ' \ 'role is not included in UserTeam.roles' do - expect { + expect do ClientApi::InvitationsService.new(user: user_one, team: team_one, role: 'abnormal_user', emails: emails_one) - }.to raise_error(ClientApi::CustomInvitationsError) + end.to raise_error(ClientApi::CustomInvitationsError) end describe '#invitation' do diff --git a/spec/services/client_api/teams/create_service_spec.rb b/spec/services/client_api/teams/create_service_spec.rb index 57207bc1d..c9ca3d995 100644 --- a/spec/services/client_api/teams/create_service_spec.rb +++ b/spec/services/client_api/teams/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' include ClientApi::Teams diff --git a/spec/services/client_api/teams_service_spec.rb b/spec/services/client_api/teams_service_spec.rb index 40025f951..2bd53bcf0 100644 --- a/spec/services/client_api/teams_service_spec.rb +++ b/spec/services/client_api/teams_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ClientApi::TeamsService do @@ -5,21 +7,21 @@ describe ClientApi::TeamsService do let(:user_one) { create :user } it 'should raise an ClientApi::CustomTeamError if user is not assigned' do - expect { + expect do ClientApi::TeamsService.new(team_id: team_one.id) - }.to raise_error(ClientApi::CustomTeamError) + end.to raise_error(ClientApi::CustomTeamError) end it 'should raise an ClientApi::CustomTeamError if team is not assigned' do - expect { + expect do ClientApi::TeamsService.new(current_user: user_one) - }.to raise_error(ClientApi::CustomTeamError) + end.to raise_error(ClientApi::CustomTeamError) end it 'should raise an ClientApi::CustomTeamError if team is not user team' do - expect { + expect do ClientApi::TeamsService.new(current_user: user_one, team_id: team_one.id) - }.to raise_error(ClientApi::CustomTeamError) + end.to raise_error(ClientApi::CustomTeamError) end describe '#change_current_team!' do @@ -75,9 +77,9 @@ describe ClientApi::TeamsService do description: "super long: #{'a' * Constants::TEXT_MAX_LENGTH}" } ) - expect { + expect do team_service.update_team! - }.to raise_error(ClientApi::CustomTeamError) + end.to raise_error(ClientApi::CustomTeamError) end it 'should update the team description if the input is valid' do diff --git a/spec/services/client_api/user_team_service_spec.rb b/spec/services/client_api/user_team_service_spec.rb index 39e8a9e89..4c277bd94 100644 --- a/spec/services/client_api/user_team_service_spec.rb +++ b/spec/services/client_api/user_team_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ClientApi::UserTeamService do @@ -7,25 +9,25 @@ describe ClientApi::UserTeamService do let(:user_team) { create :user_team, :admin, user: user_one, team: team_one } it 'should raise ClientApi::CustomUserTeamError if user is not assigned' do - expect { + expect do ClientApi::UserTeamService.new( team_id: team_one.id, user_team_id: user_team.id ) - }.to raise_error(ClientApi::CustomUserTeamError) + end.to raise_error(ClientApi::CustomUserTeamError) end it 'should raise ClientApi::CustomUserTeamError if team is not assigned' do - expect { + expect do ClientApi::UserTeamService.new(user: user_one, user_team_id: user_team.id) - }.to raise_error(ClientApi::CustomUserTeamError) + end.to raise_error(ClientApi::CustomUserTeamError) end it 'should raise ClientApi::CustomUserTeamError if ' \ 'user_team is not assigned' do - expect { + expect do ClientApi::UserTeamService.new(user: user_one, team_id: team_one.id) - }.to raise_error(ClientApi::CustomUserTeamError) + end.to raise_error(ClientApi::CustomUserTeamError) end describe '#destroy_user_team_and_assign_new_team_owner!' do @@ -36,9 +38,9 @@ describe ClientApi::UserTeamService do user_team_id: user_team.id, user: user_one ) - expect { + expect do ut_service.destroy_user_team_and_assign_new_team_owner! - }.to raise_error(ClientApi::CustomUserTeamError) + end.to raise_error(ClientApi::CustomUserTeamError) end it 'should destroy the user_team relation' do @@ -73,9 +75,9 @@ describe ClientApi::UserTeamService do team_id: team_one.id, user_team_id: user_team.id ) - expect { + expect do ut_service.update_role! - }.to raise_error(ClientApi::CustomUserTeamError) + end.to raise_error(ClientApi::CustomUserTeamError) end it 'should update user role' do @@ -101,9 +103,9 @@ describe ClientApi::UserTeamService do user_team_id: user_team.id, role: 1 ) - expect { + expect do ut_service.update_role! - }.to raise_error(ClientApi::CustomUserTeamError) + end.to raise_error(ClientApi::CustomUserTeamError) end end diff --git a/spec/services/client_api/users/update_service_spec.rb b/spec/services/client_api/users/update_service_spec.rb index 81998a9e0..d86d99cac 100644 --- a/spec/services/client_api/users/update_service_spec.rb +++ b/spec/services/client_api/users/update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' include ClientApi::Users diff --git a/spec/services/experiments/generate_workflow_image_service_spec.rb b/spec/services/experiments/generate_workflow_image_service_spec.rb index 5129ff946..d9b4382de 100644 --- a/spec/services/experiments/generate_workflow_image_service_spec.rb +++ b/spec/services/experiments/generate_workflow_image_service_spec.rb @@ -15,7 +15,6 @@ describe Experiments::GenerateWorkflowImageService do old_filename = experiment.workflowimg_file_name described_class.call(params) experiment.reload - expect(experiment.workflowimg_file_name).not_to be == old_filename end end diff --git a/spec/services/model_importers/team_importer_spec.rb b/spec/services/model_importers/team_importer_spec.rb index 9a748cb4a..95edbc094 100644 --- a/spec/services/model_importers/team_importer_spec.rb +++ b/spec/services/model_importers/team_importer_spec.rb @@ -53,7 +53,7 @@ describe TeamImporter do it { expect(@exp.restored_by_id).to be_nil } it { expect(@exp.restored_on).to be_nil } - it { expect(@exp.workflowimg.exists?).to eq(true) } + it { expect(@exp.workflowimg.attached?).to eq(true) } end describe 'Module groups' do @@ -155,9 +155,7 @@ describe TeamImporter do ) # Check for lonely tasks - if json_module['my_module_group_id'].nil? - expect(db_module.my_module_group_id).to be_nil - end + expect(db_module.my_module_group_id).to be_nil if json_module['my_module_group_id'].nil? expect(db_module.nr_of_assigned_samples).to be_zero @@ -281,13 +279,17 @@ describe TeamImporter do ) json_step['assets'].each do |json_asset| - db_asset = db_step.assets.find_by( - file_file_name: json_asset['file_file_name'] + blob_id = ActiveRecord::Base.connection.execute(\ + "SELECT active_storage_blobs.id "\ + "FROM active_storage_blobs "\ + "WHERE active_storage_blobs.filename = '#{json_asset['asset_blob']['filename']}' LIMIT 1" ) + db_asset = db_step.assets.joins(:file_attachment) + .where('active_storage_attachments.blob_id' => blob_id.as_json[0]['id'].to_i).first # Basic fields expect(db_asset.created_at).to eq( - json_asset['created_at'].to_time + json_asset['asset']['created_at'].to_time ) expect(db_asset.created_by_id).to eq USER_ID expect(db_asset.last_modified_by_id).to eq USER_ID @@ -295,24 +297,24 @@ describe TeamImporter do # Other fields expect(db_asset.estimated_size).to eq( - json_asset['estimated_size'] + json_asset['asset']['estimated_size'] ) - expect(db_asset.file_content_type).to eq( - json_asset['file_content_type'] + expect(db_asset.blob.content_type).to eq( + json_asset['asset_blob']['content_type'] ) - expect(db_asset.file_file_size).to eq( - json_asset['file_file_size'] + expect(db_asset.blob.byte_size).to eq( + json_asset['asset_blob']['byte_size'] ) - expect(db_asset.file_updated_at).to be_within(10.seconds) + expect(db_asset.blob.created_at).to be_within(10.seconds) .of(Time.now) expect(db_asset.lock).to eq( - json_asset['lock'] + json_asset['asset']['lock'] ) expect(db_asset.lock_ttl).to eq( - json_asset['lock_ttl'] + json_asset['asset']['lock_ttl'] ) expect(db_asset.version).to eq( - json_asset['version'] + json_asset['asset']['version'] ) end @@ -385,9 +387,7 @@ describe TeamImporter do end # # User assigns to the the module - unless my_module['user_my_modules'].empty? - expect(db_module.user_my_modules.first.user_id).to eq USER_ID - end + expect(db_module.user_my_modules.first.user_id).to eq USER_ID unless my_module['user_my_modules'].empty? end end end diff --git a/spec/services/model_importers/test_experiment_data/experiment.json b/spec/services/model_importers/test_experiment_data/experiment.json index 1f85e2c00..ac60279ba 100644 --- a/spec/services/model_importers/test_experiment_data/experiment.json +++ b/spec/services/model_importers/test_experiment_data/experiment.json @@ -303,22 +303,29 @@ { "assets": [ { - "created_at": "2019-01-21T13:09:35.615Z", - "created_by_id": 1, - "estimated_size": 17799, - "file_content_type": "image/png", - "file_file_name": "100gen_line_bending.png", - "file_file_size": 16181, - "file_present": true, - "file_processing": false, - "file_updated_at": "2019-01-21T13:10:05.392Z", - "id": 21, - "last_modified_by_id": null, - "lock": null, - "lock_ttl": null, - "team_id": 1, - "updated_at": "2019-01-21T13:10:06.290Z", - "version": 1 + "asset": { + "created_at": "2019-01-21T13:09:35.615Z", + "created_by_id": 1, + "estimated_size": 17799, + "file_content_type": null, + "file_file_name": null, + "file_file_size": null, + "file_present": true, + "file_processing": null, + "file_updated_at": "2019-01-21T13:10:05.392Z", + "id": 21, + "last_modified_by_id": null, + "lock": null, + "lock_ttl": null, + "team_id": 1, + "updated_at": "2019-01-21T13:10:06.290Z", + "version": 1 + }, + "asset_blob": { + "filename": "100gen_line_bending.png", + "content_type": "image/png", + "byte_size": 16181 + } } ], "checklists": [], @@ -597,22 +604,29 @@ }, { "asset": { - "created_at": "2019-01-21T12:45:29.138Z", - "created_by_id": 1, - "estimated_size": 232, - "file_content_type": "text/plain", - "file_file_name": "samples.txt", - "file_file_size": 69, - "file_present": true, - "file_processing": false, - "file_updated_at": "2019-01-21T12:45:28.889Z", - "id": 1, - "last_modified_by_id": 1, - "lock": null, - "lock_ttl": null, - "team_id": 1, - "updated_at": "2019-01-21T13:06:01.610Z", - "version": 1 + "asset": { + "created_at": "2019-01-21T12:45:29.138Z", + "created_by_id": 1, + "estimated_size": 232, + "file_content_type": null, + "file_file_name": null, + "file_file_size": null, + "file_present": true, + "file_processing": null, + "file_updated_at": "2019-01-21T12:45:28.889Z", + "id": 1, + "last_modified_by_id": 1, + "lock": null, + "lock_ttl": null, + "team_id": 1, + "updated_at": "2019-01-21T13:06:01.610Z", + "version": 1 + }, + "asset_blob": { + "filename": "samples.txt", + "content_type": "text/plain", + "byte_size": 69 + } }, "result": { "archived": true, @@ -1069,22 +1083,29 @@ { "assets": [ { - "created_at": "2019-01-21T12:45:32.296Z", - "created_by_id": 1, - "estimated_size": 1129370, - "file_content_type": "application/pdf", - "file_file_name": "G2938-90034_KitRNA6000Nano_ebook.pdf", - "file_file_size": 974068, - "file_present": true, - "file_processing": false, - "file_updated_at": "2019-01-21T12:45:32.237Z", - "id": 8, - "last_modified_by_id": 1, - "lock": null, - "lock_ttl": null, - "team_id": 1, - "updated_at": "2019-01-21T13:06:04.590Z", - "version": 1 + "asset": { + "created_at": "2019-01-21T12:45:32.296Z", + "created_by_id": 1, + "estimated_size": 1129370, + "file_content_type": null, + "file_file_name": null, + "file_file_size": null, + "file_present": true, + "file_processing": null, + "file_updated_at": "2019-01-21T12:45:32.237Z", + "id": 8, + "last_modified_by_id": 1, + "lock": null, + "lock_ttl": null, + "team_id": 1, + "updated_at": "2019-01-21T13:06:04.590Z", + "version": 1 + }, + "asset_blob": { + "filename": "G2938-90034_KitRNA6000Nano_ebook.pdf", + "content_type": "application/pdf", + "byte_size": 974068 + } } ], "checklists": [], @@ -1118,22 +1139,29 @@ "results": [ { "asset": { - "created_at": "2019-01-21T12:45:32.847Z", - "created_by_id": 1, - "estimated_size": 46131, - "file_content_type": "image/jpeg", - "file_file_name": "Bioanalyser_result.JPG", - "file_file_size": 41938, - "file_present": true, - "file_processing": false, - "file_updated_at": "2019-01-21T12:45:43.655Z", - "id": 9, - "last_modified_by_id": 1, - "lock": null, - "lock_ttl": null, - "team_id": 1, - "updated_at": "2019-01-21T12:45:44.269Z", - "version": 1 + "asset": { + "created_at": "2019-01-21T12:45:32.847Z", + "created_by_id": 1, + "estimated_size": 46131, + "file_content_type": null, + "file_file_name": null, + "file_file_size": null, + "file_present": true, + "file_processing": null, + "file_updated_at": "2019-01-21T12:45:43.655Z", + "id": 9, + "last_modified_by_id": 1, + "lock": null, + "lock_ttl": null, + "team_id": 1, + "updated_at": "2019-01-21T12:45:44.269Z", + "version": 1 + }, + "asset_blob": { + "filename": "Bioanalyser_result.JPG", + "content_type": "image/jpeg", + "byte_size": 41938 + } }, "result": { "archived": true, diff --git a/spec/services/repository_actions/duplicate_rows_spec.rb b/spec/services/repository_actions/duplicate_rows_spec.rb index 5b433ab98..24f969caf 100644 --- a/spec/services/repository_actions/duplicate_rows_spec.rb +++ b/spec/services/repository_actions/duplicate_rows_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RepositoryActions::DuplicateRows do diff --git a/spec/services/repository_datatable_service_spec.rb b/spec/services/repository_datatable_service_spec.rb index b717c5dae..7a500f138 100644 --- a/spec/services/repository_datatable_service_spec.rb +++ b/spec/services/repository_datatable_service_spec.rb @@ -65,7 +65,7 @@ describe RepositoryDatatableService do contitions = subject.send(:build_conditions, params) expect(contitions[:search_value]).to eq 'row' expect(contitions[:order_by_column]).to eq( - { column: 3, dir: 'asc' } + column: 3, dir: 'asc' ) end end diff --git a/spec/services/repository_table_state_column_update_service_spec.rb b/spec/services/repository_table_state_column_update_service_spec.rb index 982c4694a..3f97a10cf 100644 --- a/spec/services/repository_table_state_column_update_service_spec.rb +++ b/spec/services/repository_table_state_column_update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RepositoryTableStateColumnUpdateService do @@ -13,7 +15,7 @@ describe RepositoryTableStateColumnUpdateService do create :repository_column, name: 'My column 1', repository: repository, data_type: :RepositoryTextValue - end + end let!(:repository_column_2) do create :repository_column, name: 'My column 2', repository: repository, @@ -32,12 +34,12 @@ describe RepositoryTableStateColumnUpdateService do last_modified_by: user_2 end let!(:default_order) do - { '0' => ['2', 'asc'] } + { '0' => %w(2 asc) } end let!(:default_column_def) do { 'visible' => 'true', 'searchable' => 'true', - 'search' => { 'search' => '', + 'search' => { 'search' => '', 'smart' => 'true', 'regex' => 'false', 'caseInsensitive' => 'true' } } @@ -51,7 +53,7 @@ describe RepositoryTableStateColumnUpdateService do RepositoryTableStateService.new(user_1, repository).create_default_state end let!(:initial_state_2) do - RepositoryTableStateService.new(user_2, repository).create_default_state + RepositoryTableStateService.new(user_2, repository).create_default_state end it 'should keep default repository states valid' do @@ -81,11 +83,11 @@ describe RepositoryTableStateColumnUpdateService do end it 'should keep order as it was' do - initial_state_1.state['order'] = { '0' => ['3', 'desc'] } + initial_state_1.state['order'] = { '0' => %w(3 desc) } RepositoryTableStateService.new(user_1, repository).update_state( initial_state_1.state ) - initial_state_2.state['order'] = { '0' => ['4', 'asc'] } + initial_state_2.state['order'] = { '0' => %w(4 asc) } RepositoryTableStateService.new(user_2, repository).update_state( initial_state_2.state ) @@ -94,9 +96,9 @@ describe RepositoryTableStateColumnUpdateService do service.update_states_with_new_column(repository) state_1 = RepositoryTableStateService.new(user_1, repository).load_state - expect(state_1.state['order']).to eq({ '0' => ['3', 'desc'] }) + expect(state_1.state['order']).to eq('0' => %w(3 desc)) state_2 = RepositoryTableStateService.new(user_2, repository).load_state - expect(state_2.state['order']).to eq({ '0' => ['4', 'asc'] }) + expect(state_2.state['order']).to eq('0' => %w(4 asc)) end it 'should keep search as it was' do @@ -131,7 +133,7 @@ describe RepositoryTableStateColumnUpdateService do state_1 = RepositoryTableStateService.new(user_1, repository).load_state expect(state_1.state['columns']).to eq( - cols_1.merge({'8' => default_column_def, '9' => default_column_def}) + cols_1.merge('8' => default_column_def, '9' => default_column_def) ) state_2 = RepositoryTableStateService.new(user_2, repository).load_state expect(state_2.state['columns']).to eq( @@ -141,12 +143,12 @@ describe RepositoryTableStateColumnUpdateService do it 'should keep column order as it was' do initial_state_1.state['ColReorder'] = - ['5', '3', '2', '0', '1', '4', '6', '7'] + %w(5 3 2 0 1 4 6 7) RepositoryTableStateService.new(user_1, repository).update_state( initial_state_1.state ) initial_state_2.state['ColReorder'] = - ['0', '6', '1', '4', '5', '7', '2', '3'] + %w(0 6 1 4 5 7 2 3) RepositoryTableStateService.new(user_2, repository).update_state( initial_state_2.state ) @@ -156,11 +158,11 @@ describe RepositoryTableStateColumnUpdateService do state_1 = RepositoryTableStateService.new(user_1, repository).load_state expect(state_1.state['ColReorder']).to eq( - ['5', '3', '2', '0', '1', '4', '6', '7', '8', '9'] + %w(5 3 2 0 1 4 6 7 8 9) ) state_2 = RepositoryTableStateService.new(user_2, repository).load_state expect(state_2.state['ColReorder']).to eq( - ['0', '6', '1', '4', '5', '7', '2', '3', '8', '9'] + %w(0 6 1 4 5 7 2 3 8 9) ) end end @@ -170,7 +172,7 @@ describe RepositoryTableStateColumnUpdateService do RepositoryTableStateService.new(user_1, repository).create_default_state end let!(:initial_state_2) do - RepositoryTableStateService.new(user_2, repository).create_default_state + RepositoryTableStateService.new(user_2, repository).create_default_state end # For column removal, we often use the index '6' twice - first, to @@ -204,11 +206,11 @@ describe RepositoryTableStateColumnUpdateService do end it 'should keep order as it was' do - initial_state_1.state['order'] = { '0' => ['3', 'desc'] } + initial_state_1.state['order'] = { '0' => %w(3 desc) } RepositoryTableStateService.new(user_1, repository).update_state( initial_state_1.state ) - initial_state_2.state['order'] = { '0' => ['7', 'asc'] } + initial_state_2.state['order'] = { '0' => %w(7 asc) } RepositoryTableStateService.new(user_2, repository).update_state( initial_state_2.state ) @@ -217,7 +219,7 @@ describe RepositoryTableStateColumnUpdateService do service.update_states_with_removed_column(repository, '6') state_1 = RepositoryTableStateService.new(user_1, repository).load_state - expect(state_1.state['order']).to eq({ '0' => ['3', 'desc'] }) + expect(state_1.state['order']).to eq('0' => %w(3 desc)) state_2 = RepositoryTableStateService.new(user_2, repository).load_state expect(state_2.state['order']).to eq(default_order) end @@ -270,12 +272,12 @@ describe RepositoryTableStateColumnUpdateService do it 'should keep column order as it was' do initial_state_1.state['ColReorder'] = - ['5', '3', '2', '0', '1', '4', '6', '7'] + %w(5 3 2 0 1 4 6 7) RepositoryTableStateService.new(user_1, repository).update_state( initial_state_1.state ) initial_state_2.state['ColReorder'] = - ['0', '6', '1', '4', '5', '7', '2', '3'] + %w(0 6 1 4 5 7 2 3) RepositoryTableStateService.new(user_2, repository).update_state( initial_state_2.state ) @@ -285,11 +287,11 @@ describe RepositoryTableStateColumnUpdateService do state_1 = RepositoryTableStateService.new(user_1, repository).load_state expect(state_1.state['ColReorder']).to eq( - ['5', '3', '2', '0', '1', '4'] + %w(5 3 2 0 1 4) ) state_2 = RepositoryTableStateService.new(user_2, repository).load_state expect(state_2.state['ColReorder']).to eq( - ['0', '1', '4', '5', '2', '3'] + %w(0 1 4 5 2 3) ) end end @@ -308,12 +310,12 @@ describe RepositoryTableStateColumnUpdateService do let!(:initial_state) do state = RepositoryTableStateService.new(user_1, repository) .create_default_state - state.state['order'] = {'0' => ['8', 'desc']} + state.state['order'] = { '0' => %w(8 desc) } (0..9).each do |idx| state.state['columns'][idx.to_s]['search']['search'] = "search_#{idx}" end state.state['ColReorder'] = - ['0', '1', '2', '9', '8', '4', '7', '3', '5', '6'] + %w(0 1 2 9 8 4 7 3 5 6) RepositoryTableStateService.new(user_1, repository).update_state( state.state ) @@ -328,7 +330,7 @@ describe RepositoryTableStateColumnUpdateService do state = RepositoryTableStateService.new(user_1, repository).load_state expect(state).to be_valid_repository_table_state(5) expect(state.state['ColReorder']).to eq( - ['0', '1', '2', '9', '8', '4', '7', '3', '5', '6', '10'] + %w(0 1 2 9 8 4 7 3 5 6 10) ) service.update_states_with_removed_column(repository, '7') @@ -336,25 +338,25 @@ describe RepositoryTableStateColumnUpdateService do state = RepositoryTableStateService.new(user_1, repository).load_state expect(state).to be_valid_repository_table_state(4) expect(state.state['ColReorder']).to eq( - ['0', '1', '2', '8', '7', '4', '3', '5', '6', '9'] + %w(0 1 2 8 7 4 3 5 6 9) ) - expect(state.state['order']).to eq ({'0' => ['7', 'desc']}) + expect(state.state['order']).to eq('0' => %w(7 desc)) service.update_states_with_removed_column(repository, '7') state = RepositoryTableStateService.new(user_1, repository).load_state expect(state).to be_valid_repository_table_state(3) expect(state.state['ColReorder']).to eq( - ['0', '1', '2', '7', '4', '3', '5', '6', '8'] + %w(0 1 2 7 4 3 5 6 8) ) - expect(state.state['order']).to eq ({'0' => ['2', 'asc']}) + expect(state.state['order']).to eq('0' => %w(2 asc)) service.update_states_with_removed_column(repository, '7') state = RepositoryTableStateService.new(user_1, repository).load_state expect(state).to be_valid_repository_table_state(2) expect(state.state['ColReorder']).to eq( - ['0', '1', '2', '4', '3', '5', '6', '7'] + %w(0 1 2 4 3 5 6 7) ) service.update_states_with_new_column(repository) @@ -363,7 +365,7 @@ describe RepositoryTableStateColumnUpdateService do state = RepositoryTableStateService.new(user_1, repository).load_state expect(state).to be_valid_repository_table_state(4) expect(state.state['ColReorder']).to eq( - ['0', '1', '2', '4', '3', '5', '6', '7', '8', '9'] + %w(0 1 2 4 3 5 6 7 8 9) ) end end diff --git a/spec/services/repository_table_state_service_spec.rb b/spec/services/repository_table_state_service_spec.rb index 56e2075e0..a1fc4a637 100644 --- a/spec/services/repository_table_state_service_spec.rb +++ b/spec/services/repository_table_state_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RepositoryTableStateService do @@ -12,7 +14,7 @@ describe RepositoryTableStateService do create :repository_column, name: 'My column 1', repository: repository, data_type: :RepositoryTextValue - end + end let!(:repository_column_2) do create :repository_column, name: 'My column 2', repository: repository, diff --git a/spec/services/repository_zip_export_spec.rb b/spec/services/repository_zip_export_spec.rb index 695433bf6..8e5699810 100644 --- a/spec/services/repository_zip_export_spec.rb +++ b/spec/services/repository_zip_export_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' require 'zip' @@ -69,7 +71,8 @@ describe RepositoryZipExport, type: :background_job do ZipExport.skip_callback(:create, :after, :self_destruct) RepositoryZipExport.generate_zip(params, repository, user) csv_zip_file = ZipExport.first.zip_file - parsed_csv_content = Zip::File.open(csv_zip_file.path) do |zip_file| + file_path = ActiveStorage::Blob.service.public_send(:path_for, csv_zip_file.key) + parsed_csv_content = Zip::File.open(file_path) do |zip_file| csv_file = zip_file.glob('*.csv').first csv_content = csv_file.get_input_stream.read CSV.parse(csv_content, headers: true) diff --git a/spec/services/smart_annotations/html_preview_spec.rb b/spec/services/smart_annotations/html_preview_spec.rb index e74ee5f36..d502db0a5 100644 --- a/spec/services/smart_annotations/html_preview_spec.rb +++ b/spec/services/smart_annotations/html_preview_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' require 'smart_annotations/html_preview' diff --git a/spec/services/smart_annotations/permission_eval_spec.rb b/spec/services/smart_annotations/permission_eval_spec.rb index a4039f628..6d93191e0 100644 --- a/spec/services/smart_annotations/permission_eval_spec.rb +++ b/spec/services/smart_annotations/permission_eval_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe SmartAnnotations::PermissionEval do diff --git a/spec/services/smart_annotations/tag_to_html_spec.rb b/spec/services/smart_annotations/tag_to_html_spec.rb index f1cf713eb..05104dec4 100644 --- a/spec/services/smart_annotations/tag_to_html_spec.rb +++ b/spec/services/smart_annotations/tag_to_html_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe SmartAnnotations::TagToHtml do @@ -32,9 +34,9 @@ describe SmartAnnotations::TagToHtml do describe '#fetch_object/2' do it 'rises an error if type is not valid' do - expect { + expect do subject.send(:fetch_object, 'banana', project.id) - }.to raise_error(ActiveRecord::RecordNotFound) + end.to raise_error(ActiveRecord::RecordNotFound) end it 'returns the required object' do diff --git a/spec/services/smart_annotations/tag_to_text_spec.rb b/spec/services/smart_annotations/tag_to_text_spec.rb index 76d453dd1..58c40e08e 100644 --- a/spec/services/smart_annotations/tag_to_text_spec.rb +++ b/spec/services/smart_annotations/tag_to_text_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe SmartAnnotations::TagToText do @@ -29,9 +31,9 @@ describe SmartAnnotations::TagToText do describe '#fetch_object/2' do it 'rises an error if type is not valid' do - expect { + expect do subject.send(:fetch_object, 'banana', project.id) - }.to raise_error(ActiveRecord::RecordNotFound) + end.to raise_error(ActiveRecord::RecordNotFound) end it 'returns the required object' do @@ -52,7 +54,7 @@ describe SmartAnnotations::TagToText do random_text = "Sec:[@#{user_two.full_name}~#{user_two.id.base62_encode}]" expect( subject.send(:parse_users_annotations, user, team, random_text) - ).to eq "Sec:" + ).to eq 'Sec:' end end end diff --git a/spec/services/smart_annotations/text_preview_spec.rb b/spec/services/smart_annotations/text_preview_spec.rb index 42e9ade26..f2665cf6e 100644 --- a/spec/services/smart_annotations/text_preview_spec.rb +++ b/spec/services/smart_annotations/text_preview_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' require 'smart_annotations/text_preview' diff --git a/spec/services/tasks/samples_to_repository_migration_service_spec.rb b/spec/services/tasks/samples_to_repository_migration_service_spec.rb index 6730cbb78..0f3968c3c 100644 --- a/spec/services/tasks/samples_to_repository_migration_service_spec.rb +++ b/spec/services/tasks/samples_to_repository_migration_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Tasks::SamplesToRepositoryMigrationService do diff --git a/spec/services/templates_service_spec.rb b/spec/services/templates_service_spec.rb index d7799b889..7d119500b 100644 --- a/spec/services/templates_service_spec.rb +++ b/spec/services/templates_service_spec.rb @@ -50,7 +50,7 @@ describe TemplatesService do tmpl_res = tmpl_task.results.find_by_name(demo_res.name) expect(tmpl_res.name).to eq(demo_res.name) if demo_res.asset - expect(tmpl_res.asset.file.exists?).to eq(true) + expect(tmpl_res.asset.file.attached?).to eq(true) expect(demo_res.asset.file_file_name) .to eq(tmpl_res.asset.file_file_name) elsif demo_res.table @@ -65,6 +65,7 @@ describe TemplatesService do demo_task.protocol.steps.size.positive? next end + demo_task.protocol.steps.each do |demo_step| tmpl_step = tmpl_task.protocol.steps.find_by_name(demo_step.name) expect(demo_step.name).to eq(tmpl_step.name) @@ -74,7 +75,7 @@ describe TemplatesService do .to match_array(tmpl_step.assets.pluck(:file_file_name)) end tmpl_step.assets.each do |asset| - expect(asset.file.exists?).to eq(true) + expect(asset.file.attached?).to eq(true) end if demo_step.tables.present? expect(demo_step.tables.pluck(:contents)) diff --git a/vendor/assets/javascripts/marvinjslauncher.js b/vendor/assets/javascripts/marvinjslauncher.js new file mode 100644 index 000000000..31338b12e --- /dev/null +++ b/vendor/assets/javascripts/marvinjslauncher.js @@ -0,0 +1,120 @@ +(function (win) { + + function _getWrapperElement (id) { + var re = new RegExp(/^#.*/); + if (typeof id !== "string") { + return null; + } + // remove hash mark if present + return document.getElementById( (re.test(id)) ? id.substr(1) : id ); + } + + function _getPackage (wrapperElement) { + if (typeof wrapperElement.contentWindow.marvin != "undefined") { + return wrapperElement.contentWindow.marvin; + } + return null; + } + + function _createPackage(elementId, resolve, reject) { + if(elementId == null){ + reject("Element id can not be null."); + return; + } + + var wrapperElement = _getWrapperElement(elementId); + + if (wrapperElement == null) { + reject("Unable to get element with id: " + elementId); + return; + } + + var marvinPackage = _getPackage(wrapperElement); + if (marvinPackage) { + marvinPackage.onReady(function() { + resolve(marvinPackage); + }); + } else { // use listener + wrapperElement.addEventListener("load", function handleSketchLoad (e) { + var marvin = _getPackage(wrapperElement); + if (marvin) { + marvin.onReady(function() { + resolve(marvin); + }); + } else { + reject("Unable to find marvin package"); + } + + }); + } + } + + function _createEditor(elementId, resolve, reject) { + if(elementId == null){ + reject("Element id can not be null."); + return; + } + + var wrapperElement = _getWrapperElement(elementId); + + if (wrapperElement == null) { + reject("Unable to get element with id: " + elementId); + return; + } + + var marvinPackage = _getPackage(wrapperElement); + if (marvinPackage) { + marvinPackage.onReady(function() { + if (typeof marvinPackage.sketcherInstance != "undefined") { + resolve(_getPackage(wrapperElement).sketcherInstance); + return; + } else { + reject("Unable to find sketcherInstance in element with id: " + elementId); + return; + } + }); + } else { // use listener + wrapperElement.addEventListener("load", function handleSketchLoad (e) { + var marvin = _getPackage(wrapperElement); + if (marvin) { + marvin.onReady(function() { + if (typeof marvin.sketcherInstance != 'undefined') { + resolve(marvin.sketcherInstance); + } else { + reject("Unable to find sketcherInstance in iframe with id: " + elementId); + } + }); + } else { + reject("Unable to find marvin package, cannot retrieve sketcher instance"); + } + + }); + } + } + + if (!("Promise" in win) && ("ES6Promise" in win) && ("polyfill" in win.ES6Promise)) { + win.ES6Promise.polyfill(); + } + + win.MarvinJSUtil = { + "getEditor": function getEditor (elementId) { + + function createEditor (resolve, reject) { + _createEditor(elementId, resolve, reject); + }; + + return new Promise(createEditor); + } + ,"getPackage": function getPackage (elementId) { + + function createPackage (resolve, reject) { + _createPackage(elementId, resolve, reject); + }; + + return new Promise(createPackage); + } + };; + + +}(window)); + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 882fe37f0..f78327726 100644 --- a/yarn.lock +++ b/yarn.lock @@ -119,9 +119,9 @@ to-fast-properties "^2.0.0" "@fortawesome/fontawesome-free@^5.2.0": - version "5.8.2" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.8.2.tgz#fae4112c4fb47086f6ae167e32524b280000abd5" - integrity sha512-E4fDUF4fbu9AxKpaQQqCN3XBnNzb/5e0Gvd9OaQsYkK574LVI57v/EqqPfIm/mC7jYbxaPNrhvT5AF+Yzwyizg== + version "5.9.0" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.9.0.tgz#1aa5c59efb1b8c6eb6277d1e3e8c8f31998b8c8e" + integrity sha512-g795BBEzM/Hq2SYNPm/NQTIp3IWd4eXSH0ds87Na2jnrAUFX3wkyZAI4Gwj9DOaWMuz2/01i8oWI7P7T/XLkhg== "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" @@ -270,9 +270,9 @@ adjust-sourcemap-loader@^1.1.0: regex-parser "^2.2.9" agent-base@4, agent-base@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== dependencies: es6-promisify "^5.0.0" @@ -1331,9 +1331,9 @@ before-after-hook@^1.1.0: integrity sha512-l5r9ir56nda3qu14nAXIlyq1MmUSs0meCIaFAh8HwkFwP1F8eToOuS3ah2VAHHcY04jaYD7FpJC5JTXHYRbkzg== big-integer@^1.6.17: - version "1.6.43" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.43.tgz#8ac15bf13e93e509500859061233e19d8d0d99d1" - integrity sha512-9dULc9jsKmXl0Aeunug8wbF+58n+hQoFjqClN7WeZwGLh0XJUWyJJ9Ee+Ep+Ql/J9fRsTVaeThp8MhiCCrY0Jg== + version "1.6.44" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.44.tgz#4ee9ae5f5839fc11ade338fea216b4513454a539" + integrity sha512-7MzElZPTyJ2fNvBkPxtFQ2fWIkVmuzw41+BZHSzpEq3ymB2MfeKp1+yXl/tS75xCx+WnyV+yb0kp+K1C3UNwmQ== big.js@^5.2.2: version "5.2.2" @@ -1646,6 +1646,13 @@ call-me-maybe@^1.0.1: resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + caller-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" @@ -1653,11 +1660,23 @@ caller-path@^0.1.0: dependencies: callsites "^0.2.0" +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1727,14 +1746,14 @@ caniuse-api@^2.0.0: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000971" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000971.tgz#5530250a146a8fec0fae3014c94c2aae040c6cbe" - integrity sha512-ubSZfYXO2KMYtCVmDez82mjodeZa+mBYWAnBMAmFBPAn4C2PY4SD0eC/diYQD4Rj1K+WNdp0vr0JDtm0SQ6GNg== + version "1.0.30000976" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000976.tgz#33c6bc12934b003baaa9c1fa9de399122d26e2f9" + integrity sha512-QvzPHBh2cYmilQAiiamun3ooHRGKFKtVwLzzx0RG7avjpj7Gii89Yzs92EKeHNqbAA2rlcFXg4GzP/tC68OGuw== caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.30000844: - version "1.0.30000971" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000971.tgz#d1000e4546486a6977756547352bc96a4cfd2b13" - integrity sha512-TQFYFhRS0O5rdsmSbF1Wn+16latXYsQJat66f7S7lizXW1PVpWJeZw9wqqVLIjuxDRz7s7xRUj13QCfd8hKn6g== + version "1.0.30000976" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000976.tgz#d30fe12662cb2a21e130d307db9907513ca830a2" + integrity sha512-tleNB1IwPRqZiod6nUNum63xQCMN96BUO2JTeiwuRM7p9d616EHsMBjBWJMudX39qCaPuWY8KEWzMZq7A9XQMQ== canvas@1.4.x: version "1.4.0" @@ -2220,15 +2239,15 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc" - integrity sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ== +cosmiconfig@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== dependencies: + import-fresh "^2.0.0" is-directory "^0.3.1" - js-yaml "^3.9.0" + js-yaml "^3.13.1" parse-json "^4.0.0" - require-from-string "^2.0.1" create-ecdh@^4.0.0: version "4.0.3" @@ -2474,11 +2493,12 @@ cyclist@~0.2.2: integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= d@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" - integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8= + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== dependencies: - es5-ext "^0.10.9" + es5-ext "^0.10.50" + type "^1.0.1" damerau-levenshtein@^1.0.0: version "1.0.5" @@ -2788,9 +2808,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.47: - version "1.3.140" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.140.tgz#8b554dd0fdccdf1d8eb5a67426e610fef42fdd7f" - integrity sha512-gznkq18tTzRWd/nHdmt9CQ/AC9Xv/TWstqKb6bhuVWE+koLg9NFlnXMwvQAUYKxIjPI2lS5TsuqqlRsCVuVdTg== + version "1.3.166" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.166.tgz#99d267514f4b92339788172400bc527545deb75b" + integrity sha512-7XwtJz81H/PBnkmQ/07oVPOGTkBZs6ibZN8OqXNUrxjRPzR0Xj+MFcMmRZEXGilEg1Pm+97V8BZVI63qnBX1hQ== elliptic@^6.0.0: version "6.4.1" @@ -2889,7 +2909,7 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14: version "0.10.50" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778" integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw== @@ -2898,7 +2918,7 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: es6-symbol "~3.1.1" next-tick "^1.0.0" -es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: +es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= @@ -2920,9 +2940,9 @@ es6-map@^0.1.3: event-emitter "~0.3.5" es6-promise@^4.0.3: - version "4.2.6" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" - integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== es6-promisify@^5.0.0: version "5.0.0" @@ -2951,13 +2971,13 @@ es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: es5-ext "~0.10.14" es6-weak-map@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" - integrity sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8= + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== dependencies: d "1" - es5-ext "^0.10.14" - es6-iterator "^2.0.1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" es6-symbol "^3.1.1" escape-html@~1.0.3: @@ -3536,9 +3556,9 @@ faye-websocket@^0.10.0: websocket-driver ">=0.5.1" faye-websocket@~0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" - integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg= + version "0.11.3" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" + integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== dependencies: websocket-driver ">=0.5.1" @@ -4240,10 +4260,10 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-parser-js@>=0.4.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" - integrity sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w== +"http-parser-js@>=0.4.0 <0.4.11": + version "0.4.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" + integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= http-proxy-agent@^2.1.0: version "2.1.0" @@ -4354,6 +4374,14 @@ import-cwd@^2.0.0: dependencies: import-from "^2.1.0" +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + import-fresh@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" @@ -4404,11 +4432,6 @@ indexes-of@^1.0.1: resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -4417,16 +4440,21 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -4453,9 +4481,9 @@ inquirer@^3.0.6: through "^2.3.6" inquirer@^6.2.2: - version "6.3.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7" - integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA== + version "6.4.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.4.1.tgz#7bd9e5ab0567cd23b41b0180b68e0cfa82fc3c0b" + integrity sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw== dependencies: ansi-escapes "^3.2.0" chalk "^2.4.2" @@ -4484,9 +4512,9 @@ interpret@^1.0.0: integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== intl-format-cache@^2.0.5: - version "2.2.6" - resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.2.6.tgz#69839624a2b60c737e240341507c644b8124adc2" - integrity sha512-8adofiGDGj6Zo9WswzRtQojtKDSLeiZz6r8dMnttM3MYwcqVhkBBvAJ0TxOHQol4VV5PFoFirMg5Xi43Y3AMIA== + version "2.2.9" + resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.2.9.tgz#fb560de20c549cda20b569cf1ffb6dc62b5b93b4" + integrity sha512-Zv/u8wRpekckv0cLkwpVdABYST4hZNTDaX7reFetrYTJwxExR2VyTqQm+l0WmL0Qo8Mjb9Tf33qnfj0T7pjxdQ== intl-messageformat-parser@1.4.0: version "1.4.0" @@ -4862,7 +4890,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.9.0, js-yaml@^3.9.1: +js-yaml@^3.12.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.9.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -5306,9 +5334,9 @@ loglevel-colored-level-prefix@^1.0.0: loglevel "^1.4.1" loglevel@^1.4.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.2.tgz#668c77948a03dbd22502a3513ace1f62a80cc372" - integrity sha512-Jt2MHrCNdtIe1W6co3tF5KXGRkzF+TYffiQstfXa04mrss9IKXzAAXYWak8LbZseAQY03sH2GzMCMU0ZOUc9bg== + version "1.6.3" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280" + integrity sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA== longest@^1.0.1: version "1.0.1" @@ -5349,9 +5377,9 @@ lru-cache@^4.0.1, lru-cache@^4.1.1: yallist "^2.1.2" macos-release@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.2.0.tgz#ab58d55dd4714f0a05ad4b0e90f4370fef5cdea8" - integrity sha512-iV2IDxZaX8dIcM7fG6cI46uNmHUxHE4yN+Z8tKHAW1TBPMZDIKHf/3L+YnOuj/FK9il14UaVdHmiQ1tsi90ltA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" + integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== make-dir@^1.0.0: version "1.3.0" @@ -5558,7 +5586,7 @@ minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minipass@^2.2.1, minipass@^2.3.4: +minipass@^2.2.1, minipass@^2.3.5: version "2.3.5" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== @@ -5566,7 +5594,7 @@ minipass@^2.2.1, minipass@^2.3.4: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.1.1: +minizlib@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== @@ -5641,11 +5669,16 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.1, ms@^2.1.1: +ms@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -5762,9 +5795,9 @@ node-gyp@^3.8.0: which "1" node-libs-browser@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.0.tgz#c72f60d9d46de08a940dedbb25f3ffa2f9bbaa77" - integrity sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== dependencies: assert "^1.1.1" browserify-zlib "^0.2.0" @@ -5776,7 +5809,7 @@ node-libs-browser@^2.0.0: events "^3.0.0" https-browserify "^1.0.0" os-browserify "^0.3.0" - path-browserify "0.0.0" + path-browserify "0.0.1" process "^0.11.10" punycode "^1.2.4" querystring-es3 "^0.2.0" @@ -5788,7 +5821,7 @@ node-libs-browser@^2.0.0: tty-browserify "0.0.0" url "^0.11.0" util "^0.11.0" - vm-browserify "0.0.4" + vm-browserify "^1.0.1" node-pre-gyp@^0.12.0: version "0.12.0" @@ -6272,10 +6305,10 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" - integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo= +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== path-complete-extname@^0.1.0: version "0.1.0" @@ -6750,11 +6783,11 @@ postcss-js@^2.0.0: postcss "^7.0.14" postcss-load-config@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.0.0.tgz#f1312ddbf5912cd747177083c5ef7a19d62ee484" - integrity sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" + integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== dependencies: - cosmiconfig "^4.0.0" + cosmiconfig "^5.0.0" import-cwd "^2.0.0" postcss-loader@^2.1.5, postcss-loader@^2.1.6: @@ -7119,9 +7152,9 @@ postcss@^6.0, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.14, supports-color "^5.4.0" postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.6: - version "7.0.16" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.16.tgz#48f64f1b4b558cb8b52c88987724359acb010da2" - integrity sha512-MOo8zNSlIqh22Uaa3drkdIAgUGEL+AD1ESiSdmElLUmE2uVDo1QloiT/IfW9qRw8Gw+Y/w69UVMGwbufMSftxA== + version "7.0.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" + integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -7184,9 +7217,9 @@ prettier-eslint@^8.8.2: vue-eslint-parser "^2.0.2" prettier@^1.13.7, prettier@^1.7.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.1.tgz#ed64b4e93e370cb8a25b9ef7fef3e4fd1c0995db" - integrity sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg== + version "1.18.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" + integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== pretty-format@^23.0.1: version "23.6.0" @@ -7202,9 +7235,9 @@ private@^0.1.6, private@^0.1.8: integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process@^0.11.10: version "0.11.10" @@ -7271,9 +7304,9 @@ pseudomap@^1.0.2: integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.24: - version "1.1.32" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.32.tgz#3f132717cf2f9c169724b2b6caf373cf694198db" - integrity sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g== + version "1.1.33" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.33.tgz#5533d9384ca7aab86425198e10e8053ebfeab661" + integrity sha512-LTDP2uSrsc7XCb5lO7A8BI1qYxRe/8EqlRvMeEl6rsnYAqDOl8xHR+8lSAIVfrNaSAlTPTNOCgNjWcoUL3AZsw== public-encrypt@^4.0.0: version "4.0.3" @@ -7433,9 +7466,9 @@ react-bootstrap-table@^4.3.1: react-s-alert "^1.3.2" react-bootstrap-timezone-picker@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/react-bootstrap-timezone-picker/-/react-bootstrap-timezone-picker-1.0.12.tgz#8d43b018aa94c481892cbfcba3b7061739dd67fc" - integrity sha1-jUOwGKqUxIGJLL/Lo7cGFzndZ/w= + version "1.0.13" + resolved "https://registry.yarnpkg.com/react-bootstrap-timezone-picker/-/react-bootstrap-timezone-picker-1.0.13.tgz#aa77d20593415e23e8552fa130fc47fa1dc2be89" + integrity sha512-magM1u65R3y5h6oVtoNRR4mG1UAgTmFIqEMCOTtoSsvBZKS2986+B59UnsPaRNF34BMKaVHRReqryNo1nLfcow== dependencies: classnames "^2.2.5" prop-types "^15.5.10" @@ -7881,11 +7914,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -8061,7 +8089,7 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" -safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -8779,9 +8807,9 @@ table@4.0.2: string-width "^2.1.1" table@^5.0.2, table@^5.2.3: - version "5.4.0" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.0.tgz#d772a3216e68829920a41a32c18eda286c95d780" - integrity sha512-nHFDrxmbrkU7JAFKqKbDJXfzrX2UBsWmrieXFTGxiI5e4ncg3VqsZeI4EzNmX0ncp4XNGVeoxIWJXfCIXwrsvw== + version "5.4.1" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.1.tgz#0691ae2ebe8259858efb63e550b6d5f9300171e8" + integrity sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w== dependencies: ajv "^6.9.1" lodash "^4.17.11" @@ -8803,17 +8831,17 @@ tar@^2.0.0: inherits "2" tar@^4: - version "4.4.8" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" - integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== + version "4.4.10" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" + integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.4" - minizlib "^1.1.1" + minipass "^2.3.5" + minizlib "^1.2.1" mkdirp "^0.5.0" safe-buffer "^5.1.2" - yallist "^3.0.2" + yallist "^3.0.3" tcomb@^2.5.1: version "2.7.0" @@ -8953,9 +8981,9 @@ trim-right@^1.0.1: glob "^7.1.2" tslib@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== tty-browserify@0.0.0: version "0.0.0" @@ -9010,6 +9038,11 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/type/-/type-1.0.1.tgz#084c9a17fcc9151a2cdb1459905c2e45e4bb7d61" + integrity sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -9029,9 +9062,9 @@ typescript@^2.5.1: integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== ua-parser-js@^0.7.18: - version "0.7.19" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" - integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== + version "0.7.20" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" + integrity sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw== uglify-es@^3.3.4: version "3.3.9" @@ -9114,9 +9147,9 @@ unique-filename@^1.1.0: unique-slug "^2.0.0" unique-slug@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6" - integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== dependencies: imurmurhash "^0.1.4" @@ -9289,12 +9322,10 @@ viewport-dimensions@^0.2.0: resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c" integrity sha1-3nQHR9tTh/0XJfUXXpG6x2r982w= -vm-browserify@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" - integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM= - dependencies: - indexof "0.0.1" +vm-browserify@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" + integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== vue-eslint-parser@^2.0.2: version "2.0.3" @@ -9442,11 +9473,12 @@ webpack@^3.12.0: yargs "^8.0.2" websocket-driver@>=0.5.1: - version "0.7.0" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" - integrity sha1-DK+dLXVdk67gSdS90NP+LMoqJOs= + version "0.7.3" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" + integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg== dependencies: - http-parser-js ">=0.4.0" + http-parser-js ">=0.4.0 <0.4.11" + safe-buffer ">=5.1.0" websocket-extensions ">=0.1.1" websocket-extensions@>=0.1.1: @@ -9579,7 +9611,7 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2: +yallist@^3.0.0, yallist@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==