Merge pull request #2614 from biosistemika/develop

Merge develop to master
This commit is contained in:
Urban Rotnik 2020-05-27 11:58:19 +02:00 committed by GitHub
commit 0007de2a95
247 changed files with 5481 additions and 2490 deletions

View file

@ -97,7 +97,7 @@ gem 'rufus-scheduler', '~> 3.5'
gem 'discard', '~> 1.0'
gem 'ruby-graphviz', '~> 1.2' # Graphviz for rails
gem 'tinymce-rails', '~> 4.9.3' # Rich text editor - SEE BELOW
gem 'tinymce-rails', '~> 4.9.10' # Rich text editor - SEE BELOW
# Any time you update tinymce-rails Gem, also update the cache_suffix parameter
# in sitewide/tiny_mce.js - to prevent browsers from loading old, cached .js
# TinyMCE files which might cause errors

View file

@ -1,6 +1,6 @@
GIT
remote: https://github.com/biosistemika/canaid
revision: 2ac3004d728adbf1be7f4271689b83464f612b23
revision: f595a096f402900e184bf51298dca38fbb7e0820
branch: rails_6
specs:
canaid (1.0.4)
@ -42,38 +42,38 @@ GIT
GEM
remote: http://rubygems.org/
specs:
actioncable (6.0.0)
actionpack (= 6.0.0)
actioncable (6.0.3)
actionpack (= 6.0.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.0)
actionpack (= 6.0.0)
activejob (= 6.0.0)
activerecord (= 6.0.0)
activestorage (= 6.0.0)
activesupport (= 6.0.0)
actionmailbox (6.0.3)
actionpack (= 6.0.3)
activejob (= 6.0.3)
activerecord (= 6.0.3)
activestorage (= 6.0.3)
activesupport (= 6.0.3)
mail (>= 2.7.1)
actionmailer (6.0.0)
actionpack (= 6.0.0)
actionview (= 6.0.0)
activejob (= 6.0.0)
actionmailer (6.0.3)
actionpack (= 6.0.3)
actionview (= 6.0.3)
activejob (= 6.0.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.0)
actionview (= 6.0.0)
activesupport (= 6.0.0)
rack (~> 2.0)
actionpack (6.0.3)
actionview (= 6.0.3)
activesupport (= 6.0.3)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.0)
actionpack (= 6.0.0)
activerecord (= 6.0.0)
activestorage (= 6.0.0)
activesupport (= 6.0.0)
actiontext (6.0.3)
actionpack (= 6.0.3)
activerecord (= 6.0.3)
activestorage (= 6.0.3)
activesupport (= 6.0.3)
nokogiri (>= 1.8.5)
actionview (6.0.0)
activesupport (= 6.0.0)
actionview (6.0.3)
activesupport (= 6.0.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -83,27 +83,27 @@ GEM
activemodel (>= 4.1, < 6.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (6.0.0)
activesupport (= 6.0.0)
activejob (6.0.3)
activesupport (= 6.0.3)
globalid (>= 0.3.6)
activemodel (6.0.0)
activesupport (= 6.0.0)
activerecord (6.0.0)
activemodel (= 6.0.0)
activesupport (= 6.0.0)
activemodel (6.0.3)
activesupport (= 6.0.3)
activerecord (6.0.3)
activemodel (= 6.0.3)
activesupport (= 6.0.3)
activerecord-import (1.0.4)
activerecord (>= 3.2)
activestorage (6.0.0)
actionpack (= 6.0.0)
activejob (= 6.0.0)
activerecord (= 6.0.0)
activestorage (6.0.3)
actionpack (= 6.0.3)
activejob (= 6.0.3)
activerecord (= 6.0.3)
marcel (~> 0.3.1)
activesupport (6.0.0)
activesupport (6.0.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
zeitwerk (~> 2.1, >= 2.1.8)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
aes_key_wrap (1.0.1)
@ -159,7 +159,7 @@ GEM
bootstrap3-datetimepicker-rails (4.17.47)
momentjs-rails (>= 2.8.1)
bootstrap_form (2.7.0)
builder (3.2.3)
builder (3.2.4)
bullet (6.0.2)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
@ -195,10 +195,10 @@ GEM
execjs
coffee-script-source (1.12.2)
commit_param_routing (0.0.1)
concurrent-ruby (1.1.5)
concurrent-ruby (1.1.6)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.5)
crass (1.0.6)
cucumber (3.1.2)
builder (>= 2.1.2)
cucumber-core (~> 3.2.0)
@ -246,11 +246,11 @@ GEM
discard (1.1.0)
activerecord (>= 4.2, < 7)
docile (1.3.2)
doorkeeper (5.1.0)
doorkeeper (5.1.1)
railties (>= 5)
down (5.0.0)
addressable (~> 2.5)
erubi (1.8.0)
erubi (1.9.0)
et-orbi (1.2.2)
tzinfo
execjs (2.7.0)
@ -330,7 +330,7 @@ GEM
logging (2.0.0)
little-plugger (~> 1.1)
multi_json (~> 1.10)
loofah (2.3.1)
loofah (2.5.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -341,7 +341,7 @@ GEM
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0904)
mimemagic (0.3.3)
mimemagic (0.3.5)
mini_magick (4.9.5)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
@ -359,7 +359,7 @@ GEM
rails (>= 3.2.0)
newrelic_rpm (6.6.0.358)
nio4r (2.5.2)
nokogiri (1.10.8)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
nokogumbo (2.0.1)
nokogiri (~> 1.8, >= 1.8.4)
@ -404,30 +404,30 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.1)
puma (4.3.3)
puma (4.3.5)
nio4r (~> 2.0)
raabro (1.1.6)
rack (2.0.8)
rack (2.2.2)
rack-attack (6.1.0)
rack (>= 1.0, < 3)
rack-proxy (0.6.5)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.0.0)
actioncable (= 6.0.0)
actionmailbox (= 6.0.0)
actionmailer (= 6.0.0)
actionpack (= 6.0.0)
actiontext (= 6.0.0)
actionview (= 6.0.0)
activejob (= 6.0.0)
activemodel (= 6.0.0)
activerecord (= 6.0.0)
activestorage (= 6.0.0)
activesupport (= 6.0.0)
rails (6.0.3)
actioncable (= 6.0.3)
actionmailbox (= 6.0.3)
actionmailer (= 6.0.3)
actionpack (= 6.0.3)
actiontext (= 6.0.3)
actionview (= 6.0.3)
activejob (= 6.0.3)
activemodel (= 6.0.3)
activerecord (= 6.0.3)
activestorage (= 6.0.3)
activesupport (= 6.0.3)
bundler (>= 1.3.0)
railties (= 6.0.0)
railties (= 6.0.3)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x)
@ -436,8 +436,8 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.2.0)
loofah (~> 2.2, >= 2.2.2)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
@ -445,9 +445,9 @@ GEM
rails (> 3.1)
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (6.0.0)
actionpack (= 6.0.0)
activesupport (= 6.0.0)
railties (6.0.3)
actionpack (= 6.0.3)
activesupport (= 6.0.3)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@ -541,7 +541,7 @@ GEM
simplecov-html (0.10.2)
spinjs-rails (1.4)
rails (>= 3.1)
sprockets (3.7.2)
sprockets (4.0.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.1)
@ -553,12 +553,12 @@ GEM
thread_safe (0.3.6)
tilt (2.0.9)
timecop (0.9.1)
tinymce-rails (4.9.4)
tinymce-rails (4.9.10)
railties (>= 3.1.1)
turbolinks (5.1.1)
turbolinks-source (~> 5.1)
turbolinks-source (5.2.0)
tzinfo (1.2.6)
tzinfo (1.2.7)
thread_safe (~> 0.1)
uglifier (4.1.20)
execjs (>= 0.3.0, < 3)
@ -584,7 +584,7 @@ GEM
wkhtmltopdf-heroku (2.12.5.0)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.2.2)
zeitwerk (2.3.0)
PLATFORMS
ruby
@ -687,7 +687,7 @@ DEPENDENCIES
sneaky-save!
spinjs-rails
timecop
tinymce-rails (~> 4.9.3)
tinymce-rails (~> 4.9.10)
turbolinks (~> 5.1.1)
tzinfo-data
uglifier (>= 1.3.0)

View file

@ -1 +1 @@
1.18.8
1.19.0

View file

@ -0,0 +1,3 @@
//= link_tree ../images
//= link application.js
//= link application.css

View file

@ -26,6 +26,7 @@
//= require nested_form_fields
//= require highlight.pack
//= require tinymce-jquery
//= require_tree ./tinymce/plugins
//= require jsPlumb-2.0.4-min
//= require jsnetworkx
//= require bootstrap-select

View file

