Merge branch 'ux-release-1' of https://github.com/biosistemika/scinote-web into zd_SCI_2124

This commit is contained in:
zmagod 2018-04-20 10:33:15 +02:00
commit dafa29d4bf
52 changed files with 781 additions and 695 deletions

View file

@ -5,7 +5,6 @@ $.fn.findWithSelf = function(selector) {
};
var REPORT_CONTENT = "#report-content";
var SIDEBAR_PARENT_TREE = "#report-sidebar-tree";
var ADD_CONTENTS_FORM_ID = "#add-contents-form";
var SAVE_REPORT_FORM_ID = "#save-report-form";
@ -31,12 +30,14 @@ var ignoreUnsavedWorkAlert;
initializeSaveToPdf();
initializeSaveReport();
initializeAddContentsModal();
initializeSidebarNavigation();
initializeUnsavedWorkDialog();
$('.report-nav-link').each(function() {
truncateLongString($(this), <%= Constants::NAME_TRUNCATION_LENGTH %>);
});
// Automatically display the "Add content" modal
$('.new-element.initial').click();
}
/**
@ -238,9 +239,6 @@ function initializeNewElement(newEl) {
} else if (data.status == 200) {
// Add elements
addElements(el, data.responseJSON.elements);
// Update sidebar
initializeSidebarNavigation();
}
})
.on("ajax:error", function(e, xhr, settings, error) {
@ -486,140 +484,6 @@ function initializeUnsavedWorkDialog() {
$(document).on('page:before-change', beforeUnload);
}
/**
* SIDEBAR CODE
*/
/**
* Get the sidebar <li> element for the specified report element.
* @param reportEl - The .report-element in the report.
* @return The corresponding sidebar <li>.
*/
function getSidebarEl(reportEl) {
var type = reportEl.data("type");
var scrollId = reportEl.data("scroll-id");
return $(SIDEBAR_PARENT_TREE).find(
"li" +
"[data-type='" + type + "']" +
"[data-scroll-id='" + scrollId + "']"
);
}
/**
* Get the report <div.report-element> element for the specified
* sidebar element.
* @param sidebarEl - The <li> sidebar element.
* @return The corresponding report element.
*/
function getReportEl(sidebarEl) {
var type = sidebarEl.data("type");
var scrollId = sidebarEl.data("scroll-id");
return $(REPORT_CONTENT).find(
"div.report-element" +
"[data-type='" + type + "']" +
"[data-scroll-id='" + scrollId + "']"
);
}
/**
* Initialize the sidebar navigation pane.
*/
function initializeSidebarNavigation() {
var reportContent = $(REPORT_CONTENT);
var treeParent = $(SIDEBAR_PARENT_TREE);
// Remove existing contents (also remove click listeners)
treeParent.find(".report-nav-link").off("click");
treeParent.children().remove();
// Re-populate the sidebar
_.each(reportContent.children(".report-element"), function(child) {
var li = initSidebarElement($(child));
li.appendTo(treeParent);
});
// Add click listener on all links
treeParent.find(".report-nav-link").click(function(e) {
var el = $(this).closest("li");
scrollToElement(el);
e.preventDefault();
e.stopPropagation();
return false;
});
// Call to sidebar function to re-initialize tree functionality
setupSidebarTree();
}
/**
* Recursive call to initialize sidebar elements.
* @param reportEl - The report element for which to
* generate the sidebar.
* @return A <li> jQuery element containing sidebar entry.
*/
function initSidebarElement(reportEl) {
var elChildrenContainer = reportEl.children(".report-element-children");
var type = reportEl.data("type");
var name = reportEl.data("name");
var scrollId = reportEl.data("scroll-id");
var iconClass = reportEl.data("icon-class");
// Generate list element
var newLi = $(document.createElement("li"));
newLi
.attr("data-type", type)
.attr("data-scroll-id", scrollId);
var newSpan = $(document.createElement("span"));
newSpan.appendTo(newLi);
var newI = $(document.createElement("i"));
newI.appendTo(newSpan);
var newHref = $(document.createElement("a"));
newHref
.attr("href", "")
.addClass("report-nav-link")
.text(name)
.appendTo(newSpan);
var newIcon = $(document.createElement("span"));
newIcon.addClass(iconClass).prependTo(newHref);
if (elChildrenContainer.length && elChildrenContainer.length > 0) {
var elChildren = elChildrenContainer.children(".report-element");
if (elChildren.length && elChildren.length > 0) {
var newUl = $(document.createElement("ul"));
newUl.appendTo(newLi);
_.each(elChildren, function(child) {
var li = initSidebarElement($(child));
li.appendTo(newUl);
});
}
}
return newLi;
}
/**
* Scroll to the specified element in the report.
* @param sidebarEl - The sidebar element.
*/
function scrollToElement(sidebarEl) {
var el = getReportEl(sidebarEl);
if (el.length && el.length == 1) {
var content = $("body");
content.scrollTo(
el,
{
axis: 'y',
duration: 500,
offset: -150
}
);
}
}
/**
* INDIVIDUAL ELEMENTS SORTING/MODIFYING FUNCTIONS
*/
@ -683,8 +547,6 @@ function sortWholeReport(asc) {
sortElementChildren($(el), asc, true);
});
// Reinitialize sidebar
initializeSidebarNavigation();
animateLoading(false);
}
@ -734,22 +596,6 @@ function sortElementChildren(el, asc, recursive) {
sortElementChildren($(child), asc, true);
}
});
// Update sidebar
var prevEl = null;
_.each(children, function(child) {
var sidebarEl = getSidebarEl($(child));
if (sidebarEl.length && sidebarEl.length == 1) {
var sidebarParent = sidebarEl.closest("ul");
sidebarEl.detach();
if (prevEl === null) {
sidebarParent.prepend(sidebarEl);
} else {
prevEl.after(sidebarEl);
}
prevEl = sidebarEl;
}
});
}
/**
@ -844,38 +690,24 @@ function moveElement(el, up) {
return;
}
var sidebarEl;
if (up) {
var prevEl = prevNewEl.prev();
if (!prevEl.length || !prevEl.hasClass("report-element")) {
return;
}
// Move sidebar element up
sidebarEl = getSidebarEl(el);
var sidebarPrev = sidebarEl.prev();
sidebarEl.detach();
sidebarPrev.before(sidebarEl);
el.detach();
nextNewEl.detach();
prevEl.before(el);
prevEl.before(nextNewEl);
updateElementControls(prevEl);
} else {
var nextEl = nextNewEl.next();
if (!nextEl.length || !nextEl.hasClass("report-element")) {
return;
}
// Move sidebar element up
sidebarEl = getSidebarEl(el);
var sidebarNext = sidebarEl.next();
sidebarEl.detach();
sidebarNext.after(sidebarEl);
prevNewEl.detach();
el.detach();
nextEl.after(el);
@ -905,10 +737,6 @@ function removeElement(el) {
// TODO Remove event listeners
// Remove sidebar entry
var sidebarEl = getSidebarEl(el);
sidebarEl.remove();
prevNewEl.remove();
el.remove();
@ -933,10 +761,6 @@ function removeResultCommentsElement(el) {
// TODO Remove event listeners
// Remove sidebar entry
var sidebarEl = getSidebarEl(el);
sidebarEl.remove();
// Remove element, show the new element container
el.remove();
parent.children(".new-element").removeClass("hidden");
@ -1125,26 +949,6 @@ function constructElementContentsJson(el) {
return jsonEl;
}
/**
* Binds listeners to sidebar
* that truncate long strings
*/
function initializeReportSidebartruncation() {
var target = document.getElementById("report-sidebar-tree");
var observer = new MutationObserver(
function() {
$.each($("a.report-nav-link"),
function(){
truncateLongString($(this),
<%= Constants::NAME_TRUNCATION_LENGTH %>);
});
}
);
var config = { childList: true };
observer.observe(target, config);
}
$(document).ready(function() {
// Check if we are actually at new report page
if ($(REPORT_CONTENT).length) {

View file

@ -454,6 +454,10 @@ var RepositoryDatatable = (function(global) {
$(th).attr('data-type') === 'RepositoryListValue') {
input = initialListItemsRequest($(th).attr('id'));
tr.appendChild(createTdElement(input));
} else if ($(th).hasClass('repository-column') &&
$(th).attr('data-type') === 'RepositoryAssetValue') {
input = changeToInputFileField('repository_cell_file', th.attr('id'), '');
tr.appendChild(createTdElement(input));
} else {
// Column we don't care for, just add empty td
tr.appendChild(createTdElement(''));
@ -566,6 +570,29 @@ var RepositoryDatatable = (function(global) {
});
}
global.onClickCopyRepositoryRecords = function() {
animateSpinner();
$.ajax({
url: $('table' + TABLE_ID).data('copy-records'),
type: 'POST',
dataType: 'json',
data: { selected_rows: rowsSelected },
success: function(data) {
HelperModule.flashAlertMsg(data.flash, 'success');
rowsSelected = [];
onClickCancel();
},
error: function(e) {
if (e.status === 403) {
HelperModule.flashAlertMsg(
I18n.t('repositories.js.permission_error'), e.responseJSON.style
);
}
}
});
}
// Edit record
global.onClickEdit = function() {
if (rowsSelected.length !== 1) {
@ -612,19 +639,15 @@ var RepositoryDatatable = (function(global) {
var rawIndex = TABLE.column.index('fromVisible', i);
var colHeader = TABLE.column(rawIndex).header();
if ($(colHeader).hasClass('repository-column')) {
var type = $(colHeader).attr('data-type');
// Check if cell on this record exists
var cell = cells[$(colHeader).attr('id')];
if (cell) {
td.html(changeToFormField('repository_cell',
$(colHeader).attr('id'),
cell,
list_columns));
} else {
td.html(changeToFormField('repository_cell',
$(colHeader).attr('id'),
'',
list_columns));
}
var cell = cells[$(colHeader).attr('id')] || '';
td.html(changeToFormField('repository_cell',
$(colHeader).attr('id'),
type,
cell,
list_columns));
_addSelectedFile(type, cell, $(this).find('input')[0]);
_initSelectPicker();
}
});
@ -663,28 +686,32 @@ var RepositoryDatatable = (function(global) {
node = selectedRecord;
}
// First fetch all the data in input fields
var data = {
request_url: $(TABLE_ID).data('current-uri'),
repository_row_id: $(selectedRecord).attr('id'),
repository_row: {},
repository_cells: {}
};
var formData = new FormData();
formData.append('request_url', $(TABLE_ID).data('current-uri'));
formData.append('repository_row_id', $(selectedRecord).attr('id'));
// Direct record attributes
// Record name
data.repository_row.name = $('td input[data-object = repository_row]').val();
formData.append('repository_row_name', $('td input[data-object = repository_row]').val());
// Custom cells text type
$(node).find('td input[data-object = repository_cell]').each(function() {
// Send data only and only if cell is not empty
if ($(this).val().trim()) {
data.repository_cells[$(this).attr('name')] = $(this).val();
formData.append('repository_cells[' + $(this).attr('name') + ']', $(this).val());
}
});
// Custom cells file type
$(node).find('td input[data-object = repository_cell_file]').each(function() {
// Send data only and only if cell is not empty
if ($(this).context.files.length == 1) {
formData.append('repository_cells[' + $(this).attr('name') + ']', $(this).context.files[0]);
}
});
// Custom cells list type
$(node).find('td[column_id]').each(function(index, el) {
var value = $(el).attr('list_item_id');
data.repository_cells[$(el).attr('column_id')] = value;
formData.append('repository_cells[' + $(el).attr('column_id') + ']', value);
});
var url;
@ -700,7 +727,9 @@ var RepositoryDatatable = (function(global) {
url: url,
type: type,
dataType: 'json',
data: data,
data: formData,
processData: false,
contentType: false,
success: function(data) {
HelperModule.flashAlertMsg(data.flash, 'success');
SmartAnnotation.closePopup();
@ -769,6 +798,8 @@ var RepositoryDatatable = (function(global) {
$('.repository-row-selector').removeClass('disabled');
$('.repository-row-selector').prop('disabled', false);
if (rowsSelected.length === 0) {
$('#copyRepositoryRecords').prop('disabled', true);
$('#copyRepositoryRecords').addClass('disabled');
$('#editRepositoryRecord').prop('disabled', true);
$('#editRepositoryRecord').addClass('disabled');
$('#deleteRepositoryRecordsButton').prop('disabled', true);
@ -806,6 +837,8 @@ var RepositoryDatatable = (function(global) {
}
$('#deleteRepositoryRecordsButton').prop('disabled', false);
$('#deleteRepositoryRecordsButton').removeClass('disabled');
$('#copyRepositoryRecords').prop('disabled', false);
$('#copyRepositoryRecords').removeClass('disabled');
$('#assignRepositoryRecords').removeClass('disabled');
$('#assignRepositoryRecords').prop('disabled', false);
$('#unassignRepositoryRecords').removeClass('disabled');
@ -910,6 +943,15 @@ var RepositoryDatatable = (function(global) {
return _listItemDropdown(massage_response, '-1', column_id);
}
function _addSelectedFile(type, cell, input) {
if (type === 'RepositoryAssetValue' && cell.value != null) {
const dT = new ClipboardEvent('').clipboardData || // Firefox workaround exploiting https://bugzilla.mozilla.org/show_bug.cgi?id=1422655
new DataTransfer(); // specs compliant (as of March 2018 only Chrome)
dT.items.add(new File([_], cell.value.file_file_name));
input.files = dT.files;
}
}
function _initSelectPicker() {
$('.selectpicker')
.selectpicker({liveSearch: true})
@ -972,21 +1014,23 @@ var RepositoryDatatable = (function(global) {
object + "' name='" + name + "' value='" + value + "'></input></div>";
}
// Takes object and surrounds it with input
function changeToInputFileField(object, name, value) {
return "<div class='form-group'><input type='file' class='form-control' data-object='" +
object + "' name='" + name + "' value='" + value + "'></input></div>";
}
// Takes an object and creates custom html element
function changeToFormField(object, name, cell, list_columns) {
if (cell === '') {
function changeToFormField(object, name, column_type, cell, list_columns) {
var value = cell.value || '';
if (column_type === 'RepositoryListValue') {
var column = _.findWhere(list_columns, { column_id: parseInt(name) });
if (column) {
return _listItemDropdown(column.list_items, '', parseInt(name));
} else {
return changeToInputField(object, name, '');
}
var list_items = column.list_items || cell.list_items;
return _listItemDropdown(list_items, value, parseInt(name));
} else if (column_type === 'RepositoryAssetValue') {
return changeToInputFileField('repository_cell_file', name, value);
} else {
if (cell.type === 'RepositoryListValue') {
return _listItemDropdown(cell.list_items, cell.value, parseInt(name));
} else {
return changeToInputField(object, name, cell.value);
}
return changeToInputField(object, name, value);
}
}

View file

@ -189,8 +189,16 @@
}).on('ajax:error', function(e, xhr) {
animateSpinner(null, false);
if (modalID) {
var field = { "name": xhr.responseJSON.message }
$(form).renderFormErrors('repository_column', field, true, e);
if(xhr.responseJSON.message.hasOwnProperty('repository_list_items')) {
var message = xhr.responseJSON.message['repository_list_items'];
$('.dnd-error').remove();
$('#manageRepositoryColumn ').find('.bootstrap-tagsinput').after(
"<i class='dnd-error'>" + message + "</i>"
);
} else {
var field = { "name": xhr.responseJSON.message }
$(form).renderFormErrors('repository_column', field, true, e);
}
} else {
HelperModule.flashAlertMsg(xhr.responseJSON.message, 'danger');
}

View file

@ -10,7 +10,7 @@
);
$('#wrapper').css('paddingLeft', '280px');
$('.navbar-secondary').css(
{ 'margin-left': '-280px', 'padding-left': '280px' }
{ 'margin-left': '-280px', 'padding-left': '295px' }
);
}
@ -23,7 +23,7 @@
$('#wrapper').css('paddingLeft', '0');
$('.navbar-secondary').css({
'margin-left': '0',
'padding-left': '0'
'padding-left': '15px'
});
}

View file

@ -8,27 +8,33 @@
/* New page navbar */
.navbar-report {
border-left: none;
border-top: none;
border-right: none;
background: $color-concrete;
border-bottom: 4px solid $color-silver;
background: $color-concrete !important;
border-left: 0;
border-right: 0;
border-top: 0;
margin-bottom: 0;
min-width: 320px;
padding: 0 15px;
z-index: 500;
padding-right: 100px;
position: fixed;
width: 100%;
z-index: 500;
div.row {
margin-right: 0;
}
#report-menu {
margin: 15px 0;
form {
display: inline-block;
}
.form-group {
margin-bottom: 0;
}
}
& > div.row {
@ -54,43 +60,6 @@ label {
}
}
/* New page sidebar */
.report-sidebar-wrapper {
background-color: $color-white !important;
}
// Some additional styling on the treeview
.report-tree {
li {
padding: 0 0 0 15px;
a.report-nav-link:visited {
text-decoration: none;
}
a.report-nav-link:hover {
text-decoration: none;
}
[data-type='step']:not(.parent_li) {
padding-left: 27px;
}
}
}
.report-sidebar-panel-description {
margin: 10px 10px 0 10px;
}
.report-item-elements {
margin-top: 10px !important;
margin-left: 15px !important;
li {
margin: 5px 5px 5px 15px;
}
ul {
padding-left: 15px !important;
}
}
/**
* Global fix for handsontable
@ -115,19 +84,20 @@ label {
.report-container {
overflow-x: auto;
overflow-y: auto;
padding-top: 30px;
padding-bottom: 30px;
padding-left: 0;
width: auto;
}
#report-content {
color: $color-black;
@include box-shadow(0 0 58px -10px $color-black);
background: $color-white;
@include box-shadow(0px 0px 58px -10px $color-black);
max-width: 800px;
min-width: 230px;
min-height: 1200px;
color: $color-black;
margin-left: auto;
margin-right: auto;
margin-top: 50px;
max-width: 800px;
min-height: 1200px;
min-width: 230px;
padding: 45px;
}

View file

@ -270,7 +270,12 @@
padding-top: 5px;
}
li:last-child {
border-bottom: 0;
}
.btn-default {
margin-left: 15px;
text-align: right;
width: 300px;
}
@ -348,6 +353,15 @@
margin-left: 83px;
}
#search-bar {
padding-right: 0;
}
@media (max-width: 768px) {
#search-bar {
padding: 10px 30px;
}
}
// reset margins on small screens
@media (max-width: 1188px) {

View file

@ -11,6 +11,7 @@
padding-top: 16px;
position: fixed;
width: 83px;
z-index: 1001;
ul.nav > li {
padding-right: 4px;

View file

@ -37,3 +37,8 @@
float: left;
margin-right: 5px;
}
.custom-alert-info {
background-color: $brand-info;
padding: 10px;
}

View file

@ -222,7 +222,7 @@ a[data-toggle="tooltip"] {
#secondary-navigation {
white-space: nowrap;
overflow: hidden
overflow: hidden;
}
.panel-body {
@ -253,10 +253,23 @@ a[data-toggle="tooltip"] {
border-right: 0;
border-bottom: 4px solid $color-silver;
padding-right: 0;
padding-left: 0;
}
.navbar-toggle {
margin-right: 100px;
}
#secondary-menu {
padding-left: 0;
}
.nav-name {
padding: 15px 0;
}
ul.nav {
margin-right: 0;
margin-right: 60px;
& > li {
text-transform: uppercase;
@ -409,13 +422,6 @@ a[data-toggle="tooltip"] {
}
/** Protocols management */
.breadcrumb-protocols-manager {
overflow: visible;
margin-top: 15px;
margin-bottom: 15px;
background-color: $color-white;
}
.tab-pane-protocols {
& > div.protocols-description {
margin-bottom: 15px;
@ -1277,7 +1283,6 @@ table.dataTable {
.file-preview-container {
align-items: center;
background-color: $color-white;
color: $gray-dark;
display: -moz-flex;
display: -webkit-flex;

View file

@ -7,20 +7,19 @@ class ExperimentsController < ApplicationController
include Rails.application.routes.url_helpers
before_action :set_experiment,
except: [:new, :create]
except: %i(new create)
before_action :set_project,
only: [:new, :create, :samples_index, :samples, :module_archive,
:clone_modal, :move_modal, :delete_samples]
before_action :load_projects_by_teams, only: %i(canvas samples)
only: %i(new create samples_index samples module_archive
clone_modal move_modal delete_samples)
before_action :load_projects_by_teams, only: %i(canvas samples module_archive)
before_action :check_view_permissions,
only: [:canvas, :module_archive]
only: %i(canvas module_archive)
before_action :check_manage_permissions, only: :edit
before_action :check_archive_permissions, only: :archive
before_action :check_clone_permissions, only: %i(clone_modal clone)
before_action :check_move_permissions, only: %i(move_modal move)
# except parameter could be used but it is not working.
layout :choose_layout
layout 'fluid'.freeze
# Action defined in SampleActions
DELETE_SAMPLES = 'Delete'.freeze
@ -350,7 +349,8 @@ class ExperimentsController < ApplicationController
end
def load_projects_by_teams
@projects_by_teams = current_user.projects_by_teams
@projects_by_teams = current_user.projects_by_teams(current_team.id,
nil, false)
end
def check_view_permissions
@ -373,10 +373,6 @@ class ExperimentsController < ApplicationController
render_403 unless can_move_experiment?(@experiment)
end
def choose_layout
action_name.in?(%w(index archive)) ? 'main' : 'fluid'
end
def experiment_annotation_notification(old_text = nil)
smart_annotation_notification(
old_text: old_text,

View file

@ -17,8 +17,8 @@ class MyModulesController < ApplicationController
before_action :load_repository, only: %i(assign_repository_records
unassign_repository_records
repository_index)
before_action :load_projects_by_teams,
only: %i(protocols results activities samples repository)
before_action :load_projects_by_teams, only: %i(protocols results activities
samples repository archive)
before_action :check_manage_permissions,
only: %i(update destroy description due_date)
before_action :check_view_info_permissions, only: :show
@ -608,7 +608,8 @@ class MyModulesController < ApplicationController
end
def load_projects_by_teams
@projects_by_teams = current_user.projects_by_teams
@projects_by_teams = current_user.projects_by_teams(current_team.id,
nil, false)
end
def check_manage_permissions

View file

@ -5,19 +5,19 @@ class ProjectsController < ApplicationController
include InputSanitizeHelper
before_action :generate_intro_demo, only: :index
before_action :load_vars, only: [:show, :edit, :update,
:notifications, :reports,
:samples, :experiment_archive,
:delete_samples, :samples_index]
before_action :load_projects_by_teams, only: %i(index show samples)
before_action :load_vars, only: %i(show edit update
notifications reports
samples experiment_archive
delete_samples samples_index)
before_action :load_projects_by_teams, only: %i(index show samples archive
experiment_archive)
before_action :load_archive_vars, only: :archive
before_action :check_view_permissions, only: %i(show reports notifications
samples experiment_archive
samples_index)
before_action :check_create_permissions, only: [ :new, :create ]
before_action :check_create_permissions, only: %i(new create)
before_action :check_manage_permissions, only: :edit
@filter_by_archived = false
# except parameter could be used but it is not working.
layout 'fluid'
@ -36,7 +36,6 @@ class ProjectsController < ApplicationController
end
def archive
@filter_by_archived = true
index
end
@ -322,7 +321,16 @@ class ProjectsController < ApplicationController
@current_sort = params[:sort].to_s
@projects_by_teams = current_user.projects_by_teams(@current_team_id,
@current_sort,
@filter_by_archived)
false)
else
@projects_by_teams = []
end
end
def load_archive_vars
if current_user.teams.any?
@archived_projects_by_teams =
current_user.projects_by_teams(@current_team_id, @current_sort, true)
else
@projects_by_teams = []
end

View file

@ -30,8 +30,6 @@ class ReportsController < ApplicationController
# before_action :check_view_permissions, only: :index
before_action :check_manage_permissions, only: BEFORE_ACTION_METHODS
layout 'fluid'
# Index showing all reports of a single project
def index; end

View file

@ -9,7 +9,7 @@ class RepositoriesController < ApplicationController
before_action :check_manage_permissions, only:
%i(destroy destroy_modal rename_modal update)
before_action :check_create_permissions, only:
%i(create_new_modal create copy_modal copy)
%i(create_modal create copy_modal copy)
layout 'fluid'

View file

@ -28,28 +28,35 @@ class RepositoryColumnsController < ApplicationController
@repository_column.created_by = current_user
respond_to do |format|
if @repository_column.save
generate_repository_list_items(params[:list_items])
format.json do
render json: {
id: @repository_column.id,
name: escape_input(@repository_column.name),
message: t('libraries.repository_columns.create.success_flash',
name: @repository_column.name),
edit_url:
edit_repository_repository_column_path(@repository,
@repository_column),
update_url:
repository_repository_column_path(@repository,
@repository_column),
destroy_html_url:
repository_columns_destroy_html_path(@repository,
@repository_column)
},
status: :ok
end
else
format.json do
format.json do
if @repository_column.save
if generate_repository_list_items(params[:list_items])
render json: {
id: @repository_column.id,
name: escape_input(@repository_column.name),
message: t('libraries.repository_columns.create.success_flash',
name: @repository_column.name),
edit_url:
edit_repository_repository_column_path(@repository,
@repository_column),
update_url:
repository_repository_column_path(@repository,
@repository_column),
destroy_html_url:
repository_columns_destroy_html_path(@repository,
@repository_column)
},
status: :ok
else
render json: {
message: {
repository_list_items:
t('libraries.repository_columns.repository_list_items_limit',
limit: Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN)
}
}, status: :unprocessable_entity
end
else
render json: { message: @repository_column.errors.full_messages },
status: :unprocessable_entity
end
@ -74,13 +81,22 @@ class RepositoryColumnsController < ApplicationController
format.json do
@repository_column.update_attributes(repository_column_params)
if @repository_column.save
update_repository_list_items(params[:list_items])
render json: {
id: @repository_column.id,
name: escape_input(@repository_column.name),
message: t('libraries.repository_columns.update.success_flash',
name: @repository_column.name)
}, status: :ok
if update_repository_list_items(params[:list_items])
render json: {
id: @repository_column.id,
name: escape_input(@repository_column.name),
message: t('libraries.repository_columns.update.success_flash',
name: @repository_column.name)
}, status: :ok
else
render json: {
message: {
repository_list_items:
t('libraries.repository_columns.repository_list_items_limit',
limit: Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN)
}
}, status: :unprocessable_entity
end
else
render json: { message: @repository_column.errors.full_messages },
status: :unprocessable_entity
@ -162,7 +178,13 @@ class RepositoryColumnsController < ApplicationController
def generate_repository_list_items(item_names)
return unless @repository_column.data_type == 'RepositoryListValue'
column_items = @repository_column.repository_list_items.size
success = true
item_names.split(',').uniq.each do |name|
if column_items >= Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN
success = false
next
end
RepositoryListItem.create(
repository: @repository,
repository_column: @repository_column,
@ -170,11 +192,14 @@ class RepositoryColumnsController < ApplicationController
created_by: current_user,
last_modified_by: current_user
)
column_items += 1
end
success
end
def update_repository_list_items(item_names)
return unless @repository_column.data_type == 'RepositoryListValue'
column_items = @repository_column.repository_list_items.size
items_list = item_names.split(',').uniq
existing = @repository_column.repository_list_items.pluck(:data)
existing.each do |name|
@ -189,8 +214,13 @@ class RepositoryColumnsController < ApplicationController
list_item_id
).destroy_all
end
success = true
items_list.each do |name|
next if @repository_column.repository_list_items.find_by_data(name)
if column_items >= Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN
success = false
next
end
RepositoryListItem.create(
repository: @repository,
repository_column: @repository_column,
@ -198,6 +228,8 @@ class RepositoryColumnsController < ApplicationController
created_by: current_user,
last_modified_by: current_user
)
column_items += 1
end
success
end
end

View file

@ -5,9 +5,11 @@ class RepositoryRowsController < ApplicationController
before_action :load_info_modal_vars, only: :show
before_action :load_vars, only: %i(edit update)
before_action :load_repository, only: %i(create delete_records index)
before_action :load_repository,
only: %i(create delete_records index copy_records)
before_action :check_create_permissions, only: :create
before_action :check_manage_permissions, only: %i(edit update delete_records)
before_action :check_manage_permissions,
only: %i(edit update delete_records copy_records)
def index
@draw = params[:draw].to_i
@ -30,45 +32,11 @@ class RepositoryRowsController < ApplicationController
repository_cells: [] }
record.transaction do
record.name = record_params[:name] unless record_params[:name].blank?
record.name = record_params[:repository_row_name] unless record_params[:repository_row_name].blank?
errors[:default_fields] = record.errors.messages unless record.save
if cell_params
cell_params.each do |key, value|
column = @repository.repository_columns.detect do |c|
c.id == key.to_i
end
if column.data_type == 'RepositoryListValue'
next if value == '-1'
# check if item existx else revert the transaction
list_item = RepositoryListItem.where(repository_column: column)
.find(value)
cell_value = RepositoryListValue.new(
repository_list_item_id: list_item.id,
created_by: current_user,
last_modified_by: current_user,
repository_cell_attributes: {
repository_row: record,
repository_column: column
}
)
else
cell_value = RepositoryTextValue.new(
data: value,
created_by: current_user,
last_modified_by: current_user,
repository_cell_attributes: {
repository_row: record,
repository_column: column
}
)
end
if cell_value.save
record_annotation_notification(record, cell_value.repository_cell)
else
errors[:repository_cells] << {
"#{column.id}": cell_value.errors.messages
}
end
next if create_cell_value(record, key, value, errors).nil?
end
end
raise ActiveRecord::Rollback if errors[:repository_cells].any?
@ -113,9 +81,15 @@ class RepositoryRowsController < ApplicationController
# Add custom cells ids as key (easier lookup on js side)
@record.repository_cells.each do |cell|
if cell.value_type == 'RepositoryAssetValue'
cell_value = cell.value.asset if cell.value_type == 'RepositoryAssetValue'
else
cell_value = escape_input(cell.value.data)
end
json[:repository_row][:repository_cells][cell.repository_column_id] = {
repository_cell_id: cell.id,
value: escape_input(cell.value.data),
value: cell_value,
type: cell.value_type,
list_items: fetch_list_items(cell)
}
@ -134,7 +108,7 @@ class RepositoryRowsController < ApplicationController
}
@record.transaction do
@record.name = record_params[:name].blank? ? nil : record_params[:name]
@record.name = record_params[:repository_row_name].blank? ? nil : record_params[:repository_row_name]
errors[:default_fields] = @record.errors.messages unless @record.save
if cell_params
cell_params.each do |key, value|
@ -154,6 +128,16 @@ class RepositoryRowsController < ApplicationController
else
existing.delete
end
elsif existing.value_type == 'RepositoryAssetValue'
if existing.value.asset.update(file: value)
existing.value.asset.created_by = current_user
existing.value.asset.last_modified_by = current_user
existing.value.asset.post_process_file(current_team)
else
errors[:repository_cells] << {
"#{existing.repository_column_id}": { data: existing.value.asset.errors.messages[:file].first }
}
end
else
existing.value.data = value
if existing.value.save
@ -168,42 +152,7 @@ class RepositoryRowsController < ApplicationController
else
# Looks like it is a new cell, so we need to create new value, cell
# will be created automatically
column = @repository.repository_columns.detect do |c|
c.id == key.to_i
end
if column.data_type == 'RepositoryListValue'
next if value == '-1'
# check if item existx else revert the transaction
list_item = RepositoryListItem.where(repository_column: column)
.find(value)
cell_value = RepositoryListValue.new(
repository_list_item_id: list_item.id,
created_by: current_user,
last_modified_by: current_user,
repository_cell_attributes: {
repository_row: @record,
repository_column: column
}
)
else
cell_value = RepositoryTextValue.new(
data: value,
created_by: current_user,
last_modified_by: current_user,
repository_cell_attributes: {
repository_row: @record,
repository_column: column
}
)
end
if cell_value.save
record_annotation_notification(@record,
cell_value.repository_cell)
else
errors[:repository_cells] << {
"#{column.id}": cell_value.errors.messages
}
end
next if create_cell_value(@record, key, value, errors).nil?
end
end
# Clean up empty cells, not present in updated record
@ -240,6 +189,66 @@ class RepositoryRowsController < ApplicationController
end
end
def create_cell_value(record, key, value, errors)
column = @repository.repository_columns.detect do |c|
c.id == key.to_i
end
if column.data_type == 'RepositoryListValue'
return if value == '-1'
# check if item existx else revert the transaction
list_item = RepositoryListItem.where(repository_column: column)
.find(value)
cell_value = RepositoryListValue.new(
repository_list_item_id: list_item.id,
created_by: current_user,
last_modified_by: current_user,
repository_cell_attributes: {
repository_row: record,
repository_column: column
}
)
elsif column.data_type == 'RepositoryAssetValue'
asset = Asset.new(file: value,
created_by: current_user,
last_modified_by: current_user,
team: current_team)
if asset.save
asset.post_process_file(current_team)
else
errors[:repository_cells] << {
"#{column.id}": { data: asset.errors.messages[:file].first }
}
end
cell_value = RepositoryAssetValue.new(
asset: asset,
created_by: current_user,
last_modified_by: current_user,
repository_cell_attributes: {
repository_row: record,
repository_column: column
}
)
else
cell_value = RepositoryTextValue.new(
data: value,
created_by: current_user,
last_modified_by: current_user,
repository_cell_attributes: {
repository_row: record,
repository_column: column
}
)
end
if cell_value.save
record_annotation_notification(record,
cell_value.repository_cell)
else
errors[:repository_cells] << {
"#{column.id}": cell_value.errors.messages
}
end
end
def delete_records
deleted_count = 0
if selected_params
@ -276,6 +285,17 @@ class RepositoryRowsController < ApplicationController
end
end
def copy_records
duplicate_service = RepositoryActions::DuplicateRows.new(
current_user, @repository, params[:selected_rows]
)
duplicate_service.call
render json: {
flash: t('repositories.copy_records_report',
number: duplicate_service.number_of_duplicated_items)
}, status: :ok
end
private
def load_info_modal_vars
@ -313,7 +333,7 @@ class RepositoryRowsController < ApplicationController
end
def record_params
params.require(:repository_row).permit(:name).to_h
params.permit(:repository_row_name).to_h
end
def cell_params

View file

@ -9,10 +9,14 @@ module FileIconsHelper
file_ext = asset.file_file_name.split('.').last
if %w(doc docm docx dot dotm dotx odt rtf).include?(file_ext)
fa_class = 'fa-file-word'
elsif %w(csv ods xls xlsb xlsm xlsx).include?(file_ext)
elsif %w(ods xls xlsb xlsm xlsx).include?(file_ext)
fa_class = 'fa-file-excel'
elsif %w(odp pot potm potx pps ppsm ppsx ppt pptm pptx).include?(file_ext)
fa_class = 'fa-file-powerpoint'
elsif %w(pdf).include?(file_ext)
fa_class = 'fa-file-pdf'
elsif %w(txt csv tab tex).include?(file_ext)
fa_class = 'fa-file-alt'
end
# Now check for custom mappings or possible overrides

View file

@ -58,4 +58,11 @@ module RepositoryDatatableHelper
"<span class='circle disabled'>&nbsp;</span>"
end
end
def can_perform_repository_actions(repository)
team = repository.team
can_manage_repository?(repository) ||
can_create_repositories?(team) ||
can_manage_repository_rows?(team)
end
end

View file

@ -55,4 +55,14 @@ module SecondaryNavigationHelper
def is_module_archive?
action_name == 'archive'
end
def title_element
if project_page?
@project
elsif experiment_page?
@experiment
elsif module_page?
@my_module
end
end
end

View file

@ -0,0 +1,110 @@
# frozen_string_literal: true
module RepositoryActions
class DuplicateCell
def initialize(cell, new_row, user, team)
@cell = cell
@new_row = new_row
@user = user
@team = team
end
def call
self.send("duplicate_#{@cell.value_type.underscore}")
end
private
def duplicate_repository_list_value
old_value = @cell.value
RepositoryListValue.create(
old_value.attributes.merge(
id: nil, created_by: @user, last_modified_by: @user,
repository_cell_attributes: {
repository_row: @new_row,
repository_column: @cell.repository_column
}
)
)
end
def duplicate_repository_text_value
old_value = @cell.value
RepositoryTextValue.create(
old_value.attributes.merge(
id: nil, created_by: @user, last_modified_by: @user,
repository_cell_attributes: {
repository_row: @new_row,
repository_column: @cell.repository_column
}
)
)
end
def duplicate_repository_asset_value
old_value = @cell.value
new_asset = create_new_asset(old_value.asset)
RepositoryAssetValue.create(
old_value.attributes.merge(
id: nil, asset: new_asset, created_by: @user, last_modified_by: @user,
repository_cell_attributes: {
repository_row: @new_row,
repository_column: @cell.repository_column
}
)
)
end
def duplicate_repository_date_value
old_value = @cell.value
RepositoryDateValue.create(
old_value.attributes.merge(
id: nil, created_by: @user, last_modified_by: @user,
repository_cell_attributes: {
repository_row: @new_row,
repository_column: @cell.repository_column
}
)
)
end
# reuses the same code we have in copy protocols action
def create_new_asset(old_asset)
new_asset = Asset.new_empty(
old_asset.file_file_name,
old_asset.file_file_size
)
new_asset.created_by = old_asset.created_by
new_asset.team = @team
new_asset.last_modified_by = @user
new_asset.file_processing = true if old_asset.is_image?
new_asset.file = old_asset.file
new_asset.save
return unless new_asset.valid?
if new_asset.is_image?
new_asset.file.reprocess!(:large)
new_asset.file.reprocess!(:medium)
end
# Clone extracted text data if it exists
if old_asset.asset_text_datum
AssetTextDatum.create(data: new_asset.data, asset: new_asset)
end
# Update estimated size of cloned asset
# (& file_present flag)
new_asset.update(
estimated_size: old_asset.estimated_size,
file_present: true
)
# Update team's space taken
@team.reload
@team.take_space(new_asset.estimated_size)
@team.save!
new_asset
end
end
end

View file

@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'repository_actions/duplicate_cell'
module RepositoryActions
class DuplicateRows
attr_reader :number_of_duplicated_items
def initialize(user, repository, rows_ids = [])
@user = user
@repository = repository
@rows_to_duplicate = sanitize_rows_to_duplicate(rows_ids)
@number_of_duplicated_items = 0
end
def call
@rows_to_duplicate.each do |row_id|
duplicate_row(row_id)
end
end
private
def sanitize_rows_to_duplicate(rows_ids)
process_ids = rows_ids.map(&:to_i).uniq
@repository.repository_rows.where(id: process_ids).pluck(:id)
end
def duplicate_row(id)
row = RepositoryRow.find_by_id(id)
new_row = RepositoryRow.new(
row.attributes.merge(new_row_attributes(row.name))
)
if new_row.save
@number_of_duplicated_items += 1
row.repository_cells.each do |cell|
duplicate_repository_cell(cell, new_row)
end
end
end
def new_row_attributes(name)
timestamp = DateTime.now
{ id: nil,
name: "#{name} (1)",
created_at: timestamp,
updated_at: timestamp }
end
def duplicate_repository_cell(cell, new_row)
RepositoryActions::DuplicateCell.new(
cell, new_row, @user, @repository.team
).call
end
end
end

View file

@ -56,7 +56,7 @@ module SmartAnnotations
end
def trim_repository_name(name)
name.strip.slice(0..2).upcase
name.strip.slice(0..2).capitalize
end
end
end

View file

@ -53,6 +53,7 @@ module RepositoryImportParser
def import_rows!
errors = false
column_items = []
@rows.each do |row|
# Skip empty rows
next if row.empty?
@ -71,16 +72,28 @@ module RepositoryImportParser
end
row_cell_values = []
row.each.with_index do |value, index|
column = @columns[index]
size = 0
if column && value.present?
if column.data_type == 'RepositoryListValue'
current_items_column = get_items_column(column_items, column)
size = current_items_column.list_items_number
end
# uses RepositoryCellValueResolver to retrieve the correct value
cell_value_resolver =
RepositoryImportParser::RepositoryCellValueResolver.new(
column, @user, @repository
column,
@user,
@repository,
size
)
cell_value = cell_value_resolver.get_value(value, record_row)
if column.data_type == 'RepositoryListValue'
current_items_column.list_items_number =
cell_value_resolver.column_list_items_size
end
next if cell_value.nil? # checks the case if we reach items limit
unless cell_value.valid?
errors = true
raise ActiveRecord::Rollback
@ -125,5 +138,21 @@ module RepositoryImportParser
).failed_instances.any?
true
end
def get_items_column(list, column)
current_column = nil
list.each do |element|
current_column = element if element.has_column? column
end
unless current_column
new_column = RepositoryImportParser::ListItemsColumn.new(
column,
column.repository_list_items.size
)
list << new_column
return new_column
end
current_column
end
end
end

View file

@ -0,0 +1,14 @@
module RepositoryImportParser
class ListItemsColumn
attr_accessor :column, :list_items_number
def initialize(column, list_items_number)
@column = column
@list_items_number = list_items_number
end
def has_column?(column)
@column == column
end
end
end

View file

@ -4,10 +4,12 @@
# it to the repository_row
module RepositoryImportParser
class RepositoryCellValueResolver
def initialize(column, user, repository)
attr_reader :column_list_items_size
def initialize(column, user, repository, size)
@column = column
@user = user
@repository = repository
@column_list_items_size = size
end
def get_value(value, record_row)
@ -30,6 +32,7 @@ module RepositoryImportParser
def new_repository_list_value(value, record_row)
list_item = @column.repository_list_items.find_by_data(value)
list_item ||= create_repository_list_item(value)
return unless list_item
RepositoryListValue.new(
created_by: @user,
last_modified_by: @user,
@ -42,13 +45,18 @@ module RepositoryImportParser
end
def create_repository_list_item(value)
RepositoryListItem.create(
data: value,
created_by: @user,
last_modified_by: @user,
repository_column: @column,
repository: @repository
)
if @column_list_items_size >= Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN
return
end
item = RepositoryListItem.new(data: value,
created_by: @user,
last_modified_by: @user,
repository_column: @column,
repository: @repository)
if item.save
@column_list_items_size += 1
return item
end
end
end
end

View file

@ -6,14 +6,13 @@
<div class="col-xs-3">
<% i = 0 %>
<% @archived_results.each do |result| %>
<%= render partial: "my_modules/archive/result.html.erb", locals: { result: result } %>
<% i = i + 1 %>
<% end %>
<% if i == 0 %>
<em><%=t "my_modules.module_archive.no_archived_results" %></em>
<% end %>
</div>
</div>
<script>window.initPreviewModal()</script>

View file

@ -9,7 +9,10 @@
<li>
<% option_text = t("my_modules.module_archive.option_download") %>
<% if result.is_asset %>
<%= link_to option_text, download_asset_path(result.asset), data: {no_turbolink: true} %>
<%= link_to t('protocols.index.archive_results.preview'), download_asset_path(result.asset),
class: 'file-preview-link',
id: "modal_link#{result.asset.id}",
data: { no_turbolink: true, id: true, status: 'asset-present', 'preview-url': asset_file_preview_path(result.asset) } %>
<% elsif result.is_text %>
<%= link_to option_text, result_text_download_path(result.result_text_id), data: {no_turbolink: true} %>
<% elsif result.is_table %>

View file

@ -1,7 +1,7 @@
<% provide(:head_title, t("projects.archive.head_title")) %>
<%= render partial: "shared/sidebar" %>
<% if @projects_by_teams.length > 0 %>
<% if @archived_projects_by_teams.length > 0 %>
<div id="projects-toolbar">
<form class="form-inline" action="<%= projects_archive_path %>">
@ -29,7 +29,7 @@
</form>
</div>
<% @projects_by_teams.each do |team, projects| %>
<% @archived_projects_by_teams.each do |team, projects| %>
<%= render partial: 'projects/archive/team_projects',
locals: {team: team, projects: projects} %>
<% end %>

View file

@ -1,14 +0,0 @@
<ol class="breadcrumb breadcrumb-protocols-manager">
<% if action_name == "index" %>
<li><%= link_to current_team.name, projects_path(team: current_team) %></li>
<li class="active"><%= t("protocols.nav.breadcrumbs.manager") %></li>
<% else %>
<li><%= link_to current_team.name, projects_path(team: current_team) %></li>
<li><%= link_to t("protocols.nav.breadcrumbs.manager"), protocols_path(team: current_team, type: type) %></li>
<% end %>
<% if action_name == "edit" %>
<li class="active">
<%= t("protocols.nav.breadcrumbs.edit") %>
</li>
<% end %>
</ol>

View file

@ -1,10 +1,6 @@
<% provide(:head_title, t("protocols.edit.head_title")) %>
<%= render partial: 'shared/drag_n_drop_overlay' %>
<%= render partial: "protocols/breadcrumbs.html.erb",
locals: { teams: @teams,
current_team: @protocol.team,
type: @type } %>
<%= render partial: "protocols/header.html.erb" %>

View file

@ -2,7 +2,6 @@
<% provide(:head_title, t("protocols.index.head_title")) %>
<% if current_team %>
<%= render partial: "protocols/breadcrumbs.html.erb", locals: { teams: @teams, current_team: @current_team, type: @type } %>
<ul class="nav nav-tabs nav-settings">
<li role="presentation" class="<%= "active" if @type == :public %>">
<%= link_to t("protocols.index.navigation.public"), protocols_path(team: @current_team, type: :public) %>

View file

@ -1,9 +1,7 @@
<% provide(:head_title, t("projects.reports.new.head_title", project: h(@project.name)).html_safe) %>
<% provide(:body_class, "report-body") %>
<% provide(:sidebar_wrapper_class, "report-sidebar-wrapper") %>
<% provide(:container_class, "report-container") %>
<%= render partial: "reports/new/report_sidebar" %>
<%= render partial: "reports/new/report_navigation" %>
<div

View file

@ -1,65 +1,37 @@
<% content_for :secondary_navigation do %>
<div class="navbar-report">
<div>
<ul class="breadcrumb" style="margin-left: 15px;">
<li>
<% if can_read_team?(@project.team) %>
<a id="team-link"
href="<%= projects_path :team => @project.team.id %>">
<% end %>
<span class="hidden-sm hidden-md hidden-lg">Org</span>
<span class="hidden-xs"><%= @project.team.name %></span>
<% if can_read_team?(@project.team) %>
</a>
<% end %>
</li>
<li class="active">
<span class="hidden-sm hidden-md hidden-lg">Project</span>
<span class="hidden-xs"><%= @project.name %></span>
</li>
</ul>
</div>
<div class="center-block" id="report-menu">
<div class="dropdown" id="sort-report">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="glyphicon glyphicon-sort visible-xs-inline"></span>
<span class="hidden-xs"><%= t'projects.reports.new.nav_sort_by' %></span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a href="#" data-sort="desc"><%= t('projects.reports.new.nav_sort_desc') %></a></li>
<li><a href="#" data-sort="asc"><%= t('projects.reports.new.nav_sort_asc') %></a></li>
</ul>
</div>
<%= link_to "", class: "btn btn-primary", remote: true, id: "print-report" do %>
<span class="glyphicon glyphicon-print"></span>
<span class="hidden-xs"><%=t "projects.reports.new.nav_print" %></span>
<% end %>
<%= form_tag generate_project_reports_path(@project, format: :pdf), method: :post, target: "_blank", class: "get-report-pdf-form" do %>
<div class="form-group">
<%= hidden_field_tag "html", "" %>
<%= link_to "", class: "btn btn-primary", remote: true, id: "get-report-pdf" do %>
<span class="glyphicon glyphicon-save-file"></span>
<span class="hidden-xs"><%=t "projects.reports.new.nav_pdf" %></span>
<% end %>
</div>
<% end %>
<%= link_to "", class: "btn btn-primary", remote: true, id: "save-report-link" do %>
<span class="glyphicon glyphicon-ok"></span>
<span class="hidden-xs"><%=t "projects.reports.new.nav_save" %></span>
<% end %>
<%= link_to project_reports_path(@project), id: "cancel-report-link", class: "btn btn-default" do %>
<span class="glyphicon glyphicon-remove"></span>
<span class="hidden-xs"><%=t "projects.reports.new.nav_close" %></span>
<% end %>
</div>
<div class="navbar-report">
<div class="center-block" id="report-menu">
<div class="dropdown" id="sort-report">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="glyphicon glyphicon-sort visible-xs-inline"></span>
<span class="hidden-xs"><%= t'projects.reports.new.nav_sort_by' %></span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a href="#" data-sort="desc"><%= t('projects.reports.new.nav_sort_desc') %></a></li>
<li><a href="#" data-sort="asc"><%= t('projects.reports.new.nav_sort_asc') %></a></li>
</ul>
</div>
<% end %>
<%= link_to "", class: "btn btn-primary", remote: true, id: "print-report" do %>
<span class="hidden-xs"><%=t "projects.reports.new.nav_print" %></span>
<% end %>
<%= form_tag generate_project_reports_path(@project, format: :pdf), method: :post, target: "_blank", class: "get-report-pdf-form" do %>
<div class="form-group">
<%= hidden_field_tag "html", "" %>
<%= link_to "", class: "btn btn-primary", remote: true, id: "get-report-pdf" do %>
<span class="hidden-xs"><%=t "projects.reports.new.nav_pdf" %></span>
<% end %>
</div>
<% end %>
<%= link_to "", class: "btn btn-primary", remote: true, id: "save-report-link" do %>
<span class="hidden-xs"><%=t "projects.reports.new.nav_save" %></span>
<% end %>
<%= link_to project_reports_path(@project), id: "cancel-report-link", class: "btn btn-default pull-right" do %>
<span class="hidden-xs"><%=t "projects.reports.new.nav_close" %></span>
<% end %>
</div>
</div>

View file

@ -1,4 +0,0 @@
<ol class="breadcrumb breadcrumb-protocols-manager">
<li><%= link_to current_team.name, projects_path(team: current_team) %></li>
<li class="active"><%= t("repositories.nav.breadcrumbs.repositories") %></li>
</ol>

View file

@ -13,7 +13,7 @@
</div>
<div class="modal-body">
<div><%=t('zip_export.repository_header_html', repository: repository.name) %></div>
<div class="alert alert-info" role="alert"><%=t 'zip_export.files_alert' %></div>
<div class="custom-alert-info"><%=t 'zip_export.files_alert' %></div>
<div><%=t 'zip_export.repository_footer_html' %></div>
</div>
<div class="modal-footer">

View file

@ -64,8 +64,8 @@
</div>
<div class="alert alert-info" role="alert">
<ul>
<li><%= t('repositories.modal_parse.warning_1') %></li>
<li><%= t('repositories.modal_parse.warning_2') %></li>
<li><%=t 'repositories.modal_parse.warning_1', limit: Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN %></li>
<li><%=t 'repositories.modal_parse.warning_2' %></li>
</ul>
</div>
</div>

View file

@ -6,6 +6,7 @@
data-num-columns="<%= 6 + repository.repository_columns.count %>"
data-create-record="<%= repository_repository_rows_path(repository) %>"
data-delete-record="<%= repository_delete_records_path(repository) %>"
data-copy-records="<%= repository_copy_records_path(repository) %>"
data-max-dropdown-length="<%= Constants::NAME_TRUNCATION_LENGTH_DROPDOWN %>"
data-save-text="<%= I18n.t('general.save') %>"
data-edit-text="<%= I18n.t('general.edit') %>"

View file

@ -6,15 +6,19 @@
# show only if no repositories present. If the team will have them we will
# handle this in left side navigation bar
%>
<div class="jumbotron text-center" style="margin-top:25%">
<div class="jumbotron text-center" style="margin-top:18%">
<strong><%=t 'libraries.index.no_libraries.text' %></strong>
<h2><strong><%=t 'libraries.index.no_libraries.title' %><strong></h2>
<br />
<%= link_to t('libraries.index.no_libraries.create_new_button'),
create_modal_team_repositories_path(current_team),
class: "btn btn-primary btn-lg",
id: "create-new-repository",
remote: true %>
<% if can_create_repositories?(current_team) %>
<h2><strong><%=t 'libraries.index.no_libraries.title' %><strong></h2>
<br />
<%= link_to t('libraries.index.no_libraries.create_new_button'),
create_modal_team_repositories_path(current_team),
class: "btn btn-primary btn-lg",
id: "create-new-repository",
remote: true %>
<% else %>
<h2><strong><%=t 'libraries.index.no_libraries.no_permission_title' %><strong></h2>
<% end %>
</div>
<% else %>
<!-- If member of no teams -->

View file

@ -20,11 +20,12 @@
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
<%= "disabled" unless can_manage_repository?(@repository) || can_create_repositories?(@repository.team) %>>
<%= "disabled" unless can_perform_repository_actions(@repository) %>>
<span class="glyphicon glyphicon-cog"></span>
<span class="caret"></span>
</div>
<% if can_manage_repository?(@repository) || can_create_repositories?(@repository.team) %>
<% if can_perform_repository_actions(@repository) %>
<ul class="dropdown-menu pull-right">
<li class="dropdown-header">
<%= t("repositories.index.options_dropdown.header") %>
@ -36,6 +37,8 @@
class: "rename-repo-option",
remote: true %>
</li>
<% end %>
<% if can_create_repository_columns?(@repository.team) %>
<li>
<%= link_to t('repositories.index.options_dropdown.manage_columns'),
repository_repository_columns_path(@repository) %>
@ -105,12 +108,11 @@
<!-- These buttons are appended to table in javascript, after table initialization -->
<div class="toolbarButtons" style="display:none">
<button type="button" class="btn btn-default editAdd" id="editRepositoryRecord" onclick="onClickEdit()" disabled>
<span class="glyphicon glyphicon-pencil"></span>
<span class="hidden-xs-custom"><%= t("repositories.edit_record") %></span>
</button>
<% if can_manage_repository_rows?(@repository.team) %>
<button type="button" class="btn btn-default editAdd" id="editRepositoryRecord" onclick="onClickEdit()" disabled>
<span class="glyphicon glyphicon-pencil"></span>
<span class="hidden-xs-custom"><%= t("repositories.edit_record") %></span>
</button>
<button type="button" class="btn btn-default"
id="deleteRepositoryRecordsButton" data-target="#deleteRepositoryRecord" data-toggle="modal" disabled>
<span class="glyphicon glyphicon-trash"></span>
@ -118,6 +120,10 @@
<%= submit_tag I18n.t('repositories.delete_record'), :class => "hidden
delete_repository_records_submit" %>
</button>
<button type="button" class="btn btn-default copyRow" id="copyRepositoryRecords" onclick="onClickCopyRepositoryRecords()" disabled>
<span class="glyphicon glyphicon-duplicate"></span>
<span class="hidden-xs-custom"><%= t("repositories.copy_record") %></span>
</button>
<% end %>
</div>

View file

@ -20,7 +20,7 @@
<% end %>
</li>
<li class="<%= "active" if repositories_are_selected? %>">
<%= link_to repositories_path, id: "repositories-link", title: t('left_menu_bar.repositories') do %>
<%= link_to repositories_path, data: { no_turbolink: false }, id: "repositories-link", title: t('left_menu_bar.repositories') do %>
<span class="fas fa-cubes" aria-hidden="true"></span>
<span><%= t('left_menu_bar.repositories') %></span>
<% end %>

View file

@ -10,58 +10,6 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Breadcrumbs, displayed on small screens -->
<ul class="breadcrumb hidden-sm hidden-md hidden-lg">
<li>
<% if can_read_team?(@project.team) %>
<a href="<%= projects_path :team => @project.team.id %>">
<span class="glyphicon glyphicon-folder-open"></span>
</a>
<% else %>
<span class="glyphicon glyphicon-folder-open"></span>
<% end %>
</li>
<% if project_page? ||
sample_types_page_project? ||
sample_groups_page_project? %>
<li class="active">
<span class="glyphicon glyphicon-blackboard"></span>
</li>
<% else %>
<li>
<% if can_read_project?(@project) %>
<a href="<%= project_url(@project) %>">
<span class="glyphicon glyphicon-blackboard"></span>
</a>
<% else %>
<span class="glyphicon glyphicon-blackboard"></span>
<% end %>
</li>
<% end %>
<% if experiment_page? %>
<li class="active">
<i class="fas fa-flask"></i>
</li>
<% elsif module_page? %>
<li>
<% if can_read_experiment?(@experiment) %>
<%= link_to canvas_experiment_path(@experiment) do %>
<i class="fas fa-flask"></i>
<% end %>
<% else %>
<i class="fas fa-flask"></i>
<% end %>
</li>
<li class="active">
<span class="glyphicon glyphicon-credit-card"></span>
</li>
<% elsif experiment_page? %>
<li class="active">
<i class="fas fa-flask"></i>
</li>
<% end %>
</ul>
</div>
<!-- buttons -->
@ -212,72 +160,12 @@
<% end %>
</ul>
<!-- Breadcrumbs, displayed on large screens -->
<ul class="breadcrumb hidden-xs">
<li>
<% if can_read_team?(@project.team) %>
<a href="<%= projects_path :team => @project.team.id %>">
<%= truncate(@project.team.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
</a>
<% else %>
<%= truncate(@project.team.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
<% end %>
</li>
<% if project_page? %>
<li class="active">
<%= truncate(@project.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
</li>
<% else %>
<li>
<% if can_read_project?(@project) %>
<a href="<%= project_url(@project) %>">
<%= truncate(@project.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
</a>
<% else %>
<%= truncate(@project.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
<% end %>
</li>
<% end %>
<% if experiment_page? ||
module_page? ||
sample_types_page_my_module? ||
sample_groups_page_my_module? ||
sample_groups_page_experiment? ||
sample_types_page_expermient? %>
<% if !module_page? &&
!sample_types_page_my_module? &&
!sample_groups_page_my_module? %>
<li class="active">
<%= truncate(@experiment.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
</li>
<% else %>
<li>
<% if can_read_experiment?(@experiment) %>
<%= link_to truncate(@experiment.name,
length: Constants::NAME_TRUNCATION_LENGTH),
canvas_experiment_path(@experiment) %>
<% else %>
<%= truncate(@experiment.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
<% end %>
</li>
<% end %>
<% end %>
<% if module_page? ||
sample_types_page_my_module? ||
sample_groups_page_my_module? %>
<li class="active">
<%= truncate(@my_module.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
</li>
<% end %>
</ul>
<!-- Secondary navigation title -->
<div class="nav-name">
<%= truncate(title_element.name,
length: Constants::NAME_TRUNCATION_LENGTH) %>
</div>
</div>
</div>
</nav>

View file

@ -2,7 +2,7 @@
<div id="slide-panel" class="visible">
<div class="tree">
<ul>
<% if project_page? && action_name == 'index' ||
<% if project_page? && action_name.in?(%w(index archive)) ||
sample_types_page_project? ||
sample_groups_page_project? %>

View file

@ -3,7 +3,7 @@
<% @projects_by_teams.each do |team, projects| %>
<% projects.each do |project| %>
<% if (project_page? && action_name == 'show' ||
<% if (project_page? && action_name.in?(%w(show experiment_archive)) ||
sample_types_page_project? ||
sample_groups_page_project?) && project == @project %>
<li class="active" data-parent="candidate">

View file

@ -47,7 +47,7 @@ Rails.application.configure do
# config.action_dispatch.rack_cache = true
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
config.assets.js_compressor = Uglifier.new(harmony: true)
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.

View file

@ -168,9 +168,9 @@ class Constants
#=============================================================================
HTTP = 'http://'.freeze
TUTORIALS_URL = (HTTP + 'scinote.net/tutorials/').freeze
SUPPORT_URL = (HTTP + 'support.scinote.net/hc/en-us').freeze
WEBINARS_URL = (HTTP + 'scinote.net/join-a-webinar/').freeze
TUTORIALS_URL = (HTTP + 'goo.gl/YH3fXA').freeze
SUPPORT_URL = (HTTP + 'goo.gl/Jb9WXx').freeze
WEBINARS_URL = (HTTP + 'goo.gl/q3GdND').freeze
# Default user picture avatar
DEFAULT_AVATAR_URL = '/images/:style/missing.png'.freeze
@ -868,6 +868,8 @@ class Constants
EXPORTABLE_ZIP_EXPIRATION_DAYS = 7
REPOSITORY_LIST_ITEMS_PER_COLUMN = 500
# Very basic regex to check for validity of emails
BASIC_EMAIL_REGEX = URI::MailTo::EMAIL_REGEXP

View file

@ -312,8 +312,8 @@ en:
head_title: "%{project} | New report"
nav_title: "Report for: "
nav_print: "Print"
nav_pdf: "Export to PDF"
nav_save: "Save report"
nav_pdf: "Download PDF"
nav_save: "Save"
nav_close: "Close"
nav_sort_by: "Sort report by"
nav_sort_asc: "Oldest on top"
@ -329,7 +329,7 @@ en:
tasks_tab: "Choose tasks"
content_tab: "Choose content"
project_contents_inner:
instructions: "Select projects/experiment/tasks to include in the report"
instructions: "Select experiments/tasks to include in the report"
no_modules: "The project contains no tasks"
no_module_group: "No workflow"
module_contents:
@ -923,9 +923,6 @@ en:
name_placeholder: "My inventory"
submit: "Create inventory"
success_flash: "Inventory <strong>%{name}</strong> successfully created."
nav:
breadcrumbs:
repositories: "Inventories"
table:
id: 'ID'
assigned: "Assigned"
@ -947,6 +944,7 @@ en:
errors_list_title: "Items were not imported because one or more errors were found:"
no_repository_name: "Item name is required!"
edit_record: "Edit"
copy_record: "Copy"
delete_record: "Delete"
save_record: "Save"
cancel_save: "Cancel"
@ -973,7 +971,7 @@ en:
delete: "Delete column"
modal_parse:
title: 'Import items'
warning_1: 'Be careful when importing into Dropdown column/s! Each new unique cell value from the file will create a new Dropdown option. This could result in large amounts of Dropdown options.'
warning_1: 'Be careful when importing into Dropdown column/s! Each new unique cell value from the file will create a new Dropdown option. Maximum nr. of Dropdown options is %{limit}.'
warning_2: 'Importing into file columns is not supported.'
modal_import:
title: 'Import items'
@ -998,6 +996,7 @@ en:
no_records_assigned_flash: "No items were assigned to task"
no_records_unassigned_flash: "No items were unassigned from task"
default_column: 'Name'
copy_records_report: "%{number} item(s) successfully copied."
libraries:
manange_modal_column:
@ -1014,6 +1013,7 @@ en:
repository_columns:
head_title: '%{repository} | Manage Columns'
repository_list_items_limit: "Dropdown items limit reached max. %{limit}"
update:
success_flash: "Column %{name} was successfully updated."
create:
@ -1032,6 +1032,7 @@ en:
index:
head_title: "Inventories"
no_libraries:
no_permission_title: "You don't have permission to create new inventory. Please contact your team administrator."
text: "You don't have any inventories."
title: "Please create your first Inventory"
create_new_button: "Create New Inventory"
@ -1454,10 +1455,6 @@ en:
leave_flash: "Successfuly left team %{team}."
protocols:
nav:
breadcrumbs:
manager: "Protocol management"
edit: "Edit protocol"
protocols_io_import:
title_too_long: "... Text is too long so we had to cut it off."
too_long: "... <span class='label label-warning'>Text is too long so we had to cut it off.</span>"
@ -1660,6 +1657,7 @@ en:
row_success: "Archived"
row_renamed: "Archived & renamed"
row_failed: "Failed"
preview: "View"
restore_results:
title: "Restore results"
message_failed: "Failed to restore %{nr} protocols."

View file

@ -466,6 +466,9 @@ Rails.application.routes.draw do
to: 'repository_rows#delete_records',
as: 'delete_records',
defaults: { format: 'json' }
post 'copy_records',
to: 'repository_rows#copy_records',
defaults: { format: 'json' }
get 'repository_columns/:id/destroy_html',
to: 'repository_columns#destroy_html',
as: 'columns_destroy_html'

View file

@ -115,4 +115,12 @@ describe RepositoryRowsController, type: :controller do
end
end
end
describe 'POST #copy_records' do
it 'returns a success response' do
post :copy_records, params: { repository_id: repository.id,
selected_rows: [repository_row.id] }
expect(response).to have_http_status(:success)
end
end
end

View file

@ -22,14 +22,14 @@ RSpec.describe RepositoryListItem, type: :model do
end
describe 'Validations' do
let!(:user) { create :user }
let!(:repository_one) { create :repository }
it { should validate_presence_of(:data) }
it do
should validate_length_of(:data).is_at_most(Constants::TEXT_MAX_LENGTH)
end
context 'has a uniq data scoped on repository column' do
let!(:user) { create :user }
let!(:repository_one) { create :repository }
let!(:repository_column) do
create :repository_column, name: 'My column', repository: repository_one
end

View file

@ -0,0 +1,83 @@
require 'rails_helper'
describe RepositoryActions::DuplicateRows do
let!(:user) { create :user }
let!(:repository) { create :repository }
let!(:list_column) do
create(:repository_column, name: 'list',
repository: repository,
created_by: user,
data_type: 'RepositoryListValue')
end
let!(:text_column) do
create(:repository_column, name: 'text',
repository: repository,
created_by: user,
data_type: 'RepositoryTextValue')
end
describe '#call' do
before do
@rows_ids = []
3.times do |index|
row = create :repository_row, name: "row (#{index})",
repository: repository
create :repository_text_value, data: "text (#{index})",
repository_cell_attributes: {
repository_row: row,
repository_column: text_column
}
create :repository_list_value,
repository_list_item: create(:repository_list_item,
repository: repository,
repository_column: list_column,
data: "list item (#{index})"),
repository_cell_attributes: {
repository_row: row,
repository_column: list_column
}
@rows_ids << row.id.to_s
end
end
it 'generates a duplicate of selected items' do
expect(repository.repository_rows.reload.size).to eq 3
described_class.new(user, repository, @rows_ids).call
expect(repository.repository_rows.reload.size).to eq 6
end
it 'generates an exact duplicate of the row with custom column values' do
described_class.new(user, repository, [@rows_ids.first]).call
duplicated_row = repository.repository_rows.order('created_at ASC').last
expect(duplicated_row.name).to eq 'row (0) (1)'
duplicated_row.repository_cells.each do |cell|
if cell.value_type == 'RepositoryListValue'
expect(cell.value.data).to eq 'list item (0)'
else
expect(cell.value.data).to eq 'text (0)'
end
end
end
it 'prevents to duplicate items that do not already belong to repository' do
new_repository = create :repository, name: 'new repo'
new_row = create :repository_row, name: 'other row',
repository: new_repository
described_class.new(user, repository, [new_row.id]).call
expect(repository.repository_rows.reload.size).to eq 3
end
it 'returns the number of duplicated items' do
service_obj = described_class.new(user, repository, @rows_ids)
service_obj.call
expect(service_obj.number_of_duplicated_items).to eq 3
end
it 'returns the number of duplicated items' do
service_obj = described_class.new(user, repository, [])
service_obj.call
expect(service_obj.number_of_duplicated_items).to eq 0
end
end
end

View file

@ -57,7 +57,7 @@ describe SmartAnnotations::Preview do
trimmed_repository_name = subject.__send__(
:trim_repository_name, 'banana'
)
expect(trimmed_repository_name).to eq('BAN')
expect(trimmed_repository_name).to eq('Ban')
end
end
end

View file

@ -28,7 +28,7 @@ describe RepositoryImportParser::RepositoryCellValueResolver do
context 'RepositoryListValue' do
let(:subject) do
RepositoryImportParser::RepositoryCellValueResolver.new(
sample_group_column, user, repository
sample_group_column, user, repository, 0
)
end
@ -51,7 +51,7 @@ describe RepositoryImportParser::RepositoryCellValueResolver do
context 'RepositoryTextValue' do
let(:subject) do
RepositoryImportParser::RepositoryCellValueResolver.new(
custom_column, user, repository
custom_column, user, repository, 0
)
it 'returns a valid RepositoryTextValue object' do