Merge branch 'develop' into features/new_experiment_views

This commit is contained in:
aignatov-bio 2022-12-22 13:33:25 +01:00 committed by GitHub
commit ea661f3c19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
189 changed files with 2014 additions and 2945 deletions

View file

@ -273,7 +273,7 @@ Style/SymbolArray:
EnforcedStyle: percent
Style/SymbolProc:
IgnoredMethods:
AllowedMethods:
- respond_to
- define_method
@ -352,7 +352,7 @@ Metrics/AbcSize:
Enabled: false
Metrics/BlockLength:
IgnoredMethods: ['describe', 'context']
AllowedMethods: ['describe', 'context']
Metrics/ClassLength:
Enabled: false

View file

@ -73,7 +73,7 @@ gem 'i18n-js', '~> 3.6' # Localization in javascript files
gem 'jbuilder' # JSON structures via a Builder-style DSL
gem 'logging', '~> 2.0.0'
gem 'nested_form_fields'
gem 'nokogiri', '~> 1.13.6' # HTML/XML parser
gem 'nokogiri', '~> 1.13.10' # HTML/XML parser
gem 'rails_autolink', '~> 1.1', '>= 1.1.6'
gem 'rgl' # Graph framework for project diagram calculations
gem 'roo', '~> 2.8.2' # Spreadsheet parser

View file

@ -357,7 +357,7 @@ GEM
logging (2.0.0)
little-plugger (~> 1.1)
multi_json (~> 1.10)
loofah (2.18.0)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -384,7 +384,7 @@ GEM
rails (>= 3.2.0)
newrelic_rpm (6.15.0)
nio4r (2.5.8)
nokogiri (1.13.7)
nokogiri (1.13.10)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
oauth2 (1.4.4)
@ -441,7 +441,7 @@ GEM
puma (5.6.4)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.0)
racc (1.6.1)
rack (2.2.4)
rack-attack (6.4.0)
rack (>= 1.0, < 3)
@ -473,8 +473,8 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
@ -689,7 +689,7 @@ DEPENDENCIES
momentjs-rails (~> 2.17.1)
nested_form_fields
newrelic_rpm
nokogiri (~> 1.13.6)
nokogiri (~> 1.13.10)
omniauth
omniauth-azure-activedirectory
omniauth-linkedin-oauth2

View file