@ -47,6 +47,7 @@ var DasboardCurrentTasksWidget = (function() {
function filtersEnabled() {
return dropdownSelector.getValues(experimentFilter)
|| dropdownSelector.getValues(projectFilter)
|| $('.current-tasks-widget .task-search-field').val().length > 0
|| dropdownSelector.getValues(viewFilter) !== 'uncompleted';
}

View file

@ -1,23 +1,78 @@
/* global I18n dropdownSelector */
/* eslint-disable no-use-before-define */
function initTaskCollapseState() {
let taskView = '.my-modules-protocols-index';
let taskSection = '.task-section-caret';
let taskId = $(taskView).data('task-id');
function collapseStateSave() {
$(taskView).on('click', taskSection, function() {
let collapsed = $(this).attr('aria-expanded');
let taskSectionType = $(this).attr('aria-controls');
if (collapsed === 'true') {
localStorage.setItem('task_section_collapsed/' + taskId + '/' + taskSectionType, collapsed);
} else {
localStorage.removeItem('task_section_collapsed/' + taskId + '/' + taskSectionType);
}
});
}
function collapseStateLoad() {
$(taskSection).each(function() {
let taskSectionType = $(this).attr('aria-controls');
var collapsed = localStorage.getItem('task_section_collapsed/' + taskId + '/' + taskSectionType);
if (JSON.parse(collapsed)) {
$('#' + taskSectionType).collapse('hide');
}
$(this).closest('.task-section').removeClass('hidden');
});
}
collapseStateSave();
collapseStateLoad();
}
function updateStartDate() {
let updateUrl = $('#startDateContainer').data('update-url');
let val = $('#calendarStartDate').val();
$.ajax({
url: updateUrl,
type: 'PATCH',
dataType: 'json',
data: { my_module: { started_on: val } },
success: function(result) {
$('#startDateLabelContainer').html(result.start_date_label);
}
});
}
// Bind ajax for editing due dates
function initStartDatePicker() {
$('#calendarStartDate').on('dp.change', function() {
updateStartDate();
});
}
function updateDueDate() {
let updateUrl = $('.due-date-container').data('update-url');
let val = $('#calendar-due-date').val();
let updateUrl = $('#dueDateContainer').data('update-url');
let val = $('#calendarDueDate').val();
$.ajax({
url: updateUrl,
type: 'PATCH',
dataType: 'json',
data: { my_module: { due_date: val } },
success: function(result) {
$('#due-date-label-container').html(result.due_date_label);
$('#dueDateLabelContainer').html(result.due_date_label);
}
});
}
// Bind ajax for editing due dates
function initDueDatePicker() {
$('#calendar-due-date').on('dp.change', function() {
$('#calendarDueDate').on('dp.change', function() {
updateDueDate();
});
}
@ -190,7 +245,7 @@ function applyTaskCompletedCallBack() {
button.find('.btn')
.removeClass('btn-default').addClass('btn-primary');
}
$('.due-date-container').html(data.module_header_due_date);
$('#dueDateContainer').html(data.module_header_due_date);
initDueDatePicker();
$('.task-state-label').html(data.module_state_label);
button.find('button').replaceWith(data.new_btn);
@ -220,7 +275,7 @@ function initTagsSelector() {
}
return `<span class="my-module-tags-color"></span>
${data.label + ' '}
<span class="my-module-tags-create-new"> (${I18n.t('my_modules.module_header.create_new_tag')})</span>`;
<span class="my-module-tags-create-new"> (${I18n.t('my_modules.details.create_new_tag')})</span>`;
},
onOpen: function() {
$('.select-container .edit-button-container').removeClass('hidden');
@ -269,7 +324,65 @@ function initTagsSelector() {
}).getContainer(myModuleTagsSelector).addClass('my-module-tags-container');
}
function initAssignedUsersSelector() {
var manageUsersModal = $('#manage-module-users-modal');
var manageUsersModalBody = manageUsersModal.find('.modal-body');
// Initialize users editing modal remote loading
function initUsersEditLink() {
$('.task-details').on('ajax:success', '.manage-users-link', function(e, data) {
manageUsersModal.modal('show');
manageUsersModal.find('#manage-module-users-modal-module').text(data.my_module.name);
initUsersModalBody(data);
});
}
// Initialize ajax listeners and elements style on modal body.
// This function must be called when modal body is changed.
function initUsersModalBody(data) {
manageUsersModalBody.html(data.html);
manageUsersModalBody.find('.selectpicker').selectpicker();
}
// Initialize reloading manage user modal content after posting new user
manageUsersModalBody.on('ajax:success', '.add-user-form', function(e, data) {
initUsersModalBody(data);
});
// Initialize remove user from my_module links
manageUsersModalBody.on('ajax:success', '.remove-user-link', function(e, data) {
initUsersModalBody(data);
});
// Reload users HTML element when modal is closed
manageUsersModal.on('hide.bs.modal', function() {
var usersEl = $('.task-assigned-users');
// Load HTML to refresh users
$.ajax({
url: usersEl.attr('data-module-users-url'),
type: 'GET',
dataType: 'json',
success: function(data) {
$('.task-assigned-users').replaceWith(data.html);
},
error: function() {
// TODO
}
});
});
// Remove users modal content when modal window is closed.
manageUsersModal.on('hidden.bs.modal', function() {
manageUsersModalBody.html('');
});
initUsersEditLink();
}
initTaskCollapseState();
applyTaskCompletedCallBack();
initTagsSelector();
bindEditTagsAjax();
initStartDatePicker();
initDueDatePicker();
initAssignedUsersSelector();

View file

@ -68,11 +68,9 @@ function initCopyToRepository() {
var modal = $('#copy-to-repository-modal');
var modalBody = modal.find('.modal-body');
var submitBtn = modal.find(".modal-footer [data-action='submit']");
link
.on('ajax:success', function(e, data) {
modalBody.html(data.html);
modalBody.find("[data-role='copy-to-repository']")
.on('ajax:success', function(e2, data2) {
if (data2.refresh !== null) {
@ -180,8 +178,8 @@ function initLoadFromRepository() {
modal.modal('show');
// Init Datatable on public tab
initLoadFromRepositoryTable(modalBody.find('#public-tab'));
// Init Datatable on recent tab
initLoadFromRepositoryTable(modalBody.find('#recent-tab'));
modalBody.find("a[data-toggle='tab']")
.on('hide.bs.tab', function(el) {
@ -214,9 +212,7 @@ function initLoadFromRepository() {
function initLoadFromRepositoryTable(content) {
var tableEl = content.find("[data-role='datatable']");
var datatable = tableEl.DataTable({
order: [[1, 'asc']],
dom: "RBfl<'row'<'col-sm-12't>><'row'<'col-sm-7'i><'col-sm-5'p>>",
sScrollX: '100%',
sScrollXInner: '100%',
@ -224,6 +220,7 @@ function initLoadFromRepositoryTable(content) {
processing: true,
serverSide: true,
responsive: true,
order: tableEl.data('default-order') || [[1, 'asc']],
ajax: {
url: tableEl.data('source'),
type: 'POST'
@ -385,8 +382,9 @@ function refreshProtocolStatusBar() {
type: 'GET',
dataType: 'json',
success: function(data) {
$("[data-role='protocol-status-bar']").html(data.html);
$('.my-module-protocol-status').replaceWith(data.html);
initLinkUpdate();
initCopyToRepository();
}
});
}
@ -433,43 +431,16 @@ function initImport() {
});
}
function initRecentProtocols() {
var recentProtocolContainer = $('.my-module-recent-protocols');
var dropDownList = recentProtocolContainer.find('.dropdown-menu');
recentProtocolContainer.find('.dropdown-button').click(function() {
dropDownList.find('.protocol').remove();
$.get('/protocols/recent_protocols', result => {
$.each(result, (i, protocol) => {
$('<div class="protocol"><i class="fas fa-file-alt"></i>'
+ truncateLongString(protocol.name, GLOBAL_CONSTANTS.NAME_TRUNCATION_LENGTH)
+ '</div>').appendTo(dropDownList)
.click(() => {
$.post(recentProtocolContainer.data('updateUrl'), { source_id: protocol.id })
.success(() => {
location.reload();
})
.error(ev => {
HelperModule.flashAlertMsg(ev.responseJSON.message, 'warning');
});
});
});
function initProtocolSectionOpenEvent() {
$('#protocol-container').on('shown.bs.collapse', function() {
$(this).find("[data-role='hot-table']").each(function() {
var $container = $(this).find("[data-role='step-hot-table']");
var hot = $container.handsontable('getInstance');
hot.render();
});
});
$('.protocol-description-content').on('ajax:success', () => {
updateRecentProtocolsStatus();
});
}
function updateRecentProtocolsStatus() {
var recentProtocolContainer = $('.my-module-recent-protocols');
var steps = $('.step');
var protocolDescription = $('#protocol_description_view').html();
if (steps.length === 0 && protocolDescription.length === 0) {
recentProtocolContainer.css('display', '');
} else {
recentProtocolContainer.css('display', 'none');
}
}
/**
@ -484,7 +455,7 @@ function init() {
initLoadFromRepository();
refreshProtocolStatusBar();
initImport();
initRecentProtocols();
initProtocolSectionOpenEvent();
}
init();

View file

@ -0,0 +1,643 @@
/* eslint-disable no-param-reassign, no-use-before-define */
/* global DataTableHelpers PerfectScrollbar FilePreviewModal animateSpinner HelperModule
initAssignedTasksDropdown I18n */
var MyModuleRepositories = (function() {
const FULL_VIEW_MODAL = $('#myModuleRepositoryFullViewModal');
const UPDATE_REPOSITORY_MODAL = $('#updateRepositoryRecordModal');
const STATUS_POLLING_INTERVAL = 10000;
var SIMPLE_TABLE;
var FULL_VIEW_TABLE;
var FULL_VIEW_TABLE_SCROLLBAR;
var SELECTED_ROWS = {};
function reloadRepositoriesList(repositoryId) {
var repositoriesContainer = $('#assigned-items-container');
$.get(repositoriesContainer.data('repositories-list-url'), function(result) {
repositoriesContainer.html(result.html);
$('.assigned-items-title').attr('data-assigned-items-count', result.assigned_rows_count);
// expand recently updated repository
$('#assigned-items-container').collapse('show');
$('#assigned-repository-items-container-' + repositoryId).collapse('show');
});
}
function tableColumns(tableContainer, skipCheckbox = false) {
var columns = $(tableContainer).data('default-table-columns');
var customColumns = $(tableContainer).find('thead th[data-type]');
for (let i = 0; i < columns.length; i += 1) {
columns[i].data = String(i);
columns[i].defaultContent = '';
if (skipCheckbox && i === 0) columns[i].visible = false;
}
customColumns.each((i, column) => {
columns.push({
visible: true,
searchable: true,
data: String(columns.length),
defaultContent: $.fn.dataTable.render['default' + column.dataset.type](column.id)
});
});
return columns;
}
function fullViewColumnDefs() {
let columnDefs = [{
targets: 0,
visible: true,
searchable: false,
orderable: false,
className: 'dt-body-center',
sWidth: '1%',
render: function(data) {
var checked = data ? 'checked' : '';
return `<div class="sci-checkbox-container">
<input class='repository-row-selector sci-checkbox' type='checkbox' ${checked}>
<span class='sci-checkbox-label'></span>
</div>`;
}
}];
if (FULL_VIEW_MODAL.find('.table').data('type') === 'live') {
columnDefs.push({
targets: 1,
searchable: false,
className: 'assigned-column',
sWidth: '1%',
render: function(data) {
return $.fn.dataTable.render.AssignedTasksValue(data);
}
}, {
targets: 3,
render: function(data, type, row) {
return "<a href='" + row.recordInfoUrl + "' class='record-info-link'>" + data + '</a>';
}
});
}
columnDefs.push(
{
targets: '_all',
render: function(data) {
if (typeof data === 'object' && $.fn.dataTable.render[data.value_type]) {
return $.fn.dataTable.render[data.value_type](data);
}
return data;
}
}
);
return columnDefs;
}
function renderSimpleTable(tableContainer) {
if (SIMPLE_TABLE) SIMPLE_TABLE.destroy();
SIMPLE_TABLE = $(tableContainer).DataTable({
dom: "Rt<'pagination-row'<'version-label'><'pagination-actions'p>>",
processing: true,
serverSide: true,
responsive: true,
pageLength: 5,
order: [[0, 'asc']],
sScrollY: '100%',
sScrollX: '100%',
sScrollXInner: '100%',
destroy: true,
ajax: {
url: $(tableContainer).data('source'),
data: function(d) {
d.order[0].column = tableContainer.data('name-column-id');
d.assigned = 'assigned';
d.view_mode = true;
d.simple_view = true;
},
global: false,
type: 'POST'
},
columnDefs: [{
targets: 0,
render: function(data, type, row) {
return "<a href='" + row.recordInfoUrl + "'"
+ "class='record-info-link'>" + data + '</a>';
}
}],
drawCallback: function() {
var repositoryContainer = $(this).closest('.assigned-repository-container');
repositoryContainer.find('.table.dataTable').removeClass('hidden');
repositoryContainer.find('.version-label').html(tableContainer.data('version-label'));
SIMPLE_TABLE.columns.adjust();
}
});
}
function renderFullViewTable(tableContainer, options = {}) {
if (FULL_VIEW_TABLE) FULL_VIEW_TABLE.destroy();
SELECTED_ROWS = {};
FULL_VIEW_TABLE_SCROLLBAR = false;
FULL_VIEW_TABLE = $(tableContainer).DataTable({
dom: "R<'main-actions hidden'<'toolbar'><'filter-container'f>>t<'pagination-row hidden'<'pagination-info'li><'pagination-actions'p>>",
processing: true,
stateSave: true,
serverSide: true,
order: $(tableContainer).data('default-order'),
pageLength: 25,
sScrollX: '100%',
sScrollXInner: '100%',
destroy: true,
ajax: {
url: $(tableContainer).data('source'),
data: function(d) {
if (options.assigned) d.assigned = 'assigned';
d.view_mode = true;
},
global: false,
type: 'POST'
},
columns: tableColumns(tableContainer, options.skipCheckbox),
columnDefs: fullViewColumnDefs(),
fnInitComplete: function() {
var dataTableWrapper = $(tableContainer).closest('.dataTables_wrapper');
DataTableHelpers.initLengthApearance(dataTableWrapper);
DataTableHelpers.initSearchField(dataTableWrapper);
dataTableWrapper.find('.main-actions, .pagination-row').removeClass('hidden');
if (options.assign_mode) {
renderFullViewAssignButtons();
} else {
$('.table-container .toolbar').html($('#repositoryToolbarButtonsTemplate').html());
if (FULL_VIEW_MODAL.find('.modal-content').hasClass('show-sidebar')) {
FULL_VIEW_MODAL.find('#showVersionsSidebar').addClass('active');
}
}
initAssignedTasksDropdown(tableContainer);
},
drawCallback: function() {
FULL_VIEW_TABLE.columns.adjust();
FilePreviewModal.init();
renderFullViewRepositoryName(
tableContainer.attr('data-repository-name'),
tableContainer.attr('data-repository-snapshot-created'),
options.assign_mode
);
updateFullViewRowsCount(tableContainer.attr('data-assigned-items-count'));
if (FULL_VIEW_TABLE_SCROLLBAR) {
FULL_VIEW_TABLE_SCROLLBAR.update();
} else {
FULL_VIEW_TABLE_SCROLLBAR = new PerfectScrollbar(
$(tableContainer).closest('.dataTables_scrollBody')[0],
{
wheelSpeed: 0.5,
minScrollbarLength: 20
}
);
}
},
stateLoadCallback: function(settings, callback) {
var loadStateUrl = $(tableContainer).data('load-state-url');
$.post(loadStateUrl, function(json) {
if (!options.assign_mode) {
json.state.columns[0].visible = false;
}
json.state.search.search = null;
callback(json.state);
});
},
rowCallback: function(row) {
var checkbox = $(row).find('.repository-row-selector');
if (SELECTED_ROWS[row.id]) {
$(row).addClass('selected');
checkbox.attr('checked', !checkbox.attr('checked'));
}
}
});
}
function setSelectedItem() {
let versionsSidebar = FULL_VIEW_MODAL.find('.repository-versions-sidebar');
let currentId = FULL_VIEW_MODAL.find('.table').data('id');
versionsSidebar.find('.list-group-item').removeClass('active');
versionsSidebar.find(`[data-id="${currentId}"]`).addClass('active');
if (!versionsSidebar.find(`[data-id="${currentId}"]`).data('selected')) {
$('#setDefaultVersionButton').parent().removeClass('hidden');
} else {
$('#setDefaultVersionButton').parent().addClass('hidden');
}
}
function reloadTable(tableUrl) {
animateSpinner(null, true);
$.getJSON(tableUrl, (data) => {
FULL_VIEW_MODAL.find('.table-container').html(data.html);
renderFullViewTable(FULL_VIEW_MODAL.find('.table'), { assigned: true, skipCheckbox: true });
setSelectedItem();
animateSpinner(null, false);
});
}
function initSelectAllCheckbox() {
FULL_VIEW_MODAL.on('click', 'input.select-all', function() {
var selectAllCheckbox = $(this);
var rows = FULL_VIEW_MODAL.find('.dataTables_scrollBody tbody tr');
$.each(rows, function(i, row) {
var checkbox = $(row).find('.repository-row-selector');
if (checkbox.prop('checked') === selectAllCheckbox.prop('checked')) return;
checkbox.prop('checked', !checkbox.prop('checked'));
selectFullViewRow(row);
});
});
}
function refreshSelectAllCheckbox() {
var checkboxes = FULL_VIEW_MODAL.find('.dataTables_scrollBody .repository-row-selector');
var selectedCheckboxes = FULL_VIEW_MODAL.find('.dataTables_scrollBody .repository-row-selector:checked');
var selectAllCheckbox = FULL_VIEW_MODAL.find('input.select-all');
selectAllCheckbox.prop('indeterminate', false);
if (selectedCheckboxes.length === 0) {
selectAllCheckbox.prop('checked', false);
} else if (selectedCheckboxes.length === checkboxes.length) {
selectAllCheckbox.prop('checked', true);
} else {
selectAllCheckbox.prop('indeterminate', true);
}
}
function checkSnapshotStatus(snapshotItem) {
$.getJSON(snapshotItem.data('status-url'), (statusData) => {
if (statusData.status === 'ready') {
$.getJSON(snapshotItem.data('version-item-url'), (itemData) => {
snapshotItem.replaceWith(itemData.html);
});
} else {
setTimeout(function() {
checkSnapshotStatus(snapshotItem);
}, STATUS_POLLING_INTERVAL);
}
});
}
function initSimpleTable() {
$('#assigned-items-container').on('shown.bs.collapse', '.assigned-repository-container', function() {
var repositoryContainer = $(this);
var repositoryTemplate = $($('#myModuleRepositorySimpleTemplate').html());
repositoryTemplate.attr('data-source', $(this).data('repository-url'));
repositoryTemplate.attr('data-version-label', $(this).data('footer-label'));
repositoryTemplate.attr('data-name-column-id', $(this).data('name-column-id'));
repositoryContainer.html(repositoryTemplate);
renderSimpleTable(repositoryTemplate);
});
$('.navbar-secondary').on('sideBar::shown sideBar::hidden', function() {
if (SIMPLE_TABLE) {
SIMPLE_TABLE.columns.adjust();
}
});
}
function initVersionsStatusCheck() {
let sidebar = FULL_VIEW_MODAL.find('.repository-versions-sidebar');
sidebar.find('.repository-snapshot-item.provisioning').each(function() {
var snapshotItem = $(this);
setTimeout(function() {
checkSnapshotStatus(snapshotItem);
}, STATUS_POLLING_INTERVAL);
});
}
function refreshCreationSpanshotInfoText() {
var snapshotsCount = FULL_VIEW_MODAL.find('.repository-snapshot-item').length;
var createSnapshotInfo = FULL_VIEW_MODAL.find('.create-snapshot-item .info');
if (snapshotsCount) {
createSnapshotInfo.addClass('hidden');
} else {
createSnapshotInfo.removeClass('hidden');
}
}
function initVersionsSidebarActions() {
FULL_VIEW_MODAL.on('click', '#showVersionsSidebar', function(e) {
$(this).toggleClass('active');
if ($(this).hasClass('active')) {
$.getJSON(FULL_VIEW_MODAL.find('.table').data('versions-sidebar-url'), (data) => {
var snapshotsItemsScrollBar;
FULL_VIEW_MODAL.find('.repository-versions-sidebar').html(data.html);
snapshotsItemsScrollBar = new PerfectScrollbar(
FULL_VIEW_MODAL.find('.repository-snapshots-container')[0]
);
setSelectedItem();
FULL_VIEW_MODAL.find('.modal-content').addClass('show-sidebar');
initVersionsStatusCheck();
snapshotsItemsScrollBar.update();
FULL_VIEW_TABLE.columns.adjust();
});
} else {
FULL_VIEW_MODAL.find('#collapseVersionsSidebar').click();
}
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '#createRepositorySnapshotButton', function(e) {
animateSpinner(null, true);
$.ajax({
url: $(this).data('action-path'),
type: 'POST',
dataType: 'json',
success: function(data) {
let snapshotItem = $(data.html);
FULL_VIEW_MODAL.find('.snapshots-container-scrollbody').prepend(snapshotItem);
setTimeout(function() {
checkSnapshotStatus(snapshotItem);
}, STATUS_POLLING_INTERVAL);
animateSpinner(null, false);
refreshCreationSpanshotInfoText();
}
});
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '.delete-snapshot-button', function(e) {
let snapshotItem = $(this).closest('.repository-snapshot-item');
animateSpinner(null, true);
$.ajax({
url: $(this).data('action-path'),
type: 'DELETE',
dataType: 'json',
success: function() {
if (snapshotItem.data('id') === FULL_VIEW_MODAL.find('.table').data('id')) {
reloadTable(FULL_VIEW_MODAL.find('#selectLiveVersionButton').data('table-url'));
}
snapshotItem.remove();
animateSpinner(null, false);
refreshCreationSpanshotInfoText();
}
});
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '.select-snapshot-button', function(e) {
reloadTable($(this).data('table-url'));
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '.repository-snapshot-item', function(e) {
var snapshotButton = $(this).find('.select-snapshot-button');
if (!snapshotButton.hasClass('disabled')) {
snapshotButton.click();
}
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '#selectLiveVersionButton', function(e) {
reloadTable(FULL_VIEW_MODAL.find('#selectLiveVersionButton').data('table-url'));
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '#collapseVersionsSidebar', function(e) {
FULL_VIEW_MODAL.find('.modal-content').removeClass('show-sidebar');
FULL_VIEW_MODAL.find('#showVersionsSidebar').removeClass('active');
FULL_VIEW_TABLE.columns.adjust();
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '#setDefaultVersionButton', function(e) {
let data;
animateSpinner(null, true);
if (FULL_VIEW_MODAL.find('.table').data('type') === 'live') {
data = { repository_id: FULL_VIEW_MODAL.find('.table').data('id') };
} else {
data = { repository_snapshot_id: FULL_VIEW_MODAL.find('.table').data('id') };
}
$.ajax({
url: $(this).data('select-path'),
type: 'POST',
dataType: 'json',
data: data,
success: function() {
let versionsList = FULL_VIEW_MODAL.find('.repository-versions-list');
versionsList.find('.list-group-item').data('selected', false);
versionsList.find('.list-group-item.active').data('selected', true);
$('#setDefaultVersionButton').parent().addClass('hidden');
animateSpinner(null, false);
}
});
e.stopPropagation();
});
FULL_VIEW_MODAL.on('hidden.bs.modal', function() {
FULL_VIEW_MODAL.find('.repository-versions-sidebar').empty();
FULL_VIEW_MODAL.find('.modal-content').removeClass('show-sidebar');
FULL_VIEW_MODAL.find('#showVersionsSidebar').removeClass('active');
FULL_VIEW_TABLE.destroy();
});
FULL_VIEW_MODAL.on('show.bs.modal', function() {
FULL_VIEW_MODAL.find('.table-container').empty();
FULL_VIEW_MODAL.find('.repository-name').empty();
updateFullViewRowsCount('');
});
}
function initRepositoryFullView() {
$('#assigned-items-container').on('click', '.action-buttons .full-screen', function(e) {
var repositoryNameObject = $(this).closest('.assigned-repository-caret')
.find('.assigned-repository-title');
renderFullViewRepositoryName(repositoryNameObject.text());
FULL_VIEW_MODAL.modal('show');
$.getJSON($(this).data('table-url'), (data) => {
FULL_VIEW_MODAL.find('.table-container').html(data.html);
renderFullViewTable(FULL_VIEW_MODAL.find('.table'), { assigned: true, skipCheckbox: true });
});
e.stopPropagation();
});
}
function initRepositoriesDropdown() {
$('.repositories-assign-container').on('show.bs.dropdown', function() {
var dropdownContainer = $(this);
$.getJSON(dropdownContainer.data('repositories-url'), function(result) {
dropdownContainer.find('.repositories-dropdown-menu').html(result.html);
});
});
}
function selectFullViewRow(row) {
var id = row.id;
if (!SELECTED_ROWS[id]) {
SELECTED_ROWS[id] = {
row_name: $(row).find('.record-info-link').text(),
assigned: $(row).find('.repository-row-selector').prop('checked')
};
} else {
delete SELECTED_ROWS[id];
}
$(row).toggleClass('selected');
if (Object.keys(SELECTED_ROWS).length) {
$('#assignRepositoryRecords, #updateRepositoryRecords').attr('disabled', false);
} else {
$('#assignRepositoryRecords, #updateRepositoryRecords').attr('disabled', true);
}
refreshSelectAllCheckbox();
}
function renderFullViewAssignButtons() {
var toolbar = FULL_VIEW_MODAL.find('.dataTables_wrapper .toolbar');
toolbar.empty();
if (parseInt(FULL_VIEW_MODAL.data('rows-count'), 10) === 0) {
toolbar.append($('#my-module-repository-full-view-assign-button').html());
} else {
toolbar.append($('#my-module-repository-full-view-update-button').html());
}
}
function updateFullViewRowsCount(value) {
FULL_VIEW_MODAL.data('rows-count', value);
FULL_VIEW_MODAL.find('.repository-name').attr('data-rows-count', value);
}
function renderFullViewRepositoryName(name, snapshotDate, assignMode) {
var title;
var repositoryName = name || FULL_VIEW_MODAL.find('.repository-name').data('repository-name');
if (assignMode) {
title = I18n.t('my_modules.repository.full_view.assign_modal_header', {
repository_name: repositoryName
});
} else if (snapshotDate) {
title = I18n.t('my_modules.repository.full_view.modal_snapshot_header', {
repository_name: repositoryName,
snaphot_date: snapshotDate
});
} else {
title = I18n.t('my_modules.repository.full_view.modal_live_header', {
repository_name: repositoryName
});
}
FULL_VIEW_MODAL.find('.repository-name').data('repository-name', repositoryName);
FULL_VIEW_MODAL.find('.repository-name').html(title);
}
function initRepoistoryAssignView() {
$('.repositories-dropdown-menu').on('click', '.repository', function(e) {
var assignUrlModal = $(this).data('assign-url-modal');
var updateUrlModal = $(this).data('update-url-modal');
FULL_VIEW_MODAL.modal('show');
$.get($(this).data('table-url'), (data) => {
FULL_VIEW_MODAL.find('.table-container').html(data.html);
FULL_VIEW_MODAL.data('assign-url-modal', assignUrlModal);
FULL_VIEW_MODAL.data('update-url-modal', updateUrlModal);
renderFullViewTable(FULL_VIEW_MODAL.find('.table'), { assign_mode: true });
});
e.stopPropagation();
});
FULL_VIEW_MODAL.on('click', '.table tbody tr', function() {
var checkbox = $(this).find('.repository-row-selector');
checkbox.prop('checked', !checkbox.prop('checked'));
selectFullViewRow(this);
}).on('click', '.table tbody tr .repository-row-selector', function(e) {
selectFullViewRow($(this).closest('tr')[0]);
e.stopPropagation();
}).on('click', '#assignRepositoryRecords', function() {
openAssignRecordsModal();
}).on('click', '#updateRepositoryRecords', function() {
openUpdateRecordsModal();
});
UPDATE_REPOSITORY_MODAL.on('click', '.downstream-action', function() {
submitUpdateRepositoryRecord({ downstream: true });
}).on('click', '.task-action', function() {
submitUpdateRepositoryRecord({ downstream: false });
});
}
function openUpdateRecordsModal() {
var updateUrl = FULL_VIEW_MODAL.data('update-url-modal');
$.get(updateUrl, { selected_rows: SELECTED_ROWS }, function(data) {
var assignList;
var assignListScrollbar;
var unassignList;
var unassignListScrollbar;
UPDATE_REPOSITORY_MODAL.find('.modal-content').html(data.html);
UPDATE_REPOSITORY_MODAL.data('update-url', data.update_url);
assignList = UPDATE_REPOSITORY_MODAL.find('.rows-to-assign .rows-list')[0];
unassignList = UPDATE_REPOSITORY_MODAL.find('.rows-to-unassign .rows-list')[0];
if (assignList) assignListScrollbar = new PerfectScrollbar(assignList);
if (unassignList) unassignListScrollbar = new PerfectScrollbar(unassignList);
UPDATE_REPOSITORY_MODAL.modal('show');
if (assignList) assignListScrollbar.update();
if (unassignList) unassignListScrollbar.update();
});
}
function openAssignRecordsModal() {
var assignUrl = FULL_VIEW_MODAL.data('assign-url-modal');
$.get(assignUrl, { selected_rows: SELECTED_ROWS }, function(data) {
UPDATE_REPOSITORY_MODAL.find('.modal-content').html(data.html);
UPDATE_REPOSITORY_MODAL.data('update-url', data.update_url);
UPDATE_REPOSITORY_MODAL.modal('show');
});
}
function submitUpdateRepositoryRecord(options = {}) {
var rowsToAssign = [];
var rowsToUnassign = [];
$.each(Object.keys(SELECTED_ROWS), function(i, rowId) {
if (SELECTED_ROWS[rowId].assigned) {
rowsToAssign.push(rowId);
} else {
rowsToUnassign.push(rowId);
}
});
$.ajax({
url: UPDATE_REPOSITORY_MODAL.data('update-url'),
type: 'PATCH',
dataType: 'json',
data: {
rows_to_assign: rowsToAssign,
rows_to_unassign: rowsToUnassign,
downstream: options.downstream
},
success: function(data) {
UPDATE_REPOSITORY_MODAL.modal('hide');
HelperModule.flashAlertMsg(data.flash, 'success');
SELECTED_ROWS = {};
$(FULL_VIEW_TABLE.table().container()).find('.dataTable')
.attr('data-assigned-items-count', data.rows_count);
FULL_VIEW_TABLE.ajax.reload(null, false);
reloadRepositoriesList(data.repository_id);
updateFullViewRowsCount(data.rows_count);
renderFullViewAssignButtons();
},
error: function(data) {
UPDATE_REPOSITORY_MODAL.modal('hide');
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
SELECTED_ROWS = {};
FULL_VIEW_TABLE.ajax.reload(null, false);
}
});
}
return {
init: () => {
initSimpleTable();
initRepositoryFullView();
initRepositoriesDropdown();
initVersionsSidebarActions();
initRepoistoryAssignView();
initSelectAllCheckbox();
}
};
}());
MyModuleRepositories.init();

View file

@ -244,7 +244,6 @@ function initializeEdit() {
{ color: 'white', shadow: true }
);
});
$.initTooltips();
}
function destroyEdit() {
@ -900,8 +899,6 @@ function bindEditTagsAjax(elements) {
.find(".edit-tags-link")
.html(my_module.tags_html);
});
// initialize tooltips again
$.initTooltips();
},
error: function(data){
// TODO

View file

@ -100,7 +100,6 @@ function initProtocolsTable() {
fnDrawCallback: function(settings, json) {
animateSpinner(this, false);
initRowSelection();
$.initTooltips();
},
preDrawCallback: function(settings) {
animateSpinner(this);

View file

@ -115,7 +115,6 @@
toggleButtons(true);
setTimeout(function() {
$.initTooltips();
initStepsComments();
FilePreviewModal.init();
SmartAnnotation.preventPropagation('.atwho-user-popover');
@ -489,6 +488,7 @@
var $btn = $(this);
$btn.off();
animateSpinner(null, true);
$('#protocol-container').collapse('show');
$.ajax({
url: $btn.data('href'),
@ -617,9 +617,7 @@
animateSpinner(null, false);
DragNDropSteps.clearFiles();
FilePreviewModal.init();
$.initTooltips();
if (typeof refreshProtocolStatusBar === 'function') refreshProtocolStatusBar();
if (typeof updateRecentProtocolsStatus === 'function') updateRecentProtocolsStatus();
},
error: function(xhr) {
if (xhr.responseJSON['assets.file']) {

View file

@ -127,7 +127,7 @@
.selectpicker({liveSearch: true})
.ajaxSelectPicker({
ajax: {
url: '<%= Rails.application.routes.url_helpers.available_rows_path %>',
url: '<%= Rails.application.routes.url_helpers.available_rows_repositories_path %>',
type: 'POST',
dataType: 'json',
data: function () {

View file

@ -154,3 +154,27 @@ $.fn.dataTable.render.RepositoryNumberValue = function(data) {
${data.value}
</span>`;
};
$.fn.dataTable.render.AssignedTasksValue = function(data) {
if (data.tasks > 0) {
let tooltip = I18n.t('repositories.table.assigned_tooltip', {
tasks: data.tasks,
experiments: data.experiments,
projects: data.projects
});
return `<div class="assign-counter-container dropdown" title="${tooltip}"
data-task-list-url="${data.task_list_url}">
<a href="#" class="assign-counter has-assigned"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">${data.tasks}</a>
<div class="dropdown-menu" role="menu">
<div class="sci-input-container right-icon">
<input type="text" class="sci-input-field search-tasks"
placeholder="${I18n.t('repositories.table.assigned_search')}"></input>
<i class="fas fa-times-circle clear-search"></i>
</div>
<div class="tasks"></div>
</div>
</div>`;
}
return "<div class='assign-counter-container'><span class='assign-counter'>0</span></div>";
};

View file

@ -1,6 +1,7 @@
/*
globals I18n _ SmartAnnotation FilePreviewModal animateSpinner Promise dropdownSelector
HelperModule animateLoading hideAssignUnasignModal RepositoryDatatableRowEditor
globals I18n _ SmartAnnotation FilePreviewModal animateSpinner Promise DataTableHelpers
HelperModule animateLoading RepositoryDatatableRowEditor
initAssignedTasksDropdown
*/
//= require jquery-ui/widgets/sortable
@ -14,7 +15,8 @@ var RepositoryDatatable = (function(global) {
var TABLE_WRAPPER_ID = '.repository-table';
var TABLE = null;
var EDITABLE = false;
var SELECT_ALL_SELECTOR = "#checkbox > input[name=select_all]"
var SELECT_ALL_SELECTOR = '#checkbox > input[name=select_all]';
const STATUS_POLLING_INTERVAL = 10000;
var rowsSelected = [];
var rowsLocked = [];
@ -265,6 +267,12 @@ var RepositoryDatatable = (function(global) {
if ($('#assigned').text().length === 0) {
TABLE.column(1).visible(false);
}
$.getJSON($(TABLE_ID).data('toolbar-url'), (data) => {
$('#toolbarButtonsDatatable').remove();
$(data.html).appendTo('div.toolbar');
});
TABLE.ajax.reload(null, false);
changeToViewMode();
SmartAnnotation.closePopup();
@ -398,6 +406,18 @@ var RepositoryDatatable = (function(global) {
});
}
function checkSnapshottingStatus() {
$.getJSON($(TABLE_ID).data('status-url'), (statusData) => {
if (statusData.snapshot_provisioning) {
setTimeout(() => { checkSnapshottingStatus(); }, STATUS_POLLING_INTERVAL);
} else {
EDITABLE = statusData.editable;
$('.repository-provisioning-notice').remove();
resetTableView();
}
});
}
function dataTableInit() {
viewAssigned = 'assigned';
TABLE = $(TABLE_ID).DataTable({
@ -442,7 +462,7 @@ var RepositoryDatatable = (function(global) {
className: 'assigned-column',
sWidth: '1%',
render: function(data, type, row) {
let content = data;
let content = $.fn.dataTable.render.AssignedTasksValue(data);
let icon;
if (!row.recordEditable) {
icon = `<i class="repository-row-lock-icon fas fa-lock" title="${I18n.t('repositories.table.locked_item')}"></i>`;
@ -511,8 +531,10 @@ var RepositoryDatatable = (function(global) {
changeToViewMode();
updateDataTableSelectAllCtrl();
FilePreviewModal.init();
// Prevent row toggling when selecting user smart annotation link
SmartAnnotation.preventPropagation('.atwho-user-popover');
// Show number of selected rows near pages info
$('#repository-table_info').append('<span id="selected_info"></span>');
$('#selected_info').html(' (' + rowsSelected.length + ' entries selected)');
@ -546,17 +568,15 @@ var RepositoryDatatable = (function(global) {
});
},
fnInitComplete: function() {
var tableLengthSelect = $('.dataTables_length select');
var tableFilterInput = $('.dataTables_filter input');
disableCheckboxToggleOnAssetDownload();
FilePreviewModal.init();
initHeaderTooltip();
disableCheckboxToggleOnCheckboxPreview();
// Append button to inner toolbar in table
$('div.toolbarButtonsDatatable').appendTo('div.toolbar');
$('div.toolbarButtonsDatatable').show();
// Append buttons to inner toolbar in the table
$.getJSON($(TABLE_ID).data('toolbar-url'), (data) => {
$(data.html).appendTo('div.toolbar');
});
$('div.toolbar-filter-buttons').prependTo('div.filter-container');
$('div.toolbar-filter-buttons').show();
@ -565,45 +585,25 @@ var RepositoryDatatable = (function(global) {
$('div.toolbarButtons').appendTo('div.toolbar');
$('div.toolbarButtons').show();
if (EDITABLE) {
RepositoryDatatableRowEditor.initFormSubmitAction(TABLE);
initItemEditIcon();
initSaveButton();
initCancelButton();
}
RepositoryDatatableRowEditor.initFormSubmitAction(TABLE);
initItemEditIcon();
initSaveButton();
initCancelButton();
DataTableHelpers.initLengthApearance($(TABLE_ID).closest('.dataTables_wrapper'));
DataTableHelpers.initSearchField($(TABLE_ID).closest('.dataTables_wrapper'));
if ($('.repository-show').length) {
$('.dataTables_scrollBody, .dataTables_scrollHead').css('overflow', '');
}
if (tableLengthSelect.val() == null) {
tableLengthSelect.val(10).change();
}
$.each(tableLengthSelect.find('option'), (i, option) => {
option.innerHTML = I18n.t('repositories.index.show_per_page', { number: option.value });
});
$('.dataTables_length').append(tableLengthSelect).find('label').remove();
dropdownSelector.init(tableLengthSelect, {
noEmptyOption: true,
singleSelect: true,
closeOnSelect: true,
selectAppearance: 'simple'
});
tableFilterInput.attr('placeholder', I18n.t('repositories.index.filter_inventory'))
.addClass('sci-input-field')
.css('margin', 0);
$('.dataTables_filter').append(`
<div class="sci-input-container left-icon">
<i class="fas fa-search"></i>
</div>`).find('.sci-input-container').prepend(tableFilterInput);
$('.dataTables_filter').find('label').remove();
$('.main-actions, .pagination-row').removeClass('hidden');
$(TABLE_ID).find('tr[data-editable=false]').each(function(_, e) {
rowsLocked.push(parseInt($(e).attr('id'), 10));
});
initAssignedTasksDropdown(TABLE_ID);
}
});
@ -662,19 +662,6 @@ var RepositoryDatatable = (function(global) {
});
};
global.openAssignRecordsModal = function() {
$.post(
$('#assignRepositoryRecords').data('assign-url-modal'),
{ selected_rows: rowsSelected }
).done(
function(data) {
$(data.html).appendTo('body').promise().done(function() {
$('#assignRepositoryRecordModal').modal('show');
});
}
);
};
global.hideAssignUnasignModal = function(id) {
$(id).modal('hide').promise().done(
function() {
@ -683,28 +670,6 @@ var RepositoryDatatable = (function(global) {
);
};
global.submitAssignRepositoryRecord = function(option) {
animateSpinner();
$.ajax({
url: $('#assignRepositoryRecordModal').data('assign-url'),
type: 'POST',
dataType: 'json',
data: { selected_rows: rowsSelected, downstream: (option === 'downstream') },
success: function(data) {
hideAssignUnasignModal('#assignRepositoryRecordModal');
HelperModule.flashAlertMsg(data.flash, 'success');
resetTableView();
clearRowSelection();
},
error: function(data) {
hideAssignUnasignModal('#assignRepositoryRecordModal');
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
resetTableView();
clearRowSelection();
}
});
}
global.openUnassignRecordsModal = function() {
$.post(
$('#unassignRepositoryRecords').data('unassign-url'),
@ -718,28 +683,6 @@ var RepositoryDatatable = (function(global) {
);
};
global.submitUnassignRepositoryRecord = function(option) {
animateSpinner();
$.ajax({
url: $('#unassignRepositoryRecordModal').data('unassign-url'),
type: 'POST',
dataType: 'json',
data: { selected_rows: rowsSelected, downstream: (option === 'downstream') },
success: function(data) {
hideAssignUnasignModal('#unassignRepositoryRecordModal');
HelperModule.flashAlertMsg(data.flash, 'success');
resetTableView();
clearRowSelection();
},
error: function(data) {
hideAssignUnasignModal('#unassignRepositoryRecordModal');
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
resetTableView();
clearRowSelection();
}
});
}
global.onClickDeleteRecord = function() {
animateSpinner();
$.ajax({
@ -834,6 +777,9 @@ var RepositoryDatatable = (function(global) {
TABLE_ID = id;
EDITABLE = $(TABLE_ID).data('editable');
TABLE = dataTableInit();
if ($(TABLE_ID).data('snapshot-provisioning')) {
setTimeout(() => { checkSnapshottingStatus(); }, STATUS_POLLING_INTERVAL);
}
}
function destroy() {

View file

@ -105,7 +105,7 @@ var RepositoryDatatableRowEditor = (function() {
TABLE.ajax.reload(() => {
animateSpinner(null, false);
HelperModule.flashAlertMsg(data.flash, 'success');
$('html, body').animate({scrollLeft: 0}, 300);
$('html, body').animate({ scrollLeft: 0 }, 300);
});
});

View file

@ -35,7 +35,8 @@ var RepositoryChecklistColumnType = (function() {
previewContainer,
itemsTextarea,
delimiterDropdown,
dropdownOptions
dropdownOptions,
GLOBAL_CONSTANTS.REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN
);
$('.changing-existing-list-items-warning').removeClass('hidden');
initChecklistDropdown();
@ -45,7 +46,8 @@ var RepositoryChecklistColumnType = (function() {
previewContainer,
itemsTextarea,
delimiterDropdown,
dropdownOptions
dropdownOptions,
GLOBAL_CONSTANTS.REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN
);
initChecklistDropdown();
initUpdatePlaceholder(this);
@ -55,7 +57,8 @@ var RepositoryChecklistColumnType = (function() {
previewContainer,
itemsTextarea,
delimiterDropdown,
dropdownOptions
dropdownOptions,
GLOBAL_CONSTANTS.REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN
);
initChecklistDropdown();
})
@ -71,7 +74,7 @@ var RepositoryChecklistColumnType = (function() {
checkValidation: () => {
var $manageModal = $(manageModal);
var count = $manageModal.find('.items-count').attr('data-count');
return count < GLOBAL_CONSTANTS.REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN;
return count <= GLOBAL_CONSTANTS.REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN;
},
loadParams: () => {
var repositoryColumnParams = {};

View file

@ -83,7 +83,7 @@ var RepositoryListColumnType = (function() {
});
}
function refreshCounter(number) {
function refreshCounter(number, limit) {
var $manageModal = $(manageModal);
var $counterContainer = $manageModal.find('.limit-counter-container');
var $btn = $manageModal.find('.column-save-btn');
@ -91,7 +91,7 @@ var RepositoryListColumnType = (function() {
$counterContainer.find('.items-count').html(number).attr('data-count', number);
if (number >= GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN) {
if (number > limit) {
$counterContainer.addClass('error-to-many-items');
$textarea.addClass('too-many-items');
$btn.addClass('disabled');
@ -102,11 +102,11 @@ var RepositoryListColumnType = (function() {
}
}
function refreshPreviewDropdownList(preview, textarea, delimiterContainer, dropdown) {
function refreshPreviewDropdownList(preview, textarea, delimiterContainer, dropdown, limit) {
var items = textToItems($(textarea).val(), delimiterContainer);
var hashItems = [];
drawDropdownPreview(items, preview);
refreshCounter(items.length);
refreshCounter(items.length, limit);
$.each(items, (index, option) => {
hashItems.push({ data: option });
@ -126,7 +126,8 @@ var RepositoryListColumnType = (function() {
previewContainer,
itemsTextarea,
delimiterDropdown,
dropdownOptions
dropdownOptions,
GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN
);
initListDropdown();
$('.changing-existing-list-items-warning').removeClass('hidden');
@ -137,7 +138,8 @@ var RepositoryListColumnType = (function() {
previewContainer,
itemsTextarea,
delimiterDropdown,
dropdownOptions
dropdownOptions,
GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN
);
initListDropdown();
})
@ -146,7 +148,8 @@ var RepositoryListColumnType = (function() {
previewContainer,
itemsTextarea,
delimiterDropdown,
dropdownOptions
dropdownOptions,
GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN
);
initListDropdown();
})
@ -162,7 +165,7 @@ var RepositoryListColumnType = (function() {
checkValidation: () => {
var $manageModal = $(manageModal);
var count = $manageModal.find('.items-count').attr('data-count');
return count < GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN;
return count <= GLOBAL_CONSTANTS.REPOSITORY_LIST_ITEMS_PER_COLUMN;
},
loadParams: () => {
var repositoryColumnParams = {};
@ -172,8 +175,8 @@ var RepositoryListColumnType = (function() {
return repositoryColumnParams;
},
refreshPreviewDropdownList: (preview, textarea, delimiter, dropdown) => {
refreshPreviewDropdownList(preview, textarea, delimiter, dropdown);
refreshPreviewDropdownList: (preview, textarea, delimiter, dropdown, limit) => {
refreshPreviewDropdownList(preview, textarea, delimiter, dropdown, limit);
},
initListDropdown: () => {

View file

@ -21,9 +21,6 @@ var RepositoryColumns = (function() {
function reloadDataTablePartial() {
// Append buttons for inventory datatable
$('div.toolbarButtonsDatatable').appendTo('.repository-show');
$('div.toolbarButtonsDatatable').hide();
$('div.toolbar-filter-buttons').appendTo('.repository-show');
$('div.toolbar-filter-buttons').hide();

View file

@ -26,11 +26,18 @@
function toggle() {
var btn = $('#sidebar-arrow');
var sideBarEvent;
if (btn.is('[data-shown]')) {
hide();
sideBarEvent = 'sideBar::hidden'
} else {
show();
sideBarEvent = 'sideBar::shown'
}
$('.navbar-secondary').one("transitionend",function(event) {
$('.navbar-secondary').trigger(sideBarEvent);
})
}
function isShown() {

View file

@ -381,8 +381,15 @@ var SmartAnnotation = (function() {
function init() {
$(field)
.on("reposition.atwho", function(event, flag, query) {
if (query.$inputor.offset().left > $(window).width()) {
query.$el.find('.atwho-view').css('left', (flag.left + $(window).scrollLeft()) + 'px');
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');
}
if ($('.repository-show').length) {
query.$el.find('.atwho-view').css('top', flag.top + 'px');

View file

@ -19,16 +19,16 @@ var Comments = (function() {
function newCommentValidation(textarea, submitBtn) {
textarea.off().on('focus', function() {
$(this).addClass('border');
if (this.value.length > 0) {
if (this.value.trim().length > 0) {
submitBtn.addClass('show');
}
}).on('blur', function() {
if (this.value.length === 0) {
if (this.value.trim().length === 0) {
$(this).removeClass('border');
submitBtn.removeClass('show');
}
}).on('keyup', function() {
if (this.value.length > 0) {
if (this.value.trim().length > 0) {
submitBtn.addClass('show');
} else {
submitBtn.removeClass('show');

View file

@ -0,0 +1,37 @@
/* global dropdownSelector I18n */
var DataTableHelpers = (function() {
return {
initLengthApearance: function(dataTableWraper) {
var tableLengthSelect = $(dataTableWraper).find('.dataTables_length select');
if (tableLengthSelect.val() == null) {
tableLengthSelect.val(10).change();
}
$.each(tableLengthSelect.find('option'), (i, option) => {
option.innerHTML = I18n.t('repositories.index.show_per_page', { number: option.value });
});
$(dataTableWraper).find('.dataTables_length')
.append(tableLengthSelect).find('label')
.remove();
dropdownSelector.init(tableLengthSelect, {
noEmptyOption: true,
singleSelect: true,
closeOnSelect: true,
disableSearch: true,
selectAppearance: 'simple'
});
},
initSearchField: function(dataTableWraper) {
var tableFilterInput = $(dataTableWraper).find('.dataTables_filter input');
tableFilterInput.attr('placeholder', I18n.t('repositories.index.filter_inventory'))
.addClass('sci-input-field')
.css('margin', 0);
$('.dataTables_filter').append(`
<div class="sci-input-container left-icon">
<i class="fas fa-search"></i>
</div>`).find('.sci-input-container').prepend(tableFilterInput);
$('.dataTables_filter').find('label').remove();
}
};
}());

View file

@ -158,7 +158,6 @@
function init(location) {
LOCATION = location;
global.addEventListener('paste', listener, false);
$.initTooltips();
}
function destroy() {
@ -274,11 +273,9 @@
if (totalSize > fileMaxSize) {
filesValid = false;
disableSubmitButton();
$.each($('.panel-step-attachment-new'), function() {
if (!$(this).find('p').hasClass('dnd-total-error')) {
$(this)
.find('.panel-body')
.append("<p class='dnd-total-error'>" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '</p>');
$.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 {

View file

@ -62,17 +62,18 @@ var dropdownSelector = (function() {
var modalContainer = container.closest('.modal-dialog');
var modalContainerBottom = 0;
var maxHeight = 0;
const bottomTreshold = 280;
if (modalContainer.length) {
windowHeight = modalContainer.height() + modalContainer[0].getBoundingClientRect().top;
containerPositionLeft -= modalContainer[0].getBoundingClientRect().left;
modalContainerBottom = modalContainer[0].getBoundingClientRect().bottom;
if (modalContainer.length && windowHeight - modalContainer.height() > bottomTreshold) {
let modalClientRect = modalContainer[0].getBoundingClientRect();
windowHeight = modalContainer.height() + modalClientRect.top;
containerPositionLeft -= modalClientRect.left;
modalContainerBottom = windowHeight + modalClientRect.bottom;
maxHeight += modalContainerBottom;
}
bottomSpace = windowHeight - containerPosition - containerHeight;
if ((modalContainerBottom + bottomSpace) < 280) {
if ((modalContainerBottom + bottomSpace) < bottomTreshold) {
container.addClass('inverse');
container.find('.dropdown-container').css('max-height', `${(containerPosition - 122 + maxHeight)}px`)
.css('margin-bottom', `${(containerPosition * -1)}px`)

View file

@ -18,3 +18,34 @@
initUnsavedWorkDialog();
}());
function initAssignedTasksDropdown(table) {
function loadTasks(counterContainer) {
var tasksContainer = counterContainer.find('.tasks');
var tasksUrl = counterContainer.data('task-list-url');
var searchQuery = counterContainer.find('.search-tasks').val();
$.get(tasksUrl, { query: searchQuery }, function(result) {
tasksContainer.html(result.html);
});
}
$(table).on('show.bs.dropdown', '.assign-counter-container', function() {
var cell = $(this);
loadTasks(cell);
});
$(table).on('click', '.assign-counter-container .dropdown-menu', function(e) {
e.stopPropagation();
});
$(table).on('click', '.assign-counter-container .clear-search', function() {
var cell = $(this).closest('.assign-counter-container');
$(this).prev('.search-tasks').val('');
loadTasks(cell);
});
$(table).on('keyup', '.assign-counter-container .search-tasks', function() {
var cell = $(this).closest('.assign-counter-container');
loadTasks(cell);
});
}

View file

@ -126,7 +126,7 @@ var TinyMCE = (function() {
}
tinyMCE.init({
cache_suffix: '?v=4.9.3', // This suffix should be changed any time library is updated
cache_suffix: '?v=4.9.10', // This suffix should be changed any time library is updated
selector: selector,
convert_urls: false,
menubar: 'file edit view insert format',

View file

@ -1,130 +0,0 @@
(function() {
'use strict';
$.initTooltips = function() {
var popoversArray = [];
var leaveTimeout;
var enterTimeout;
if ($(document.body).data('tooltips-enabled') === true || $(document.body).data('tooltips-enabled') == null) {
$('.tooltip_open').remove(); // Destroy all (if any) old open popovers
$('.help_tooltips').each(function(i, obj) {
var popoverObject = obj;
popoversArray.push(popoverObject);
});
$('.help_tooltips').each(function(i, obj) {
var link = $(obj).data('tooltiplink');
var textData = $(obj).data('tooltipcontent');
var customStyle = $(obj).data('tooltipstyle');
obj.dataset.tooltipId = i;
$(obj)
.popover({
html: true,
container: 'body',
placement: 'auto right',
trigger: 'manual',
content: 'popovers will not display if empty',
template:
'<div class="popover tooltip_' + i + '_window tooltip-open" '
+ 'role="tooltip" style="' + customStyle + '" data-popover-id="' + i + '">'
+ '<div class="popover-body" >' + textData + '</div>'
+ '<br><br><br>'
+ '<div class="popover-footer">'
+ '<a class="btn btn-link text-nowrap" href="' + link + '" target="_blank" rel="noopener noreferrer" >'
+ 'Read more&nbsp;&nbsp;&nbsp;<i class="fas fa-external-link-alt"></i>'
+ '</a>'
+ '</div>'
+ '</div>'
})
.off('shown.bs.popover')
.on('shown.bs.popover', function() {
// hide popup if object element hidden
if (!$(obj).is(':visible') || $(obj).is(':disabled')) {
$(obj).popover('hide');
}
// hide all other popovers
popoversArray.forEach(function(arrayItem) {
if (obj !== arrayItem) {
$(arrayItem).popover('hide');
}
});
})
.off('mouseleave')
.on('mouseleave', function() {
clearTimeout(enterTimeout);
leaveTimeout = setTimeout(function() {
if (!$('.tooltip_' + i + '_window:hover').length > 0) {
$(obj).popover('hide');
}
}, 100);
})
.off('mouseenter')
.on('mouseenter', function() {
clearTimeout(leaveTimeout);
enterTimeout = setTimeout(function() {
var top;
if ($(obj).hover().length > 0) {
$(obj).popover('show');
$('.tooltip_' + i + '_window').removeClass('tooltip-enter');
top = $(obj).offset().top;
$('.tooltip_' + i + '_window').css({
top: (top) + 'px'
});
$('.tooltip_' + i + '_window').off('mouseleave').on('mouseleave', function() {
$('.tooltip_' + i + '_window').removeClass('tooltip-enter');
$(obj).popover('hide');
});
$('.tooltip_' + i + '_window').off('mouseenter').on('mouseenter', function() {
$('.tooltip_' + i + '_window').addClass('tooltip-enter');
});
}
}, 1000);
});
});
}
$(document.body).on('click', function() {
$('.help_tooltips').each(function(i, obj) {
$(obj).popover('hide');
});
$('.popover.tooltip-open').each(function(i, obj) {
if ($('*[data-tooltip-id="' + obj.dataset.popoverId + '"]').length === 0) {
$(obj).remove();
}
});
});
$(document.body).on('mousemove', function(e) {
var mouse = { x: e.clientX, y: e.clientY };
$('.popover.tooltip-open').each(function(i, obj) {
var tooltipObj = '*[data-tooltip-id="' + obj.dataset.popoverId + '"]';
var objHeight;
var objWidth;
var objLeft;
var objTop;
var objCorners;
if ($(tooltipObj).length === 0) return;
objHeight = $(tooltipObj)[0].clientHeight;
objWidth = $(tooltipObj)[0].clientWidth;
objLeft = $(tooltipObj)[0].offsetLeft;
objTop = $(tooltipObj)[0].offsetTop;
objCorners = {
tl: { x: objLeft, y: objTop },
tr: { x: (objLeft + objWidth), y: objTop },
bl: { x: objLeft, y: (objTop + objHeight) },
br: { x: (objLeft + objWidth), y: (objTop + objHeight) }
};
if (
!(mouse.x > objCorners.tl.x && mouse.x < objCorners.br.x)
|| !(mouse.y > objCorners.tl.y && mouse.y < objCorners.br.y)
) {
$(tooltipObj).popover('hide');
}
});
});
};
$(document).on('turbolinks:load', function() {
$.initTooltips();
});
}());

View file

@ -76,19 +76,6 @@
);
}
// Initialize tooltips settings form
function tooltipSettings() {
var toggleInput = $('[name="tooltips_enabled"]');
toggleInput
.checkboxpicker({ onActiveCls: 'btn-toggle', offActiveCls: 'btn-toggle' });
if (toggleInput.attr('value') === 'true') {
toggleInput.prop('checked', true);
} else {
toggleInput.prop('checked', false);
}
}
// triggers submit action when the user clicks
function initTogglableSettingsForm() {
$('#togglable-settings-panel')
@ -148,6 +135,5 @@
initTimeZoneSelector();
initDateFormatSelector();
notificationsSettings();
tooltipSettings();
initTogglableSettingsForm();
})();

View file

@ -120,8 +120,8 @@
.tag-label {
display: inline-block;
margin-bottom: 1px;
margin-right: 5px;
margin-top: 1px;
max-width: 500px;
overflow: hidden;
text-overflow: ellipsis;

View file

@ -1,6 +1,8 @@
// Place all the styles related to the MyModules controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
// scss-lint:disable SelectorDepth SelectorFormat
// scss-lint:disable NestingDepth QualifyingElement
@import "constants";
@ -55,9 +57,6 @@
// Create wopi file
.create-wopi-file-btn {
border: 0;
display: contents;
img {
margin-right: 5px;
height: 20px;
@ -116,3 +115,80 @@
}
}
}
// Mobile view
@media (max-width: 700px) {
.task-section {
border-left: 0;
padding-left: 0;
.task-section-header {
.actions-block {
flex-wrap: wrap;
justify-content: flex-start;
margin-bottom: 5px;
width: 100%;
.dropdown {
margin-bottom: 5px;
min-width: 100%;
}
}
}
}
.task-details {
.module-tags {
.dropdown-selector-container {
.input-field {
padding-right: 36px;
}
}
}
.datetime-container {
.date-text {
margin-right: 0;
}
.dropdown-menu {
left: -50px !important;
}
}
}
#steps {
.panel-heading {
flex-wrap: wrap;
}
.panel-options {
display: flex;
flex-wrap: wrap;
max-width: 100%;
.complete-step-btn {
width: 100%;
}
}
}
.attachments {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important;
.attachment-placeholder {
margin: 4px 0 16px;
width: 200px;
}
}
#filePreviewModal {
.modal-body {
width: 100%;
.file-preview-container {
width: 90%;
}
}
}
}

View file

@ -6,149 +6,222 @@
@import "constants";
@import "mixins";
#manage-module-tags-modal {
.well {
border: 0;
box-shadow: none;
}
.content-pane.my-modules-protocols-index {
padding: 10px;
}
.module-header {
display: inline-block;
position: relative;
width: 100%;
.task-section {
border-left: 3px solid $color-concrete;
margin: 16px 0;
padding-left: 16px;
+ .protocol-title {
font-size: 22px;
font-weight: bold;
margin: 10px 0 15px;
}
.task-section-caret {
color: $color-volcano;
display: inline-block;
text-decoration: none;
.fas.block-icon {
color: $color-silver;
font-size: 18px;
margin-right: 5px;
}
.header-container {
display: flex;
flex-wrap: wrap;
float: left;
margin-bottom: 10px;
width: calc(100% - 280px);
.flex-block {
align-items: center;
display: flex;
flex-grow: 1;
margin-right: 10px;
.flex-block-label {
align-items: center;
display: flex;
margin-right: 3px;
}
.fas {
margin-right: 5px;
}
.due-date-container {
align-items: center;
display: inline-flex;
&:not(.collapsed) .fas {
@include rotate(90deg);
}
}
.date-text {
border: 1px solid transparent;
font-weight: bold;
line-height: 34px;
margin-right: 25px;
padding: 0 3px;
position: relative;
.task-section-title {
display: inline-block;
.alert-green {
color: $brand-success;
}
h2 {
margin: 10px 0;
.alert-yellow {
color: $brand-warning;
}
.alert-red {
color: $brand-danger;
}
.clear-date {
color: $color-silver;
cursor: pointer;
display: none;
font-size: 20px;
left: 100%;
line-height: 34px;
margin-left: 5px;
position: absolute;
top: 0;
&.assigned-items-title {
&::after {
@include font-h3;
color: $color-alto;
content: '[' attr(data-assigned-items-count) ']';
display: inline;
line-height: 22px;
padding-left: 5px;
}
}
}
}
.datetime-picker-container {
color: $color-emperor;
left: 0;
position: absolute;
top: 0;
width: 100%;
.task-section-header {
align-items: center;
display: flex;
flex-wrap: wrap;
#calendar-due-date {
opacity: 0;
}
.actions-block {
display: flex;
flex-grow: 1;
justify-content: flex-end;
.fa-calendar-alt {
display: none;
}
.caret {
margin-left: 25px;
}
&:hover {
.date-text[data-editable=true] {
border-color: $color-silver;
border-radius: 3px;
.repositories-assign-container {
flex-grow: 1;
max-width: 200px;
.clear-date {
display: inline;
.btn {
text-align: left;
.caret {
margin: 8px 0;
}
}
.repositories-dropdown-menu {
.repository {
@include font-button;
cursor: pointer;
display: flex;
padding: 8px 16px;
.assigned-items,
.shared-icon {
flex-shrink: 0;
.fas {
padding-right: 5px;
}
}
.assigned-items {
color: $color-alto;
}
.name {
flex-grow: 1;
}
}
}
}
.dropdown-menu {
@include font-button;
a {
padding: 8px 20px;
}
.fas {
padding-right: 5px;
}
}
.dropdown {
margin-right: 5px;
}
}
}
}
.complete-button-container {
display: inline;
float: right;
width: 260px;
.my_module-state-buttons {
padding-top: 0;
}
}
.task-details {
.fas.block-icon {
margin-right: 8px;
}
.flex-block {
align-items: center;
display: flex;
line-height: 34px;
.flex-block-label {
align-items: center;
display: flex;
margin-right: 4px;
}
}
.complete-button-container {
float: right;
width: 260px;
.my_module-state-buttons {
padding-top: 0;
}
.empty-label {
color: $color-silver-chalice;
font-weight: normal;
}
.module-description {
float: left;
width: 100%;
.datetime-container {
align-items: center;
display: inline-flex;
.no-description {
font-size: 16px;
}
.title {
font-size: 22px;
.date-text {
border: 1px solid transparent;
font-weight: bold;
padding: 20px 0 5px;
line-height: 32px;
margin-right: 25px;
padding: 0 4px;
position: relative;
.alert-green {
color: $brand-success;
}
.alert-yellow {
color: $brand-warning;
}
.alert-red {
color: $brand-danger;
}
.clear-date {
color: $color-silver;
cursor: pointer;
display: none;
font-size: 20px;
left: 100%;
line-height: 34px;
margin-left: 5px;
position: absolute;
top: 0;
}
}
.my-module-description-content {
margin-left: 10px;
.datetime-picker-container {
color: $color-emperor;
left: 0;
position: absolute;
top: 0;
width: 100%;
#calendarDueDate {
opacity: 0;
}
#calendarStartDate {
opacity: 0;
}
.fa-calendar-alt {
display: none;
}
}
&:hover {
.date-text[data-editable=true] {
background-color: $color-concrete;
border-radius: 4px;
.clear-date {
display: inline;
}
}
}
}
.module-tags {
float: left;
width: 100%;
#module-tags {
align-items: center;
display: flex;
@ -185,7 +258,7 @@
&:not(.view-mode):hover {
.input-field {
border: 1px solid $color-gainsboro;
border: 1px solid $color-alto;
}
}
}
@ -201,7 +274,7 @@
display: inline-block;
font-size: 14px;
line-height: 32px;
width: 37px;
margin-right: 4px;
}
.select-container {
@ -209,7 +282,7 @@
flex-basis: 100px;
flex-grow: 1;
flex-shrink: 1;
max-width: calc(100% - 65px);
max-width: 100%;
position: relative;
z-index: 110;
@ -250,56 +323,195 @@
float: left;
line-height: 36px;
}
}
}
}
}
#manage-module-tags-modal {
.well {
border: 0;
box-shadow: none;
}
}
.task-assigned-users {
align-items: center;
border-radius: 17px;
display: flex;
&.empty {
border-radius: 4px;
padding: 0 4px;
}
&:hover {
background-color: $color-concrete;
text-decoration: none;
}
}
.assign-new-user {
background-color: $color-alto;
color: $color-volcano;
text-align: center;
.fa-plus {
font-size: 16px;
}
}
}
.task-notes {
display: inline-block;
position: relative;
width: 100%;
.no-description {
font-size: 16px;
}
.task-notes-content {
margin-left: 10px;
}
}
.my-module-protocol-status {
position: relative;
.status-label {
@include font-h3;
color: $color-alto;
float: left;
margin: 0 3px;
&.linked {
color: $brand-primary;
}
}
.status-info {
@include font-h2;
color: inherit;
text-decoration: none;
&:hover,
&:active {
color: inherit;
text-decoration: none;
}
&.protocol-newer {
color: $brand-focus;
}
&.parent-newer {
color: $brand-warning;
}
}
.status-info-dropdown {
left: -125px;
max-width: 100vw;
width: 650px;
.dropdown-body {
border-bottom: $border-tertiary;
padding: 10px 32px;
.info-line {
align-items: center;
display: flex;
flex-wrap: wrap;
margin: 9px 0;
.description {
@include font-button;
flex-grow: 1;
min-width: 120px;
}
.value {
@include font-h3;
flex-shrink: 0;
}
&.new-parent-version {
.value {
color: $brand-warning;
&::before {
@include font-awesome;
content: "\f2f1";
margin-right: 5px;
}
}
}
}
.notification-line {
@include font-button;
color: $color-silver-chalice;
display: flex;
margin: 8px 0;
.fas {
line-height: 21px;
margin-right: 3px;
}
&.new-parent-version {
color: $brand-warning;
}
&.new-protocol-version {
color: $brand-focus;
}
}
}
.dropdown-footer {
padding: 16px;
}
}
}
.task-details-dropdown-container {
.task-details-button {
@include font-h2;
cursor: pointer;
margin: 0 3px;
}
.dropdown-menu {
@include font-button;
min-width: 500px;
padding: 1em 2em;
.task-details-value {
@include font-h3;
}
.row-v-margin {
margin-bottom: .5em;
margin-top: .5em;
}
}
}
@media (max-width: 700px) {
.my-module-protocol-status {
.status-info-dropdown {
left: -75px;
width: 300px;
.dropdown-footer {
.btn {
float: left !important;
margin: 5px 0;
width: auto;
}
}
}
}
}
.my-module-recent-protocols {
flex-grow: 1;
height: 36px;
margin-bottom: 5px;
position: relative;
.btn-group {
align-items: center;
display: flex;
float: right;
height: 33px;
}
.title {
font-size: 14px;
}
.dropdown-button {
cursor: pointer;
padding: 10px 5px;
}
.dropdown-menu {
left: auto;
padding: 0;
right: 0;
width: 402px;
.protocol {
cursor: pointer;
display: inline-block;
float: left;
padding: 5px 10px;
transition: $md-transaction;
width: 200px;
&:hover {
background: $color-gainsboro;
}
.fas {
margin-right: 5px;
}
}
}
}

View file

@ -1,6 +1,27 @@
// scss-lint:disable SelectorDepth SelectorFormat QualifyingElement
// scss-lint:disable NestingDepth ImportantRule
@mixin my-module-repository-title {
@include font-h3;
line-height: 22px;
overflow: hidden;
padding-right: 55px;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
&::after {
color: $color-alto;
content: '[' attr(data-rows-count) ']';
display: inline-block;
line-height: 22px;
padding-left: 5px;
position: absolute;
right: 0;
width: 55px;
}
}
.my-module-inventories {
.main-actions {
@ -74,3 +95,434 @@
}
}
}
#assigned-items-container {
padding-top: 10px;
.assigned-repository {
border: $border-default;
border-radius: $border-radius-modal;
margin-bottom: 10px;
overflow: hidden;
.assigned-repository-caret {
align-items: center;
color: inherit;
display: flex;
height: 52px;
padding: 0 18px;
text-decoration: none;
&.collapsed:hover,
&.collapsed:active {
background: $color-concrete;
border-radius: 6px;
text-decoration: none;
}
&:not(.collapsed) .fa-caret-right {
@include rotate(90deg);
}
.fa-caret-right {
flex-shrink: 0;
margin-right: 10px;
}
.assigned-repository-title {
@include my-module-repository-title;
}
.action-buttons {
flex-grow: 1;
flex-shrink: 0;
text-align: right;
.full-screen:hover {
background: $color-alto;
}
}
.snapshot-tag {
background-color: $color-concrete;
color: $color-silver-chalice;
padding: .3em;
}
}
.assigned-repository-container {
.table.dataTable {
margin-top: 0 !important;
.row-name {
border-left: 0;
}
}
.pagination-row {
border-top: $border-default;
padding: 5px 10px;
.pagination {
display: inline-flex;
}
.dataTables_paginate {
height: 38px;
}
}
}
}
}
#myModuleRepositoryFullViewModal {
padding-left: 0 !important;
z-index: 1045;
.modal-dialog {
height: 100vh;
margin: 0;
width: 100vw;
.modal-content {
border: 0;
border-radius: 0;
box-shadow: none;
display: grid;
grid-template-areas: 'header sidebar'
'table sidebar';
grid-template-columns: minmax(50%, 100%) 0;
grid-template-rows: 55px calc(100% - 55px);
height: inherit;
transition: all $timing-function-sharp;
&.show-sidebar {
grid-template-columns: minmax(50%, 100%) minmax(250px, 400px);
}
.modal-header {
align-items: center;
display: flex;
grid-area: header;
height: 55px;
padding: 10px 24px;
.close {
flex-shrink: 0;
text-align: center;
width: 20px;
}
.header-container {
flex-grow: 1;
max-width: calc(100% - 20px);
.repository-name {
@include my-module-repository-title;
@include font-h2;
}
.breadcrumbs {
align-items: center;
color: $color-silver-chalice;
display: flex;
font-size: 10px;
height: 20px;
width: 90%;
.my-module,
.project,
.experiment {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.slash {
flex-basis: 20px;
text-align: center;
}
}
}
}
.modal-body {
grid-area: table;
padding: 0;
}
}
}
.dataTables_scrollBody {
flex-grow: 1;
tbody tr.selected {
background: $brand-warning-light;
}
.assigned-column {
position: relative;
.assign-counter-container {
border-radius: $border-radius-tag;
cursor: pointer;
line-height: 35px;
position: absolute;
text-align: center;
top: 1px;
width: calc(100% - 16px);
.assign-counter {
display: inline-block;
height: 100%;
width: 100%;
&.has-assigned {
color: $brand-primary;
}
}
&:hover {
background-color: $color-alto;
}
.dropdown-menu {
min-width: 320px;
padding: 8px;
}
}
}
}
.dataTables_scrollHead {
flex-shrink: 0;
}
.table-container {
height: 100%;
padding: 1em 1.5em 0;
width: 100%;
.dataTables_wrapper {
display: flex;
flex-direction: column;
height: inherit;
.dataTables_scroll {
display: flex;
flex-direction: column;
flex-grow: 1;
max-height: 100%;
overflow: auto;
}
// Checklists
.checklist-dropdown {
.dropdown-menu {
min-width: 220px;
.checklist-item {
line-height: 18px;
padding: 5px 15px;
}
}
span {
color: $brand-primary;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
}
.main-actions {
flex-shrink: 0;
margin-bottom: 10px;
}
.pagination-row {
border-top: $border-default;
flex-shrink: 0;
margin-left: -1.5em;
padding: 1em 1.5em;
width: calc(100% + 3em);
}
}
.repository-versions-sidebar {
background-color: $color-concrete;
grid-area: sidebar;
overflow: hidden;
.sidebar-collapse-button {
color: $color-volcano;
text-decoration: none;
}
.repository-versions-header {
border-bottom: 1px solid $color-alto;
height: 55px;
padding: 0 1em;
h4 {
line-height: 55px;
margin: 0;
}
}
.repository-versions-list {
display: grid;
grid-template-rows: min-content min-content minmax(0, 100%);
grid-auto-rows: min-content;
height: calc(100% - 55px);
margin-bottom: 0;
.repository-snapshots-container {
overflow: auto;
position: relative;
}
}
.list-group-item {
align-items: center;
background-color: $color-concrete;
border: 0;
border-radius: 0;
.list-group-item-text {
@include font-small;
color: $color-silver-chalice;
}
.version-button {
color: $color-volcano;
text-decoration: none;
}
.delete-snapshot-button {
display: none;
}
&.disabled {
color: $color-alto;
&:hover {
background-color: $color-concrete;
}
}
&.repository-snapshot-item:hover,
&.live-version-item:hover {
background-color: $color-alto;
.delete-snapshot-button {
display: block;
}
}
&.active {
background-color: $brand-primary;
&:hover {
background-color: $brand-primary;
}
.list-group-item-heading,
.list-group-item-text,
.delete-snapshot-button {
background: transparent;
color: $color-white;
&:hover {
border-color: $color-white;
}
}
}
}
.repository-snapshot-item:not(.provisioning){
cursor: pointer;
}
.create-snapshot-item {
border-bottom: 1px solid $color-alto;
padding: 12px 10px;
}
}
}
.update-repository-record-modal {
.rows-list-container {
display: flex;
margin: 0 -10px;
.header {
font-weight: bold;
.fas {
margin-right: 3px;
}
}
.rows-list {
background: $color-concrete;
height: 170px;
list-style: none;
overflow: hidden;
padding-left: 16px;
position: relative;
li {
margin: 6px 0;
}
}
.rows-to-assign,
.rows-to-unassign {
flex-grow: 1;
margin: 0 10px;
}
.rows-to-assign .header .fas {
color: $brand-success;
}
.rows-to-unassign .header .fas {
color: $brand-danger;
}
}
}
@media (max-width: 700px) {
#myModuleRepositoryFullViewModal {
.modal-dialog {
.modal-content {
grid-template-areas: 'header'
'table'
'sidebar';
grid-template-columns: 100%;
grid-template-rows: 55px calc(100% - 55px) 0;
&.show-sidebar {
grid-template-columns: 100%;
grid-template-rows: 55px 0 calc(100% - 55px);
.modal-body {
overflow: hidden;
}
}
}
}
.sidebar-collapse-button {
transform: rotateZ(90deg);
}
}
}

View file

@ -10,12 +10,11 @@
.add-result-toolbar {
align-items: center;
display: flex;
flex-wrap: wrap;
}
.add-result-text {
display: inline-block;
line-height: 36px;
margin-bottom: 5px;
margin-right: 5px;
}
}

View file

@ -123,8 +123,8 @@
.fas-custom {
float: right;
margin-right: 5px;
top: 2px;
margin-right: 15px;
top: 17px;
}
.active {

View file

@ -70,7 +70,7 @@
.key-words-container {
display: inline-block;
flex-grow: 1;
margin-left: 5px;
margin: 0 40px 0 5px;
.dropdown-selector-container {
.input-field {

View file

@ -211,6 +211,7 @@ label {
.user-time {
color: $color-emperor;
margin-left: 15px;
white-space: nowrap;
}
.controls {
margin-right: 15px;
@ -276,6 +277,12 @@ label {
margin-left: 15px;
}
.module-start-date,
.module-due-date {
margin-left: 5px;
white-space: nowrap;
}
.module-tags {
margin-left: 0;
margin-top: 10px;
@ -328,6 +335,7 @@ label {
.user-time {
display: inline-block;
white-space: nowrap;
}
}

View file

@ -116,6 +116,10 @@
float: right;
}
}
.repository-provisioning-notice {
color: $brand-info;
}
}
.dataTables_scroll {
@ -187,29 +191,6 @@
}
}
.repositories-dropdown-menu {
border: 1px solid $color-gainsboro;
border-top: 0;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, .05);
height: auto;
max-height: 400px;
overflow-x: hidden;
text-transform: initial;
width: 300px;
li:not(:first-child) {
border-top: 1px solid $color-gainsboro;
}
.fas-custom {
float: right;
}
a.muted {
opacity: .7;
}
}
.repository-share-status {
display: contents !important;
@ -373,7 +354,7 @@
}
}
.toolbarButtonsDatatable {
#toolbarButtonsDatatable {
.view-only-label {
opacity: .6;
}

View file

@ -41,7 +41,6 @@
.assign-counter-container {
border-radius: $border-radius-tag;
cursor: pointer;
display: inline-block;
line-height: 35px;
position: absolute;
@ -49,7 +48,16 @@
width: calc(100% - 40px);
.assign-counter {
margin-left: 5px;
display: inline-block;
height: 100%;
padding-left: 5px;
width: 100%;
&:hover,
&:visited,
&:focus {
text-decoration: none;
}
&.has-assigned {
color: $brand-primary;
@ -59,6 +67,19 @@
&:hover {
background-color: $color-alto;
}
.dropdown-menu {
padding: 8px;
width: 320px;
.search-tasks:placeholder-shown + .fa-times-circle {
display: none;
}
.fa-times-circle {
cursor: pointer;
}
}
}
.circle-icon {

View file

@ -20,6 +20,14 @@
}
}
.task-assigned-users .global-avatar-container {
margin: 2px;
img {
vertical-align: baseline;
}
}
.new-avatar-preview-container {
height: 200px;
margin-bottom: 45px;

View file

@ -39,6 +39,7 @@
}
.comment-message {
@include font-main;
float: left;
width: 100%;
@ -218,6 +219,7 @@
textarea {
border: 1px solid transparent;
border-radius: $border-radius-default;
box-shadow: none;
outline: none;
overflow: hidden;

View file

@ -0,0 +1,62 @@
// scss-lint:disable SelectorDepth SelectorFormat
// scss-lint:disable NestingDepth QualifyingElement
.dataTables_wrapper {
.main-actions {
display: flex;
flex-wrap: wrap;
.toolbar {
flex-grow: 1;
}
}
.pagination-row {
align-items: center;
display: flex;
flex-wrap: wrap;
min-height: 68px;
width: 100%;
.pagination-info,
.pagination-actions {
flex-grow: 1;
}
.pagination-info {
align-items: center;
display: flex;
flex-wrap: wrap;
.dataTables_info {
padding-top: 0;
}
@media (max-width: 1000px) {
.dataTables_info {
display: none;
}
}
.dataTables_length {
margin-right: 24px;
width: 170px;
.dropdown-selector-container {
width: inherit;
}
label {
margin-bottom: 0;
}
}
}
@media (max-width: 767px) {
.pagination-info {
display: none;
}
}
}
}

View file

@ -89,8 +89,8 @@
.tag-label {
display: inline-block;
margin-bottom: 1px;
margin-right: 5px;
margin-top: 1px;
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis;

View file

@ -56,4 +56,40 @@
}
}
}
.private-tasks-counter {
@include font-button;
border-top: $border-tertiary;
color: $color-silver-chalice;
padding-top: .5em;
}
.no-results-placeholder {
color: $color-silver-chalice;
padding: 2em 0 4em;
text-align: center;
.fa-stack {
@include font-h1;
.fas {
line-height: inherit;
width: 100%;
}
}
.title {
margin: .5em 0 0;
}
}
.archived {
@include font-small;
background: $brand-warning;
border-radius: $border-radius-tag;
color: $color-white;
line-height: 14px;
margin-right: 3px;
padding: 2px 3px;
}
}

View file

@ -45,7 +45,7 @@ $color-alabaster: $color-concrete;
$color-gainsboro: $color-concrete;
$color-silver: $color-alto;
$color-dove-gray: $color-volcano;
$color-emperor: $color-volcano;
$color-emperor: $color-black;
$brand-default: $color-white;
$brand-info: $brand-focus;
$brand-other: $brand-success;

View file

@ -6,6 +6,7 @@
border-radius: $border-radius-default;
cursor: pointer;
display: inline-block;
height: 36px;
line-height: 20px;
outline: 0;
padding: 7px 16px;
@ -24,7 +25,9 @@
text-decoration: none;
}
&:active {
&:active,
&.active {
box-shadow: none;
text-decoration: none;
}
@ -43,7 +46,8 @@
color: $color-white;
}
&:active {
&:active,
&.active {
background: $brand-primary-press;
color: $color-white;
}
@ -65,7 +69,8 @@
color: $color-volcano;
}
&:active {
&:active,
&.active {
background: $color-alto;
border: $border-secondary;
color: $color-volcano;
@ -88,7 +93,8 @@
color: $color-volcano;
}
&:active {
&:active,
&.active {
background: $color-alto;
border: $border-transparent;
color: $color-volcano;
@ -110,7 +116,8 @@
color: $color-white;
}
&:active {
&:active,
&.active {
background: $brand-danger-press;
color: $color-white;
}

View file

@ -0,0 +1,32 @@
.sci-dropdown {
[data-toggle="dropdown"] {
&:focus {
box-shadow: none;
}
}
&.open {
[data-toggle="dropdown"] {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-color: $brand-focus;
.caret {
transform: rotateX(180deg)
}
}
.dropdown-menu {
border-top-left-radius: 0;
border-top-right-radius: 0;
box-shadow: $flyout-shadow;
margin-top: -1px;
width: 100%;
li:hover {
background: $color-concrete;
}
}
}
}

View file

@ -44,15 +44,20 @@
display: inline-block;
}
.panel-heading {
.step-heading {
align-items: center;
border: 0;
height: 46px;
display: flex;
min-height: 46px;
padding-bottom: 0;
padding-top: 0;
.panel-options {
bottom: 0;
flex-grow: 1;
flex-shrink: 0;
line-height: 46px;
text-align: right;
}
span.step-number {
@ -65,7 +70,9 @@
.left-floats {
align-items: center;
display: flex;
height: 100%;
max-width: 100%;
min-height: inherit;
overflow: hidden;
padding-right: 15px;
.step-name-link {
@ -299,9 +306,11 @@
.attachments-actions {
align-items: center;
display: flex;
flex-wrap: wrap;
.title {
flex-grow: 1;
flex-shrink: 0;
}
.attachments-order {
@ -327,3 +336,7 @@
.comments-title {
color: $color-emperor;
}
.expand-all-steps {
margin: 0 0 15px 15px;
}

View file

@ -750,7 +750,7 @@ ul.double-line > li {
.panel-options {
position: relative;
bottom: 6px;
bottom: 8px;
}
.panel-footer {
@ -921,13 +921,6 @@ ul.content-activities {
flex-wrap: wrap;
margin-bottom: 5px;
.protocol-button {
.sci-btn-group {
float: left;
}
}
.protocol-status-bar {
display: flex;
height: 40px;
@ -1899,10 +1892,6 @@ th.custom-field .modal-tooltiptext {
background-color: $color-alto;
}
.my_module-state-buttons {
padding-top: 6px;
}
.parse-records-table {
max-height: 200px;
}

View file

@ -67,8 +67,7 @@ module Api
params.require(:data).require(:attributes)
params.permit(data: { attributes: %i(data) })[:data].merge(
created_by: @current_user,
last_modified_by: @current_user,
repository: @inventory
last_modified_by: @current_user
)
end

View file

@ -71,8 +71,7 @@ module Api
params.require(:data).require(:attributes)
params.permit(data: { attributes: %i(data) })[:data].merge(
created_by: @current_user,
last_modified_by: @current_user,
repository: @inventory
last_modified_by: @current_user
)
end

View file

@ -61,8 +61,7 @@ module Api
params.require(:data).require(:attributes)
params.permit(data: { attributes: %i(status icon) })[:data].merge(
created_by: @current_user,
last_modified_by: @current_user,
repository: @inventory
last_modified_by: @current_user
)
end

View file

@ -32,7 +32,7 @@ class AssetsController < ApplicationController
can_manage_protocol_in_module?(@protocol) || can_manage_protocol_in_repository?(@protocol)
elsif @assoc.class == Result
can_manage_module?(@my_module)
elsif @assoc.class == RepositoryCell
elsif @assoc.class == RepositoryCell && !@repository.is_a?(RepositorySnapshot)
can_manage_repository_rows?(@repository)
end
if response_json['type'] == 'previewable'

View file

@ -3,6 +3,7 @@
module Dashboard
class CalendarsController < ApplicationController
include IconsHelper
include MyModulesHelper
def show
date = DateTime.parse(params[:date])
@ -26,9 +27,10 @@ module Dashboard
.where(projects: { archived: false })
.where('DATE(my_modules.due_date) = DATE(?)', date)
.where(projects: { team_id: current_team.id })
.my_modules_list_partial
render json: {
html: render_to_string(partial: 'shared/my_modules_list_partial.html.erb', locals: { task_groups: my_modules })
html: render_to_string(partial: 'shared/my_modules_list_partial.html.erb', locals: {
my_modules: my_modules
})
}
end
end

View file

@ -0,0 +1,162 @@
# frozen_string_literal: true
class MyModuleRepositoriesController < ApplicationController
include ApplicationHelper
before_action :load_my_module
before_action :load_repository, except: %i(repositories_dropdown_list repositories_list_html)
before_action :check_my_module_view_permissions
before_action :check_repository_view_permissions, except: %i(repositories_dropdown_list repositories_list_html)
before_action :check_assign_repository_records_permissions, only: :update
def index_dt
@draw = params[:draw].to_i
per_page = params[:length] == '-1' ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
page = (params[:start].to_i / per_page) + 1
datatable_service = RepositoryDatatableService.new(@repository, params, current_user, @my_module)
@datatable_params = {
view_mode: params[:view_mode],
my_module: @my_module
}
@all_rows_count = datatable_service.all_count
@columns_mappings = datatable_service.mappings
if params[:simple_view]
repository_rows = datatable_service.repository_rows
rows_view = 'repository_rows/simple_view_index.json'
else
repository_rows = datatable_service.repository_rows.preload(:repository_columns,
:created_by,
repository_cells: @repository.cell_preload_includes)
rows_view = 'repository_rows/index.json'
end
@repository_rows = repository_rows.page(page).per(per_page)
render rows_view
end
def update
service = RepositoryRows::MyModuleAssignUnassignService.call(my_module: @my_module,
repository: @repository,
user: current_user,
params: params)
if service.succeed? &&
(service.assigned_rows_count.positive? ||
service.unassigned_rows_count.positive?)
flash = update_flash_message(service)
status = :ok
else
flash = t('my_modules.repository.flash.update_error')
status = :bad_request
end
respond_to do |format|
format.json do
render json: {
flash: flash,
rows_count: @my_module.repository_rows_count(@repository),
repository_id: @repository.repository_snapshots.find_by(selected: true)&.id || @repository.id
}, status: status
end
end
end
def update_repository_records_modal
modal = render_to_string(
partial: 'my_modules/modals/update_repository_records_modal_content.html.erb',
locals: { my_module: @my_module,
repository: @repository,
selected_rows: params[:selected_rows] }
)
render json: {
html: modal,
update_url: my_module_repository_path(@my_module, @repository)
}, status: :ok
end
def assign_repository_records_modal
modal = render_to_string(
partial: 'my_modules/modals/assign_repository_records_modal_content.html.erb',
locals: { my_module: @my_module,
repository: @repository,
selected_rows: params[:selected_rows] }
)
render json: {
html: modal,
update_url: my_module_repository_path(@my_module, @repository)
}, status: :ok
end
def repositories_list_html
@assigned_repositories = @my_module.live_and_snapshot_repositories_list
render json: {
html: render_to_string(partial: 'my_modules/repositories/repositories_list'),
assigned_rows_count: @assigned_repositories.map{|i| i.assigned_rows_count}.sum
}
end
def full_view_table
render json: {
html: render_to_string(partial: 'my_modules/repositories/full_view_table')
}
end
def repositories_dropdown_list
@repositories = Repository.accessible_by_teams(current_team).order(:name)
render json: { html: render_to_string(partial: 'my_modules/repositories/repositories_dropdown_list') }
end
private
def load_my_module
@my_module = MyModule.find_by(id: params[:my_module_id])
render_404 unless @my_module
end
def load_repository
@repository = Repository.find_by(id: params[:id])
render_404 unless @repository
end
def check_my_module_view_permissions
render_403 unless can_read_experiment?(@my_module.experiment)
end
def check_repository_view_permissions
render_403 unless can_read_repository?(@repository)
end
def check_assign_repository_records_permissions
render_403 unless can_assign_repository_rows_to_module?(@my_module)
end
def update_flash_message(service)
assigned_count = service.assigned_rows_count
unassigned_count = service.unassigned_rows_count
if params[:downstream] == 'true'
if assigned_count && unassigned_count
t('my_modules.repository.flash.assign_and_unassign_from_task_and_downstream_html',
assigned_items: assigned_count,
unassigned_items: unassigned_count)
elsif assigned_count
t('my_modules.repository.flash.assign_to_task_and_downstream_html',
assigned_items: assigned_count)
elsif unassigned_count
t('my_modules.repository.flash.unassign_from_task_and_downstream_html',
unassigned_items: unassigned_count)
end
elsif assigned_count && unassigned_count
t('my_modules.repository.flash.assign_and_unassign_from_task_html',
assigned_items: assigned_count,
unassigned_items: unassigned_count)
elsif assigned_count
t('my_modules.repository.flash.assign_to_task_html',
assigned_items: assigned_count)
elsif unassigned_count
t('my_modules.repository.flash.unassign_from_task_html',
unassigned_items: unassigned_count)
end
end
end

View file

@ -0,0 +1,129 @@
# frozen_string_literal: true
class MyModuleRepositorySnapshotsController < ApplicationController
before_action :load_my_module
before_action :load_repository, only: :create
before_action :load_repository_snapshot, except: %i(create full_view_sidebar select)
before_action :check_view_permissions, except: %i(create destroy select)
before_action :check_manage_permissions, only: %i(create destroy select)
def index_dt
@draw = params[:draw].to_i
per_page = params[:length] == '-1' ? Constants::REPOSITORY_DEFAULT_PAGE_SIZE : params[:length].to_i
page = (params[:start].to_i / per_page) + 1
datatable_service = RepositorySnapshotDatatableService.new(@repository_snapshot, params, current_user, @my_module)
@all_rows_count = datatable_service.all_count
@columns_mappings = datatable_service.mappings
if params[:simple_view]
repository_rows = datatable_service.repository_rows
rows_view = 'repository_rows/simple_view_index.json'
else
repository_rows = datatable_service.repository_rows
.preload(:repository_columns,
:created_by,
repository_cells: @repository_snapshot.cell_preload_includes)
rows_view = 'repository_rows/snapshot_index.json'
end
@repository_rows = repository_rows.page(page).per(per_page)
render rows_view
end
def create
repository_snapshot = @repository.provision_snapshot(@my_module, current_user)
render json: {
html: render_to_string(partial: 'my_modules/repositories/full_view_version',
locals: { repository_snapshot: repository_snapshot,
can_delete_snapshot: can_manage_my_module_repository_snapshots?(@my_module) })
}
end
def status
render json: {
status: @repository_snapshot.status
}
end
def show
render json: {
html: render_to_string(partial: 'my_modules/repositories/full_view_version',
locals: { repository_snapshot: @repository_snapshot,
can_delete_snapshot: can_manage_my_module_repository_snapshots?(@my_module) })
}
end
def destroy
@repository_snapshot.destroy!
render json: {}
end
def full_view_table
render json: {
html: render_to_string(partial: 'my_modules/repositories/full_view_snapshot_table')
}
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_snapshots = @my_module.repository_snapshots
.where(parent_id: params[:repository_id])
.order(created_at: :desc)
render json: {
html: render_to_string(
partial: 'my_modules/repositories/full_view_sidebar',
locals: {
live_items_present: @repository ? @my_module.repository_rows_count(@repository).positive? : false
}
)
}
end
def select
if params[:repository_id]
@my_module.repository_snapshots.where(parent_id: params[:repository_id]).update(selected: nil)
else
repository_snapshot = @my_module.repository_snapshots.find_by(id: params[:repository_snapshot_id])
return render_404 unless repository_snapshot
@my_module.repository_snapshots
.where(original_repository: repository_snapshot.original_repository)
.update(selected: nil)
repository_snapshot.update!(selected: true)
end
render json: {}
end
private
def load_my_module
@my_module = MyModule.find_by(id: params[:my_module_id])
render_404 unless @my_module
end
def load_repository
@repository = Repository.find_by(id: params[:repository_id])
render_404 unless @repository
render_403 unless can_read_repository?(@repository)
end
def load_repository_snapshot
@repository_snapshot = @my_module.repository_snapshots.find_by(id: params[:id])
render_404 unless @repository_snapshot
end
def check_view_permissions
render_403 unless can_read_experiment?(@my_module.experiment)
end
def check_manage_permissions
render_403 unless can_manage_my_module_repository_snapshots?(@my_module)
end
end

View file

@ -143,21 +143,15 @@ class MyModulesController < ApplicationController
end
def update
update_params = my_module_params
if update_params[:due_date].present?
update_params[:due_date] =
Time.zone.strptime(update_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
@my_module.assign_attributes(update_params)
@my_module.assign_attributes(my_module_params)
@my_module.last_modified_by = current_user
description_changed = @my_module.description_changed?
start_date_changes = @my_module.changes[:started_on]
due_date_changes = @my_module.changes[:due_date]
if @my_module.archived_changed?(from: false, to: true)
saved = @my_module.archive(current_user)
elsif @my_module.archived_changed?(from: true, to: false)
saved = @my_module.restore(current_user)
if saved
restored = true
@ -165,28 +159,14 @@ class MyModulesController < ApplicationController
end
else
saved = @my_module.save
if saved
if description_changed
log_activity(:change_module_description)
TinyMceAsset.update_images(@my_module, params[:tiny_mce_images], current_user)
end
if due_date_changes
# rubocop:disable Metrics/BlockNesting # temporary solution
type_of = if due_date_changes[0].nil? # set due_date
message_items = { my_module_duedate: @my_module.due_date }
:set_task_due_date
elsif due_date_changes[1].nil? # remove due_date
message_items = { my_module_duedate: due_date_changes[0] }
:remove_task_due_date
else # change due_date
message_items = { my_module_duedate: @my_module.due_date }
:change_task_due_date
end
# rubocop:enable Metrics/BlockNesting
log_activity(type_of, @my_module, message_items)
end
log_start_date_change_activity(start_date_changes) if start_date_changes.present?
log_due_date_change_activity(due_date_changes) if due_date_changes.present?
end
end
respond_to do |format|
@ -199,7 +179,7 @@ class MyModulesController < ApplicationController
redirect_to module_archive_experiment_path(@my_module.experiment)
end
elsif saved
format.json {
format.json do
alerts = []
alerts << 'alert-green' if @my_module.completed?
unless @my_module.completed?
@ -208,6 +188,10 @@ class MyModulesController < ApplicationController
end
render json: {
status: :ok,
start_date_label: render_to_string(
partial: 'my_modules/start_date_label.html.erb',
locals: { my_module: @my_module }
),
due_date_label: render_to_string(
partial: 'my_modules/due_date_label.html.erb',
locals: { my_module: @my_module }
@ -226,12 +210,12 @@ class MyModulesController < ApplicationController
),
alerts: alerts
}
}
end
else
format.json {
format.json do
render json: @my_module.errors,
status: :unprocessable_entity
}
end
end
end
end
@ -282,11 +266,7 @@ class MyModulesController < ApplicationController
def protocols
@protocol = @my_module.protocol
@recent_protcols_positive = Protocol.recent_protocols(
current_user,
current_team,
Constants::RECENT_PROTOCOL_LIMIT
).any?
@assigned_repositories = @my_module.live_and_snapshot_repositories_list
current_team_switch(@protocol.team)
end
@ -416,123 +396,45 @@ class MyModulesController < ApplicationController
# Submit actions
def assign_repository_records
if params[:selected_rows].present? && params[:repository_id].present?
records_names = []
downstream = ActiveModel::Type::Boolean.new.cast(params[:downstream])
downstream_my_modules = []
dowmstream_records = {}
RepositoryRow
.where(id: params[:selected_rows],
repository_id: params[:repository_id])
.find_each do |record|
unless @my_module.repository_rows.include?(record)
record.last_modified_by = current_user
record.save
service = RepositoryRows::MyModuleAssigningService.call(my_module: @my_module,
repository: @repository,
user: current_user,
params: params)
MyModuleRepositoryRow.create!(
my_module: @my_module,
repository_row: record,
assigned_by: current_user
)
records_names << record.name
end
if service.succeed? && service.assigned_rows_names.any?
names = service.assigned_rows_names.map { |name| escape_input(name) }
message = params[:downstream].blank? ? 'assigned_records_flash' : 'assigned_records_downstream_flash'
flash = I18n.t("repositories.#{message}", records: names.join(', '))
status = :ok
else
flash = t('repositories.no_records_assigned_flash')
status = :bad_request
end
next unless downstream
@my_module.downstream_modules.each do |my_module|
next if my_module.repository_rows.include?(record)
dowmstream_records[my_module.id] = [] unless dowmstream_records[my_module.id]
MyModuleRepositoryRow.create!(
my_module: my_module,
repository_row: record,
assigned_by: current_user
)
dowmstream_records[my_module.id] << record.name
downstream_my_modules.push(my_module)
end
end
if records_names.any?
records_names.uniq!
log_activity(:assign_repository_record,
@my_module,
repository: @repository.id,
record_names: records_names.join(', '))
downstream_my_modules.uniq.each do |my_module|
log_activity(:assign_repository_record,
my_module,
repository: @repository.id,
record_names: dowmstream_records[my_module.id].join(', '))
end
records_names.map! { |n| escape_input(n) }
flash = I18n.t('repositories.assigned_records_flash',
records: records_names.join(', '))
flash = I18n.t('repositories.assigned_records_downstream_flash',
records: records_names.join(', ')) if downstream
respond_to do |format|
format.json { render json: { flash: flash }, status: :ok }
end
else
respond_to do |format|
format.json do
render json: {
flash: t('repositories.no_records_assigned_flash')
}, status: :bad_request
end
end
respond_to do |format|
format.json do
render json: { flash: flash }, status: status
end
end
end
def unassign_repository_records
if params[:selected_rows].present? && params[:repository_id].present?
downstream = ActiveModel::Type::Boolean.new.cast(params[:downstream])
service = RepositoryRows::MyModuleUnassigningService.call(my_module: @my_module,
repository: @repository,
user: current_user,
params: params)
if service.succeed? && service.unassigned_rows_names.any?
flash = I18n.t('repositories.unassigned_records_flash',
records: service.unassigned_rows_names.map { |name| escape_input(name) }.join(', '))
status = :ok
else
flash = t('repositories.no_records_unassigned_flash')
status = :bad_request
end
records = RepositoryRow.assigned_on_my_module(params[:selected_rows],
@my_module)
@my_module.repository_rows.destroy(records & @my_module.repository_rows)
if downstream
@my_module.downstream_modules.each do |my_module|
assigned_records = RepositoryRow.assigned_on_my_module(
params[:selected_rows],
my_module
)
my_module.repository_rows.destroy(
assigned_records & my_module.repository_rows
)
assigned_records.update_all(last_modified_by_id: current_user.id)
next unless assigned_records.any?
log_activity(:unassign_repository_record,
my_module,
repository: @repository.id,
record_names: assigned_records.map(&:name).join(', '))
end
end
# update last last_modified_by
records.update_all(last_modified_by_id: current_user.id)
if records.any?
log_activity(:unassign_repository_record,
@my_module,
repository: @repository.id,
record_names: records.map(&:name).join(', '))
flash = I18n.t('repositories.unassigned_records_flash',
records: records.map { |r| escape_input(r.name) }.join(', '))
respond_to do |format|
format.json { render json: { flash: flash }, status: :ok }
end
else
respond_to do |format|
format.json do
render json: {
flash: t('repositories.no_records_unassigned_flash')
}, status: :bad_request
end
end
respond_to do |format|
format.json do
render json: { flash: flash }, status: status
end
end
end
@ -672,7 +574,7 @@ class MyModulesController < ApplicationController
end
def load_repository
@repository = Repository.find_by_id(params[:repository_id])
@repository = Repository.find_by(id: params[:repository_id])
render_404 unless @repository
render_403 unless can_read_repository?(@repository)
end
@ -725,8 +627,46 @@ class MyModulesController < ApplicationController
end
def my_module_params
params.require(:my_module).permit(:name, :description, :due_date,
:archived)
update_params = params.require(:my_module).permit(:name, :description, :started_on, :due_date, :archived)
if update_params[:started_on].present?
update_params[:started_on] =
Time.zone.strptime(update_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
if update_params[:due_date].present?
update_params[:due_date] =
Time.zone.strptime(update_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
update_params
end
def log_start_date_change_activity(start_date_changes)
type_of = if start_date_changes[0].nil? # set started_on
message_items = { my_module_started_on: @my_module.started_on }
:set_task_start_date
elsif start_date_changes[1].nil? # remove started_on
message_items = { my_module_started_on: start_date_changes[0] }
:remove_task_start_date
else # change started_on
message_items = { my_module_started_on: @my_module.started_on }
:change_task_start_date
end
log_activity(type_of, @my_module, message_items)
end
def log_due_date_change_activity(due_date_changes)
type_of = if due_date_changes[0].nil? # set due_date
message_items = { my_module_duedate: @my_module.due_date }
:set_task_due_date
elsif due_date_changes[1].nil? # remove due_date
message_items = { my_module_duedate: due_date_changes[0] }
:remove_task_due_date
else # change due_date
message_items = { my_module_duedate: @my_module.due_date }
:change_task_due_date
end
log_activity(type_of, @my_module, message_items)
end
def log_activity(type_of, my_module = nil, message_items = {})
@ -747,5 +687,4 @@ class MyModulesController < ApplicationController
:page, :starting_timestamp, :from_date, :to_date, types: [], users: [], subjects: {}
)
end
end

View file

@ -23,6 +23,7 @@ class ProtocolsController < ApplicationController
linked_children
linked_children_datatable
)
before_action :switch_team_with_param, only: :index
before_action :check_view_all_permissions, only: %i(
index
datatable
@ -107,14 +108,6 @@ class ProtocolsController < ApplicationController
end
end
def recent_protocols
render json: Protocol.recent_protocols(
current_user,
current_team,
Constants::RECENT_PROTOCOL_LIMIT
).select(:id, :name)
end
def linked_children
respond_to do |format|
format.json do

View file

@ -31,6 +31,7 @@ class ReportsController < ApplicationController
only: %i(new edit available_repositories)
before_action :check_manage_permissions, only: BEFORE_ACTION_METHODS
before_action :switch_team_with_param, only: :index
# Index showing all reports of a single project
def index; end

View file

@ -5,20 +5,17 @@ class RepositoriesController < ApplicationController
include ActionView::Helpers::TagHelper
include ActionView::Context
include IconsHelper
include TeamsHelper
before_action :load_vars,
except: %i(index create create_modal parse_sheet)
before_action :load_parent_vars, except:
%i(repository_table_index parse_sheet)
before_action :switch_team_with_param, only: :show
before_action :load_repository, except: %i(index create create_modal)
before_action :load_repositories, only: %i(index show)
before_action :check_view_all_permissions, only: :index
before_action :check_view_permissions, only: %i(load_table export_repository show)
before_action :check_manage_permissions, only:
%i(destroy destroy_modal rename_modal update)
before_action :check_view_permissions, except: %i(index create_modal create update destroy parse_sheet import_records)
before_action :check_manage_permissions, only: %i(destroy destroy_modal rename_modal update)
before_action :check_share_permissions, only: :share_modal
before_action :check_create_permissions, only:
%i(create_modal create)
before_action :check_copy_permissions, only:
%i(copy_modal copy)
before_action :check_create_permissions, only: %i(create_modal create)
before_action :check_copy_permissions, only: %i(copy_modal copy)
before_action :set_inline_name_editing, only: %i(show)
layout 'fluid'
@ -32,6 +29,20 @@ class RepositoriesController < ApplicationController
@display_edit_button = can_create_repository_rows?(@repository)
@display_delete_button = can_delete_repository_rows?(@repository)
@display_duplicate_button = can_create_repository_rows?(@repository)
@snapshot_provisioning = @repository.repository_snapshots.provisioning.any?
end
def table_toolbar
render json: {
html: render_to_string(partial: 'repositories/toolbar_buttons.html.erb')
}
end
def status
render json: {
editable: can_manage_repository_rows?(@repository),
snapshot_provisioning: @repository.repository_snapshots.provisioning.any?
}
end
def load_table
@ -67,7 +78,7 @@ class RepositoriesController < ApplicationController
def create
@repository = Repository.new(
team: @team,
team: current_team,
created_by: current_user
)
@repository.assign_attributes(repository_params)
@ -144,7 +155,7 @@ class RepositoriesController < ApplicationController
def copy_modal
@tmp_repository = Repository.new(
team: @team,
team: current_team,
created_by: current_user,
name: @repository.name
)
@ -161,7 +172,7 @@ class RepositoriesController < ApplicationController
def copy
@tmp_repository = Repository.new(
team: @team,
team: current_team,
created_by: current_user
)
@tmp_repository.assign_attributes(repository_params)
@ -194,25 +205,15 @@ class RepositoriesController < ApplicationController
# AJAX actions
def repository_table_index
if @repository.nil? || !can_read_repository?(@repository)
render_403
else
respond_to do |format|
format.html
format.json do
render json: ::RepositoryDatatable.new(view_context,
@repository,
nil,
current_user)
end
respond_to do |format|
format.json do
render json: ::RepositoryDatatable.new(view_context, @repository, nil, current_user)
end
end
end
def parse_sheet
repository = Repository.accessible_by_teams(current_team).find_by_id(import_params[:id])
render_403 unless can_create_repository_rows?(repository)
render_403 unless can_create_repository_rows?(@repository)
unless import_params[:file]
repository_response(t('teams.parse_sheet.errors.no_file_selected'))
@ -221,7 +222,7 @@ class RepositoriesController < ApplicationController
begin
parsed_file = ImportRepository::ParseRepository.new(
file: import_params[:file],
repository: repository,
repository: @repository,
session: session
)
if parsed_file.too_large?
@ -328,16 +329,14 @@ class RepositoriesController < ApplicationController
)
end
def load_vars
def load_repository
repository_id = params[:id] || params[:repository_id]
@repository = Repository.accessible_by_teams(current_team).find_by_id(repository_id)
@repository = Repository.accessible_by_teams(current_team).find_by(id: repository_id)
render_404 unless @repository
end
def load_parent_vars
@team = current_team
render_404 unless @team
@repositories = Repository.accessible_by_teams(@team).order('repositories.created_at ASC')
def load_repositories
@repositories = Repository.accessible_by_teams(current_team).order('repositories.created_at ASC')
end
def set_inline_name_editing
@ -354,7 +353,7 @@ class RepositoriesController < ApplicationController
end
def check_view_all_permissions
render_403 unless can_read_team?(@team)
render_403 unless can_read_team?(current_team)
end
def check_view_permissions
@ -362,11 +361,11 @@ class RepositoriesController < ApplicationController
end
def check_create_permissions
render_403 unless can_create_repositories?(@team)
render_403 unless can_create_repositories?(current_team)
end
def check_copy_permissions
render_403 if !can_create_repositories?(@team) || @repository.shared_with?(current_team)
render_403 if !can_create_repositories?(current_team) || @repository.shared_with?(current_team)
end
def check_manage_permissions
@ -405,7 +404,7 @@ class RepositoriesController < ApplicationController
.call(activity_type: type_of,
owner: current_user,
subject: @repository,
team: @team,
team: current_team,
message_items: message_items)
end
end

View file

@ -164,7 +164,7 @@ class RepositoryColumnsController < ApplicationController
end
def available_columns
render json: { columns: @repository.available_columns_ids }, status: :ok
render json: { columns: @repository.repository_columns.pluck(:id) }, status: :ok
end
private

View file

@ -2,19 +2,15 @@ class RepositoryRowsController < ApplicationController
include InputSanitizeHelper
include ActionView::Helpers::TextHelper
include ApplicationHelper
include MyModulesHelper
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
copy_records
available_rows)
before_action :load_repository
before_action :load_repository_row, only: %i(update show assigned_task_list)
before_action :check_read_permissions, except: %i(create update delete_records copy_records)
before_action :check_snapshotting_status, only: %i(create update delete_records copy_records)
before_action :check_create_permissions, only: :create
before_action :check_delete_permissions, only: :delete_records
before_action :check_manage_permissions,
only: %i(edit update copy_records)
before_action :check_manage_permissions, only: %i(update copy_records)
def index
@draw = params[:draw].to_i
@ -53,6 +49,10 @@ class RepositoryRowsController < ApplicationController
end
def show
@assigned_modules = @repository_row.my_modules.joins(experiment: :project)
@viewable_modules = @assigned_modules.viewable_by_user(current_user, current_user.teams)
@private_modules = @assigned_modules - @viewable_modules
respond_to do |format|
format.json do
render json: {
@ -64,54 +64,26 @@ class RepositoryRowsController < ApplicationController
end
end
def edit
json = {
repository_row: {
name: escape_input(@record.name),
repository_cells: {},
repository_column_items: fetch_columns_list_items
}
}
# 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
else
cell_value = escape_input(cell.value.data)
end
json[:repository_row][:repository_cells][cell.repository_column_id] = {
repository_cell_id: cell.id,
cell_column_id: cell.repository_column.id, # needed for mappings
value: cell_value,
type: cell.value_type,
list_items: fetch_list_items(cell)
}
end
respond_to do |format|
format.html
format.json { render json: json }
end
end
def update
row_update = RepositoryRows::UpdateRepositoryRowService
.call(repository_row: @record, user: current_user, params: update_params)
.call(repository_row: @repository_row, user: current_user, params: update_params)
if row_update.succeed?
if row_update.record_updated
log_activity(:edit_item_inventory, @record)
@record.repository_cells.where(value_type: 'RepositoryTextValue').each do |repository_cell|
record_annotation_notification(@record, repository_cell)
log_activity(:edit_item_inventory, @repository_row)
@repository_row.repository_cells.where(value_type: 'RepositoryTextValue').each do |repository_cell|
record_annotation_notification(@repository_row, repository_cell)
end
end
render json: { id: @record.id, flash: t('repositories.update.success_flash',
record: escape_input(@record.name),
repository: escape_input(@repository.name)) },
status: :ok
render json: {
id: @repository_row.id,
flash: t(
'repositories.update.success_flash',
record: escape_input(@repository_row.name),
repository: escape_input(@repository.name)
)
}, status: :ok
else
render json: row_update.errors, status: :bad_request
end
@ -121,7 +93,7 @@ class RepositoryRowsController < ApplicationController
deleted_count = 0
if selected_params
selected_params.each do |row_id|
row = @repository.repository_rows.find_by_id(row_id)
row = @repository.repository_rows.find_by(id: row_id)
next unless row && can_manage_repository_rows?(@repository)
log_activity(:delete_item_inventory, row)
@ -180,38 +152,57 @@ class RepositoryRowsController < ApplicationController
end
end
def assigned_task_list
assigned_modules = @repository_row.my_modules.joins(experiment: :project)
viewable_modules = assigned_modules.viewable_by_user(current_user, current_user.teams)
private_modules = assigned_modules - viewable_modules
viewable_modules = viewable_modules.where_attributes_like(
['my_modules.name', 'experiments.name', 'projects.name'],
params[:query],
whole_phrase: true
)
render json: {
html: render_to_string(partial: 'shared/my_modules_list_partial.html.erb', locals: {
my_modules: viewable_modules,
private_modules: private_modules
})
}
end
private
include StringUtility
AvailableRepositoryRow = Struct.new(:id, :name, :has_file_attached)
def load_info_modal_vars
@repository_row = RepositoryRow.eager_load(:created_by, repository: [:team])
.find_by_id(params[:id])
@assigned_modules = MyModuleRepositoryRow.eager_load(
my_module: [{ experiment: :project }]
).where(repository_row: @repository_row)
render_404 and return unless @repository_row
render_403 unless can_read_repository?(@repository_row.repository)
end
def load_vars
def load_repository
@repository = Repository.accessible_by_teams(current_team)
.eager_load(:repository_columns)
.find_by_id(params[:repository_id])
@record = @repository.repository_rows
.eager_load(:repository_columns)
.find_by_id(params[:id])
render_404 unless @repository && @record
.find_by(id: params[:repository_id])
render_404 unless @repository
end
def load_repository
@repository = Repository.accessible_by_teams(current_team).find_by_id(params[:repository_id])
render_404 unless @repository
def load_repository_row
@repository_row = @repository.repository_rows.eager_load(:repository_columns).find_by(id: params[:id])
render_404 unless @repository_row
end
def check_read_permissions
render_403 unless can_read_repository?(@repository)
end
def check_snapshotting_status
return if @repository.repository_snapshots.provisioning.none?
respond_to do |format|
format.json do
render json: {
flash: t('repositories.index.snapshot_provisioning_in_progress')
}, status: :unprocessable_entity
end
end
end
def check_create_permissions
render_403 unless can_create_repository_rows?(@repository)
end

View file

@ -1,16 +1,16 @@
class UserMyModulesController < ApplicationController
before_action :load_vars
before_action :check_view_permissions, only: :index
before_action :check_view_permissions, only: %i(index index_old)
before_action :check_manage_permissions, only: %i(create index_edit destroy)
def index
def index_old
@user_my_modules = @my_module.user_my_modules
respond_to do |format|
format.json do
render json: {
html: render_to_string(
partial: 'index.html.erb'
partial: 'index_old.html.erb'
),
my_module_id: @my_module.id,
counter: @my_module.users.count # Used for counter badge
@ -19,6 +19,18 @@ class UserMyModulesController < ApplicationController
end
end
def index
respond_to do |format|
format.json do
render json: {
html: render_to_string(
partial: 'index.html.erb'
)
}
end
end
end
def index_edit
@user_my_modules = @my_module.user_my_modules
@unassigned_users = @my_module.unassigned_users

View file

@ -30,7 +30,7 @@ class UserRepositoriesController < ApplicationController
private
def load_vars
@repository = Repository.find_by_id(params[:repository_id])
@repository = RepositoryBase.find_by(id: params[:repository_id])
render_403 if @repository.nil? || !can_read_repository?(@repository)
end
end

View file

@ -122,10 +122,10 @@ module Users
Activities::CreateActivityService
.call(activity_type: :invite_user_to_team,
owner: current_user,
subject: current_team,
team: current_team,
subject: @team,
team: @team,
message_items: {
team: current_team.id,
team: @team.id,
user_invited: user.id,
role: user_team.role_str
})

View file

@ -43,9 +43,6 @@ module Users
read_from_params(:system_message_notification_email) do |val|
@user.system_message_email_notification = val
end
read_from_params(:tooltips_enabled) do |val|
@user.settings[:tooltips_enabled] = val
end
if @user.save
respond_to do |format|
format.json do

View file

@ -36,9 +36,9 @@ class WopiController < ActionController::Base
when 'REFRESH_LOCK'
refresh_lock
when 'GET_SHARE_URL'
render body: nil, status: 501 and return
render body: nil, status: :not_implemented
else
render body: nil, status: 404 and return
render body: nil, status: :not_found
end
end
@ -53,61 +53,60 @@ class WopiController < ActionController::Base
asset_owner_id = @asset.created_by_id.to_s if @asset.created_by_id
msg = {
BaseFileName: @asset.file_name,
OwnerId: asset_owner_id,
Size: @asset.file_size,
UserId: @user.id.to_s,
Version: @asset.version.to_s,
SupportsExtendedLockLength: true,
SupportsGetLock: true,
SupportsLocks: true,
SupportsUpdate: true,
BaseFileName: @asset.file_name,
OwnerId: asset_owner_id,
Size: @asset.file_size,
UserId: @user.id.to_s,
Version: @asset.version.to_s,
SupportsExtendedLockLength: true,
SupportsGetLock: true,
SupportsLocks: true,
SupportsUpdate: true,
# Setting all users to business until we figure out
# which should NOT be business
LicenseCheckForEditIsEnabled: true,
UserFriendlyName: @user.name,
UserCanWrite: @can_write,
UserCanNotWriteRelative: true,
CloseUrl: @close_url,
DownloadUrl: url_for(controller: 'assets', action: 'file_url',
id: @asset.id, host: ENV['WOPI_USER_HOST']),
HostEditUrl: url_for(controller: 'assets', action: 'edit',
id: @asset.id, host: ENV['WOPI_USER_HOST']),
HostViewUrl: url_for(controller: 'assets', action: 'view',
id: @asset.id, host: ENV['WOPI_USER_HOST']),
BreadcrumbBrandName: @breadcrumb_brand_name,
BreadcrumbBrandUrl: @breadcrumb_brand_url,
LicenseCheckForEditIsEnabled: true,
UserFriendlyName: @user.name,
UserCanWrite: @can_write,
UserCanNotWriteRelative: true,
CloseUrl: @close_url,
DownloadUrl: url_for(controller: 'assets', action: 'file_url', id: @asset.id, host: ENV['WOPI_USER_HOST']),
HostEditUrl: url_for(controller: 'assets', action: 'edit', id: @asset.id, host: ENV['WOPI_USER_HOST']),
HostViewUrl: url_for(controller: 'assets', action: 'view', id: @asset.id, host: ENV['WOPI_USER_HOST']),
BreadcrumbBrandName: @breadcrumb_brand_name,
BreadcrumbBrandUrl: @breadcrumb_brand_url,
BreadcrumbFolderName: @breadcrumb_folder_name,
BreadcrumbFolderUrl: @breadcrumb_folder_url
BreadcrumbFolderUrl: @breadcrumb_folder_url
}
response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL']
response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL']
response.headers['X-WOPI-ServerVersion'] = Scinote::Application::VERSION
render json: msg and return
render json: msg
end
def put_relative
render body: nil, status: 501 and return
render body: nil, status: :not_implemented
end
def lock
lock = request.headers['X-WOPI-Lock']
logger.warn 'WOPI: lock; ' + lock.to_s
render body: nil, status: 404 and return if lock.nil? || lock.blank?
return render body: nil, status: :not_found if lock.blank?
@asset.with_lock do
if @asset.locked?
if @asset.lock == lock
@asset.refresh_lock
response.headers['X-WOPI-ItemVersion'] = @asset.version
render body: nil, status: 200 and return
return render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
else
@asset.lock_asset(lock)
response.headers['X-WOPI-ItemVersion'] = @asset.version
render body: nil, status: 200 and return
return render body: nil, status: :ok
end
end
end
@ -116,82 +115,79 @@ class WopiController < ActionController::Base
logger.warn 'lock and relock'
lock = request.headers['X-WOPI-Lock']
old_lock = request.headers['X-WOPI-OldLock']
if lock.nil? || lock.blank? || old_lock.blank?
render body: nil, status: 400 and return
end
return render body: nil, status: :bad_request if lock.blank? || old_lock.blank?
@asset.with_lock do
if @asset.locked?
if @asset.lock == old_lock
@asset.unlock
@asset.lock_asset(lock)
response.headers['X-WOPI-ItemVersion'] = @asset.version
render body: nil, status: 200 and return
return render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
else
response.headers['X-WOPI-Lock'] = ' '
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
end
end
def unlock
lock = request.headers['X-WOPI-Lock']
render body: nil, status: 400 and return if lock.nil? || lock.blank?
return render body: nil, status: :bad_request if lock.blank?
@asset.with_lock do
if @asset.locked?
logger.warn "WOPI: current asset lock: #{@asset.lock},
unlocking lock #{lock}"
logger.warn "WOPI: current asset lock: #{@asset.lock}, unlocking lock #{lock}"
if @asset.lock == lock
@asset.unlock
@asset.post_process_file # Space is already taken in put_file
create_wopi_file_activity(@user, false)
response.headers['X-WOPI-ItemVersion'] = @asset.version
render body: nil, status: 200 and return
return render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
else
logger.warn 'WOPI: tried to unlock non-locked file'
response.headers['X-WOPI-Lock'] = ' '
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
end
end
def refresh_lock
lock = request.headers['X-WOPI-Lock']
render body: nil, status: 400 and return if lock.nil? || lock.blank?
return render body: nil, status: :bad_request if lock.nil? || lock.blank?
@asset.with_lock do
if @asset.locked?
if @asset.lock == lock
@asset.refresh_lock
response.headers['X-WOPI-ItemVersion'] = @asset.version
response.headers['X-WOPI-ItemVersion'] = @asset.version
render body: nil, status: 200 and return
return render body: nil, status: :ok
else
response.headers['X-WOPI-Lock'] = @asset.lock
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
else
response.headers['X-WOPI-Lock'] = ' '
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
end
end
def get_lock
@asset.with_lock do
if @asset.locked?
response.headers['X-WOPI-Lock'] = @asset.lock
else
response.headers['X-WOPI-Lock'] = ' '
end
render body: nil, status: 200 and return
response.headers['X-WOPI-Lock'] = @asset.locked? ? @asset.lock : ' '
return render body: nil, status: :ok
end
end
@ -210,14 +206,14 @@ class WopiController < ActionController::Base
@team.take_space(@asset.estimated_size)
@team.save
@protocol.update(updated_at: Time.now) if @protocol
@protocol&.update(updated_at: Time.now.utc)
response.headers['X-WOPI-ItemVersion'] = @asset.version
render body: nil, status: 200 and return
return render body: nil, status: :ok
else
logger.warn 'WOPI: wrong lock used to try and modify file'
response.headers['X-WOPI-Lock'] = @asset.lock
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
elsif !@asset.file_size.nil? && @asset.file_size.zero?
logger.warn 'WOPI: initializing empty file'
@ -229,21 +225,21 @@ class WopiController < ActionController::Base
@team.save
response.headers['X-WOPI-ItemVersion'] = @asset.version
render body: nil, status: 200 and return
return render body: nil, status: :ok
else
logger.warn 'WOPI: trying to modify unlocked file'
response.headers['X-WOPI-Lock'] = ' '
render body: nil, status: 409 and return
return render body: nil, status: :conflict
end
end
end
def load_vars
@asset = Asset.find_by_id(params[:id])
@asset = Asset.find_by(id: params[:id])
if @asset.nil?
render body: nil, status: 404 and return
render body: nil, status: :not_found
else
logger.warn 'Found asset: ' + @asset.id.to_s
logger.warn "Found asset: #{@asset.id}"
step_assoc = @asset.step
result_assoc = @asset.result
repository_cell_assoc = @asset.repository_cell
@ -270,16 +266,15 @@ class WopiController < ActionController::Base
wopi_token = params[:access_token]
if wopi_token.nil?
logger.warn 'WOPI: nil wopi token'
render body: nil, status: 401 and return
return render body: nil, status: :unauthorized
end
@user = User.find_by_valid_wopi_token(wopi_token)
if @user.nil?
logger.warn 'WOPI: no user with this token found'
render body: nil, status: 401 and return
return render body: nil, status: :unauthorized
end
logger.warn 'WOPI: user found by token ' + wopi_token +
' ID: ' + @user.id.to_s
logger.warn "WOPI: user found by token #{wopi_token} ID: #{@user.id}"
# This is what we get for settings permission methods with
# current_user
@ -288,25 +283,19 @@ class WopiController < ActionController::Base
if @protocol.in_module?
@can_read = can_read_protocol_in_module?(@protocol)
@can_write = can_manage_protocol_in_module?(@protocol)
@close_url = protocols_my_module_url(@protocol.my_module,
only_path: false,
host: ENV['WOPI_USER_HOST'])
@close_url = protocols_my_module_url(@protocol.my_module, only_path: false, host: ENV['WOPI_USER_HOST'])
project = @protocol.my_module.experiment.project
@breadcrumb_brand_name = project.name
@breadcrumb_brand_url = project_url(project,
only_path: false,
host: ENV['WOPI_USER_HOST'])
@breadcrumb_brand_name = project.name
@breadcrumb_brand_url = project_url(project, only_path: false, host: ENV['WOPI_USER_HOST'])
@breadcrumb_folder_name = @protocol.my_module.name
else
@can_read = can_read_protocol_in_repository?(@protocol)
@can_write = can_manage_protocol_in_repository?(@protocol)
@close_url = protocols_url(only_path: false,
host: ENV['WOPI_USER_HOST'])
@close_url = protocols_url(only_path: false, host: ENV['WOPI_USER_HOST'])
@breadcrump_brand_name = 'Projects'
@breadcrumb_brand_url = root_url(only_path: false,
host: ENV['WOPI_USER_HOST'])
@breadcrump_brand_name = 'Projects'
@breadcrumb_brand_url = root_url(only_path: false, host: ENV['WOPI_USER_HOST'])
@breadcrumb_folder_name = 'Protocol managament'
end
@breadcrumb_folder_url = @close_url
@ -314,9 +303,7 @@ class WopiController < ActionController::Base
@can_read = can_read_experiment?(@my_module.experiment)
@can_write = can_manage_module?(@my_module)
@close_url = results_my_module_url(@my_module,
only_path: false,
host: ENV['WOPI_USER_HOST'])
@close_url = results_my_module_url(@my_module, only_path: false, host: ENV['WOPI_USER_HOST'])
@breadcrumb_brand_name = @my_module.experiment.project.name
@breadcrumb_brand_url = project_url(@my_module.experiment.project,
@ -326,11 +313,9 @@ class WopiController < ActionController::Base
@breadcrumb_folder_url = @close_url
elsif @assoc.class == RepositoryCell
@can_read = can_read_repository?(@repository)
@can_write = can_edit_wopi_file_in_repository_rows?
@can_write = !@repository.is_a?(RepositorySnapshot) && can_edit_wopi_file_in_repository_rows?
@close_url = repository_url(@repository,
only_path: false,
host: ENV['WOPI_USER_HOST'])
@close_url = repository_url(@repository, only_path: false, host: ENV['WOPI_USER_HOST'])
@breadcrumb_brand_name = @team.name
@breadcrumb_brand_url = @close_url
@ -338,7 +323,7 @@ class WopiController < ActionController::Base
@breadcrumb_folder_url = @close_url
end
render body: nil, status: 404 and return unless @can_read
return render body: nil, status: :not_found unless @can_read
end
def verify_proof!
@ -349,24 +334,22 @@ class WopiController < ActionController::Base
url = request.original_url.upcase.encode('utf-8')
if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now
if current_wopi_discovery.verify_proof(token, timestamp, signed_proof,
signed_proof_old, url)
if current_wopi_discovery.verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
logger.warn 'WOPI: proof verification: successful'
else
logger.warn 'WOPI: proof verification: not verified'
render body: nil, status: 500 and return
render body: nil, status: :internal_server_error
end
else
logger.warn 'WOPI: proof verification: timestamp too old; ' +
timestamp.to_s
render body: nil, status: 500 and return
render body: nil, status: :internal_server_error
end
rescue => e
rescue StandardError => e
logger.warn 'WOPI: proof verification: failed; ' + e.message
render body: nil, status: 500 and return
render body: nil, status: :internal_server_error
end
# Overwrriten in electronic signature for locked inventory items
def can_edit_wopi_file_in_repository_rows?
can_manage_repository_rows?(@repository)
end

View file

@ -93,13 +93,21 @@ class LoadFromRepositoryProtocolsDatatable < CustomDatatable
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id')
.where('protocols.protocol_type = ?',
Protocol.protocol_types[:in_repository_public])
else
elsif @type == :private
records =
records
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id')
.where('protocols.protocol_type = ?',
Protocol.protocol_types[:in_repository_private])
.where(added_by: @user)
else
records =
records
.joins('LEFT OUTER JOIN users ON users.id = protocols.added_by_id')
.where('(protocols.protocol_type = ? OR (protocols.protocol_type = ? AND added_by_id = ?))',
Protocol.protocol_types[:in_repository_public],
Protocol.protocol_types[:in_repository_private],
@user.id)
end
records.group('"protocols"."id"')

View file

@ -198,12 +198,8 @@ class ProtocolsDatatable < CustomDatatable
end
def modules_html(record)
"<a href='#' data-action='load-linked-children' class='help_tooltips' " \
"data-tooltiplink='" +
I18n.t('tooltips.link.protocol.num_linked') +
"' data-tooltipcontent='" +
I18n.t('tooltips.text.protocol.num_linked') +
"' data-url='#{linked_children_protocol_path(record)}'>" \
"<a href='#' data-action='load-linked-children'" \
"data-url='#{linked_children_protocol_path(record)}'>" \
"#{record.nr_of_linked_children}" \
"</a>"
end

View file

@ -6,7 +6,8 @@ module ApplicationHelper
include InputSanitizeHelper
def module_page?
controller_name == 'my_modules'
controller_name == 'my_modules' ||
controller_name == 'my_module_repositories'
end
def experiment_page?

View file

@ -2,6 +2,7 @@ module BootstrapFormHelper
# Extend Bootstrap form builder
class BootstrapForm::FormBuilder
include BootstrapFormHelper
# Returns Bootstrap date-time picker of the "datetime" type tailored for accessing a specified datetime attribute (identified by +name+) on an object
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
@ -22,18 +23,9 @@ module BootstrapFormHelper
def datetime_picker(name, options = {})
id = "#{@object_name}_#{name.to_s}"
input_name = "#{@object_name}[#{name.to_s}]"
date_format = I18n.backend.date_format.dup
value = options[:value] ? options[:value].strftime(date_format + ' %H:%M') : ''
value = options[:value] ? options[:value].strftime("#{I18n.backend.date_format} %H:%M") : ''
js_locale = I18n.locale.to_s
js_format = date_format
js_format.gsub!(/%-d/, 'D')
js_format.gsub!(/%d/, 'DD')
js_format.gsub!(/%-m/, 'M')
js_format.gsub!(/%m/, 'MM')
js_format.gsub!(/%b/, 'MMM')
js_format.gsub!(/%B/, 'MMMM')
js_format.gsub!('%Y', 'YYYY')
js_format << ' HH:mm' if options[:time] == true
js_format = options[:time] ? datetime_picker_format_full : datetime_picker_format_date_only
label = options[:label] || name.to_s.humanize
@ -266,4 +258,24 @@ module BootstrapFormHelper
text_area(name, options)
end
end
# Returns date only format string for Bootstrap DateTimePicker
def datetime_picker_format_date_only
js_format = I18n.backend.date_format.dup
js_format.gsub!(/%-d/, 'D')
js_format.gsub!(/%d/, 'DD')
js_format.gsub!(/%-m/, 'M')
js_format.gsub!(/%m/, 'MM')
js_format.gsub!(/%b/, 'MMM')
js_format.gsub!(/%B/, 'MMMM')
js_format.gsub!('%Y', 'YYYY')
js_format
end
# Returns date and time format string for Bootstrap DateTimePicker
def datetime_picker_format_full
js_format = datetime_picker_format_date_only
js_format << ' HH:mm'
js_format
end
end

View file

@ -42,17 +42,17 @@ module GlobalActivitiesHelper
# Not link for now
return current_value
when Team
path = projects_path
path = projects_path(team: obj.id)
when Repository
path = repository_path(obj)
path = repository_path(obj, team: obj.team.id)
when RepositoryRow
return current_value unless obj.repository
path = repository_path(obj.repository)
path = repository_path(obj.repository, team: obj.repository.team.id)
when RepositoryColumn
return current_value unless obj.repository
path = repository_path(obj.repository)
path = repository_path(obj.repository, team: obj.repository.team.id)
when Project
path = obj.archived? ? projects_path : project_path(obj)
when Experiment
@ -65,19 +65,15 @@ module GlobalActivitiesHelper
path = if obj.archived?
module_archive_experiment_path(obj.experiment)
else
path = if %w(assign_repository_record unassign_repository_record).include? activity.type_of
repository_my_module_path(obj, activity.values['message_items']['repository']['id'])
else
protocols_my_module_path(obj)
end
protocols_my_module_path(obj)
end
when Protocol
if obj.in_repository_public?
path = protocols_path(type: :public)
path = protocols_path(type: :public, team: obj.team.id)
elsif obj.in_repository_private?
path = protocols_path(type: :private)
path = protocols_path(type: :private, team: obj.team.id)
elsif obj.in_repository_archived?
path = protocols_path(type: :archive)
path = protocols_path(type: :archive, team: obj.team.id)
elsif obj.my_module.navigable?
path = protocols_my_module_path(obj.my_module)
else
@ -90,7 +86,7 @@ module GlobalActivitiesHelper
when Step
return current_value
when Report
path = reports_path
path = reports_path(team: obj.team.id)
else
return current_value
end

View file

@ -55,4 +55,49 @@ module MyModulesHelper
def is_results_page?
action_name == 'results'
end
def grouped_by_prj_exp(my_modules)
ungrouped_tasks = my_modules.joins(experiment: :project)
.select('experiments.name as experiment_name,
experiments.archived as experiment_archived,
projects.name as project_name,
projects.archived as project_archived,
my_modules.*')
ungrouped_tasks.group_by { |i| [i[:project_name], i[:experiment_name]] }.map do |group, tasks|
{
project_name: group[0],
project_archived: tasks[0]&.project_archived,
experiment_name: group[1],
experiment_archived: tasks[0]&.experiment_archived,
tasks: tasks
}
end
end
def assigned_repository_full_view_table_path(my_module, repository)
if repository.is_a?(RepositorySnapshot)
return full_view_table_my_module_repository_snapshot_path(my_module, repository)
end
full_view_table_my_module_repository_path(my_module, repository)
end
def assigned_repository_simple_view_index_path(my_module, repository)
return index_dt_my_module_repository_snapshot_path(my_module, repository) if repository.is_a?(RepositorySnapshot)
index_dt_my_module_repository_path(my_module, repository)
end
def assigned_repository_simple_view_footer_label(repository)
if repository.is_a?(RepositorySnapshot)
return t('my_modules.repository.snapshots.simple_view.snapshot_bottom_label',
date_time: l(repository.created_at, format: :full))
end
t('my_modules.repository.snapshots.simple_view.live_bottom_label')
end
def assigned_repository_simple_view_name_column_id(repository)
repository.is_a?(RepositorySnapshot) ? 2 : 3
end
end

View file

@ -1,22 +1,5 @@
module ProtocolStatusHelper
def protocol_status_href(protocol)
parent = protocol.parent
res = ''
res << '<a href="#" data-toggle="popover" data-html="true" class="preview-protocol"'
res << 'data-trigger="focus" data-placement="bottom" title="'
res << protocol_status_popover_title(parent) +
'" data-content="' + protocol_status_popover_content(parent) +
'">' + protocol_name(parent).truncate(Constants::NAME_TRUNCATION_LENGTH) + '</a>'
res.html_safe
end
private
def protocol_private_for_current_user?(protocol)
protocol.in_repository_private? && protocol.added_by != current_user
end
def protocol_name(protocol)
if protocol_private_for_current_user?(protocol)
I18n.t('my_modules.protocols.protocol_status_bar.private_parent')
@ -25,43 +8,9 @@ module ProtocolStatusHelper
end
end
def protocol_status_popover_title(protocol)
res = ""
if protocol.in_repository_public?
res << "<span class='fas fa-eye' title='" + I18n.t("my_modules.protocols.protocol_status_bar.public_desc") + "'></span>"
elsif protocol.in_repository_private?
res << "<span class='fas fa-eye-slash' title='" + I18n.t("my_modules.protocols.protocol_status_bar.private_desc") + "'></span>"
end
res << "&nbsp;"
if can_read_protocol_in_repository?(protocol)
res << "<a href='" + edit_protocol_path(protocol) + "' target='_blank'>" + protocol_name(protocol) + "</a>"
else
res << "<span style='font-weight: bold;'>" + protocol_name(protocol) + "</span>"
end
res << "&nbsp;-&nbsp;"
res << "<span style='font-style: italic;'>" + I18n.t("my_modules.protocols.protocol_status_bar.added_by") + "&nbsp;"
res << "<a href='#' data-toggle='tooltip' data-placement='right' title='" +
I18n.t('my_modules.protocols.protocol_status_bar.added_by_tooltip',
ts: I18n.l(protocol.created_at, format: :full)) + "'>" +
escape_input(protocol.added_by.full_name) + '</a></span>'
res
end
private
def protocol_status_popover_content(protocol)
if protocol_private_for_current_user?(protocol)
res = '<p><em>' + I18n.t('my_modules.protocols.protocol_status_bar.private_protocol_desc') + '</em></p>'
else
res = '<p><b>' + I18n.t('my_modules.protocols.protocol_status_bar.keywords') + ':</b>&nbsp;'
if protocol.protocol_keywords.size.positive?
protocol.protocol_keywords.each do |kw|
res << kw.name + ', '
end
res = res[0..-3]
else
res << '<em>' + I18n.t('my_modules.protocols.protocol_status_bar.no_keywords') + '</em>'
end
res << '</p>'
end
escape_input(res)
def protocol_private_for_current_user?(protocol)
protocol.in_repository_private? && protocol.added_by != current_user
end
end

View file

@ -122,6 +122,12 @@ module ReportsHelper
)
end
def assign_repository_or_snapshot(my_module, element_id, snapshot, repository)
original_repository = Repository.find_by(id: element_id) if element_id
repository ||= snapshot
repository || my_module.active_snapshot_or_live(original_repository) || original_repository
end
def step_status_label(step)
if step.completed
style = 'success'

View file

@ -3,13 +3,8 @@
module RepositoryDatatableHelper
include InputSanitizeHelper
def prepare_row_columns(repository_rows,
repository,
columns_mappings,
team)
parsed_records = []
repository_rows.each do |record|
def prepare_row_columns(repository_rows, repository, columns_mappings, team, options = {})
repository_rows.map do |record|
row = {
'DT_RowId': record.id,
'1': assigned_row(record),
@ -17,49 +12,64 @@ module RepositoryDatatableHelper
'3': escape_input(record.name),
'4': I18n.l(record.created_at, format: :full),
'5': escape_input(record.created_by.full_name),
'recordEditUrl': Rails.application.routes.url_helpers
.edit_repository_repository_row_path(
repository,
record.id
),
'recordUpdateUrl': Rails.application.routes.url_helpers
.repository_repository_row_path(
repository,
record.id
),
'recordInfoUrl': Rails.application.routes.url_helpers
.repository_row_path(record.id),
'recordEditable': record.editable?
'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(repository, record)
}
unless options[:view_mode]
row['recordUpdateUrl'] =
Rails.application.routes.url_helpers.repository_repository_row_path(repository, record)
row['recordEditable'] = record.editable?
end
row['0'] = record[:row_assigned] if options[:my_module]
# Add custom columns
record.repository_cells.each do |cell|
row[columns_mappings[cell.repository_column.id]] =
display_cell_value(cell, team)
end
parsed_records << row
row
end
end
def prepare_simple_view_row_columns(repository_rows)
repository_rows.map do |record|
{
'DT_RowId': record.id,
'0': escape_input(record.name),
'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record)
}
end
end
def prepare_snapshot_row_columns(repository_rows, columns_mappings, team)
repository_rows.map do |record|
row = {
'DT_RowId': record.id,
'1': record.parent_id,
'2': escape_input(record.name),
'3': I18n.l(record.created_at, format: :full),
'4': escape_input(record.created_by.full_name),
'recordInfoUrl': Rails.application.routes.url_helpers.repository_repository_row_path(record.repository, record)
}
# Add custom columns
record.repository_cells.each do |cell|
row[columns_mappings[cell.repository_column.id]] = display_cell_value(cell, team)
end
row
end
parsed_records
end
def assigned_row(record)
if @my_module
if record.assigned_my_modules_count.positive?
"<span class='circle-icon'>&nbsp;</span>"
else
"<span class='circle-icon disabled'>&nbsp;</span>"
end
elsif record.assigned_my_modules_count.positive?
tooltip = t('repositories.table.assigned_tooltip',
tasks: record.assigned_my_modules_count,
{
tasks: record.assigned_my_modules_count,
experiments: record.assigned_experiments_count,
projects: record.assigned_projects_count)
"<div class='assign-counter-container' title='#{tooltip}'>"\
"<span class='assign-counter has-assigned'>#{record.assigned_my_modules_count}</span></div>"
else
"<div class='assign-counter-container'><span class='assign-counter'>0</span></div>"
end
projects: record.assigned_projects_count,
task_list_url: assigned_task_list_repository_repository_row_path(record.repository, record)
}
end
def can_perform_repository_actions(repository)
@ -77,6 +87,14 @@ module RepositoryDatatableHelper
Constants::REPOSITORY_TABLE_DEFAULT_STATE['columns'].to_json
end
def default_snapshot_table_order_as_js_array
Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['order'].to_json
end
def default_snapshot_table_columns
Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['columns'].to_json
end
def display_cell_value(cell, team)
value_name = cell.repository_column.data_type.demodulize.underscore
serializer_class = "RepositoryDatatable::#{cell.repository_column.data_type}Serializer".constantize

View file

@ -30,11 +30,6 @@ module SidebarHelper
samples_my_module_url(my_module)
elsif action_name.in?(%w(archive module_archive experiment_archive))
archive_my_module_url(my_module)
elsif action_name == 'repository' && @repository
repository_my_module_url(
id: my_module.id,
repository_id: @repository.id
)
else
protocols_my_module_url(my_module)
end

View file

@ -20,4 +20,8 @@ module TeamsHelper
def team_created_by(team)
User.find_by_id(team.created_by_id)
end
def switch_team_with_param
current_team_switch(Team.find_by(id: params[:team])) if params[:team]
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
discard_on ActiveJob::DeserializationError
end

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
class RepositorySnapshotProvisioningJob < ApplicationJob
queue_as :high_priority
def perform(repository_snapshot)
service = Repositories::SnapshotProvisioningService.call(repository_snapshot: repository_snapshot)
repository_snapshot.failed! unless service.succeed?
end
end

View file

@ -48,7 +48,7 @@ class Activity < ApplicationRecord
}
scope :repositories_joins, lambda {
joins("LEFT JOIN repositories ON subject_type = 'Repository' AND subject_id = repositories.id")
joins("LEFT JOIN repositories ON subject_type = 'RepositoryBase' AND subject_id = repositories.id")
}
scope :reports_joins, lambda {

View file

@ -9,6 +9,7 @@ module TeamBySubjectModel
valid_subjects = Extends::ACTIVITY_SUBJECT_CHILDREN
# Check all activity subject
valid_subjects.each do |subject, _children|
subject = subject.to_s.camelize.to_sym
next unless subjects[subject]
parent_array = [subject.to_s.underscore]

View file

@ -40,21 +40,12 @@ class MyModule < ApplicationRecord
has_many :my_module_tags, inverse_of: :my_module, dependent: :destroy
has_many :tags, through: :my_module_tags
has_many :task_comments, foreign_key: :associated_id, dependent: :destroy
has_many :inputs,
class_name: 'Connection',
foreign_key: 'input_id',
inverse_of: :to,
dependent: :destroy
has_many :outputs,
class_name: 'Connection',
foreign_key: 'output_id',
inverse_of: :from,
dependent: :destroy
has_many :my_modules, through: :outputs, source: :to
has_many :my_module_antecessors,
through: :inputs,
source: :from,
class_name: 'MyModule'
has_many :inputs, class_name: 'Connection', foreign_key: 'input_id', inverse_of: :to, dependent: :destroy
has_many :outputs, class_name: 'Connection', foreign_key: 'output_id', inverse_of: :from, dependent: :destroy
has_many :my_modules, through: :outputs, source: :to, class_name: 'MyModule'
has_many :my_module_antecessors, through: :inputs, source: :from, class_name: 'MyModule'
has_many :sample_my_modules,
inverse_of: :my_module,
dependent: :destroy
@ -62,6 +53,9 @@ class MyModule < ApplicationRecord
has_many :my_module_repository_rows,
inverse_of: :my_module, dependent: :destroy
has_many :repository_rows, through: :my_module_repository_rows
has_many :repository_snapshots,
dependent: :destroy,
inverse_of: :my_module
has_many :user_my_modules, inverse_of: :my_module, dependent: :destroy
has_many :users, through: :user_my_modules
has_many :report_elements, inverse_of: :my_module, dependent: :destroy
@ -205,6 +199,61 @@ class MyModule < ApplicationRecord
.count
end
def assigned_repositories
team = experiment.project.team
team.repositories
.joins(repository_rows: :my_module_repository_rows)
.where(my_module_repository_rows: { my_module_id: id })
.group(:id)
end
def live_and_snapshot_repositories_list
snapshots = repository_snapshots.left_outer_joins(:original_repository)
selected_snapshots = snapshots.where(selected: true)
.or(snapshots.where(original_repositories_repositories: { id: nil }))
.or(snapshots.where.not(parent_id: assigned_repositories.select(:id)))
.select('DISTINCT ON ("repositories"."parent_id") "repositories".*')
.select('COUNT(repository_rows.id) AS assigned_rows_count')
.joins(:repository_rows)
.group(:parent_id, :id)
.order(:parent_id, updated_at: :desc)
live_repositories = assigned_repositories
.select('repositories.*, COUNT(repository_rows.id) AS assigned_rows_count')
.where.not(id: repository_snapshots.where(selected: true).select(:parent_id))
(live_repositories + selected_snapshots).sort_by { |r| r.name.downcase }
end
def active_snapshot_or_live(rep_or_snap, exclude_snpashot_ids: [])
return unless rep_or_snap
parent_id = rep_or_snap.is_a?(Repository) ? rep_or_snap.id : rep_or_snap.parent_id
selected_snapshot_for_repo(parent_id, exclude_snpashot_ids: exclude_snpashot_ids) ||
assigned_repositories&.where(id: parent_id)&.first ||
repository_snapshots
.where(parent_id: parent_id)
.where.not(id: exclude_snpashot_ids)
.order(updated_at: :desc).first
end
def update_report_repository_references(rep_or_snap)
ids = if rep_or_snap.is_a?(Repository)
RepositorySnapshot.where(parent_id: rep_or_snap.id).pluck(:id)
else
Repository.where(id: rep_or_snap.parent_id).pluck(:id) +
RepositorySnapshot.where(parent_id: rep_or_snap.parent_id).pluck(:id)
end
report_elements.where(repository_id: ids).update(repository_id: rep_or_snap.id)
end
def selected_snapshot_for_repo(repository_id, exclude_snpashot_ids: [])
repository_snapshots.where(parent_id: repository_id).where.not(id: exclude_snpashot_ids).where(selected: true).first
end
def unassigned_users
User.find_by_sql(
"SELECT DISTINCT users.id, users.full_name FROM users " +
@ -377,14 +426,12 @@ class MyModule < ApplicationRecord
# Generate the repository rows belonging to this module
# in JSON form, suitable for display in handsontable.js
def repository_json_hot(repository_id, order)
def repository_json_hot(repository, order)
data = []
repository_rows
.includes(:created_by)
.where(repository_id: repository_id)
.order(created_at: order).find_each do |row|
rows = repository.assigned_rows(self).includes(:created_by).order(created_at: order)
rows.find_each do |row|
row_json = []
row_json << row.id
row_json << (row.repository.is_a?(RepositorySnapshot) ? row.parent_id : row.id)
row_json << row.name
row_json << I18n.l(row.created_at, format: :full)
row_json << row.created_by.full_name
@ -401,29 +448,7 @@ class MyModule < ApplicationRecord
{ data: data, headers: headers }
end
def repository_json(repository_id, order, user)
headers = [
I18n.t('repositories.table.id'),
I18n.t('repositories.table.row_name'),
I18n.t('repositories.table.added_on'),
I18n.t('repositories.table.added_by')
]
repository = Repository.find_by_id(repository_id)
return false unless repository
repository.repository_columns.order(:id).each do |column|
headers.push(column.name)
end
params = { assigned: 'assigned', search: {}, order: { values: { column: '1', dir: order } } }
records = RepositoryDatatableService.new(repository,
params,
user,
self)
{ headers: headers, data: records }
end
def repository_docx_json(repository_id)
def repository_docx_json(repository)
headers = [
I18n.t('repositories.table.id'),
I18n.t('repositories.table.row_name'),
@ -431,7 +456,6 @@ class MyModule < ApplicationRecord
I18n.t('repositories.table.added_by')
]
custom_columns = []
repository = Repository.find_by(id: repository_id)
return false unless repository
repository.repository_columns.order(:id).each do |column|
@ -439,7 +463,7 @@ class MyModule < ApplicationRecord
custom_columns.push(column.id)
end
records = repository_rows.where(repository_id: repository_id).select(:id, :name, :created_at, :created_by_id)
records = repository.assigned_rows(self).select(:id, :name, :created_at, :created_by_id)
{ headers: headers, rows: records, custom_columns: custom_columns }
end
@ -524,21 +548,6 @@ class MyModule < ApplicationRecord
self.completed_on = nil
end
def self.my_modules_list_partial
ungrouped_tasks = joins(experiment: :project)
.select('experiments.name as experiment_name,
projects.name as project_name,
my_modules.name as task_name,
my_modules.id')
ungrouped_tasks.group_by { |i| [i[:project_name], i[:experiment_name]] }.map do |group, tasks|
{
project_name: group[0],
experiment_name: group[1],
tasks: tasks.map { |task| { id: task.id, task_name: task.task_name } }
}
end
end
def assign_user(user, assigned_by = nil)
user_my_modules.create(
assigned_by: assigned_by || user,

View file

@ -9,6 +9,5 @@ class MyModuleRepositoryRow < ApplicationRecord
touch: true,
inverse_of: :my_module_repository_rows
validates :repository_row, :my_module, presence: true
validates :repository_row, uniqueness: { scope: :my_module }
end

View file

@ -17,12 +17,6 @@ class Protocol < ApplicationRecord
in_repository_archived: 4
}
scope :recent_protocols, lambda { |user, team, amount|
where(team: team, protocol_type: :in_repository_public)
.or(where(team: team, protocol_type: :in_repository_private, added_by: user))
.order(updated_at: :desc).limit(amount)
}
auto_strip_attributes :name, :description, nullify: false
# Name is required when its actually specified (i.e. :in_repository? is true)
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }

View file

@ -33,7 +33,8 @@ class ReportElement < ApplicationRecord
belongs_to :checklist, inverse_of: :report_elements, optional: true
belongs_to :asset, inverse_of: :report_elements, optional: true
belongs_to :table, inverse_of: :report_elements, optional: true
belongs_to :repository, inverse_of: :report_elements, optional: true
belongs_to :repository, inverse_of: :report_elements, optional: true,
foreign_key: :repository_id, class_name: 'RepositoryBase'
def has_children?
children.length > 0

View file

@ -1,36 +1,26 @@
# frozen_string_literal: true
class Repository < ApplicationRecord
class Repository < RepositoryBase
include SearchableModel
include SearchableByNameModel
include RepositoryImportParser
include Discard::Model
enum permission_level: Extends::SHARED_INVENTORIES_PERMISSION_LEVELS
attribute :discarded_by_id, :integer
belongs_to :team
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
has_many :repository_columns, dependent: :destroy
has_many :repository_rows, dependent: :destroy
has_many :repository_table_states,
inverse_of: :repository, dependent: :destroy
has_many :report_elements, inverse_of: :repository, dependent: :destroy
has_many :repository_list_items, inverse_of: :repository, dependent: :destroy
has_many :repository_checklist_items, inverse_of: :repository, dependent: :destroy
has_many :team_repositories, inverse_of: :repository, dependent: :destroy
has_many :teams_shared_with, through: :team_repositories, source: :team
has_many :repository_snapshots,
class_name: 'RepositorySnapshot',
foreign_key: :parent_id,
inverse_of: :original_repository
before_save :sync_name_with_snapshots, if: :name_changed?
auto_strip_attributes :name, nullify: false
validates :name,
presence: true,
uniqueness: { scope: :team_id, case_sensitive: false },
length: { maximum: Constants::NAME_MAX_LENGTH }
validates :team, presence: true
validates :created_by, presence: true
default_scope -> { kept }
scope :accessible_by_teams, lambda { |teams|
left_outer_joins(:team_repositories)
.where('repositories.team_id IN (?) '\
@ -97,6 +87,10 @@ class Repository < ApplicationRecord
end
end
def default_columns_count
Constants::REPOSITORY_TABLE_DEFAULT_STATE['length']
end
def i_shared?(team)
shared_with_anybody? && self.team == team
end
@ -139,10 +133,6 @@ class Repository < ApplicationRecord
where('repositories.name ILIKE ?', "%#{query}%")
end
def available_columns_ids
repository_columns.pluck(:id)
end
def importable_repository_fields
fields = {}
# First and foremost add record name
@ -203,11 +193,26 @@ class Repository < ApplicationRecord
importer.run
end
def destroy_discarded(discarded_by_id = nil)
self.discarded_by_id = discarded_by_id
destroy
def provision_snapshot(my_module, created_by = nil)
created_by ||= self.created_by
repository_snapshot = dup.becomes(RepositorySnapshot)
repository_snapshot.assign_attributes(type: RepositorySnapshot.name,
original_repository: self,
my_module: my_module,
created_by: created_by)
repository_snapshot.provisioning!
repository_snapshot.reload
RepositorySnapshotProvisioningJob.perform_later(repository_snapshot)
repository_snapshot
end
def assigned_rows(my_module)
repository_rows.joins(:my_module_repository_rows).where(my_module_repository_rows: { my_module_id: my_module.id })
end
private
def sync_name_with_snapshots
repository_snapshots.update(name: name)
end
handle_asynchronously :destroy_discarded,
queue: :clear_discarded_repository,
priority: 20
end

View file

@ -45,6 +45,24 @@ class RepositoryAssetValue < ApplicationRecord
asset.save! && save!
end
def snapshot!(cell_snapshot)
value_snapshot = dup
asset_snapshot = asset.dup
asset_snapshot.save!
# ActiveStorage::Blob is immutable, so we can just attach it to the new snapshot
asset_snapshot.file.attach(asset.blob)
value_snapshot.assign_attributes(
repository_cell: cell_snapshot,
asset: asset_snapshot,
created_at: created_at,
updated_at: updated_at
)
value_snapshot.save!
end
def self.new_with_payload(payload, attributes)
value = new(attributes)
team = value.repository_cell.repository_column.repository.team

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
class RepositoryBase < ApplicationRecord
include Discard::Model
self.table_name = 'repositories'
attribute :discarded_by_id, :integer
belongs_to :team
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
has_many :repository_columns, foreign_key: :repository_id, inverse_of: :repository, dependent: :destroy
has_many :repository_rows, foreign_key: :repository_id, inverse_of: :repository, dependent: :destroy
has_many :repository_table_states, foreign_key: :repository_id, inverse_of: :repository, dependent: :destroy
has_many :report_elements, inverse_of: :repository, dependent: :destroy, foreign_key: :repository_id
auto_strip_attributes :name, nullify: false
validates :team, presence: true
validates :created_by, presence: true
# Not discarded
default_scope -> { kept }
def cell_preload_includes
cell_includes = []
repository_columns.pluck(:data_type).each do |data_type|
cell_includes << data_type.constantize::PRELOAD_INCLUDE
end
cell_includes
end
def destroy_discarded(discarded_by_id = nil)
self.discarded_by_id = discarded_by_id
destroy
end
handle_asynchronously :destroy_discarded, queue: :clear_discarded_repository, priority: 20
end

View file

@ -126,6 +126,20 @@ class RepositoryCell < ApplicationRecord
cell
end
def snapshot!(row_snapshot)
cell_snapshot = dup
column_snapshot = row_snapshot.repository
.repository_columns
.find { |c| c.parent_id == repository_column.id }
cell_snapshot.assign_attributes(
repository_row: row_snapshot,
repository_column: column_snapshot,
created_at: created_at,
updated_at: updated_at
)
value.snapshot!(cell_snapshot)
end
private
def repository_column_data_type

View file

@ -1,7 +1,6 @@
# frozen_string_literal: true
class RepositoryChecklistItem < ApplicationRecord
belongs_to :repository, inverse_of: :repository_checklist_items
belongs_to :repository_column
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User',
inverse_of: :created_repository_checklist_types

View file

@ -48,6 +48,22 @@ class RepositoryChecklistValue < ApplicationRecord
save!
end
def snapshot!(cell_snapshot)
value_snapshot = dup
item_values = repository_checklist_items.pluck(:data)
checklist_items_snapshot = cell_snapshot.repository_column
.repository_checklist_items
.select { |snapshot_item| item_values.include?(snapshot_item.data) }
value_snapshot.assign_attributes(
repository_cell: cell_snapshot,
repository_checklist_items: checklist_items_snapshot,
created_at: created_at,
updated_at: updated_at
)
value_snapshot.save!
end
def self.new_with_payload(payload, attributes)
item_ids = payload.is_a?(String) ? JSON.parse(payload) : payload
value = new(attributes)
@ -67,8 +83,7 @@ class RepositoryChecklistValue < ApplicationRecord
if checklist_item.blank?
checklist_item = column.repository_checklist_items.new(data: text,
created_by: value.created_by,
last_modified_by: value.last_modified_by,
repository: column.repository)
last_modified_by: value.last_modified_by)
return nil unless checklist_item.save
end

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class RepositoryColumn < ApplicationRecord
belongs_to :repository
belongs_to :repository, class_name: 'RepositoryBase'
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
has_many :repository_cells, dependent: :destroy
has_many :repository_rows, through: :repository_cells
@ -75,4 +75,44 @@ class RepositoryColumn < ApplicationRecord
def importable?
Extends::REPOSITORY_IMPORTABLE_TYPES.include?(data_type.to_sym)
end
def deep_dup
new_column = super
extra_method_name = "#{data_type.underscore}_deep_dup"
__send__(extra_method_name, new_column) if respond_to?(extra_method_name, true)
new_column
end
def snapshot!(repository_snapshot)
column_snapshot = deep_dup
column_snapshot.assign_attributes(
repository: repository_snapshot,
parent_id: id,
created_at: created_at,
updated_at: updated_at
)
column_snapshot.save!
end
private
def repository_list_value_deep_dup(new_column)
repository_list_items.each do |item|
new_column.repository_list_items << item.deep_dup
end
end
def repository_checklist_value_deep_dup(new_column)
repository_checklist_items.each do |item|
new_column.repository_checklist_items << item.deep_dup
end
end
def repository_status_value_deep_dup(new_column)
repository_status_items.each do |item|
new_column.repository_status_items << item.deep_dup
end
end
end

View file

@ -7,7 +7,7 @@ class RepositoryDateTimeRangeValueBase < ApplicationRecord
inverse_of: :created_repository_date_time_values
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User', optional: true,
inverse_of: :modified_repository_date_time_values
has_one :repository_cell, as: :value, dependent: :destroy, inverse_of: :repository_date_time_value
has_one :repository_cell, as: :value, dependent: :destroy, inverse_of: :value
accepts_nested_attributes_for :repository_cell
validates :repository_cell, :start_time, :end_time, :type, presence: true
@ -33,4 +33,14 @@ class RepositoryDateTimeRangeValueBase < ApplicationRecord
self.last_modified_by = user
save!
end
def snapshot!(cell_snapshot)
value_snapshot = dup
value_snapshot.assign_attributes(
repository_cell: cell_snapshot,
created_at: created_at,
updated_at: updated_at
)
value_snapshot.save!
end
end

View file

@ -24,4 +24,14 @@ class RepositoryDateTimeValueBase < ApplicationRecord
self.last_modified_by = user
save!
end
def snapshot!(cell_snapshot)
value_snapshot = dup
value_snapshot.assign_attributes(
repository_cell: cell_snapshot,
created_at: created_at,
updated_at: updated_at
)
value_snapshot.save!
end
end

View file

@ -2,7 +2,6 @@
class RepositoryListItem < ApplicationRecord
has_many :repository_list_values, inverse_of: :repository_list_item, dependent: :destroy
belongs_to :repository, inverse_of: :repository_list_items
belongs_to :repository_column, inverse_of: :repository_list_items
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User'

View file

@ -42,6 +42,20 @@ class RepositoryListValue < ApplicationRecord
save!
end
def snapshot!(cell_snapshot)
value_snapshot = dup
list_item = cell_snapshot.repository_column
.repository_list_items
.find { |item| item.data == repository_list_item.data }
value_snapshot.assign_attributes(
repository_cell: cell_snapshot,
repository_list_item: list_item,
created_at: created_at,
updated_at: updated_at
)
value_snapshot.save!
end
def self.new_with_payload(payload, attributes)
value = new(attributes)
value.repository_list_item = value.repository_cell
@ -59,8 +73,7 @@ class RepositoryListValue < ApplicationRecord
if list_item.blank?
list_item = column.repository_list_items.new(data: text,
created_by: value.created_by,
last_modified_by: value.last_modified_by,
repository: column.repository)
last_modified_by: value.last_modified_by)
return nil unless list_item.save
end

View file

@ -28,6 +28,16 @@ class RepositoryNumberValue < ApplicationRecord
save!
end
def snapshot!(cell_snapshot)
value_snapshot = dup
value_snapshot.assign_attributes(
repository_cell: cell_snapshot,
created_at: created_at,
updated_at: updated_at
)
value_snapshot.save!
end
def self.new_with_payload(payload, attributes)
value = new(attributes)
value.data = BigDecimal(payload)

View file

@ -4,7 +4,7 @@ class RepositoryRow < ApplicationRecord
include SearchableModel
include SearchableByNameModel
belongs_to :repository, optional: true
belongs_to :repository, class_name: 'RepositoryBase'
belongs_to :created_by, foreign_key: :created_by_id, class_name: 'User'
belongs_to :last_modified_by, foreign_key: :last_modified_by_id, class_name: 'User'
has_many :repository_cells, -> { order(:id) }, dependent: :destroy
@ -23,11 +23,6 @@ class RepositoryRow < ApplicationRecord
where(repository: Repository.viewable_by_user(user, teams))
end
def self.assigned_on_my_module(ids, my_module)
where(id: ids).joins(:my_module_repository_rows)
.where('my_module_repository_rows.my_module' => my_module)
end
def self.name_like(query)
where('repository_rows.name ILIKE ?', "%#{query}%")
end
@ -41,4 +36,17 @@ class RepositoryRow < ApplicationRecord
def editable?
true
end
def snapshot!(repository_snapshot)
row_snapshot = dup
row_snapshot.assign_attributes(
repository: repository_snapshot,
parent_id: id,
created_at: created_at,
updated_at: updated_at
)
row_snapshot.save!
repository_cells.each { |cell| cell.snapshot!(row_snapshot) }
end
end

View file

@ -0,0 +1,62 @@
# frozen_string_literal: true
class RepositorySnapshot < RepositoryBase
enum status: { provisioning: 0, ready: 1, failed: 2 }
after_save :refresh_report_references, if: :saved_change_to_selected
before_destroy :refresh_report_references_for_destroy, prepend: true
belongs_to :original_repository, foreign_key: :parent_id,
class_name: 'Repository',
inverse_of: :repository_snapshots,
optional: true
belongs_to :my_module, optional: true
validates :name, presence: true, length: { maximum: Constants::NAME_MAX_LENGTH }
validates :status, presence: true
validate :only_one_selected_for_my_module, if: ->(obj) { obj.changed.include? :selected }
scope :with_deleted_parent_by_team, lambda { |team|
joins(my_module: { experiment: :project })
.where('projects.team_id = ?', team.id)
.left_outer_joins(:original_repository)
.where(original_repositories_repositories: { id: nil })
.select('DISTINCT ON ("repositories"."parent_id") "repositories".*')
.order(:parent_id, updated_at: :desc)
}
def default_columns_count
Constants::REPOSITORY_SNAPSHOT_TABLE_DEFAULT_STATE['length']
end
def assigned_rows(_my_module)
repository_rows
end
private
def only_one_selected_for_my_module
return unless selected
if my_module.repository_snapshots.where(original_repository: original_repository, selected: true).any?
errors.add(:selected, I18n.t('activerecord.errors.models.repository_snapshot.attributes.selected.already_taken'))
end
end
def refresh_report_references
if selected
ids = Repository.where(id: parent_id).pluck(:id) +
RepositorySnapshot.where(parent_id: parent_id).pluck(:id)
ReportElement.where(my_module: my_module).where(repository_id: ids).update(repository_id: id)
elsif original_repository && !my_module.selected_snapshot_for_repo(original_repository.id)
my_module.update_report_repository_references(original_repository)
end
end
def refresh_report_references_for_destroy
repository_or_snap = original_repository || self
default_view_candidate =
my_module.active_snapshot_or_live(repository_or_snap, exclude_snpashot_ids: [id])
my_module.update_report_repository_references(default_view_candidate) if default_view_candidate
end
end

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