@ -1 +1 @@
1.26.0.5
1.26.4

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -56,8 +56,6 @@
//= require_directory ./access_permissions
//= require sidebar
//= require turbolinks
//= require 'BrowserPrint-3.0.216.min'
//= require 'BrowserPrint-Zebra-1.0.216.min'
// Initialize links for submitting forms. This is useful for submitting
// forms with clicking on links outside form in cases when other than
@ -321,7 +319,7 @@ window.I18n = I18n
// Multiple modal support
// https://stackoverflow.com/a/24914782
$(document).on('show.bs.modal', '.modal', function() {
$(document).on('shown.bs.modal', '.modal', function() {
const zIndex = 1040 + 10 * $('.modal:visible').length;
$(this).css('z-index', zIndex);
setTimeout(() => $('.modal-backdrop').not('.modal-stack').css('z-index', zIndex - 1).addClass('modal-stack'));

View file

@ -5,11 +5,18 @@ var DasboardRecentWorkWidget = (function() {
function renderRecentWorkItem(data, container) {
$.each(data, (i, item) => {
var recentWorkItem = $($('#recent-work-item-template').html());
var recentWorkItemType = recentWorkItem.find('.object-type span');
recentWorkItem.attr('href', item.url);
recentWorkItem.find('.object-name').html(item.name);
recentWorkItem.find('.object-type').html(I18n.t('dashboard.recent_work.subject_type.' + item.subject_type));
recentWorkItemType.html(item.code || item.type);
recentWorkItem.find('.object-changed').html(item.last_change);
container.append(recentWorkItem);
if (item.code) {
recentWorkItemType.attr('data-toggle', 'tooltip');
recentWorkItemType.attr('title', `${item.type} ID: ${item.code}`);
recentWorkItemType.tooltip();
}
});
}

View file

@ -1,4 +1,4 @@
/* global TinyMCE I18n animateSpinner importProtocolFromFile truncateLongString */
/* global TinyMCE I18n animateSpinner importProtocolFromFile */
/* global HelperModule GLOBAL_CONSTANTS */
/* eslint-disable no-use-before-define, no-alert, no-restricted-globals, no-underscore-dangle */
@ -33,44 +33,6 @@ function initEditProtocolDescription() {
TinyMCE.initIfHasDraft(viewObject);
}
// 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';
@ -464,7 +426,6 @@ function initProtocolSectionOpenEvent() {
function init() {
initEditMyModuleDescription();
initEditProtocolDescription();
initEditDescription();
initLinkUpdate();
initLoadFromRepository();
refreshProtocolStatusBar();

View file

@ -1131,7 +1131,7 @@ function updateModuleHtml(module, id, name, gridDistX, gridDistY) {
module.find(".panel-title").html(name);
module.find(".ep").html($("#drag-connections-placeholder").text().trim());
module.find(".ep").html($("#drag-connections-placeholder").html());
// Add dropdown
var dropdown = document.createElement("div");

View file

@ -519,7 +519,7 @@ var ProjectsIndex = (function() {
$(projectsPageSelector)
.on('ajax:success', '.change-projects-view-type-form', function(ev, data) {
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
$(cardsWrapper).removeClass('list cards').addClass(data.cards_view_type_class);
$(projectsPageSelector).find('.cards-switch .button-to').removeClass('selected');
$(ev.target).find('.button-to').addClass('selected');
$(ev.target).parents('.dropdown.view-switch').removeClass('open');

View file

@ -55,7 +55,7 @@
function initProjectsViewModeSwitch() {
$(experimentsPage)
.on('ajax:success', '.change-experiments-view-type-form', function(ev, data) {
$(cardsWrapper).removeClass('list').addClass(data.cards_view_type_class);
$(cardsWrapper).removeClass('list cards').addClass(data.cards_view_type_class);
$(experimentsPage).find('.cards-switch .button-to').removeClass('selected');
$(ev.target).find('.button-to').addClass('selected');
$(ev.target).parents('.dropdown.view-switch').removeClass('open');

View file

@ -53,7 +53,7 @@ function initProtocolsTable() {
</div>`;
}
}, {
targets: [ 1, 2, 3, 4, 5 ],
targets: [ 1, 2, 3, 4, 5, 6 ],
searchable: true,
orderable: true
}],
@ -61,13 +61,14 @@ function initProtocolsTable() {
{ data: "0" },
{ data: "1" },
{ data: "2" },
{ data: "3" },
{
data: "3",
data: "4",
visible: repositoryType != "archive"
},
{ data: "4" },
{ data: "5" },
{ data: "6" }
{ data: "6" },
{ data: "7" }
],
oLanguage: {
sSearch: I18n.t('general.filter')
@ -78,11 +79,6 @@ function initProtocolsTable() {
$(row).attr("data-row-id", rowId);
// Append URLs if they exist
if (data["DT_CanEdit"]) {
$(row).attr("data-can-edit", "true");
$(row).attr("data-edit-url", data["DT_EditUrl"]);
}
if (data["DT_CanClone"]) {
$(row).attr("data-can-clone", "true");
$(row).attr("data-clone-url", data["DT_CloneUrl"]);
@ -384,7 +380,6 @@ function updateDataTableSelectAllCheckbox() {
}
function updateButtons() {
var editBtn = $("[data-action='edit']");
var cloneBtn = $("[data-action='clone']");
var makePrivateBtn = $("[data-action='make-private']");
var publishBtn = $("[data-action='publish']");
@ -396,13 +391,6 @@ function updateButtons() {
if (rowsSelected.length === 1) {
// 1 ROW SELECTED
if (row.is('[data-can-edit]')) {
editBtn.removeClass('disabled hidden');
editBtn.off('click').on('click', function() { editSelectedProtocol(); });
} else {
editBtn.removeClass('hidden').addClass('disabled');
editBtn.off('click');
}
if (row.is('[data-can-clone]')) {
cloneBtn.removeClass('disabled hidden');
cloneBtn.off('click').on('click', function() { cloneSelectedProtocol(); });
@ -447,8 +435,6 @@ function updateButtons() {
}
} else if (rowsSelected.length === 0) {
// 0 ROWS SELECTED
editBtn.addClass('disabled hidden');
editBtn.off('click');
cloneBtn.addClass('disabled hidden');
cloneBtn.off('click');
makePrivateBtn.addClass('disabled hidden');
@ -470,8 +456,6 @@ function updateButtons() {
// Only enable button if all selected rows can
// be published/archived/restored/exported
editBtn.removeClass('hidden').addClass('disabled');
editBtn.off('click');
cloneBtn.removeClass('hidden').addClass('disabled');
cloneBtn.off('click');
if (!rows.is(':not([data-can-make-private])')) {
@ -524,15 +508,6 @@ function exportProtocols(ids) {
}
}
function editSelectedProtocol() {
if (rowsSelected.length && rowsSelected.length == 1) {
var row = $("tr[data-row-id='" + rowsSelected[0] + "']");
// Redirect to edit page
$(location).attr("href", row.attr("data-edit-url"));
}
}
function cloneSelectedProtocol() {
if (rowsSelected.length && rowsSelected.length == 1) {
var row = $("tr[data-row-id='" + rowsSelected[0] + "']");

View file

@ -3,216 +3,6 @@
(function(global) {
'use strict';
// Sets callbacks for toggling checkboxes
function applyCheckboxCallBack() {
$("[data-action='check-item']").off().on('click', function (e) {
var checkboxitem = $(this).find("input");
if (checkboxitem.prop("disabled")) { return; }
var checked = checkboxitem.is(":checked");
$.ajax({
url: checkboxitem.data("link-url"),
type: "POST",
dataType: "json",
data: {checklistitem_id: checkboxitem.data("id"), checked: checked},
success: function (data) {
checkboxitem.prop("checked", checked);
},
error: function (data) {
checkboxitem.prop("checked", !checked);
}
});
});
}
// Sets callback for completing/uncompleting step
function applyStepCompletedCallBack() {
// First, remove old event handlers, as we use turbolinks
$("[data-action='complete-step'], [data-action='uncomplete-step']")
.off().on('click', function(e){
var button = $(this);
var step = $(this).parents(".step");
var completed = !step.hasClass("completed");
$.ajax({
url: button.data("link-url"),
type: "POST",
dataType: "json",
data: {completed: completed},
success: function (data) {
step.toggleClass("completed");
step.find(".toggle-step-complete[data-action='complete-step']").toggleClass('hidden');
step.find(".toggle-step-complete[data-action='uncomplete-step']").toggleClass('hidden');
step.find(".button-step-complete[data-action='complete-step']").toggleClass('hidden');
step.find(".button-step-complete[data-action='uncomplete-step']").toggleClass('hidden');
},
error: function(response) {
if (response.status === 403) {
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
}
}
});
});
}
function applyCancelCallBack() {
//Click on cancel button
$("[data-action='cancel-edit']")
.on("ajax:success", function(e, data) {
var $form = $(this).closest("form");
$form.after(data.html);
var $new_step = $(this).next();
$(this).remove();
initCallBacks();
initHandsOnTable($new_step);
toggleButtons(true);
setTimeout(function() {
initStepsComments();
SmartAnnotation.preventPropagation('.atwho-user-popover');
TinyMCE.destroyAll();
DragNDropSteps.clearFiles();
MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
}, 1000);
})
.on("ajax:error", function(e, xhr, status, error) {
// TODO: error handling
});
}
// Set callback for click on edit button
function applyEditCallBack() {
$("[data-action='edit-step']")
.on("ajax:success", function(e, data) {
var $step = $(this).closest(".step");
var $edit_step = $step.after(data.html);
var $form = $step.next();
$step.remove();
formCallback($form);
initEditableHandsOnTable($form);
applyCancelCallBack();
formEditAjax($form);
toggleButtons(false);
initializeCheckboxSorting();
animateSpinner(null, false);
DragNDropSteps.clearFiles();
if (tinyMCE.editors.step_description_textarea) tinyMCE.editors.step_description_textarea.remove();
TinyMCE.init('#step_description_textarea');
$form.on('change', '.checklist-item-text, .checklist-name', function() {
$form.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
});
$("#new-step-checklists fieldset.nested_step_checklists ul").each(function () {
enableCheckboxSorting(this);
});
$("#step_name").focus();
$("#new-step-main-tab a").on("shown.bs.tab", function() {
$("#step_name").focus();
tinyMCE.editors.step_description_textarea.remove();
TinyMCE.init('#step_description_textarea');
});
})
.on("ajax:error", function(e, response) {
if (response.status === 403) {
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
}
});
}
// Set callback for click on move step
function applyMoveStepCallBack() {
$('#steps').on('ajax:success', "[data-action='move-step']", function(e, data) {
if ($.isEmptyObject(data)) return;
let $step = $(this).closest('.step');
let steps = $('#steps').find('.step');
$('#steps').append($.map(data.steps_order, function(step_data) {
let step = $('#steps').find(`.step[data-id=${step_data.id}]`);
step.find('.step-number').html(`${step_data.position + 1}.`);
return step;
}));
$('html, body').animate({ scrollTop: $step.offset().top - window.innerHeight / 2 });
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
})
.on("ajax:error", function(e, xhr) {
if (xhr.status === 403) {
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
}
});
}
function formCallback($form) {
$form
.on("fields_added.nested_form_fields", function(e, param) {
if (param.object_class == "table") {
initEditableHandsOnTable($form);
}
})
.on("fields_removed.nested_form_fields", function(e, param) {
if (param.object_class == "asset") {
// Clear file input
$(e.target).find("input[type='file']").val("");
}
});
}
// Init ajax success/error for edit form
function formEditAjax($form) {
$form
.on("ajax:beforeSend", function () {
$(".nested_step_checklists ul").each(function () {
reorderCheckboxData(this);
});
})
.on("ajax:success", function(e, data) {
$(this).after(data.html);
var $new_step = $(this).next();
$(this).remove();
initCallBacks();
initHandsOnTable($new_step);
toggleButtons(true);
TinyMCE.destroyAll();
SmartAnnotation.preventPropagation('.atwho-user-popover');
// Show the edited step
$new_step.find(".panel-collapse:first").addClass("collapse in");
//Rerender tables
$new_step.find("[data-role='step-hot-table']").each(function() {
renderTable($(this));
});
})
.on("ajax:error", function(e, xhr, status, error) {
$form.renderFormErrors('step', xhr.responseJSON, true, e);
formCallback($form);
initEditableHandsOnTable($form);
applyCancelCallBack();
SmartAnnotation.preventPropagation('.atwho-user-popover');
//Rerender tables
$form.find("[data-role='step-hot-table']").each(function() {
renderTable($(this));
});
});
}
function toggleButtons(show) {
if (show) {
$("[data-action='new-step']").show();
$("[data-action='edit-step']").show();
if ($('#steps .step').length > 0) {
$(".expand-all-steps").show();
}
} else {
$("[data-action='new-step']").hide();
$("[data-action='edit-step']").hide();
}
}
// Creates handsontable where needed
function initHandsOnTable(root) {
root.find("[data-role='hot-table']").each(function() {
@ -249,383 +39,16 @@
});
}
// Init handsontable which can be edited
function initEditableHandsOnTable(root) {
root.find("[data-role='editable-table']").each(function() {
var $container = $(this).find(".hot");
// On init
if ($container.is("[initialized]")) {
return true;
}
var contents = $(this).find('.hot-contents');
var data = null;
if (contents.attr("value")) {
data = JSON.parse(contents.attr("value")).data;
}
if ($container.is(":visible")) {
$(this).css("width", $("#new-step-tables").css("width"));
}
$container.handsontable({
data: data,
startRows: <%= Constants::HANDSONTABLE_INIT_ROWS_CNT %>,
startCols: <%= Constants::HANDSONTABLE_INIT_COLS_CNT %>,
minRows: 1,
minCols: 1,
rowHeaders: true,
colHeaders: true,
contextMenu: true,
formulas: true,
preventOverflow: 'horizontal',
afterChange: function() {
$container.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
}
});
$container.attr("initialized", true);
renderTable($container);
});
$("#new-step-tables-tab a")
.on("shown.bs.tab", function() {
$(this).parents("form").find("div.hot").each(function() {
$(this).parent().css("width", $("#new-step-tables").css("width"));
renderTable($(this));
});
});
}
function applyCancelOnNew() {
$("[data-action='cancel-new']").click(function(event) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
var $form = $(this).closest("form");
$form.parent().remove().promise().done(function() {
newStepHandler();
});
toggleButtons(true);
DragNDropSteps.clearFiles();
tinyMCE.editors.step_description_textarea.remove();
});
}
function initDeleteStep(){
$("[data-action='delete-step']").on("confirm:complete", function (e, answer) {
if (answer) {
animateLoading();
}
});
}
function initCallBacks() {
if (typeof(applyCreateWopiFileCallback) === 'function') applyCreateWopiFileCallback();
applyCheckboxCallBack();
applyStepCompletedCallBack();
applyEditCallBack();
applyMoveStepCallBack();
initDeleteStep();
TinyMCE.highlight();
}
/*
* Correction for sorting with "Sortable.min" JS library to work correctly with
* "nested_form_fields" gem.
*/
function reorderCheckboxData(checkboxUl) {
// Make sure checkbox item insertion script is always at the bottom of "ul"
// tag, otherwise item will not be inserted at bottom
if(!$(checkboxUl).children().last().is('script')) {
$(checkboxUl).find("script").appendTo(checkboxUl);
}
var $checkboxes = $(checkboxUl).find(".nested_fields");
$checkboxes.each(function (itemPos) {
var $this = $(this);
var $formGroup = $this.find(".form-group");
var $label = $formGroup.find(".control-label");
var $textInput = $formGroup.find(".checklist-item-text");
var $posInput = $formGroup.parent().find(".checklist-item-pos");
var $destroyLink = $this.find(".remove_nested_fields_link");
var labelFor = $label.attr("for");
var textName = $textInput.attr("name");
var textId = $textInput.attr("id");
var posName = $posInput.attr("name");
var posId = $posInput.attr("id");
var destroyLink = $destroyLink.attr("data-delete-association-field-name");
labelFor = labelFor.replace(/\d+_text/, itemPos + "_text");
textName = textName.replace(/\[\d+\]\[text\]/, "[" + itemPos + "][text]");
textId = textId.replace(/\d+_text/, itemPos + "_text");
posName = posName.replace(/\[\d+\]\[position\]/, "[" + itemPos + "][position]");
posId = posId.replace(/\d+_position/, itemPos + "_position");
destroyLink = destroyLink.replace(/\[\d+\]\[_destroy\]/, "[" + itemPos + "][_destroy]");
$label.attr("for", labelFor);
$textInput.attr("name", textName); // Actually needed for sorting to work
$textInput.attr("id", textId);
$posInput.attr("name", posName);
$posInput.attr("id", posId);
$posInput.val(itemPos);
$destroyLink.attr("data-delete-association-field-name", destroyLink);
var $idInput = $this.find("> input");
if ($idInput.length) {
var idName = $idInput.attr("name");
var idId = $idInput.attr("id");
idName = idName.replace(/\[\d+\]\[id\]/, "[" + itemPos + "][id]");
idId = idId.replace(/\d+_id/, itemPos + "_id");
$idInput.attr("name", idName);
$idInput.attr("id", idId);
}
if ($this.css('display') == 'none') {
// Actually needed for deleting to work
var $destroyInput = $this.prev();
var destroyName = $destroyInput.attr("name");
destroyName = destroyName.replace(/\[\d+\]\[_destroy\]/, "[" + itemPos + "][_destroy]");
$destroyInput.attr("name", destroyName);
}
$formGroup.addClass(GLOBAL_CONSTANTS.HAS_UNSAVED_DATA_CLASS_NAME);
});
}
function enableCheckboxSorting(el) {
Sortable.create(el, {
draggable: 'fieldset',
handle: '.fa-circle',
onUpdate: function () {
reorderCheckboxData(el);
}
});
}
function initializeCheckboxSorting() {
var el = $("#new-step-checklists a[data-association-path=step_checklists]");
el.click(function () {
// calling code below must be defered because at this step HTML is not
// inserted into DOM.
setTimeout(function () {
var list = el.parent().find("fieldset.nested_step_checklists:last ul");
enableCheckboxSorting(list.get(0));
});
});
}
// New step AJAX
function newStepHandler() {
$("[data-action='new-step']").off().on('click', function(event) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
var $btn = $(this);
$btn.off();
animateSpinner(null, true);
$('#protocol-container').collapse('show');
$.ajax({
url: $btn.data('href'),
method: 'GET',
success: function(data) {
var $form = $(data.html);
$('#steps').append($form).promise().done(function() {
animateSpinner(null, false);
// Scroll to bottom
$('html, body').animate({
scrollTop: $(document).height() - $(window).height()
});
formCallback($form);
applyCancelOnNew();
toggleButtons(false);
initializeCheckboxSorting();
$('#step_name').focus();
$('#new-step-main-tab a')
.on('shown.bs.tab', function() {
$('#step_name').focus();
tinyMCE.editors.step_description_textarea.remove();
TinyMCE.init('#step_description_textarea');
})
TinyMCE.init('#step_description_textarea');
});
},
error: function(response) {
if (response.status === 403) {
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
animateSpinner(null, false);
}
}
})
});
}
// Needed because server-side validation failure clears locations of
// files to be uploaded and checklist's items etc. Also user
// experience is improved
global.processStep = function processStep(ev, editMode) {
ev.stopPropagation();
ev.preventDefault();
ev.stopImmediatePropagation();
var $form = $(ev.target.form);
$form.clearFormErrors();
$form.removeBlankFileForms();
var $checklists = $form.find(".nested_step_checklists");
var checklistsValid = checklistsValidator(ev, $checklists, editMode);
var $nameInput = $form.find("#step_name");
var nameValid = textValidator(ev, $nameInput, 1,
<%= Constants::NAME_MAX_LENGTH %>);
var $descrTextarea = $form.find("#step_description_textarea");
var $tinyMCEInput = tinyMCE.editors.step_description_textarea.getContent();
var descriptionValid = textValidator(ev, $descrTextarea, 0,
<%= Constants::RICH_TEXT_MAX_LENGTH %>, false, $tinyMCEInput);
var tableNamesValidArray = [];
// iterate over table titles and validate their length
$form.find(".table_name").each(function(index , tableName) {
if (tableName.value.length > 0) {
tableNamesValidArray[index] = textValidator(ev, tableName, 1,
<%= Constants::NAME_MAX_LENGTH %> );
} else {
tableNamesValidArray[index] = true;
}
});
var tableNamesValid = true;
// passes if all table names are valid
for (var i=0;i<tableNamesValidArray.length;i++){
if(tableNamesValidArray[i] !== true){
tableNamesValid = false;
break;
}
}
if (DragNDropSteps.filesStatus() &&
checklistsValid &&
nameValid &&
descriptionValid &&
tableNamesValid ) {
$form.find("[data-role='editable-table']").each(function() {
let hot = $(this).find(".hot").handsontable('getInstance');
let contents = $(this).find('.hot-contents');
let tableData = JSON.stringify({data: hot.getData()});
contents.attr("value", tableData);
});
setTimeout(function() {
initStepsComments();
SmartAnnotation.preventPropagation('.atwho-user-popover');
}, 1000);
animateSpinner(null, true);
$(".nested_step_checklists ul").each(function () {
reorderCheckboxData(this);
});
DragNDropSteps.appendFilesToForm(ev).then(formData => {
formData.append('step[description]', $tinyMCEInput);
$.ajax({
url: $form.attr('action'),
method: 'POST',
data: formData,
contentType: false,
processData: false,
success: function(data) {
$($form.closest('.well')).after(data.html);
var $new_step = $($form.closest('.well')).next();
$($form.closest('.well')).remove();
initCallBacks();
initHandsOnTable($new_step);
expandStep($new_step);
toggleButtons(true);
SmartAnnotation.preventPropagation('.atwho-user-popover');
$new_step.find('.attachments').trigger('reorder');
tinyMCE.editors.step_description_textarea.remove();
MarvinJsEditor.initNewButton('.new-marvinjs-upload-button');
PdfPreview.initCanvas();
//Rerender tables
$new_step.find("div.step-result-hot-table").each(function() {
$(this).handsontable("render");
});
animateSpinner(null, false);
DragNDropSteps.clearFiles();
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
},
error: function(xhr) {
if (xhr.responseJSON['assets.file']) {
$('#new-step-assets-group').addClass('has-error');
$('#new-step-assets-tab').addClass('has-error');
$.each(xhr.responseJSON['assets.file'], function(_, value) {
$('#new-step-assets-group').prepend('<span class="help-block">' + value + '</span>');
});
}
animateSpinner(null, false);
SmartAnnotation.preventPropagation('.atwho-user-popover');
}
});
newStepHandler();
}).catch(() => {
$('#new-step-assets-tab').addClass('has-error');
$('#new-step-assets-group')
.addClass('has-error')
.prepend('<span class="help-block">' + I18n.t('general.file.upload_failure') + '</span>');
animateSpinner(null, false);
});
}
}
// Expand all steps
function expandAllSteps() {
$('.step .panel-collapse').collapse('show');
$(document).find("[data-role='step-hot-table']").each(function() {
renderTable($(this));
});
}
function expandStep(step) {
$('.panel-collapse', step).collapse('show');
$(step).find("div.step-result-hot-table").each(function() {
renderTable($(this));
});
}
function renderTable(table) {
$(table).handsontable("render");
// Yet another dirty hack to solve HandsOnTable problems
if (parseInt($(table).css('height'), 10) < parseInt($(table).css('max-height'), 10) - 30) {
$(table).find('.ht_master .wtHolder').css({ 'height': '100%',
'width': '100%' });
}
}
function initStepsComments() {
Comments.init();
}
// On init
initCallBacks();
initHandsOnTable($(document));
expandAllSteps();
TinyMCE.highlight();
SmartAnnotation.preventPropagation('.atwho-user-popover');
newStepHandler();
$(function () {
$("[data-action='collapse-steps']").click(function () {
$('.step .panel-collapse').collapse('hide');
});
$("[data-action='expand-steps']").click(expandAllSteps);
});
global.initHandsOnTable = initHandsOnTable;

View file

@ -135,7 +135,7 @@
if (data.archived) {
$(row).addClass('archived');
}
if (data['3'].processing || data['4'].processing) {
if (data['4'].processing || data['5'].processing) {
$(row).addClass('processing');
}
}
@ -225,7 +225,7 @@
var $table = $('#reports-table');
REPORTS_TABLE = $table.DataTable({
dom: "Rt<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
order: [[8, 'desc']],
order: [[9, 'desc']],
sScrollX: '100%',
sScrollXInner: '100%',
processing: true,
@ -244,13 +244,13 @@
render: renderCheckboxHTML
},
{
targets: 3,
targets: 4,
searchable: false,
sWidth: '60',
render: renderPdfFile
},
{
targets: 4,
targets: 5,
searchable: false,
sWidth: '60',
render: renderDocxFile

View file

@ -32,12 +32,13 @@ var AssetColumnHelper = (function() {
} else {
$el
.prev('.file-hidden-field-container')
.html(`<input type="hidden"
form="${$el.attr('form')}"
name="repository_cells[${$el.data('col-id')}]"
.html(`<input type="hidden"
form="${$el.attr('form')}"
name="repository_cells[${$el.data('col-id')}]"
value="${blob.signed_id}"/>`);
filesUploadedCntr += 1;
if (filesUploadedCntr === filesToUploadCntr) {
resolve('done');
}
@ -52,12 +53,13 @@ var AssetColumnHelper = (function() {
let empty = $cell.is(':empty');
let fileName = $cell.find('a.file-preview-link').text();
let placeholder = I18n.t('repositories.table.assets.select_file_btn', { max_size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB });
let rowId = $cell.parent().attr('id');
$cell.html(`
<div class="file-editing">
<div class="file-hidden-field-container hidden"></div>
<input class=""
id="repository_file_${columnId}"
id="repository_file_${columnId}_${rowId}"
form="${formId}"
type="file"
data-col-id="${columnId}"
@ -66,7 +68,7 @@ var AssetColumnHelper = (function() {
data-type="RepositoryAssetValue">
<div class="file-upload-button ${empty ? 'new-file' : ''}">
<i class="fas fa-paperclip icon"></i>
<label data-placeholder="${placeholder}" for="repository_file_${columnId}">${fileName}</label>
<label data-placeholder="${placeholder}" for="repository_file_${columnId}_${rowId}">${fileName}</label>
<span class="delete-action fas fa-trash"> </span>
</div>
</div>`);

View file

@ -38,7 +38,7 @@ var ChecklistColumnHelper = (function() {
}
function initialChecklistEditMode(formId, columnId, cell, values) {
var select = 'checklist-' + columnId;
var select = `checklist-${columnId}-${cell.parent()[0].id}`;
var checklistUrl = $('.repository-column#' + columnId).data('items-url');
var $select = checklistSelect(select, checklistUrl, values);
var $hiddenField = checklistHiddenField(formId, columnId, values);

View file

@ -4,7 +4,7 @@
var ListColumnHelper = (function() {
function listSelect(select, url, value) {
var selectedOption = '';
var selectObject = $(`<select id="${select}"
var selectObject = $(`<select id="${select}"
data-placeholder = "${I18n.t('repositories.table.list.select_item')}"
data-ajax-url = "${url}" >${selectedOption}</select>`);
@ -26,7 +26,7 @@ var ListColumnHelper = (function() {
}
function initialListEditMode(formId, columnId, cell, value = null) {
var select = 'list-' + columnId;
var select = `list-${columnId}-${cell.parent()[0].id}`;
var listUrl = $('.repository-column#' + columnId).data('items-url');
var $select = listSelect(select, listUrl, value);
var $hiddenField = listHiddenField(formId, columnId, value);

View file

@ -4,7 +4,7 @@
var StatusColumnHelper = (function() {
function statusSelect(select, url, value) {
var selectedOption = '';
var selectObject = $(`<select id="${select}"
var selectObject = $(`<select id="${select}"
data-placeholder = "${I18n.t('repositories.table.status.set_status')}"
data-ajax-url = "${url}" ></select>`);
@ -26,7 +26,7 @@ var StatusColumnHelper = (function() {
}
function initialStatusEditMode(formId, columnId, cell, value = null) {
var select = 'status-list-' + columnId;
var select = `status-list-${columnId}-${cell.parent()[0].id}`;
var listUrl = $('.repository-column#' + columnId).data('items-url');
var $select = statusSelect(select, listUrl, value);
var $hiddenField = statusHiddenField(formId, columnId, value);

View file

@ -40,7 +40,7 @@ $.fn.dataTable.render.editRepositoryTextValue = function(formId, columnId, cell)
data-type="RepositoryTextValue">
</div>`);
$cell.find('input').val(text);
SmartAnnotation.init($cell.find('input'));
SmartAnnotation.init($cell.find('input'), true);
};
$.fn.dataTable.render.editRepositoryListValue = function(formId, columnId, cell) {

View file

@ -42,12 +42,24 @@ var RepositoryDatatable = (function(global) {
return value;
});
function allSelectedRowsAreOnPage() {
let visibleRowIds = $(
`#repository-table-${$(TABLE_ID).data('repository-id')} tbody tr`
).toArray().map((r) => parseInt(r.id, 10));
return rowsSelected.every(r => visibleRowIds.includes(r));
}
// Enable/disable edit button
function updateButtons() {
if (currentMode === 'viewMode') {
$(TABLE_WRAPPER_ID).removeClass('editing');
$('.repository-save-changes-link').off('click');
$('.repository-edit-overlay').hide();
$('#saveCancel').hide();
$('.manage-repo-column-index').prop('disabled', false);
$('#shareRepoBtn').removeClass('disabled');
$('#viewSwitchButton').removeClass('disabled');
$('#addRepositoryRecord').prop('disabled', false);
$('.dataTables_length select').prop('disabled', false);
$('#repository-acitons-dropdown').prop('disabled', false);
@ -71,11 +83,7 @@ var RepositoryDatatable = (function(global) {
$('#editDeleteCopy').hide();
$('#toolbarPrintLabel').hide();
} else {
if (rowsSelected.length === 1) {
$('#editRepositoryRecord').prop('disabled', false);
} else {
$('#editRepositoryRecord').prop('disabled', true);
}
$('#editRepositoryRecord').prop('disabled', !allSelectedRowsAreOnPage());
$('#exportRepositoriesButton').removeClass('disabled');
$('#archiveRepositoryRecordsButton').prop('disabled', false);
$('#copyRepositoryRecords').prop('disabled', false);
@ -90,8 +98,13 @@ var RepositoryDatatable = (function(global) {
$('#editDeleteCopy').show();
$('#toolbarPrintLabel').show();
}
$('#team-switch').css({ 'pointer-events': 'auto', opacity: 1 });
$('#navigationGoBtn').prop('disabled', false);
} else if (currentMode === 'editMode') {
$(TABLE_WRAPPER_ID).addClass('editing');
$('.repository-save-changes-link').on('click', function() {
$('#saveRecord').click();
});
$('#importRecordsButton').hide();
$('#editDeleteCopy').hide();
$('#addRepositoryRecord').hide();
@ -101,6 +114,8 @@ var RepositoryDatatable = (function(global) {
}
$('#saveCancel').show();
$('.manage-repo-column-index').prop('disabled', true);
$('#shareRepoBtn').addClass('disabled');
$('#viewSwitchButton').addClass('disabled');
$('#repository-acitons-dropdown').prop('disabled', true);
$('.dataTables_length select').prop('disabled', true);
$('#addRepositoryRecord').prop('disabled', true);
@ -113,6 +128,9 @@ var RepositoryDatatable = (function(global) {
$('.repository-row-selector').prop('disabled', true);
$('.dataTables_filter input').prop('disabled', true);
$('#toolbarPrintLabel').hide();
$('.repository-edit-overlay').show();
$('#team-switch').css({ 'pointer-events': 'none', opacity: 0.6 });
$('#navigationGoBtn').prop('disabled', true);
}
}
@ -140,10 +158,8 @@ var RepositoryDatatable = (function(global) {
function changeToEditMode() {
currentMode = 'editMode';
// Table specific stuff
TABLE.button(0).enable(false);
clearRowSelection();
$(TABLE_WRAPPER_ID).find('tr:not(.editing)').addClass('blocked');
updateButtons();
}
@ -259,9 +275,8 @@ var RepositoryDatatable = (function(global) {
checkAvailableColumns();
$(TABLE_ID).find('.repository-row-edit-icon').remove();
RepositoryDatatableRowEditor.switchRowToEditMode(row);
changeToEditMode();
});
}
@ -562,6 +577,10 @@ var RepositoryDatatable = (function(global) {
// Show number of selected rows near pages info
$('#repository-table_info').append('<span id="selected_info"></span>');
$('#selected_info').html(' (' + rowsSelected.length + ' entries selected)');
// Hide edit button if not all selected rows are on the current page
$('#editRepositoryRecord').prop('disabled', !allSelectedRowsAreOnPage());
if ($('.repository-show').hasClass('archived')) {
TABLE.columns([6, 7]).visible(true);
}
@ -717,6 +736,7 @@ var RepositoryDatatable = (function(global) {
checkAvailableColumns();
RepositoryDatatableRowEditor.addNewRow(TABLE);
changeToEditMode();
$('.tooltip').remove();
})
.on('click', '#copyRepositoryRecords', function() {
animateSpinner();
@ -795,15 +815,12 @@ var RepositoryDatatable = (function(global) {
.on('click', '#editRepositoryRecord', function() {
checkAvailableColumns();
if (rowsSelected.length !== 1) {
return;
}
let row = TABLE.row('#' + rowsSelected[0]);
$(TABLE_ID).find('.repository-row-edit-icon').remove();
RepositoryDatatableRowEditor.switchRowToEditMode(row);
rowsSelected.forEach(function(rowNumber) {
RepositoryDatatableRowEditor.switchRowToEditMode(TABLE.row('#' + rowNumber));
});
changeToEditMode();
adjustTableHeader();
})
@ -825,6 +842,7 @@ var RepositoryDatatable = (function(global) {
success: function() {
$('#hideRepositoryReminders').remove();
TABLE.ajax.reload();
$('.tooltip').remove();
}
});
});

View file

@ -16,7 +16,7 @@ var RepositoryDatatableRowEditor = (function() {
function initSmartAnnotation($row) {
$row.find('[data-object="repository_cell"]').each(function(el) {
if (el.data('atwho')) {
SmartAnnotation.init(el);
SmartAnnotation.init(el, true);
}
});
}
@ -26,7 +26,7 @@ var RepositoryDatatableRowEditor = (function() {
let $row = $form.closest('tr');
let valid = true;
let directUrl = $table.data('direct-upload-url');
let $files = $row.find('input[type=file]');
let $files = $table.find('input[type=file]');
$row.find('.has-error').removeClass('has-error').find('span').remove();
// Validations here

View file

@ -23,7 +23,7 @@
_formAjaxResultAsset($form);
Results.initCancelFormButton($form, initNewResultAsset);
Results.toggleResultEditButtons(false);
dragNdropAssetsInit('results');
dragNdropAssetsInit();
},
error: function(xhr, status, e) {
$(this).renderFormErrors('result', xhr.responseJSON, true, e);

View file

@ -0,0 +1,140 @@
/* globals I18n */
(function() {
'use strict';
var expireOn;
var timeoutID;
const oneSecondTimeout = 1000;
const expireLimit = 900000; // 15min in miliseconds
function padTimeStr(i) {
var s = ('0' + Math.floor(i));
return s.substring(s.length - 2);
}
function newTimerStr(expirationTime) {
var m = (expirationTime / 60) % 60;
var s = (expirationTime % 60);
return [m, s].map(padTimeStr).join(':');
}
function getCurrentTime() {
var dateNow = new Date();
return dateNow.getTime();
}
function sessionExpireIn() {
expireOn = window.localStorage.getItem('sessionEnd');
return expireOn - getCurrentTime();
}
function setSessionTimeout(functionCallback, timeoutTime) {
if (timeoutID) {
clearTimeout(timeoutID);
}
timeoutID = setTimeout(functionCallback, timeoutTime);
}
function toogleDocumentTitle(timeString = null) {
var sleepEmoticon = String.fromCodePoint(0x1F62A);
var originalTitle = document.title.split(sleepEmoticon).pop().trim();
if (timeString) {
document.title = timeString + ' ' + sleepEmoticon + ' ' + originalTitle;
} else {
document.title = originalTitle;
}
}
function initializeSessionCountdown() {
var expirationUrl = $('meta[name=\'expiration-url\']').attr('content');
var expireIn;
if (expirationUrl) {
$.get(expirationUrl, function(data) {
expireOn = Math.max(window.localStorage.getItem('sessionEnd'), getCurrentTime() + parseInt(data, 10));
window.localStorage.setItem('sessionEnd', expireOn);
expireIn = sessionExpireIn();
if (expireIn <= 0) {
$('#session-finished').modal();
} else if (expireIn <= expireLimit + oneSecondTimeout) {
// eslint-disable-next-line no-use-before-define
setSessionTimeout(sessionCountdown, oneSecondTimeout);
} else {
setSessionTimeout(initializeSessionCountdown, (expireIn - expireLimit));
}
});
}
}
function reviveSession() {
$.post($('meta[name=\'revive-url\']').attr('content'));
toogleDocumentTitle();
window.localStorage.removeItem('sessionEnd');
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
}
function initializeSessionReviveCallbacks() {
$('#session-expire').modal().off('hide.bs.modal').on('hide.bs.modal', function() {
if (sessionExpireIn() > 0) {
reviveSession();
}
});
// for manual page reload
$(window).off('beforeunload').on('beforeunload', function() {
if (sessionExpireIn() > 0) {
reviveSession();
}
});
}
function sessionCountdown() {
var timeString;
var expireIn = sessionExpireIn();
if (!expireOn) {
initializeSessionCountdown();
} else if (expireIn > 0 && expireIn <= expireLimit) {
timeString = newTimerStr(expireIn / 1000);
toogleDocumentTitle(timeString);
$('.expiring').text(I18n.t('devise.sessions.expire_modal.session_end_in.header', { time: timeString }));
if (!$('#session-expire').hasClass('in')) {
initializeSessionReviveCallbacks();
}
setSessionTimeout(sessionCountdown, oneSecondTimeout);
} else if (expireIn <= 0) {
toogleDocumentTitle();
$('#session-expire').modal('hide');
$('#session-finished').modal();
}
}
window.localStorage.removeItem('sessionEnd');
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
$(document).on('click', '.session-login', function() {
window.location.reload();
});
$(window).on('storage', (event) => {
if (event.originalEvent.storageArea !== localStorage) return;
if (event.originalEvent.key === 'sessionEnd') {
if (!expireOn && event.originalEvent.newValue) {
if ($('#session-finished').hasClass('in')) {
$('#session-finished').modal('hide');
}
setSessionTimeout(initializeSessionCountdown, oneSecondTimeout);
} else if (expireOn && !event.originalEvent.newValue) {
toogleDocumentTitle();
}
expireOn = event.originalEvent.newValue;
$('#session-expire').modal().off('hide.bs.modal').modal('hide');
}
});
}());

View file

@ -11,7 +11,7 @@ var SmartAnnotation = (function() {
});
}
function SetAtWho(field) {
function SetAtWho(field, deferred) {
var FilterTypeEnum = Object.freeze({
USER: { tag: 'users', dataUrl: $(document.body).attr('data-atwho-users-url') },
TASK: { tag: 'sa-tasks', dataUrl: $(document.body).attr('data-atwho-task-url') },
@ -121,87 +121,96 @@ var SmartAnnotation = (function() {
};
}
function init() {
$(field)
.on('shown.atwho', function() {
var $currentAtWho = $('.atwho-view[style]:not(.old)');
var atWhoId = $currentAtWho.find('.atwho-header-res').data('at-who-key');
$currentAtWho.addClass('old').attr('data-at-who-id', atWhoId);
$(field).attr('data-smart-annotation', atWhoId);
function initAtwho() {
if ($(this).data('atwho-initialized')) return;
$currentAtWho.find('.tab-button').off().on('shown.bs.tab', function() {
$(field).click().focus();
$(this).closest('.nav-tabs').find('.tab-button').removeClass('active');
$(this).addClass('active');
});
$currentAtWho.find('.repository-object').off().on('click', function() {
$(this).parent().find('.repository-object').removeClass('btn-primary')
.addClass('btn-light');
$(this).addClass('btn-primary').removeClass('btn-light');
$(field).click().focus();
});
$(field).on('shown.atwho', function() {
var $currentAtWho = $('.atwho-view[style]:not(.old)');
var atWhoId = $currentAtWho.find('.atwho-header-res').data('at-who-key');
$currentAtWho.addClass('old').attr('data-at-who-id', atWhoId);
$(field).attr('data-smart-annotation', atWhoId);
if ($currentAtWho.find('.tab-pane.active').length === 0) {
let filterType = DEFAULT_SEARCH_FILTER.tag;
let teamId = $currentAtWho.find('.atwho-header-res').data('team-id');
let remeberedState = localStorage.getItem('smart_annotation_states/teams/' + teamId);
if (remeberedState) {
try {
remeberedState = JSON.parse(remeberedState);
filterType = remeberedState.tag;
$currentAtWho.find(`.repository-object[data-object-id=${remeberedState.repository}]`)
.addClass('btn-primary');
} catch (error) {
console.error(error);
}
$currentAtWho.find('.tab-button').off().on('shown.bs.tab', function() {
$(field).click().focus();
$(this).closest('.nav-tabs').find('.tab-button').removeClass('active');
$(this).addClass('active');
});
$currentAtWho.find('.repository-object').off().on('click', function() {
$(this).parent().find('.repository-object').removeClass('btn-primary')
.addClass('btn-light');
$(this).addClass('btn-primary').removeClass('btn-light');
$(field).click().focus();
});
if ($currentAtWho.find('.tab-pane.active').length === 0) {
let filterType = DEFAULT_SEARCH_FILTER.tag;
let teamId = $currentAtWho.find('.atwho-header-res').data('team-id');
let remeberedState = localStorage.getItem('smart_annotation_states/teams/' + teamId);
if (remeberedState) {
try {
remeberedState = JSON.parse(remeberedState);
filterType = remeberedState.tag;
$currentAtWho.find(`.repository-object[data-object-id=${remeberedState.repository}]`)
.addClass('btn-primary');
} catch (error) {
console.error(error);
}
$currentAtWho.find(`.${filterType}`).click();
}
})
.on('reposition.atwho', function(event, flag, query) {
let inputFieldLeft = query.$inputor.offset().left;
if (inputFieldLeft > $(window).width()) {
let leftPosition;
if (inputFieldLeft < flag.left + $(window).scrollLeft()) {
leftPosition = inputFieldLeft;
} else {
leftPosition = flag.left + $(window).scrollLeft();
}
query.$el.find('.atwho-view').css('left', leftPosition + 'px');
$currentAtWho.find(`.${filterType}`).click();
}
}).on('reposition.atwho', function(event, flag, query) {
let inputFieldLeft = query.$inputor.offset().left;
if (inputFieldLeft > $(window).width()) {
let leftPosition;
if (inputFieldLeft < flag.left + $(window).scrollLeft()) {
leftPosition = inputFieldLeft;
} else {
leftPosition = flag.left + $(window).scrollLeft();
}
if ($('.repository-show').length) {
query.$el.find('.atwho-view').css('top', flag.top + 'px');
}
})
.atwho({
at: '@',
callbacks: {
remoteFilter: function(query, callback) {
$.getJSON(FilterTypeEnum.USER.dataUrl, { query: query }, function(data) {
callback(data.users);
});
},
tplEval: function(_tpl, items) {
var $items = $(items.name);
$items.find('li').data('item-data', { 'atwho-at': '@' }); // Emulate at.js insertContentFor method
return $items;
},
highlighter: function(li, query) {
return matchHighlighter(li, query);
},
beforeInsert: function(value, li) {
return `[@${li.attr('data-full-name')}~${li.attr('data-id')}]`;
}
query.$el.find('.atwho-view').css('left', leftPosition + 'px');
}
if ($('.repository-show').length) {
query.$el.find('.atwho-view').css('top', flag.top + 'px');
}
}).atwho({
at: '@',
callbacks: {
remoteFilter: function(query, callback) {
$.getJSON(FilterTypeEnum.USER.dataUrl, { query: query }, function(data) {
callback(data.users);
});
},
startsWithSpace: true,
acceptSpaceBar: true,
displayTimeout: 120000
})
tplEval: function(_tpl, items) {
var $items = $(items.name);
$items.find('li').data('item-data', { 'atwho-at': '@' }); // Emulate at.js insertContentFor method
return $items;
},
highlighter: function(li, query) {
return matchHighlighter(li, query);
},
beforeInsert: function(value, li) {
return `[@${li.attr('data-full-name')}~${li.attr('data-id')}]`;
}
},
startsWithSpace: true,
acceptSpaceBar: true,
displayTimeout: 120000
})
.atwho(atWhoSettings('#'));
// .atwho(atWhoSettings('task#', FilterTypeEnum.TASK)) Waiting for better times
// .atwho(atWhoSettings('project#', FilterTypeEnum.PROJECT))
// .atwho(atWhoSettings('experiment#', FilterTypeEnum.EXPERIMENT))
// .atwho(atWhoSettings('sample#', FilterTypeEnum.REPOSITORY));
$(this).data('atwho-initialized', true);
}
function init() {
if (deferred) {
$(field).on('focus', initAtwho);
} else {
initAtwho();
}
}
return {
@ -214,8 +223,8 @@ var SmartAnnotation = (function() {
$('.atwho-header-res').find('.fa-times').click();
}
function initialize(field) {
var atWho = new SetAtWho(field);
function initialize(field, deferred) {
var atWho = new SetAtWho(field, deferred);
atWho.init();
}

View file

@ -1,6 +1,6 @@
/* global Promise ActiveStorage animateSpinner copyFromClipboard I18n
Results ResultAssets FilePreviewModal Comments truncateLongString
DragNDropSteps DragNDropResults initFormSubmitLinks dragNdropAssetsInit
DragNDropResults initFormSubmitLinks dragNdropAssetsInit
GLOBAL_CONSTANTS */
(function(global) {
@ -9,7 +9,6 @@
// Copy from clipboard
global.copyFromClipboard = (function() {
var UPLOADED_IMAGE = {};
var LOCATION = '';
function retrieveImageFromClipboardAsBlob(pasteEvent, callback) {
if (pasteEvent.clipboardData === false) {
@ -72,12 +71,7 @@
// close modal
closeModal();
// reuse file upload from drag'n drop :)
if (LOCATION === 'steps') {
DragNDropSteps.init(inputArray);
} else {
DragNDropResults.init(inputArray);
}
DragNDropResults.init(inputArray);
// clear all uploaded images
UPLOADED_IMAGE = {};
});
@ -155,8 +149,7 @@
});
}
function init(location) {
LOCATION = location;
function init() {
global.addEventListener('paste', listener, false);
}
@ -170,194 +163,6 @@
});
}());
// Module to handle file uploading in Steps
global.DragNDropSteps = (function() {
var droppedFiles = [];
var filesValid = true;
var totalSize = 0;
var fileMaxSizeMb;
var fileMaxSize;
// return the status of files if they are ready to submit
function filesStatus() {
return filesValid;
}
function clearFiles() {
droppedFiles = [];
}
function dragNdropAssetsOff() {
$('body').off('drag dragstart dragend dragover dragenter dragleave drop');
$('.is-dragover').hide();
// remove listeners for clipboard images
copyFromClipboard.destroy();
}
// append the files to the form before submit
function appendFilesToForm(ev) {
return new Promise((resolve, reject) => {
const form = $(ev.target).closest('form').get(0);
const url = $(form).find('#drag-n-drop-assets').data('directUploadUrl');
const regex = /step\[assets_attributes\]\[[0-9]*\]\[id\]/;
const lastIndex = droppedFiles.length - 1;
let prevEls = $('input').filter(function() {
return this.name.match(regex);
});
let fd = new FormData(form);
let index = 0;
fd.delete('step[file][]');
if (droppedFiles.length === 0) {
resolve(fd);
return;
}
const uploadFile = (file) => {
let upload = new ActiveStorage.DirectUpload(file, url);
upload.create(function(error, blob) {
if (error) {
reject(error);
} else {
fd.append('step[assets_attributes][' + (index + prevEls.length) + '][signed_blob_id]', blob.signed_id);
if (index === lastIndex) {
resolve(fd);
return;
}
index += 1;
uploadFile(droppedFiles[index]);
}
});
};
uploadFile(droppedFiles[index]);
filesValid = true;
totalSize = 0;
dragNdropAssetsOff();
});
}
function disableSubmitButton() {
$('.step-save').prop('disabled', true);
}
function enableSubmitButton() {
$('.step-save').prop('disabled', false);
}
function filerAndCheckFiles() {
for (let i = 0; i < droppedFiles.length; i += 1) {
if (droppedFiles[i].isValid === false) {
return false;
}
}
return (droppedFiles.length > 0);
}
function validateFilesSize(file) {
var fileSize = file.size;
totalSize += parseInt(fileSize, 10);
if (fileSize > fileMaxSize) {
file.isValid = false;
disableSubmitButton();
return "<p class='dnd-error'>" + I18n.t('general.file.size_exceeded', { file_size: fileMaxSizeMb }) + '</p>';
}
return '';
}
function validateTotalSize() {
if (totalSize > fileMaxSize) {
filesValid = false;
disableSubmitButton();
$.each($('.attachment-placeholder.new'), function() {
if (!$(this).find('p').hasClass('dnd-error')) {
$(this).append("<p class='dnd-total-error'>" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '</p>');
}
});
} else {
$('.dnd-total-error').remove();
if (filerAndCheckFiles()) {
filesValid = true;
enableSubmitButton();
}
}
}
function uploadedAssetPreview(asset, i) {
var html = `<div class="attachment-container pull-left new">
<div class="attachment-preview no-shadow new %>">
<i class="fas fa-image"></i>
</div>
<div class="attachment-label">
${truncateLongString(asset.name, GLOBAL_CONSTANTS.FILENAME_TRUNCATION_LENGTH)}
</div>
<div class="attachment-metadata"></div>
<div class="remove-icon pull-right">
<a data-item-id="${i}" href="#">
<span class="fas fa-trash"></span>
</a>
</div>
${validateFilesSize(asset)}
</div>`;
return html;
}
function removeItemHandler(id, callback) {
$('[data-item-id="' + id + '"]').off('click').on('click', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
let $el = $(this);
let index = $el.data('item-id');
totalSize -= parseInt(droppedFiles[index].size, 10);
droppedFiles.splice(index, 1);
callback();
});
}
// loops through a list of files and display each file in a separate panel
function listItems() {
totalSize = 0;
enableSubmitButton();
$('.attachment-container.new').remove();
dragNdropAssetsOff();
for (let i = 0; i < droppedFiles.length; i += 1) {
$('.attachments.edit')
.append(uploadedAssetPreview(droppedFiles[i], i))
.promise()
.done(function() {
removeItemHandler(i, listItems);
});
}
validateTotalSize();
dragNdropAssetsInit('steps');
}
function init(files) {
fileMaxSizeMb = GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB;
fileMaxSize = fileMaxSizeMb * 1024 * 1024;
for (let i = 0; i < files.length; i += 1) {
files[i].uuid = Math.random().toString(36);
droppedFiles.push(files[i]);
}
listItems();
}
return Object.freeze({
init: init,
appendFilesToForm: appendFilesToForm,
listItems: listItems,
filesStatus: filesStatus,
clearFiles: clearFiles
});
}());
// Module to handle file uploading in Results
global.DragNDropResults = (function() {
var droppedFiles = [];
@ -444,7 +249,6 @@
},
error: function() {
animateSpinner();
location.reload();
}
});
}
@ -584,7 +388,7 @@
});
}
validateTotalSize();
dragNdropAssetsInit('results');
dragNdropAssetsInit();
}
}
@ -609,7 +413,7 @@
});
}());
global.dragNdropAssetsInit = function(location) {
global.dragNdropAssetsInit = function() {
var inWindow = true;
$('body')
@ -632,13 +436,9 @@
.on('drop', function(e) {
$('.is-dragover').hide();
let files = e.originalEvent.dataTransfer.files;
if (location === 'steps') {
DragNDropSteps.init(files);
} else {
DragNDropResults.init(files);
}
DragNDropResults.init(files);
});
copyFromClipboard.init(location);
copyFromClipboard.init();
};
}(window));

View file

@ -59,12 +59,14 @@
$(document).on('click', '.print-label-button', function() {
var selectedRows = $(this).data('rows');
PrintModalComponent.showModal = true;
if (selectedRows.length) {
$('#modal-info-repository-row').modal('hide');
PrintModalComponent.row_ids = selectedRows;
} else {
PrintModalComponent.row_ids = RepositoryDatatable.selectedRows();
if (typeof PrintModalComponent !== 'undefined') {
PrintModalComponent.showModal = true;
if (selectedRows.length) {
$('#modal-info-repository-row').modal('hide');
PrintModalComponent.row_ids = selectedRows;
} else {
PrintModalComponent.row_ids = RepositoryDatatable.selectedRows();
}
}
});
}());

View file

@ -1,8 +1,18 @@
$(document).on('turbolinks:load', function() {
function initShowPassword() {
$('.fas.fa-eye.show-password').remove();
$.each($('input[type="password"]'), function(i, e) {
$('<i class="fas fa-eye show-password" style="cursor: pointer; z-index: 10"></i>').insertAfter(e);
$(`<i class="fas fa-eye show-password"
style="
cursor: pointer;
z-index: 10;
top: ${$(e).position().top}px
"></i>`).insertAfter(e);
$(e).parent().addClass('right-icon');
});
}
$(document).on('turbolinks:load', function() {
initShowPassword();
});
$(document).on('click', '.show-password', function() {

View file

@ -234,6 +234,7 @@ var TinyMCE = (function() {
setTimeout(() => {
$(editor.editorContainer).addClass('show');
$('.tinymce-placeholder').remove();
updateScrollPosition($(editor.editorContainer).closest('.tinymce-container'));
}, 400);
// Init saved status label
@ -310,7 +311,6 @@ var TinyMCE = (function() {
editorForm.find('.tinymce-status-badge').addClass('hidden');
editorForm.find('.tinymce-view').removeClass('hidden');
editor.remove();
updateScrollPosition(editorForm);
if (onSaveCallback) { onSaveCallback(); }
})

View file

@ -19,6 +19,7 @@ var zebraPrint = (function() {
const PRINTER_STATUS_ERROR = 'error';
const PRINTER_STATUS_PRINTING = 'printing';
const PRINTER_STATUS_DONE = 'done';
const PRINTER_STATUS_SEARCH = 'search';
var getPrinterStatus = function(device) {
return new Promise((resolve) => {
@ -115,11 +116,13 @@ var zebraPrint = (function() {
return selectedDevice && selectedDevice.length ? selectedDevice[0] : null;
}
function updateProgressModalData(progressModal, printerName, printerStatus, printingStatus, numberOfCopies) {
function updateProgressModalData(progressModal, printerName, printerStatus, printingStatus) {
$(progressModal + ' .title').html(stripPrinterNameHtml(printerName));
$(progressModal + ' .printer-status').attr('data-status', printerStatus);
$(progressModal + ' .printer-status').html(I18n.t('label_printers.modal_printing_status.printer_status.'
+ printerStatus));
if (printerStatus !== PRINTER_STATUS_SEARCH) {
$(progressModal + ' .printer-status').html(I18n.t('label_printers.modal_printing_status.printer_status.'
+ printerStatus));
}
$(progressModal + ' .printing-status').attr('data-status', printingStatus);
$(progressModal + ' .printing-status').html(I18n.t('label_printers.modal_printing_status.printing_status.'
+ printingStatus));
@ -190,21 +193,23 @@ var zebraPrint = (function() {
modal.replaceWith(dataZebra.responseJSON.html);
} else {
$('body').append($(dataZebra.responseJSON.html));
}
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_READY, PRINTER_STATUS_PRINTING);
device = findDevice(printData.printer_name);
new Zebra.Printer(device).isPrinterReady(function() {
$(document).on('click', progressModal, function() {
$(this).closest(progressModal).remove();
});
}
print(
device, progressModal, printData.number_of_copies,
printData.printer_name, dataZebra.responseJSON.labels
);
}, function() {
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR);
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_SEARCH, PRINTER_STATUS_SEARCH);
device = findDevice(printData.printer_name);
getPrinterStatus(device).then((device) => {
if (device.status === I18n.t('label_printers.modal_printing_status.printer_status.ready')) {
print(
device, progressModal, printData.number_of_copies,
printData.printer_name, dataZebra.responseJSON.labels
);
} else {
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR);
}
});
});
}

View file

@ -8,7 +8,7 @@
* false to set form to view mode.
*/
/* global _ filesValidator animateSpinner FileTypeEnum */
/* global _ filesValidator animateSpinner FileTypeEnum initShowPassword */
var forms = $('form[data-for]');
@ -18,6 +18,7 @@
form.find("[data-part='view']").hide();
form.find("[data-part='edit']").show();
form.find("[data-part='edit'] input:not([type='file']):not([type='submit']):first").focus();
initShowPassword();
} else {
form.find("[data-part='view']").show();
form.find("[data-part='edit'] input").blur();

View file

@ -48,12 +48,13 @@
.cards-wrapper {
--card-min-width: 350px;
--list-columns-number: 8;
grid-auto-rows: auto;
.card {
grid-row: span 7;
align-items: center;
.experiment-code-cell {
display: none;
display: block;
}
&.experiment-card {
@ -85,31 +86,25 @@
.experiment-name-cell {
@include font-h3;
-webkit-box-orient: vertical;
display: -webkit-box;
height: 3em;
-webkit-line-clamp: 2;
margin: .25em 1.75em -.25em;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 1em;
width: 100%;
a {
color: inherit;
display: inline-block;
line-height: 21px;
}
}
.actions {
position: absolute;
right: .2em;
top: .2em;
a {
-webkit-box-orient: vertical;
color: inherit;
display: -webkit-box;
height: 3em;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
}
.dates-and-img-container {
display: flex;
height: 7em;
height: 6em;
width: 100%;
.dates-container {
@ -117,11 +112,16 @@
}
}
.actions-cell {
height: 36px;
margin-left: auto;
}
.data-row {
display: flex;
line-height: 34px;
.card-label {
color: $color-silver-chalice;
width: 7em;
}
@ -156,7 +156,7 @@
&::after {
background: linear-gradient(to right, $color-transparent, $color-white 50%);
bottom: .75em;
bottom: .4em;
content: "";
height: 1.75em;
position: absolute;
@ -167,7 +167,7 @@
}
.more-button {
bottom: .8em;
bottom: .5em;
position: absolute;
right: 1em;
}
@ -248,7 +248,7 @@
}
&.list {
grid-auto-rows: 1px 5em;
grid-auto-rows: 1px minmax(3em, auto);
grid-template-columns: max-content minmax(100px, auto) minmax(80px, max-content) repeat(calc(var(--list-columns-number) - 4), minmax(100px, auto)) max-content;
grid-template-rows: 3em;
@ -262,9 +262,10 @@
.workflow-img-wrapper {
flex-shrink: 0;
height: 3.5em;
margin: .25em 1em .25em .5em;
width: 3.5em;
height: 3em;
margin: 0 .75em 0 .25em;
padding: .25em;
width: 3em;
.archived-icon-plceholder {
font-size: 2em;
@ -275,7 +276,7 @@
text-align: center;
img {
max-height: 3em;
max-height: 2.5em;
}
}
}
@ -308,6 +309,8 @@
.experiment-code-cell {
display: block;
grid-column: 3;
line-height: 1.25em;
padding-top: .5em;
}
.start-date-cell {
@ -327,8 +330,8 @@
position: relative;
.description-text {
height: 4.5em;
-webkit-line-clamp: 3;
height: 3em;
-webkit-line-clamp: 2;
&::after {
bottom: .5em;
@ -343,6 +346,7 @@
.actions-cell {
grid-column: 8;
height: 100%;
padding-top: 3px;
position: initial;
}
@ -422,12 +426,6 @@
}
}
&.readonly {
.experiment-name-cell {
margin-left: 0 !important;
}
}
&.last-page {
padding-bottom: 5em;
position: relative;
@ -474,7 +472,10 @@
.cards-wrapper {
.card.experiment-card {
.workflow-img-wrapper {
align-items: center;
background-color: $color-alto;
display: flex;
justify-content: center;
}
.progress-bar {

View file

@ -52,6 +52,21 @@
}
.module-large {
.nav-tabs {
display: flex;
}
.task-code {
align-items: center;
color: $color-volcano;
display: flex;
height: 30px;
margin-left: auto;
padding-right: .5em;
text-align: end;
}
.description-label {
word-break: break-all;
word-wrap: break-word;
@ -63,6 +78,12 @@
}
}
.archived-task-card-code {
bottom: 30px;
position: absolute;
right: 25px;
}
// Create wopi file
.create-wopi-file-btn {
cursor: pointer;

View file

@ -93,7 +93,7 @@
.repositories-dropdown-menu {
max-height: 250px;
overflow: auto;
.repository {
@include font-button;
cursor: pointer;
@ -135,16 +135,6 @@
}
}
.complete-button-container {
display: inline;
float: right;
width: 260px;
.my_module-state-buttons {
padding-top: 0;
}
}
.task-details {
.fas.block-icon {
margin-right: 8px;
@ -430,9 +420,31 @@
max-width: 100vw;
width: 650px;
.dropdown-header,
.dropdown-body {
padding: 10px 32px;
}
.dropdown-header {
background: $color-white;
border-bottom: $border-tertiary;
.protocol-name {
color: $color-black;
margin-top: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.protocol-header-info {
color: $color-black;
font-size: .875em;
font-weight: 400;
}
}
.dropdown-body {
border-bottom: $border-tertiary;
padding: 10px 32px;
.info-line {
align-items: center;
@ -657,3 +669,8 @@
}
}
}
.task-details-code {
display: inline-block;
margin-left: 4px;
}

View file

@ -199,12 +199,24 @@ path, ._jsPlumb_endpoint {
left: 0;
}
}
.panel-body {
height: 90px;
}
.ep {
font-style: italic;
.task-card-code {
color: $color-volcano;
font-style: normal;
font-weight: 400;
margin-bottom: 4px;
.new-my-module-canvas {
color: $color-silver-chalice;
}
}
}
.dropdown {
@ -673,7 +685,6 @@ li.module-hover {
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
@ -696,18 +707,13 @@ li.module-hover {
.project-name-cell {
align-items: center;
display: flex;
height: 2em;
margin: 0 1.75em;
overflow: hidden;
a {
color: inherit;
overflow: hidden;
white-space: nowrap;
}
.name {
line-height: 2em;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
@ -720,6 +726,8 @@ li.module-hover {
top: .2em;
.project-actions-menu {
height: 40px;
a {
@include font-button;
padding: .5em 1em;
@ -782,6 +790,7 @@ li.module-hover {
height: 2em;
line-height: 2em;
margin-right: .25em;
position: unset;
width: 2em;
}
@ -861,7 +870,8 @@ li.module-hover {
}
&.list {
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 3), minmax(100px, auto)) max-content max-content;
grid-auto-rows: minmax(3em, auto) 1px;
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 2), minmax(100px, auto)) max-content max-content;
.projects-group {
display: contents;
@ -909,7 +919,7 @@ li.module-hover {
}
.name {
grid-column: 5 span;
grid-column: 6 span;
line-height: 3em;
&:before {
@ -933,28 +943,39 @@ li.module-hover {
grid-column: 2;
height: 100%;
margin: 0;
padding: 5px 0;
h3 {
line-height: 1.25em;
}
a:hover {
color: $brand-primary-light;
}
}
.start-date-cell {
.project-code-cell {
display: flex;
align-items: center;
grid-column: 3;
}
.visibility-cell {
.start-date-cell {
grid-column: 4;
}
.user-cell {
.visibility-cell {
grid-column: 5;
}
.user-cell {
grid-column: 6;
}
.actions-cell {
align-items: center;
display: flex;
grid-column: 6;
grid-column: 7;
position: initial;
}
}
@ -1024,8 +1045,11 @@ li.module-hover {
display: none !important;
}
.projects-container {
.project-actions-menu {
height: 40px;
.btn-light:hover {
background: $color-alto;
}
@ -1034,6 +1058,58 @@ li.module-hover {
.cards-wrapper {
grid-auto-rows: 2.5em;
.visibility-cell {
.value {
color: $color-black;
}
}
.start-date-cell {
.value {
color: $color-black;
}
}
.archived-date-cell {
z-index: 2;
.value {
color: $color-black;
}
}
&.last-page.cards {
.project-card {
.start-date-cell {
top: 36px;
.value {
color: $color-black;
}
}
.archived-date-cell {
top: 26px;
.value {
color: $color-black;
}
}
.visibility-cell {
top: 16px;
.value {
color: $color-black;
}
}
.user-cell {
top: 6px;
}
}
}
.card.project-card {
.data-row {
color: $color-silver-chalice;
@ -1053,25 +1129,33 @@ li.module-hover {
}
&.list {
--list-columns-number: 7;
grid-auto-rows: 3em 1px;
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 2), minmax(100px, auto)) max-content;
--list-columns-number: 8;
grid-auto-rows: minmax(3em, auto) 1px;
grid-template-columns: max-content repeat(calc(var(--list-columns-number) - 3), minmax(100px, auto)) max-content max-content;
.card {
&.folder-card {
.name {
grid-column: 6 span;
grid-column: 7 span;
}
}
.user-cell {
grid-column: 7;
}
.archived-date-cell {
grid-column: 6;
grid-column: 5;
}
.actions-cell {
grid-column: 7;
grid-column: 8;
position: initial;
}
.visibility-cell {
grid-column: 6;
}
}
}
}
@ -1089,3 +1173,56 @@ li.module-hover {
margin: 1em 0;
}
}
.cards-wrapper.cards {
grid-gap: 25px;
.project-card {
.project-name-cell {
align-items: start !important;
top: 12%;
min-height: 35px;
position: absolute;
left: 14px;
top: 42px;
a {
color: inherit;
}
.name {
-webkit-box-orient: vertical;
display: -webkit-box;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
.project-code-cell {
height: min-content;
margin-top: 3px;
position: relative;
}
.data-row {
position: relative;
top: 5%;
.card-label {
color: #808080 !important;
}
&.start-date-cell {
top: 30px;
}
&.visibility-cell {
top: 18px;
}
&.user-cell {
top: 5px;
}
}
}
}

View file

@ -344,7 +344,6 @@
.preview-holder {
height: calc(100% - 40px);
margin: 0 21px 40px;
max-width: 900px;
overflow: hidden;
position: absolute;
width: calc(100% - 42px);
@ -400,18 +399,3 @@
}
}
}
@media (min-width: 1520px) {
.external-protocols-tab {
.main-protocol-panel {
.protocol-preview-panel {
.preview-holder {
margin: 0 calc(50% - 450px) 40px;
}
}
}
}
}

View file

@ -541,3 +541,47 @@
}
}
}
.repository-table {
.repository-edit-overlay {
background: $color-white;
color: $color-silver-chalice;
display: none;
z-index: 1000;
.repository-save-changes-link {
cursor: pointer;
}
}
.repository-edit-overlay--toolbar {
height: 3em;
line-height: 2em;
padding: .5em 1em .5em .5em;
position: absolute;
right: 0;
text-align: right;
top: 2em;
width: 50%;
}
.repository-edit-overlay--pagination {
bottom: 0;
height: 5em;
line-height: 5em;
padding: .5em;
position: fixed;
text-align: center;
width: calc(100% - var(--repository-sidebar-margin));
hr {
margin-top: -2.5em;
}
span {
background: $color-white;
display: inline-block;
padding: 0 .5em;
}
}
}

View file

@ -40,6 +40,10 @@
border-color: $brand-danger;
color: $color-white;
}
&[data-status="search"] {
visibility: hidden;
}
}
}

View file

@ -68,8 +68,14 @@
background-color: $color-concrete;
display: flex;
justify-content: center;
max-height: 300px;
min-height: 200px;
overflow-y: auto;
padding: .5em 0;
img {
max-width: 100%;
}
}
.label-template-option {

View file

@ -345,6 +345,13 @@
opacity: 1;
}
}
&.editing {
.assigned-column .repository-row-edit-icon {
opacity: 0;
pointer-events: none;
}
}
}
.editing {
@ -363,10 +370,6 @@
tr:hover {
.assigned-column {
.repository-row-edit-icon {
opacity: 0;
}
.assign-counter-container {
background-color: transparent;
}

View file

@ -102,7 +102,7 @@
}
}
@media (max-width: 1299px) {
@media (max-width: 1624px) {
.repository-toolbar {
.btn:not(.prevent-shrink) {

View file

@ -0,0 +1,39 @@
// scss-lint:disable NestingDepth ImportantRule
.session-modal {
.modal-body {
padding-bottom: 0;
}
ul {
list-style-type: none;
padding-left: 0;
li {
padding: .25em 0;
}
}
a:hover {
text-decoration: none;
}
.instruction-session-collapse {
cursor: pointer;
margin-bottom: .5em;
margin-top: 20px;
.fa-angle-up {
margin-left: .5em;
}
&.collapsed {
.fa-angle-up {
@include rotate(-180deg);
}
}
}
}

View file

@ -34,6 +34,18 @@
}
}
ol {
padding-left: 0;
li {
padding: .5em 0;
}
.indented_row {
padding-left: 4px;
}
}
.zebra-settings-collapse {
border-left: 3px solid $color-concrete;
margin-top: 14px;
@ -42,6 +54,10 @@
.collapse {
padding-left: 2.5em;
ol {
padding-left: 1.5em;
}
}
.collapse-row {
@ -99,6 +115,7 @@
.row-title {
@include font-h2;
margin-left: .5em;
margin-top: .25em;
}
.api-key-container {

View file

@ -59,9 +59,6 @@
display: flex;
height: 2em;
justify-content: center;
left: .5em;
position: absolute;
top: .5em;
width: 2em;
}
}

View file

@ -0,0 +1,40 @@
// scss-lint:disable SelectorDepth NestingDepth
.bootstrap-datetimepicker-widget {
.glyphicon-calendar {
margin: 0;
}
.timepicker-picker {
padding: 0 60px;
td {
height: initial;
line-height: initial;
a {
padding: 0;
}
.timepicker-hour,
.timepicker-minute {
height: initial;
line-height: initial;
padding: 6px 0;
}
}
.glyphicon-chevron-up,
.glyphicon-chevron-down {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
line-height: 1;
}
.separator {
width: 10px;
}
}
}

View file

@ -0,0 +1,6 @@
.field_with_errors,
.sci-input-container {
.help-block {
color: $brand-danger;
}
}

View file

@ -25,7 +25,7 @@
margin-bottom: 24px;
}
.sci-input-container {
.sci-input-container:not(.field_with_errors) {
margin-bottom: 24px;
.minimum-password-length {

View file

@ -10,28 +10,6 @@
@import "constants";
@import "mixins";
#new_step,
.panel-step-attachment {
ul {
list-style: none;
li {
> div > span.pull-left {
margin-top: 10px;
}
}
}
.checklist-name-container,
.table-name-container {
align-items: center;
display: flex;
.remove-container {
padding-top: 10px;
}
}
}
#steps {
// hack only for firefox

View file

@ -83,7 +83,7 @@ module AccessPermissions
def update_default_public_user_role
@project.update!(permitted_default_public_user_role_params)
UserAssignments::GroupAssignmentJob.perform_later(current_team, @project, current_user)
UserAssignments::ProjectGroupAssignmentJob.perform_later(current_team, @project, current_user)
end
private

View file

@ -13,48 +13,6 @@ module StepsActions
end
end
def fetch_new_checklists_data
checklists = []
new_checklists = step_params[:checklists_attributes]
if new_checklists
new_checklists.to_h.each do |e|
list = PreviousChecklist.new(
e.second[:id].to_i,
e.second[:name]
)
if e.second[:checklist_items_attributes]
e.second[:checklist_items_attributes].each do |el|
list.add_checklist(
PreviousChecklistItem.new(el.second[:id].to_i, el.second[:text])
)
end
end
checklists << list
end
end
checklists
end
def fetch_old_checklists_data(step)
checklists = []
if step.checklists
step.checklists.each do |e|
list = PreviousChecklist.new(
e.id,
e.name
)
e.checklist_items.each do |el|
list.add_checklist(
PreviousChecklistItem.new(el.id, el.text)
)
end
checklists << list
end
end
checklists
end
# used for step update action it traverse through the input params and
# generates notifications
def update_annotation_notifications(step,

View file

@ -475,6 +475,7 @@ class ExperimentsController < ApplicationController
end
def check_read_permissions
current_team_switch(@experiment.project.team) if current_team != @experiment.project.team
render_403 unless can_read_experiment?(@experiment) ||
@experiment.archived? && can_read_archived_experiment?(@experiment)
end

View file

@ -130,7 +130,9 @@ class LabelTemplatesController < ApplicationController
def zpl_preview
service = LabelTemplatesPreviewService.new(params, current_user)
payload = service.generate_zpl_preview!
# only render last generated label image
payload = service.generate_zpl_preview!.split.last
if service.error.blank?
render json: { base64_preview: payload }

View file

@ -71,12 +71,7 @@ class MyModuleRepositorySnapshotsController < ApplicationController
end
def full_view_sidebar
@repository = Repository.find_by(id: params[:repository_id])
if @repository
return render_403 unless can_read_repository?(@repository)
end
@repository = Repository.viewable_by_user(current_user, current_team).find_by(id: params[:repository_id])
@repository_snapshots = @my_module.repository_snapshots
.where(parent_id: params[:repository_id])
.order(created_at: :desc)

View file

@ -315,7 +315,6 @@ class MyModulesController < ApplicationController
def protocols
@protocol = @my_module.protocol
@assigned_repositories = @my_module.live_and_snapshot_repositories_list
current_team_switch(@protocol.team)
end
def protocol
@ -338,27 +337,23 @@ class MyModulesController < ApplicationController
end
def results
current_team_switch(@my_module
.experiment
.project
.team)
@results_order = params[:order] || 'new'
@results = @my_module.archived_branch? ? @my_module.results : @my_module.results.active
@results = @results.page(params[:page]).per(Constants::RESULTS_PER_PAGE_LIMIT)
@results = case @results_order
when 'old' then @results.order(updated_at: :asc)
when 'old' then @results.order(created_at: :asc)
when 'old_updated' then @results.order(updated_at: :asc)
when 'new_updated' then @results.order(updated_at: :desc)
when 'atoz' then @results.order(name: :asc)
when 'ztoa' then @results.order(name: :desc)
else @results.order(updated_at: :desc)
else @results.order(created_at: :desc)
end
end
def archive
@archived_results = @my_module.archived_results
current_team_switch(@my_module.experiment.project.team)
end
def restore_group
@ -470,6 +465,7 @@ class MyModulesController < ApplicationController
end
def check_read_permissions
current_team_switch(@project.team) if current_team != @project.team
render_403 unless can_read_my_module?(@my_module)
end

View file

@ -85,18 +85,18 @@ class RepositoryRowsController < ApplicationController
label_template = LabelTemplate.where(team_id: current_team.id).find(params[:label_template_id])
label_code = LabelTemplates::RepositoryRowService.new(label_template, @repository_row.first).render
render json: { label_code: label_code }
rescue StandardError => e
label_code = LabelTemplates::RepositoryRowService.new(label_template, @repository_row.first, true).render
render json: { error: e, label_code: label_code }, status: :unprocessable_entity
if label_code[:error].empty?
render json: { label_code: label_code[:label] }
else
render json: { error: label_code[:error].first, label_code: label_code[:label] }, status: :unprocessable_entity
end
end
def print_zpl
label_template = LabelTemplate.find_by(id: params[:label_template_id])
labels = @repository_row.flat_map do |repository_row|
LabelTemplates::RepositoryRowService.new(label_template,
repository_row,
true).render
repository_row).render[:label]
end
render(
@ -128,8 +128,7 @@ class RepositoryRowsController < ApplicationController
LabelPrinters::PrintJob.perform_later(
label_printer,
LabelTemplates::RepositoryRowService.new(label_template,
repository_row,
true).render,
repository_row).render[:label],
params[:copies].to_i
).job_id
end

View file

@ -43,6 +43,7 @@ module StepElements
@step.step_orderable_elements.where('position > ?', position).order(position: :desc).each do |element|
element.update(position: element.position + 1)
end
@checklist.name += ' (1)'
new_checklist = @checklist.duplicate(@step, current_user, position + 1)
log_step_activity(:checklist_duplicated, { checklist_name: @checklist.name })
render_step_orderable_element(new_checklist)

View file

@ -48,6 +48,7 @@ module StepElements
@step.step_orderable_elements.where('position > ?', position).order(position: :desc).each do |element|
element.update(position: element.position + 1)
end
@table.name += ' (1)'
new_table = @table.duplicate(@step, current_user, position + 1)
log_step_activity(:table_duplicated, { table_name: new_table.name })
render_step_orderable_element(new_table.step_table)

View file

@ -4,18 +4,18 @@ class StepsController < ApplicationController
include StepsActions
include MarvinJsActions
before_action :load_vars, only: %i(edit update destroy show toggle_step_state checklistitem_state update_view_state
move_up move_down update_asset_view_mode elements
before_action :load_vars, only: %i(update destroy show toggle_step_state update_view_state
update_asset_view_mode elements
attachments upload_attachment duplicate)
before_action :load_vars_nested, only: %i(new create index reorder)
before_action :load_vars_nested, only: %i(create index reorder)
before_action :convert_table_contents_to_utf8, only: %i(create update)
before_action :check_protocol_manage_permissions, only: %i(reorder)
before_action :check_view_permissions, only: %i(show index attachments elements)
before_action :check_create_permissions, only: %i(new create)
before_action :check_manage_permissions, only: %i(edit update destroy move_up move_down
before_action :check_create_permissions, only: %i(create)
before_action :check_manage_permissions, only: %i(update destroy
update_view_state update_asset_view_mode upload_attachment)
before_action :check_complete_and_checkbox_permissions, only: %i(toggle_step_state checklistitem_state)
before_action :check_complete_and_checkbox_permissions, only: %i(toggle_step_state)
def index
render json: @protocol.steps.in_order, each_serializer: StepSerializer, user: current_user
@ -76,18 +76,6 @@ class StepsController < ApplicationController
user: current_user
end
def new
@step = Step.new
respond_to do |format|
format.json do
render json: {
html: render_to_string(partial: 'new.html.erb')
}
end
end
end
def create
@step = Step.new(
name: t('protocols.steps.default_name'),
@ -106,86 +94,6 @@ class StepsController < ApplicationController
render json: @step, serializer: StepSerializer, user: current_user
end
def create_old
@step = Step.new
@step.transaction do
new_step_params = step_params
# Attach newly uploaded files, and than remove their blob ids from the parameters
new_step_params[:assets_attributes]&.each do |key, value|
next unless value[:signed_blob_id]
asset = Asset.create!(created_by: current_user, last_modified_by: current_user, team: current_team)
asset.file.attach(value[:signed_blob_id])
@step.assets << asset
new_step_params[:assets_attributes].delete(key)
end
@step.assign_attributes(new_step_params)
# gerate a tag that replaces img tag in database
@step.completed = false
@step.position = @protocol.number_of_steps
@step.protocol = @protocol
@step.user = current_user
@step.last_modified_by = current_user
@step.tables.each do |table|
table.created_by = current_user
table.team = current_team
end
# Update default checked state
@step.checklists.each do |checklist|
checklist.checklist_items.each do |checklist_item|
checklist_item.checked = false
end
end
# link tiny_mce_assets to the step
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
@step.save!
# Post process all assets
@step.assets.each do |asset|
asset.post_process_file(@protocol.team)
end
# link tiny_mce_assets to the step
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
create_annotation_notifications(@step)
# Generate activity
if @protocol.in_module?
log_activity(
:create_step,
@my_module.experiment.project,
{ my_module: @my_module.id }.merge(step_message_items)
)
else
log_activity(:add_step_to_protocol_repository, nil, { protocol: @protocol.id }.merge(step_message_items))
end
end
respond_to do |format|
if @step.errors.blank?
format.json do
render json: {
html: render_to_string(
partial: 'steps/step.html.erb',
locals: { step: @step }
)
},
status: :ok
end
else
format.json do
render json: @step.errors.to_json, status: :bad_request
end
end
end
end
def show
respond_to do |format|
format.json do
@ -200,17 +108,6 @@ class StepsController < ApplicationController
end
end
def edit
respond_to do |format|
format.json do
render json: {
html: render_to_string(partial: 'edit.html.erb')
},
status: :ok
end
end
end
def update
if @step.update(step_params)
# Generate activity
@ -259,92 +156,6 @@ class StepsController < ApplicationController
head :unprocessable_entity
end
def update_old
respond_to do |format|
old_description = @step.description
old_checklists = fetch_old_checklists_data(@step)
new_checklists = fetch_new_checklists_data
previous_size = @step.space_taken
step_params_all = step_params
# process only destroy update on step references. This prevents
# skipping deleting reference in case update validation fails.
# NOTE - step_params_all variable is updated
destroy_attributes(step_params_all)
# Attach newly uploaded files, and than remove their blob ids from the parameters
new_assets = []
step_params_all[:assets_attributes]&.each do |key, value|
next unless value[:signed_blob_id]
new_asset = @step.assets.create!(
created_by: current_user,
last_modified_by: current_user,
team: current_team,
view_mode: @step.assets_view_mode
)
new_asset.file
.attach(value[:signed_blob_id])
new_assets.push(new_asset.id)
step_params_all[:assets_attributes].delete(key)
end
@step.assign_attributes(step_params_all)
@step.last_modified_by = current_user
@step.tables.each do |table|
table.created_by = current_user if table.new_record?
table.last_modified_by = current_user unless table.new_record?
table.team = current_team
end
update_checklist_items_without_callback(step_params_all)
if @step.save
TinyMceAsset.update_images(@step, params[:tiny_mce_images], current_user)
@step.reload
# generates notification on step upadate
update_annotation_notifications(@step,
old_description,
new_checklists,
old_checklists)
# Release team's space taken
team = @protocol.team
team.release_space(previous_size)
team.save
# Post process step assets
@step.assets.each do |asset|
asset.post_process_file(team) if new_assets.include? asset.id
end
# Generate activity
if @protocol.in_module?
log_activity(:edit_step, @my_module.experiment.project, my_module: @my_module.id)
else
log_activity(:edit_step_in_protocol_repository, nil, protocol: @protocol.id)
end
format.json {
render json: {
html: render_to_string({
partial: 'step.html.erb',
locals: { step: @step }
})
}
}
else
format.json {
render json: @step.errors.to_json, status: :bad_request
}
end
end
end
def update_view_state
view_state = @step.current_view_state(current_user)
view_state.state['assets']['sort'] = params.require(:assets).require(:order)
@ -398,48 +209,6 @@ class StepsController < ApplicationController
render json: @step, serializer: StepSerializer, user: current_user
end
# Responds to checkbox toggling in steps view
def checklistitem_state
respond_to do |format|
checked = params[:checked] == 'true'
changed = @chk_item.checked != checked
@chk_item.checked = checked
if @chk_item.save
format.json { render json: {}, status: :accepted }
# Create activity
if changed
completed_items = @chk_item.checklist.checklist_items
.where(checked: true).count
all_items = @chk_item.checklist.checklist_items.count
text_activity = smart_annotation_parser(@chk_item.text)
.gsub(/\s+/, ' ')
type_of = if checked
:check_step_checklist_item
else
:uncheck_step_checklist_item
end
# This should always hold true (only in module can
# check items be checked, but still check just in case)
if @protocol.in_module?
log_activity(type_of,
@protocol.my_module.experiment.project,
my_module: @my_module.id,
step: @chk_item.checklist.step.id,
step_position: { id: @chk_item.checklist.step.id,
value_for: 'position_plus_one' },
checkbox: text_activity,
num_completed: completed_items.to_s,
num_all: all_items.to_s)
end
end
else
format.json { render json: {}, status: :unprocessable_entity }
end
end
end
# Complete/uncomplete step
def toggle_step_state
@step.completed = params[:completed] == 'true'
@ -473,30 +242,6 @@ class StepsController < ApplicationController
end
end
def move_up
respond_to do |format|
format.json do
@step.move_up
render json: {
steps_order: @protocol.steps.order(:position).select(:id, :position)
}
end
end
end
def move_down
respond_to do |format|
format.json do
@step.move_down
render json: {
steps_order: @protocol.steps.order(:position).select(:id, :position)
}
end
end
end
def reorder
@protocol.with_lock do
params[:step_positions].each do |id, position|
@ -518,72 +263,6 @@ class StepsController < ApplicationController
private
# This function is used for partial update of step references and
# it's useful when you want to execute destroy action on attribute
# collection separately from normal update action, for example if
# you don't want that update validation interupt destroy action.
# In case of step model you can delete checkboxes, assets or tables.
def destroy_attributes(params)
update_params = {}
delete_step_tables(params)
extract_destroy_params(params, update_params)
@step.update(update_params) unless update_params.blank?
end
# Delete the step table
def delete_step_tables(params)
return unless params[:tables_attributes].present?
params[:tables_attributes].each do |_, table|
next unless table['_destroy']
table_to_destroy = Table.find_by_id(table['id'])
next if table_to_destroy.nil?
table_to_destroy.report_elements.destroy_all
end
end
# Checks if hash contains destroy parameter '_destroy' and returns
# boolean value.
def has_destroy_params?(params)
params.each do |key, values|
next unless values.respond_to?(:each)
params[key].each do |_, attrs|
return true if attrs[:_destroy] == '1'
end
end
false
end
# Extracts part of hash that contains destroy parameters. It deletes
# values that contains destroy parameters from original variable and
# puts them into update_params variable.
def extract_destroy_params(params, update_params)
params.each do |key, values|
next unless values.respond_to?(:each)
update_params[key] = {} unless update_params[key]
attr_params = update_params[key]
params[key].each do |pos, attrs|
if attrs[:_destroy] == '1'
if attrs[:id].present?
asset = Asset.find_by_id(attrs[:id])
if asset.try(&:locked?)
asset.errors.add(:base, 'This file is locked.')
else
attr_params[pos] = { id: attrs[:id], _destroy: '1' }
end
end
params[key].delete(pos)
elsif has_destroy_params?(params[key][pos])
attr_params[pos] = { id: attrs[:id] }
extract_destroy_params(params[key][pos], attr_params[pos])
end
end
end
end
def load_vars
@step = Step.find_by(id: params[:id])
return render_404 unless @step
@ -611,22 +290,6 @@ class StepsController < ApplicationController
end
end
def update_checklist_items_without_callback(params)
params.dig('checklists_attributes')&.values&.each do |cl|
ck = @step.checklists.find_by(id: cl[:id])
next if ck.nil? # ck is new checklist, skip update positions
cl['checklist_items_attributes']&.each do |item|
# Here item is somehow array of index and parameters [0, paramteters<Object>], should be fixed on FE also
item_record = ck.checklist_items.find_by(id: item[1][:id])
next unless item_record
item_record.update_attribute('position', item[1][:position])
end
end
end
def check_view_permissions
render_403 unless can_read_protocol_in_module?(@protocol) || can_read_protocol_in_repository?(@protocol)
end
@ -655,43 +318,6 @@ class StepsController < ApplicationController
params.require(:step).permit(:name)
end
def step_params_old
params.require(:step).permit(
:name,
:description,
checklists_attributes: [
:id,
:name,
:_destroy,
checklist_items_attributes: [
:id,
:text,
:position,
:_destroy
]
],
assets_attributes: [
:id,
:_destroy,
:signed_blob_id
],
tables_attributes: [
:id,
:name,
:contents,
:_destroy
],
marvin_js_assets_attributes: %i(
id
_destroy
),
bio_eddie_assets_attributes: %i(
id
_destroy
)
)
end
def log_activity(type_of, project = nil, message_items = {})
Activities::CreateActivityService
.call(activity_type: type_of,

View file

@ -99,7 +99,7 @@ module Users
result[:status] = :user_exists_and_in_team
else
# Also generate user team relation
team.user_assignments.create(user: user, user_role: @user_role, assigned_by: current_user)
team.user_assignments.create!(user: user, user_role: @user_role, assigned_by: current_user)
generate_notification(
@user,

View file

@ -4,6 +4,7 @@ class Users::SessionsController < Devise::SessionsController
layout :session_layout
after_action :after_sign_in, only: %i(create authenticate_with_two_factor)
before_action :remove_authenticate_mesasge_if_root_path, only: :new
prepend_before_action :skip_timeout, only: :expire_in
rescue_from ActionController::InvalidAuthenticityToken do
redirect_to new_user_session_path
@ -34,6 +35,17 @@ class Users::SessionsController < Devise::SessionsController
generate_templates_project
end
def expire_in
if current_user.remember_created_at.nil? || (current_user.remember_created_at + Devise.remember_for).past?
render plain: (Devise.timeout_in.to_i - (Time.now.to_i - user_session['last_request_at']).round) * 1000
else
render plain: [(Devise.remember_for - (Time.now.to_i - current_user.remember_created_at.to_i).round) * 1000,
(Devise.timeout_in.to_i - (Time.now.to_i - user_session['last_request_at']).round) * 1000].max
end
end
def revive_session; end
def two_factor_recovery
unless session[:otp_user_id]
redirect_to new_user_session_path
@ -92,6 +104,10 @@ class Users::SessionsController < Devise::SessionsController
private
def skip_timeout
request.env['devise.skip_trackable'] = true
end
def remove_authenticate_mesasge_if_root_path
if session[:user_return_to] == root_path && flash[:alert] == I18n.t('devise.failure.unauthenticated')
flash[:alert] = nil

View file

@ -196,6 +196,8 @@ module Users
protocol.archived_by = new_owner unless protocol.archived_by.nil?
protocol.restored_by = new_owner unless protocol.restored_by.nil?
protocol.save!
protocol.user_assignments.find_by(user: new_owner)&.destroy!
protocol.user_assignments.create!(user: new_owner, user_role: UserRole.find_predefined_owner_role)
end
# Make new owner author of all inventory items that were added

View file

@ -23,6 +23,7 @@ class ProtocolsDatatable < CustomDatatable
def sortable_columns
@sortable_columns ||= [
"Protocol.name",
"Protocol.id",
"protocol_keywords_str",
"Protocol.nr_of_linked_children",
"full_username_str",
@ -34,6 +35,7 @@ class ProtocolsDatatable < CustomDatatable
def searchable_columns
@searchable_columns ||= [
"Protocol.name",
"Protocol.#{Protocol::PREFIXED_ID_SQL}",
timestamp_db_column,
"Protocol.updated_at"
]
@ -56,9 +58,11 @@ class ProtocolsDatatable < CustomDatatable
# now the method checks if the column is the created_at or updated_at and generate a custom SQL to parse
# it back to the caller method
def new_search_condition(column, value)
model, column = column.split('.')
model, column = column.split('.', 2)
model = model.constantize
case column
when Protocol::PREFIXED_ID_SQL
casted_column = ::Arel::Nodes::SqlLiteral.new(Protocol::PREFIXED_ID_SQL)
when 'published_on'
casted_column = ::Arel::Nodes::NamedFunction.new('CAST',
[ Arel.sql("to_char( protocols.created_at, '#{ formated_date }' ) AS VARCHAR") ] )
@ -81,12 +85,6 @@ class ProtocolsDatatable < CustomDatatable
protocol = Protocol.find(record.id)
result_data << {
'DT_RowId': record.id,
'DT_CanEdit': can_manage_protocol_in_repository?(protocol),
'DT_EditUrl': if can_manage_protocol_in_repository?(protocol)
edit_protocol_path(protocol,
team: @team,
type: @type)
end,
'DT_CanClone': can_clone_protocol_in_repository?(protocol),
'DT_CloneUrl': if can_clone_protocol_in_repository?(protocol)
clone_protocol_path(protocol,
@ -103,11 +101,12 @@ class ProtocolsDatatable < CustomDatatable
else
name_html(record)
end,
'2': keywords_html(record),
'3': modules_html(record),
'4': escape_input(record.full_username_str),
'5': timestamp_column_html(record),
'6': I18n.l(record.updated_at, format: :full)
'2': escape_input(record.code),
'3': keywords_html(record),
'4': modules_html(record),
'5': escape_input(record.full_username_str),
'6': timestamp_column_html(record),
'7': I18n.l(record.updated_at, format: :full)
}
end
result_data

View file

@ -7,6 +7,7 @@ class ReportDatatable < CustomDatatable
TABLE_COLUMNS = %w(
Report.project_name
Report.name
Report.code
Report.pdf_file
Report.docx_file
Report.created_by_name
@ -40,6 +41,9 @@ class ReportDatatable < CustomDatatable
records.left_joins(:pdf_file_attachment)
.order(active_storage_attachments: sort_direction(order_params))
.order(pdf_file_status: sort_direction(order_params) == 'ASC' ? :desc : :asc)
when 'reports.code'
sort_by = "reports.id #{sort_direction(order_params)}"
records.order(sort_by)
else
sort_by = "#{sort_column(order_params)} #{sort_direction(order_params)}"
records.order(sort_by)
@ -54,12 +58,13 @@ class ReportDatatable < CustomDatatable
'0' => record.id,
'1' => sanitize_input(record.project_name),
'2' => sanitize_input(record.name),
'3' => pdf_file(record),
'4' => docx_file(record),
'5' => sanitize_input(record.created_by_name),
'6' => sanitize_input(record.modified_by_name),
'7' => I18n.l(record.created_at, format: :full),
'8' => I18n.l(record.updated_at, format: :full),
'3' => sanitize_input(record.code),
'4' => pdf_file(record),
'5' => docx_file(record),
'6' => sanitize_input(record.created_by_name),
'7' => sanitize_input(record.modified_by_name),
'8' => I18n.l(record.created_at, format: :full),
'9' => I18n.l(record.updated_at, format: :full),
'archived' => record.project.archived?,
'edit' => edit_project_report_path(record.project_id, record.id),
'status' => status_project_report_path(record.project_id, record.id),
@ -106,7 +111,7 @@ class ReportDatatable < CustomDatatable
def filter_records(records)
records.where_attributes_like(
['project_name', 'reports.name', 'reports.description'],
['project_name', 'reports.name', 'reports.description', "('RP' || reports.id)"],
dt_params.dig(:search, :value)
)
end

View file

@ -2,6 +2,6 @@
module CardsViewHelper
def cards_view_type_class(view_type)
view_type == 'table' ? 'list' : ''
view_type == 'table' ? 'list' : 'cards'
end
end

View file

@ -48,6 +48,10 @@ module ProjectsHelper
records.sort_by { |c| c.name.downcase }
when 'ztoa'
records.sort_by { |c| c.name.downcase }.reverse!
when 'id_asc'
records.sort_by(&:id)
when 'id_desc'
records.sort_by(&:id).reverse!
when 'archived_old'
records.sort_by(&:archived_on)
when 'archived_new'

View file

@ -93,9 +93,13 @@ module ReportsHelper
when 'ztoa'
results.order(name: :desc)
when 'new'
results.order(created_at: :desc)
when 'old_updated'
results.order(updated_at: :asc)
when 'new_updated'
results.order(updated_at: :desc)
else
results.order(updated_at: :asc)
results.order(created_at: :asc)
end
end

View file

@ -7,35 +7,37 @@ Vue.prototype.i18n = window.I18n;
function initPrintModalComponent() {
const container = $('.print-label-modal-container');
window.PrintModalComponent = new Vue({
el: '.print-label-modal-container',
name: 'PrintModalComponent',
components: {
'print-modal-container': PrintModalContainer
},
data() {
return {
showModal: false,
row_ids: [],
zebraEnabled: container.data('zebra-enabled'),
urls: {
print: container.data('print-url'),
zebraProgress: container.data('zebra-progress-url'),
printers: container.data('printers-url'),
labelTemplates: container.data('label-templates-url'),
rows: container.data('rows-url'),
fluicsInfo: container.data('fluics-info-url'),
printValidation: container.data('print-validation-url'),
labelPreview: container.data('label-preview-url')
if (container.length) {
window.PrintModalComponent = new Vue({
el: '.print-label-modal-container',
name: 'PrintModalComponent',
components: {
'print-modal-container': PrintModalContainer
},
data() {
return {
showModal: false,
row_ids: [],
zebraEnabled: container.data('zebra-enabled'),
urls: {
print: container.data('print-url'),
zebraProgress: container.data('zebra-progress-url'),
printers: container.data('printers-url'),
labelTemplates: container.data('label-templates-url'),
rows: container.data('rows-url'),
fluicsInfo: container.data('fluics-info-url'),
printValidation: container.data('print-validation-url'),
labelPreview: container.data('label-preview-url')
}
};
},
methods: {
closeModal() {
this.showModal = false;
}
};
},
methods: {
closeModal() {
this.showModal = false;
}
}
});
});
}
}
initPrintModalComponent();

View file

@ -101,7 +101,7 @@
DPMM_RESOLUTION_OPTIONS,
DPI_RESOLUTION_OPTIONS,
optionsOpen: false,
width: this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm ,
width: this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm,
height: this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm,
unit: this.template.attributes.unit,
density: this.template.attributes.density,
@ -125,17 +125,22 @@
},
watch: {
unit() {
this.recalculateUnits();
this.setDefaults();
},
zpl() {
this.refreshPreview();
},
template() {
this.unit = this.template.attributes.unit
this.width = this.template.attributes.unit == 'in' ? this.template.attributes.width_mm / 25.4 : this.template.attributes.width_mm
this.height = this.template.attributes.unit == 'in' ? this.template.attributes.height_mm / 25.4 : this.template.attributes.height_mm
this.density = this.template.attributes.density
}
},
methods: {
setDefaults() {
!this.unit && (this.unit = 'in');
!this.density && (this.density = 8);
!this.density && (this.density = 12);
!this.width && (this.width = this.unit === 'in' ? 2 : 50.8);
!this.height && (this.height = this.unit === 'in' ? 1 : 25.4);
},
@ -151,6 +156,8 @@
refreshPreview() {
if (this.zpl.length === 0) return;
this.base64Image = null;
$.ajax({
url: this.previewUrl,
type: 'GET',
@ -176,7 +183,9 @@
});
},
updateUnit(unit) {
if (this.unit === unit) return;
this.unit = unit;
this.recalculateUnits();
this.$emit('unit:update', this.unit);
},
updateDensity(density) {

View file

@ -174,7 +174,7 @@
this.previewContent = this.labelTemplate.attributes.content
this.newLabelWidth = this.labelTemplate.attributes.width_mm
this.newLabelHeight = this.labelTemplate.attributes.height_mm
this.newLabelDensity = this.labelTemplate.attributes.newDensity
this.newLabelDensity = this.labelTemplate.attributes.density
this.newLabelUnit = this.labelTemplate.attributes.unit
})
},
@ -227,6 +227,8 @@
updateContent() {
this.previewValid = true;
this.saveLabelDimensions();
if (!this.editingContent) return;
if (this.skipSave) {
@ -242,20 +244,39 @@
type: 'PATCH',
data: {label_template: {
content: this.newContent,
width_mm: this.newLabelWidth,
height_mm: this.newLabelHeight,
density: this.newLabelDensity,
unit: this.newLabelUnit
}},
success: (result) => {
this.labelTemplate.attributes.content = result.data.attributes.content;
this.labelTemplate.attributes.width_mm = result.data.attributes.width_mm;
this.labelTemplate.attributes.height_mm = result.data.attributes.height_mm;
this.editingContent = false;
}
});
});
},
saveLabelDimensions() {
if (this.newLabelWidth == this.labelTemplate.attributes.width_mm &&
this.newLabelHeight == this.labelTemplate.attributes.height_mm &&
this.newLabelDensity == this.labelTemplate.attributes.density &&
this.newLabelUnit == this.labelTemplate.attributes.unit) {
return
}
$.ajax({
url: this.labelTemplate.attributes.urls.update,
type: 'PATCH',
data: {label_template: {
width_mm: this.newLabelWidth,
height_mm: this.newLabelHeight,
density: this.newLabelDensity,
unit: this.newLabelUnit
}},
success: (result) => {
this.labelTemplate.attributes.width_mm = result.data.attributes.width_mm;
this.labelTemplate.attributes.height_mm = result.data.attributes.height_mm;
this.labelTemplate.attributes.density = result.data.attributes.density;
this.labelTemplate.attributes.unit = result.data.attributes.unit;
}
});
},
generatePreview(skipSave = false) {
this.skipSave = skipSave;
if (!skipSave && this.previewContent === this.newContent && this.previewValid) {

View file

@ -57,12 +57,12 @@
</button>
</div>
<div class="step-actions-container">
<div ref="actionsDropdownButton" v-if="urls.update_url" class="dropdown">
<div ref="elementsDropdownButton" v-if="urls.update_url" class="dropdown">
<button class="btn btn-light dropdown-toggle insert-button" type="button" :id="'stepInserMenu_' + step.id" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{{ i18n.t('protocols.steps.insert.button') }}
<span class="caret"></span>
</button>
<ul ref="actionsDropdown" class="dropdown-menu insert-element-dropdown dropdown-menu-right" :aria-labelledby="'stepInserMenu_' + step.id">
<ul ref="elementsDropdown" class="dropdown-menu insert-element-dropdown dropdown-menu-right" :aria-labelledby="'stepInserMenu_' + step.id">
<li class="title">
{{ i18n.t('protocols.steps.insert.title') }}
</li>
@ -101,11 +101,11 @@
</span>
</a>
<div v-if="urls.update_url" class="step-actions-container">
<div class="dropdown">
<div ref="actionsDropdownButton" class="dropdown">
<button class="btn btn-light dropdown-toggle insert-button" type="button" :id="'stepOptionsMenu_' + step.id" data-toggle="dropdown" data-display="static" aria-haspopup="true" aria-expanded="true">
<i class="fas fa-ellipsis-h"></i>
</button>
<ul class="dropdown-menu dropdown-menu-right insert-element-dropdown" :aria-labelledby="'stepOptionsMenu_' + step.id">
<ul ref="actionsDropdown" class="dropdown-menu dropdown-menu-right insert-element-dropdown" :aria-labelledby="'stepOptionsMenu_' + step.id">
<li class="title">
{{ i18n.t('protocols.steps.options_dropdown.title') }}
</li>
@ -249,7 +249,12 @@
mounted() {
$(this.$refs.comments).data('closeCallback', this.closeCommentsSidebar);
$(this.$refs.comments).data('openCallback', this.closeCommentsSidebar);
$(this.$refs.actionsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', this.handleDropdownPosition);
$(this.$refs.actionsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', () => {
this.handleDropdownPosition(this.$refs.actionsDropdownButton, this.$refs.actionsDropdown)
});
$(this.$refs.elementsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', () => {
this.handleDropdownPosition(this.$refs.elementsDropdownButton, this.$refs.elementsDropdown)
});
},
computed: {
reorderableElements() {
@ -438,8 +443,8 @@
closeReorderModal() {
this.reordering = false;
},
handleDropdownPosition() {
this.$refs.actionsDropdownButton.classList.toggle("dropup", !this.isInViewport(this.$refs.actionsDropdown));
handleDropdownPosition(refButton, refDropdown) {
refButton.classList.toggle("dropup", !this.isInViewport(refDropdown));
},
isInViewport(el) {
let rect = el.getBoundingClientRect();

View file

@ -79,7 +79,7 @@
</ul>
<deleteAttachmentModal
v-if="deleteModal"
fileName="attachment.attributes.file_name"
:fileName="attachment.attributes.file_name"
@confirm="deleteAttachment"
@cancel="deleteModal = false"
/>

View file

@ -73,7 +73,7 @@
},
mounted() {
if (this.isNew) {
this.enableEditMode()
this.enableEditMode();
}
},
methods: {

View file

@ -2,18 +2,18 @@
<div ref="modal" class="modal fade" id="modal-print-repository-row-label" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div v-if="printers_dropdown.length > 0" class="printers-available">
<div v-if="availablePrinters.length > 0" class="printers-available">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<p class="modal-title">
<template v-if="rows.length == 1">
{{ i18n.t('repository_row.modal_print_label.head_title', {repository_row: rows[0].attributes.name}) }}
<b>{{ i18n.t('repository_row.modal_print_label.head_title', {repository_row: rows[0].attributes.name}) }}</b>
<span class="id-label">
{{ i18n.t('repository_row.modal_print_label.id_label', {repository_row_id: rows[0].attributes.code}) }}
</span>
</template>
<template v-else>
{{ i18n.t('repository_row.modal_print_label.head_title_multiple', {repository_rows: rows.length}) }}
<b>{{ i18n.t('repository_row.modal_print_label.head_title_multiple', {repository_rows: rows.length}) }}</b>
</template>
</p>
</div>
@ -24,7 +24,7 @@
</label>
<DropdownSelector
:disableSearch="true"
:options="printers_dropdown"
:options="availablePrinters"
:selectorId="`LabelPrinterSelector`"
@dropdown:changed="selectPrinter"
/>
@ -38,7 +38,7 @@
<DropdownSelector
ref="labelTemplateDropdown"
:disableSearch="true"
:options="templates_dropdown"
:options="availableTemplates"
:selectorId="`LabelTemplateSelector`"
:optionLabel="templateOption"
:onOpen="initTooltip"
@ -138,7 +138,7 @@
}
},
computed: {
templates_dropdown() {
availableTemplates() {
let templates = this.templates;
if (this.selectedPrinter && this.selectedPrinter.attributes.type_of === 'zebra') {
templates = templates.filter(i => i.attributes.type === 'ZebraLabelTemplate')
@ -155,7 +155,7 @@
}
})
},
printers_dropdown() {
availablePrinters() {
return this.printers.map(i => {
return {
value: i.id,

View file

@ -85,6 +85,8 @@
inEditMode() {
if (this.inEditMode) {
this.initTinymce();
} else {
this.wrapTables();
}
},
characterCount() {
@ -113,6 +115,8 @@
mounted() {
if (this.inEditMode) {
this.initTinymce();
} else {
this.wrapTables();
}
},
methods: {
@ -130,7 +134,7 @@
if (data) {
this.$emit('update', data)
}
this.$emit('editingDisabled')
this.$emit('editingDisabled');
}).then(() => {
this.active = true;
this.initCharacterCount();
@ -140,6 +144,11 @@
getStaticUrl(name) {
return $(`meta[name=\'${name}\']`).attr('content');
},
wrapTables() {
this.$nextTick(() => {
$(this.$el).find('.tinymce-view table').css('float', 'none').wrapAll('<div style="overflow: auto"></div>');
});
},
initCharacterCount() {
if (!this.editorInstance()) return;

View file

@ -14,6 +14,10 @@ module UserAssignments
assign_users_to_my_module(object)
when Repository
assign_users_to_repository(object)
when Protocol
assign_users_to_protocol(object)
when Report
assign_users_to_report(object)
end
end
end
@ -41,6 +45,19 @@ module UserAssignments
end
end
def assign_users_to_protocol(protocol)
return unless protocol.in_repository_public?
protocol.add_team_users_as_viewers!(@assigned_by)
end
def assign_users_to_report(report)
team = report.team
team.user_assignments.find_each do |user_assignment|
create_or_update_user_assignment(user_assignment, report)
end
end
def create_or_update_user_assignment(parent_user_assignment, object)
user_role = parent_user_assignment.user_role
user = parent_user_assignment.user

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
module UserAssignments
class GroupAssignmentJob < ApplicationJob
class ProjectGroupAssignmentJob < ApplicationJob
queue_as :high_priority
def perform(team, project, assigned_by)
@ -18,7 +18,7 @@ module UserAssignments
next if user_assignment.manually_assigned?
user_assignment.update!(
user_role: project.default_public_user_role,
user_role: project.default_public_user_role || UserRole.find_predefined_viewer_role,
assigned_by: @assigned_by
)
@ -26,7 +26,7 @@ module UserAssignments
UserAssignments::PropagateAssignmentJob.perform_later(
project,
user,
project.default_public_user_role,
project.default_public_user_role || UserRole.find_predefined_viewer_role,
@assigned_by
)
end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
module UserAssignments
class GroupUnAssignmentJob < ApplicationJob
class ProjectGroupUnAssignmentJob < ApplicationJob
queue_as :high_priority
def perform(project)

View file

@ -1,13 +0,0 @@
# frozen_string_literal: true
module UserAssignments
class RemoveTeamUserAssignmentJob < ApplicationJob
queue_as :high_priority
def perform(user, team)
ActiveRecord::Base.transaction do
RemoveTeamUserAssignmentService.new(user, team).call
end
end
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
module UserAssignments
class RemoveTeamUserAssignmentsJob < ApplicationJob
queue_as :high_priority
def perform(team_user_assignment)
ActiveRecord::Base.transaction do
RemoveTeamUserAssignmentsService.new(team_user_assignment).call
end
end
end
end

View file

@ -1,13 +0,0 @@
# frozen_string_literal: true
module UserAssignments
class UpdateTeamUserAssignmentJob < ApplicationJob
queue_as :high_priority
def perform(user, team, user_role)
ActiveRecord::Base.transaction do
UpdateTeamUserAssignmentService.new(user, team, user_role).call
end
end
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
module UserAssignments
class UpdateTeamUserAssignmentsJob < ApplicationJob
queue_as :high_priority
def perform(team_user_assignment)
ActiveRecord::Base.transaction do
UpdateTeamUserAssignmentsService.new(team_user_assignment).call
end
end
end
end

View file

@ -8,7 +8,7 @@ class FluicsLabelTemplate < LabelTemplate
height_mm: 12.7,
content: Extends::DEFAULT_LABEL_TEMPLATE[:zpl],
unit: 0,
density: 8
density: 12
)
end

View file

@ -19,6 +19,10 @@ class LabelPrinter < ApplicationRecord
validates :type_of, presence: true
validates :language_type, presence: true
def self.zebra_print_enabled?
RepositoryBase.stock_management_enabled?.present?
end
def done?
current_print_job_ids.blank? && ready?
end

View file

@ -16,7 +16,7 @@ class LabelTemplate < ApplicationRecord
validate :ensure_single_default_template!
def self.enabled?
ApplicationSettings.instance.values['label_templates_enabled']
RepositoryBase.stock_management_enabled?
end
def icon

View file

@ -1,7 +1,9 @@
# frozen_string_literal: true
class MyModule < ApplicationRecord
SEARCHABLE_ATTRIBUTES = ['my_modules.name', 'my_modules.description']
ID_PREFIX = 'TA'
include PrefixedIdModel
SEARCHABLE_ATTRIBUTES = ['my_modules.name', 'my_modules.description', PREFIXED_ID_SQL].freeze
include ArchivableModel
include SearchableModel
@ -11,6 +13,9 @@ class MyModule < ApplicationRecord
include Assignable
include Cloneable
ID_PREFIX = 'TA'
include PrefixedIdModel
attr_accessor :transition_error_rollback
enum state: Extends::TASKS_STATES

View file

@ -1,4 +1,10 @@
# frozen_string_literal: true
class Project < ApplicationRecord
ID_PREFIX = 'PR'
include PrefixedIdModel
SEARCHABLE_ATTRIBUTES = ['projects.name', PREFIXED_ID_SQL].freeze
include ArchivableModel
include SearchableModel
include SearchableByNameModel
@ -80,7 +86,7 @@ class Project < ApplicationRecord
)
new_query = Project.viewable_by_user(user, current_team || user.teams)
.where_attributes_like('projects.name', query, options)
.where_attributes_like(SEARCHABLE_ATTRIBUTES, query, options)
new_query = new_query.active unless include_archived
# Show all results if needed
@ -127,8 +133,8 @@ class Project < ApplicationRecord
def validate_view_state(view_state)
if %w(cards table).exclude?(view_state.state.dig('experiments', 'view_type')) ||
%w(new old atoz ztoa).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('experiments', 'archived', 'sort'))
%w(new old atoz ztoa id_asc id_desc).exclude?(view_state.state.dig('experiments', 'active', 'sort')) ||
%w(new old atoz ztoa id_asc id_desc archived_new archived_old).exclude?(view_state.state.dig('experiments', 'archived', 'sort'))
view_state.errors.add(:state, :wrong_state)
end
end
@ -171,6 +177,8 @@ class Project < ApplicationRecord
when 'old' then { created_at: :asc }
when 'atoz' then { name: :asc }
when 'ztoa' then { name: :desc }
when 'id_asc' then { id: :asc }
when 'id_desc' then { id: :desc }
when 'archived_new' then { archived_on: :desc }
when 'archived_old' then { archived_on: :asc }
else { created_at: :desc }
@ -326,7 +334,7 @@ class Project < ApplicationRecord
def auto_assign_project_members
return if skip_user_assignments
UserAssignments::GroupAssignmentJob.perform_now(
UserAssignments::ProjectGroupAssignmentJob.perform_now(
team,
self,
last_modified_by || created_by
@ -345,7 +353,7 @@ class Project < ApplicationRecord
if visible?
auto_assign_project_members
else
UserAssignments::GroupUnAssignmentJob.perform_now(self)
UserAssignments::ProjectGroupUnAssignmentJob.perform_now(self)
end
end
end

View file

@ -1,6 +1,11 @@
# frozen_string_literal: true
class Protocol < ApplicationRecord
ID_PREFIX = 'PT'
include PrefixedIdModel
SEARCHABLE_ATTRIBUTES = ['protocols.name', 'protocols.description',
'protocols.authors', 'protocol_keywords.name', PREFIXED_ID_SQL].freeze
include SearchableModel
include RenamingUtil
include SearchableByNameModel
@ -8,6 +13,7 @@ class Protocol < ApplicationRecord
include PermissionCheckableModel
include TinyMceImages
after_update :update_user_assignments, if: -> { saved_change_to_protocol_type? && in_repository? }
after_destroy :decrement_linked_children
after_save :update_linked_children
skip_callback :create, :after, :create_users_assignments, if: -> { in_module? }
@ -179,15 +185,7 @@ class Protocol < ApplicationRecord
.joins('LEFT JOIN protocol_keywords ' \
'ON protocol_keywords.id = ' \
'protocol_protocol_keywords.protocol_keyword_id')
.where_attributes_like(
[
'protocols.name',
'protocols.description',
'protocols.authors',
'protocol_keywords.name'
],
query, options
)
.where_attributes_like(SEARCHABLE_ATTRIBUTES, query, options)
# Show all results if needed
if page == Constants::SEARCH_NO_LIMIT
@ -657,8 +655,29 @@ class Protocol < ApplicationRecord
steps.map(&:can_destroy?).all?
end
def add_team_users_as_viewers!(assigned_by)
viewer_role = UserRole.find_predefined_viewer_role
team.user_assignments.where.not(user: assigned_by).find_each do |user_assignment|
new_user_assignment = UserAssignment.find_or_initialize_by(user: user_assignment.user, assignable: self)
new_user_assignment.user_role = viewer_role
new_user_assignment.assigned_by = assigned_by
new_user_assignment.assigned = :automatically
new_user_assignment.save!
end
end
private
def update_user_assignments
case protocol_type
when 'in_repository_public'
assigned_by = protocol_type_was == 'in_repository_archived' && restored_by || added_by
add_team_users_as_viewers!(assigned_by)
when 'in_repository_private', 'in_repository_archived'
automatic_user_assignments.where.not(user: added_by).destroy_all
end
end
def deep_clone(clone, current_user)
# Save cloned protocol first
success = clone.save

View file

@ -1,6 +1,10 @@
# frozen_string_literal: true
class Report < ApplicationRecord
ID_PREFIX = 'RP'
include PrefixedIdModel
SEARCHABLE_ATTRIBUTES = ['reports.name', 'reports.description', PREFIXED_ID_SQL].freeze
include SettingsModel
include Assignable
include PermissionCheckableModel
@ -55,7 +59,7 @@ class Report < ApplicationRecord
table_results: true,
text_results: true,
result_comments: true,
result_order: 'atoz',
result_order: 'new',
activities: true
}
}.freeze
@ -74,7 +78,7 @@ class Report < ApplicationRecord
new_query = Report.distinct
.where(reports: { project_id: project_ids })
.where_attributes_like(%i(name description), query, options)
.where_attributes_like(SEARCHABLE_ATTRIBUTES, query, options)
# Show all results if needed
if page == Constants::SEARCH_NO_LIMIT
@ -85,7 +89,8 @@ class Report < ApplicationRecord
end
def self.viewable_by_user(user, teams)
where(project: Project.viewable_by_user(user, teams))
with_granted_permissions(user, ReportPermissions::READ)
.where(project: Project.viewable_by_user(user, teams))
end
def self.filter_by_teams(teams = [])

View file

@ -125,24 +125,6 @@ class Step < ApplicationRecord
st
end
def asset_position(asset)
assets.order(:updated_at).each_with_index do |step_asset, i|
return { count: assets.count, pos: i } if asset.id == step_asset.id
end
end
def move_up
return if position.zero?
move_in_protocol(:up)
end
def move_down
return if position == protocol.steps.count - 1
move_in_protocol(:down)
end
def comments
step_comments
end
@ -194,32 +176,6 @@ class Step < ApplicationRecord
end
private
def move_in_protocol(direction)
transaction do
re_index_following_steps
case direction
when :up
new_position = position - 1
when :down
new_position = position + 1
else
return
end
step_to_swap = protocol.steps.find_by(position: new_position)
position_to_swap = position
if step_to_swap
step_to_swap.update!(position: -1)
update!(position: new_position)
step_to_swap.update!(position: position_to_swap)
else
update!(position: new_position)
end
end
end
def adjust_positions_after_destroy
re_index_following_steps
protocol.steps.where('position > ?', position).order(:position).each do |step|

View file

@ -5,7 +5,11 @@ class StepText < ApplicationRecord
include ActionView::Helpers::TextHelper
auto_strip_attributes :text, nullify: false
validates :text, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
validates :text, length:
{
maximum:
ENV['STEP_TEXT_MAX_LENGTH'].present? ? ENV['STEP_TEXT_MAX_LENGTH'].to_i : Constants::RICH_TEXT_MAX_LENGTH
}
belongs_to :step, inverse_of: :step_texts, touch: true
has_one :step_orderable_element, as: :orderable, dependent: :destroy

View file

@ -84,8 +84,8 @@ class Team < ApplicationRecord
end
def validate_view_state(view_state)
if %w(new old atoz ztoa).exclude?(view_state.state.dig('projects', 'active', 'sort')) ||
%w(new old atoz ztoa archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort')) ||
if %w(new old atoz ztoa id_asc id_desc).exclude?(view_state.state.dig('projects', 'active', 'sort')) ||
%w(new old atoz ztoa id_asc id_desc archived_new archived_old).exclude?(view_state.state.dig('projects', 'archived', 'sort')) ||
%w(cards table).exclude?(view_state.state.dig('projects', 'view_type'))
view_state.errors.add(:state, :wrong_state)
end

View file

@ -32,15 +32,12 @@ class TeamSharedObject < ApplicationRecord
def not_globally_shared
errors.add(:shared_object_id, :is_globally_shared) if shared_object.globally_shared?
end
def assign_shared_inventories
viewer_role = UserRole.find_by(name: UserRole.public_send('viewer_role').name)
normal_user_role = UserRole.find_by(name: UserRole.public_send('normal_user_role').name)
team.users.find_each do |user|
def assign_shared_inventories
team.user_assignments.find_each do |user_assignment|
shared_object.user_assignments.create!(
user: user,
user_role: shared_write? ? normal_user_role : viewer_role,
user: user_assignment.user,
user_role: user_assignment.user_role,
team: team
)
end

View file

@ -403,7 +403,7 @@ class User < ApplicationRecord
result = result.where.not(confirmed_at: nil) if active_only
if team_to_ignore.present?
ignored_ids = UserTeam.select(:user_id).where(team_id: team_to_ignore.id)
ignored_ids = UserAssignment.select(:user_id).where(assignable: team_to_ignore)
result = result.where.not(users: { id: ignored_ids })
end
@ -629,7 +629,7 @@ class User < ApplicationRecord
query_teams = teams.pluck(:id)
query_teams &= filters[:teams].map(&:to_i) if filters[:teams]
query_teams &= User.team_by_subject(filters[:subjects]) if filters[:subjects]
User.where(id: UserTeam.where(team_id: query_teams).select(:user_id))
User.where(id: UserAssignment.where(assignable_id: query_teams, assignable_type: 'Team').select(:user_id))
.search(false, search_query)
.select(:full_name, :id)
.map { |i| { label: escape_input(i[:full_name]), value: i[:id] } }

View file

@ -2,10 +2,8 @@
class UserAssignment < ApplicationRecord
before_validation -> { self.team ||= (assignable.is_a?(Team) ? assignable : assignable.team) }
after_create :assign_shared_inventories, if: -> { assignable.is_a?(Team) }
after_create :assign_public_projects, if: -> { assignable.is_a?(Team) }
after_create :assign_team_child_objects, if: -> { assignable.is_a?(Team) }
after_update :update_team_children_assignments, if: -> { assignable.is_a?(Team) && saved_change_to_user_role_id? }
before_destroy :unassign_shared_inventories, if: -> { assignable.is_a?(Team) }
before_destroy :unassign_team_child_objects, if: -> { assignable.is_a?(Team) }
belongs_to :assignable, polymorphic: true, touch: true
@ -20,46 +18,15 @@ class UserAssignment < ApplicationRecord
private
def assign_shared_inventories
viewer_role = UserRole.find_by(name: UserRole.public_send('viewer_role').name)
normal_user_role = UserRole.find_by(name: UserRole.public_send('normal_user_role').name)
assignable.team_shared_repositories.find_each do |team_shared_repository|
assignable.repository_sharing_user_assignments.create!(
user: user,
user_role: team_shared_repository.shared_write? ? normal_user_role : viewer_role,
assignable: team_shared_repository.shared_object
)
end
Repository.globally_shared.find_each do |repository|
assignable.repository_sharing_user_assignments.create!(
user: user,
user_role: repository.shared_write? ? normal_user_role : viewer_role,
assignable: repository
)
end
end
def assign_public_projects
assignable.projects.visible.find_each do |project|
UserAssignments::GroupAssignmentJob.perform_later(
assignable,
project,
assigned_by
)
end
def assign_team_child_objects
UserAssignments::CreateTeamUserAssignmentsService.new(self).call
end
def update_team_children_assignments
UserAssignments::UpdateTeamUserAssignmentService.new(user, assignable, user_role).call
end
def unassign_shared_inventories
assignable.repository_sharing_user_assignments.where(user: user).find_each(&:destroy!)
UserAssignments::UpdateTeamUserAssignmentsService.new(self).call
end
def unassign_team_child_objects
UserAssignments::RemoveTeamUserAssignmentService.new(user, assignable).call
UserAssignments::RemoveTeamUserAssignmentsService.new(self).call
end
end

View file

@ -57,6 +57,10 @@ class UserRole < ApplicationRecord
predefined.find_by(name: UserRole.public_send('normal_user_role').name)
end
def self.find_predefined_viewer_role
predefined.find_by(name: UserRole.public_send('viewer_role').name)
end
def owner?
predefined? && name == I18n.t('user_roles.predefined.owner')
end

View file

@ -57,7 +57,7 @@ class UserTeam < ApplicationRecord
def assign_user_to_visible_projects
team.projects.visible.each do |project|
UserAssignments::GroupAssignmentJob.perform_later(
UserAssignments::ProjectGroupAssignmentJob.perform_later(
team,
project,
assigned_by

View file

@ -8,7 +8,7 @@ class ZebraLabelTemplate < LabelTemplate
height_mm: 12.7,
content: Extends::DEFAULT_LABEL_TEMPLATE[:zpl],
unit: 0,
density: 8
density: 12
)
end
end

View file

@ -20,7 +20,8 @@ Canaid::Permissions.register_for(Project) do
export_project)
.each do |perm|
can perm do |user, project|
project.permission_granted?(user, ProjectPermissions::READ)
project.permission_granted?(user, ProjectPermissions::READ) ||
project.team.permission_granted?(user, TeamPermissions::MANAGE)
end
end

View file

@ -4,7 +4,7 @@ Canaid::Permissions.register_for(RepositoryBase) do
# repository: read/export
can :read_repository do |user, repository|
if repository.is_a?(RepositorySnapshot)
user.teams.include?(repository.team)
can_read_my_module?(user, repository.my_module)
else
user.teams.include?(repository.team) || repository.shared_with?(user.current_team)
end
@ -24,6 +24,16 @@ Canaid::Permissions.register_for(Repository) do
end
end
%i(create_repository_rows
manage_repository_rows
manage_repository_assets
delete_repository_rows)
.each do |perm|
can perm do |user, repository|
repository.shared_with?(user.current_team) ? repository.shared_with_write?(user.current_team) : true
end
end
# repository: update, delete
can :manage_repository do |user, repository|
!repository.shared_with?(user.current_team) && repository.permission_granted?(user, RepositoryPermissions::MANAGE)
@ -61,12 +71,7 @@ Canaid::Permissions.register_for(Repository) do
next false if repository.is_a?(BmtRepository)
next false if repository.archived?
if repository.shared_with?(user.current_team)
repository.shared_with_write?(user.current_team) &&
repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
else
repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
end
repository.permission_granted?(user, RepositoryPermissions::ROWS_CREATE)
end
can :manage_repository_assets do |user, repository|

View file

@ -72,9 +72,7 @@ end
Canaid::Permissions.register_for(Protocol) do
# protocol in repository: read, export, read step, read/download step asset
can :read_protocol_in_repository do |user, protocol|
user.member_of_team?(protocol.team) &&
(protocol.in_repository_public? ||
protocol.in_repository_private? && user == protocol.added_by)
protocol.in_repository_active? && protocol.permission_granted?(user, ProtocolPermissions::READ)
end
# protocol in repository: update, create/update/delete/reorder step,
@ -96,11 +94,11 @@ end
Canaid::Permissions.register_for(Report) do
can :read_report do |user, report|
report.permission_granted?(user, ReportPermissions::READ)
can_read_project?(report.project) && report.permission_granted?(user, ReportPermissions::READ)
end
can :manage_report do |user, report|
report.permission_granted?(user, ReportPermissions::MANAGE)
can_read_project?(report.project) && report.permission_granted?(user, ReportPermissions::MANAGE)
end
can :manage_report_users do |user, report|

View file

@ -106,6 +106,7 @@ module Dashboard
ordered_query = ordered_query.where(query_filter) unless @mode == 'all'
recent_objects = ordered_query.as_json.map do |recent_object|
object_class = override_subject_type(recent_object).constantize
recent_object.deep_symbolize_keys!
recent_object.delete_if { |_k, v| v.nil? }
@ -115,6 +116,10 @@ module Dashboard
)
recent_object[:subject_type] = override_subject_type(recent_object)
recent_object[:name] = escape_input(recent_object[:name])
recent_object[:type] = I18n.t("activerecord.models.#{object_class.name.underscore}")
if object_class.include?(PrefixedIdModel)
recent_object[:code] = object_class::ID_PREFIX + recent_object[:group_id][3..]
end
recent_object[:url] = generate_url(recent_object)
recent_object
end
@ -231,7 +236,7 @@ module Dashboard
end
def generate_url(recent_object)
object_id = recent_object[:group_id].gsub(/[^0-9]/, '')
object_id = recent_object.with_indifferent_access[:group_id].gsub(/[^0-9]/, '')
case recent_object[:subject_type]
when 'MyModule'
@ -250,17 +255,19 @@ module Dashboard
end
def override_subject_type(recent_object)
if recent_object[:group_id].include?('pro')
group_id = recent_object.with_indifferent_access[:group_id]
if group_id.include?('pro')
'Project'
elsif recent_object[:group_id].include?('exp')
elsif group_id.include?('exp')
'Experiment'
elsif recent_object[:group_id].include?('tsk')
elsif group_id.include?('tsk')
'MyModule'
elsif recent_object[:group_id].include?('prt')
elsif group_id.include?('prt')
'Protocol'
elsif recent_object[:group_id].include?('inv')
elsif group_id.include?('inv')
'RepositoryBase'
elsif recent_object[:group_id].include?('rpt')
elsif group_id.include?('rpt')
'Report'
end
end

Some files were not shown because too many files have changed in this diff Show more