diff --git a/Gemfile b/Gemfile
index 3d4171794..047d64b3b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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
diff --git a/Gemfile.lock b/Gemfile.lock
index ef655a677..61f6a4b45 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/VERSION b/VERSION
index 1a31d398c..815d5ca06 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.18.8
+1.19.0
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
new file mode 100644
index 000000000..b625b75ea
--- /dev/null
+++ b/app/assets/config/manifest.js
@@ -0,0 +1,3 @@
+//= link_tree ../images
+//= link application.js
+//= link application.css
diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb
index a1397cc76..c1d4e71b6 100644
--- a/app/assets/javascripts/application.js.erb
+++ b/app/assets/javascripts/application.js.erb
@@ -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
diff --git a/app/assets/javascripts/dashboard/current_tasks.js b/app/assets/javascripts/dashboard/current_tasks.js
index 53e7d8d53..88bbf0a97 100644
--- a/app/assets/javascripts/dashboard/current_tasks.js
+++ b/app/assets/javascripts/dashboard/current_tasks.js
@@ -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';
}
diff --git a/app/assets/javascripts/my_modules.js b/app/assets/javascripts/my_modules.js
index 2616c2480..bb2505b1a 100644
--- a/app/assets/javascripts/my_modules.js
+++ b/app/assets/javascripts/my_modules.js
@@ -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 `
${data.label + ' '}
- (${I18n.t('my_modules.module_header.create_new_tag')}) `;
+ (${I18n.t('my_modules.details.create_new_tag')}) `;
},
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();
diff --git a/app/assets/javascripts/my_modules/protocols.js b/app/assets/javascripts/my_modules/protocols.js
index b573200ca..bf028dd1e 100644
--- a/app/assets/javascripts/my_modules/protocols.js
+++ b/app/assets/javascripts/my_modules/protocols.js
@@ -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) => {
- $('
'
- + truncateLongString(protocol.name, GLOBAL_CONSTANTS.NAME_TRUNCATION_LENGTH)
- + '
').appendTo(dropDownList)
- .click(() => {
- $.post(recentProtocolContainer.data('updateUrl'), { source_id: protocol.id })
- .success(() => {
- location.reload();
- })
- .error(ev => {
- HelperModule.flashAlertMsg(ev.responseJSON.message, 'warning');
- });
- });
- });
+
+
+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();
diff --git a/app/assets/javascripts/my_modules/repositories.js b/app/assets/javascripts/my_modules/repositories.js
new file mode 100644
index 000000000..ff64a2995
--- /dev/null
+++ b/app/assets/javascripts/my_modules/repositories.js
@@ -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 `
+
+
+
`;
+ }
+ }];
+
+ 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 "" + data + ' ';
+ }
+ });
+ }
+
+ 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 "" + data + ' ';
+ }
+ }],
+ 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();
diff --git a/app/assets/javascripts/projects/canvas.js.erb b/app/assets/javascripts/projects/canvas.js.erb
index a9ed88fda..bf0372817 100644
--- a/app/assets/javascripts/projects/canvas.js.erb
+++ b/app/assets/javascripts/projects/canvas.js.erb
@@ -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
diff --git a/app/assets/javascripts/protocols/index.js b/app/assets/javascripts/protocols/index.js
index b9acb7b5d..92653a5cd 100644
--- a/app/assets/javascripts/protocols/index.js
+++ b/app/assets/javascripts/protocols/index.js
@@ -100,7 +100,6 @@ function initProtocolsTable() {
fnDrawCallback: function(settings, json) {
animateSpinner(this, false);
initRowSelection();
- $.initTooltips();
},
preDrawCallback: function(settings) {
animateSpinner(this);
diff --git a/app/assets/javascripts/protocols/steps.js.erb b/app/assets/javascripts/protocols/steps.js.erb
index 13f92463b..4d0ed3162 100644
--- a/app/assets/javascripts/protocols/steps.js.erb
+++ b/app/assets/javascripts/protocols/steps.js.erb
@@ -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']) {
diff --git a/app/assets/javascripts/reports/save_pdf_to_inventory.js.erb b/app/assets/javascripts/reports/save_pdf_to_inventory.js.erb
index 43b7b1127..15078a43b 100644
--- a/app/assets/javascripts/reports/save_pdf_to_inventory.js.erb
+++ b/app/assets/javascripts/reports/save_pdf_to_inventory.js.erb
@@ -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 () {
diff --git a/app/assets/javascripts/repositories/renderers/view_renderers.js b/app/assets/javascripts/repositories/renderers/view_renderers.js
index ac0b9bf4f..cfca42aeb 100644
--- a/app/assets/javascripts/repositories/renderers/view_renderers.js
+++ b/app/assets/javascripts/repositories/renderers/view_renderers.js
@@ -154,3 +154,27 @@ $.fn.dataTable.render.RepositoryNumberValue = function(data) {
${data.value}
`;
};
+
+$.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 ``;
+ }
+ return "0
";
+};
diff --git a/app/assets/javascripts/repositories/repository_datatable.js b/app/assets/javascripts/repositories/repository_datatable.js
index 8797133dc..4d0a6652a 100644
--- a/app/assets/javascripts/repositories/repository_datatable.js
+++ b/app/assets/javascripts/repositories/repository_datatable.js
@@ -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 = ` `;
@@ -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(' ');
$('#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(`
-
-
-
`).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() {
diff --git a/app/assets/javascripts/repositories/row_editor.js b/app/assets/javascripts/repositories/row_editor.js
index 7534e987e..aef9c6381 100644
--- a/app/assets/javascripts/repositories/row_editor.js
+++ b/app/assets/javascripts/repositories/row_editor.js
@@ -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);
});
});
diff --git a/app/assets/javascripts/repository_columns/columns_initializers/checklist_column_type.js b/app/assets/javascripts/repository_columns/columns_initializers/checklist_column_type.js
index b4051ddf4..f8df6c13c 100644
--- a/app/assets/javascripts/repository_columns/columns_initializers/checklist_column_type.js
+++ b/app/assets/javascripts/repository_columns/columns_initializers/checklist_column_type.js
@@ -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 = {};
diff --git a/app/assets/javascripts/repository_columns/columns_initializers/list_column_type.js b/app/assets/javascripts/repository_columns/columns_initializers/list_column_type.js
index 7142e02a5..379e77b42 100644
--- a/app/assets/javascripts/repository_columns/columns_initializers/list_column_type.js
+++ b/app/assets/javascripts/repository_columns/columns_initializers/list_column_type.js
@@ -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: () => {
diff --git a/app/assets/javascripts/repository_columns/index.js b/app/assets/javascripts/repository_columns/index.js
index a800b9e25..e6f270bbc 100644
--- a/app/assets/javascripts/repository_columns/index.js
+++ b/app/assets/javascripts/repository_columns/index.js
@@ -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();
diff --git a/app/assets/javascripts/sidebar_toggle.js.erb b/app/assets/javascripts/sidebar_toggle.js.erb
index 104c19882..0b4ef6b83 100644
--- a/app/assets/javascripts/sidebar_toggle.js.erb
+++ b/app/assets/javascripts/sidebar_toggle.js.erb
@@ -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() {
diff --git a/app/assets/javascripts/sitewide/atwho_res.js.erb b/app/assets/javascripts/sitewide/atwho_res.js.erb
index 2433fbb63..b74f09884 100644
--- a/app/assets/javascripts/sitewide/atwho_res.js.erb
+++ b/app/assets/javascripts/sitewide/atwho_res.js.erb
@@ -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');
diff --git a/app/assets/javascripts/sitewide/comments.js b/app/assets/javascripts/sitewide/comments.js
index 45e4a3ef9..f05db26c7 100644
--- a/app/assets/javascripts/sitewide/comments.js
+++ b/app/assets/javascripts/sitewide/comments.js
@@ -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');
diff --git a/app/assets/javascripts/sitewide/datatable_helpers.js b/app/assets/javascripts/sitewide/datatable_helpers.js
new file mode 100644
index 000000000..57f346f89
--- /dev/null
+++ b/app/assets/javascripts/sitewide/datatable_helpers.js
@@ -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(`
+
+
+
`).find('.sci-input-container').prepend(tableFilterInput);
+ $('.dataTables_filter').find('label').remove();
+ }
+ };
+}());
diff --git a/app/assets/javascripts/sitewide/drag_n_drop.js b/app/assets/javascripts/sitewide/drag_n_drop.js
index 361a19f6f..09fb99043 100644
--- a/app/assets/javascripts/sitewide/drag_n_drop.js
+++ b/app/assets/javascripts/sitewide/drag_n_drop.js
@@ -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("" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '
');
+ $.each($('.attachment-placeholder.new'), function() {
+ if (!$(this).find('p').hasClass('dnd-error')) {
+ $(this).append("" + I18n.t('general.file.total_size', { size: fileMaxSizeMb }) + '
');
}
});
} else {
diff --git a/app/assets/javascripts/sitewide/dropdown_selector.js b/app/assets/javascripts/sitewide/dropdown_selector.js
index a2906788d..55a73ec96 100644
--- a/app/assets/javascripts/sitewide/dropdown_selector.js
+++ b/app/assets/javascripts/sitewide/dropdown_selector.js
@@ -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`)
diff --git a/app/assets/javascripts/sitewide/repository_helper.js b/app/assets/javascripts/sitewide/repository_helper.js
index 436f92264..25bd2ad09 100644
--- a/app/assets/javascripts/sitewide/repository_helper.js
+++ b/app/assets/javascripts/sitewide/repository_helper.js
@@ -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);
+ });
+}
diff --git a/app/assets/javascripts/sitewide/tiny_mce.js b/app/assets/javascripts/sitewide/tiny_mce.js
index eb95e2355..fc6a67634 100644
--- a/app/assets/javascripts/sitewide/tiny_mce.js
+++ b/app/assets/javascripts/sitewide/tiny_mce.js
@@ -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',
diff --git a/app/assets/javascripts/sitewide/tooltip_helper.js b/app/assets/javascripts/sitewide/tooltip_helper.js
deleted file mode 100644
index 749b72118..000000000
--- a/app/assets/javascripts/sitewide/tooltip_helper.js
+++ /dev/null
@@ -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:
- ''
- })
- .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();
- });
-}());
diff --git a/app/assets/javascripts/users/settings/account/preferences/index.js b/app/assets/javascripts/users/settings/account/preferences/index.js
index a9af7f131..7c169ae75 100644
--- a/app/assets/javascripts/users/settings/account/preferences/index.js
+++ b/app/assets/javascripts/users/settings/account/preferences/index.js
@@ -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();
})();
diff --git a/app/assets/stylesheets/global_activities.scss b/app/assets/stylesheets/global_activities.scss
index f31906054..01c7fb989 100644
--- a/app/assets/stylesheets/global_activities.scss
+++ b/app/assets/stylesheets/global_activities.scss
@@ -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;
diff --git a/app/assets/stylesheets/my_modules.scss b/app/assets/stylesheets/my_modules.scss
index 54e68f9d2..cb5c1af77 100644
--- a/app/assets/stylesheets/my_modules.scss
+++ b/app/assets/stylesheets/my_modules.scss
@@ -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%;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/my_modules/protocols/index.scss b/app/assets/stylesheets/my_modules/protocols/index.scss
index ad8fd4e25..c25da1795 100644
--- a/app/assets/stylesheets/my_modules/protocols/index.scss
+++ b/app/assets/stylesheets/my_modules/protocols/index.scss
@@ -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;
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/my_modules/repositories.scss b/app/assets/stylesheets/my_modules/repositories.scss
index 34443255a..8ec431826 100644
--- a/app/assets/stylesheets/my_modules/repositories.scss
+++ b/app/assets/stylesheets/my_modules/repositories.scss
@@ -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);
+ }
+ }
+}
diff --git a/app/assets/stylesheets/my_modules/results/index.scss b/app/assets/stylesheets/my_modules/results/index.scss
index a5c9e84b4..654793e81 100644
--- a/app/assets/stylesheets/my_modules/results/index.scss
+++ b/app/assets/stylesheets/my_modules/results/index.scss
@@ -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;
}
}
diff --git a/app/assets/stylesheets/partials/_tree_view.scss b/app/assets/stylesheets/partials/_tree_view.scss
index 1fb5581dc..d3117037c 100644
--- a/app/assets/stylesheets/partials/_tree_view.scss
+++ b/app/assets/stylesheets/partials/_tree_view.scss
@@ -123,8 +123,8 @@
.fas-custom {
float: right;
- margin-right: 5px;
- top: 2px;
+ margin-right: 15px;
+ top: 17px;
}
.active {
diff --git a/app/assets/stylesheets/protocols/protocol.scss b/app/assets/stylesheets/protocols/protocol.scss
index c92dba8bc..274650e05 100644
--- a/app/assets/stylesheets/protocols/protocol.scss
+++ b/app/assets/stylesheets/protocols/protocol.scss
@@ -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 {
diff --git a/app/assets/stylesheets/reports.scss b/app/assets/stylesheets/reports.scss
index 2acec9c31..cd23ec5c8 100644
--- a/app/assets/stylesheets/reports.scss
+++ b/app/assets/stylesheets/reports.scss
@@ -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;
}
}
diff --git a/app/assets/stylesheets/repositories.scss b/app/assets/stylesheets/repositories.scss
index b9c797564..478e9e526 100644
--- a/app/assets/stylesheets/repositories.scss
+++ b/app/assets/stylesheets/repositories.scss
@@ -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;
}
diff --git a/app/assets/stylesheets/repository/repository_table.scss b/app/assets/stylesheets/repository/repository_table.scss
index 70ec10044..4247e003c 100644
--- a/app/assets/stylesheets/repository/repository_table.scss
+++ b/app/assets/stylesheets/repository/repository_table.scss
@@ -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 {
diff --git a/app/assets/stylesheets/shared/avatar.scss b/app/assets/stylesheets/shared/avatar.scss
index cdcfa501b..6a9934721 100644
--- a/app/assets/stylesheets/shared/avatar.scss
+++ b/app/assets/stylesheets/shared/avatar.scss
@@ -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;
diff --git a/app/assets/stylesheets/shared/comments.scss b/app/assets/stylesheets/shared/comments.scss
index 93356a7db..ef5742c0a 100644
--- a/app/assets/stylesheets/shared/comments.scss
+++ b/app/assets/stylesheets/shared/comments.scss
@@ -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;
@@ -246,7 +248,7 @@
padding: 4px;
position: absolute;
right: -36px;
- text-align: center;
+ text-align: center;
top: 0;
transition: $md-transaction;
width: 26px;
diff --git a/app/assets/stylesheets/shared/datatable.scss b/app/assets/stylesheets/shared/datatable.scss
new file mode 100644
index 000000000..bf5c57d86
--- /dev/null
+++ b/app/assets/stylesheets/shared/datatable.scss
@@ -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;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/shared/dropdown_selector.scss b/app/assets/stylesheets/shared/dropdown_selector.scss
index bf1ebba5e..13930d610 100644
--- a/app/assets/stylesheets/shared/dropdown_selector.scss
+++ b/app/assets/stylesheets/shared/dropdown_selector.scss
@@ -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;
diff --git a/app/assets/stylesheets/shared/my_modules_list_partial.scss b/app/assets/stylesheets/shared/my_modules_list_partial.scss
index cf3be7bb5..7a6858940 100644
--- a/app/assets/stylesheets/shared/my_modules_list_partial.scss
+++ b/app/assets/stylesheets/shared/my_modules_list_partial.scss
@@ -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;
+ }
}
diff --git a/app/assets/stylesheets/shared_styles/constants/colors.scss b/app/assets/stylesheets/shared_styles/constants/colors.scss
index d8cab82b5..d5d69dfd7 100644
--- a/app/assets/stylesheets/shared_styles/constants/colors.scss
+++ b/app/assets/stylesheets/shared_styles/constants/colors.scss
@@ -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;
diff --git a/app/assets/stylesheets/shared_styles/elements/buttons.scss b/app/assets/stylesheets/shared_styles/elements/buttons.scss
index bfb9c5813..991f5506e 100644
--- a/app/assets/stylesheets/shared_styles/elements/buttons.scss
+++ b/app/assets/stylesheets/shared_styles/elements/buttons.scss
@@ -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;
}
diff --git a/app/assets/stylesheets/shared_styles/elements/dropdown.scss b/app/assets/stylesheets/shared_styles/elements/dropdown.scss
new file mode 100644
index 000000000..e8043ced5
--- /dev/null
+++ b/app/assets/stylesheets/shared_styles/elements/dropdown.scss
@@ -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;
+ }
+ }
+ }
+
+}
diff --git a/app/assets/stylesheets/steps.scss b/app/assets/stylesheets/steps.scss
index 32fc129da..e085f2459 100644
--- a/app/assets/stylesheets/steps.scss
+++ b/app/assets/stylesheets/steps.scss
@@ -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;
+}
diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss
index a6cdea83a..93cc686ab 100644
--- a/app/assets/stylesheets/themes/scinote.scss
+++ b/app/assets/stylesheets/themes/scinote.scss
@@ -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;
}
diff --git a/app/controllers/api/v1/inventory_checklist_items_controller.rb b/app/controllers/api/v1/inventory_checklist_items_controller.rb
index 999a69486..28b4d8469 100644
--- a/app/controllers/api/v1/inventory_checklist_items_controller.rb
+++ b/app/controllers/api/v1/inventory_checklist_items_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/inventory_list_items_controller.rb b/app/controllers/api/v1/inventory_list_items_controller.rb
index 0bff724cb..c4b87bff2 100644
--- a/app/controllers/api/v1/inventory_list_items_controller.rb
+++ b/app/controllers/api/v1/inventory_list_items_controller.rb
@@ -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
diff --git a/app/controllers/api/v1/inventory_status_items_controller.rb b/app/controllers/api/v1/inventory_status_items_controller.rb
index 8e36adad7..ccd0675c3 100644
--- a/app/controllers/api/v1/inventory_status_items_controller.rb
+++ b/app/controllers/api/v1/inventory_status_items_controller.rb
@@ -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
diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb
index 07898005f..df92415b4 100644
--- a/app/controllers/assets_controller.rb
+++ b/app/controllers/assets_controller.rb
@@ -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'
diff --git a/app/controllers/dashboard/calendars_controller.rb b/app/controllers/dashboard/calendars_controller.rb
index 65afdd28d..5f098cfdf 100644
--- a/app/controllers/dashboard/calendars_controller.rb
+++ b/app/controllers/dashboard/calendars_controller.rb
@@ -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
diff --git a/app/controllers/my_module_repositories_controller.rb b/app/controllers/my_module_repositories_controller.rb
new file mode 100644
index 000000000..a8ae22c54
--- /dev/null
+++ b/app/controllers/my_module_repositories_controller.rb
@@ -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
diff --git a/app/controllers/my_module_repository_snapshots_controller.rb b/app/controllers/my_module_repository_snapshots_controller.rb
new file mode 100644
index 000000000..4493b2343
--- /dev/null
+++ b/app/controllers/my_module_repository_snapshots_controller.rb
@@ -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
diff --git a/app/controllers/my_modules_controller.rb b/app/controllers/my_modules_controller.rb
index 70adfbabe..4221269eb 100644
--- a/app/controllers/my_modules_controller.rb
+++ b/app/controllers/my_modules_controller.rb
@@ -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
diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb
index 7bd814a51..28f3a27e4 100644
--- a/app/controllers/protocols_controller.rb
+++ b/app/controllers/protocols_controller.rb
@@ -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
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
index 6658c8aba..9a372166b 100644
--- a/app/controllers/reports_controller.rb
+++ b/app/controllers/reports_controller.rb
@@ -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
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 943737507..9e0eb9f42 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -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
diff --git a/app/controllers/repository_columns_controller.rb b/app/controllers/repository_columns_controller.rb
index ec3def75f..be4410c92 100644
--- a/app/controllers/repository_columns_controller.rb
+++ b/app/controllers/repository_columns_controller.rb
@@ -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
diff --git a/app/controllers/repository_rows_controller.rb b/app/controllers/repository_rows_controller.rb
index f13fd2990..fdb82f49c 100644
--- a/app/controllers/repository_rows_controller.rb
+++ b/app/controllers/repository_rows_controller.rb
@@ -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
diff --git a/app/controllers/user_my_modules_controller.rb b/app/controllers/user_my_modules_controller.rb
index 61338902c..2eb5b1ef1 100644
--- a/app/controllers/user_my_modules_controller.rb
+++ b/app/controllers/user_my_modules_controller.rb
@@ -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
diff --git a/app/controllers/user_repositories_controller.rb b/app/controllers/user_repositories_controller.rb
index c1b28ce73..848444616 100644
--- a/app/controllers/user_repositories_controller.rb
+++ b/app/controllers/user_repositories_controller.rb
@@ -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
diff --git a/app/controllers/users/invitations_controller.rb b/app/controllers/users/invitations_controller.rb
index 259dd2ce3..44042ac48 100644
--- a/app/controllers/users/invitations_controller.rb
+++ b/app/controllers/users/invitations_controller.rb
@@ -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
})
diff --git a/app/controllers/users/settings/account/preferences_controller.rb b/app/controllers/users/settings/account/preferences_controller.rb
index 3fc4e0057..5c53c1949 100644
--- a/app/controllers/users/settings/account/preferences_controller.rb
+++ b/app/controllers/users/settings/account/preferences_controller.rb
@@ -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
diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb
index ffcdf03d3..d2ee1ed9d 100644
--- a/app/controllers/wopi_controller.rb
+++ b/app/controllers/wopi_controller.rb
@@ -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
diff --git a/app/datatables/load_from_repository_protocols_datatable.rb b/app/datatables/load_from_repository_protocols_datatable.rb
index d43ecf0ea..a6d3ffd36 100644
--- a/app/datatables/load_from_repository_protocols_datatable.rb
+++ b/app/datatables/load_from_repository_protocols_datatable.rb
@@ -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"')
diff --git a/app/datatables/protocols_datatable.rb b/app/datatables/protocols_datatable.rb
index 2b0728474..9923f11e0 100644
--- a/app/datatables/protocols_datatable.rb
+++ b/app/datatables/protocols_datatable.rb
@@ -198,12 +198,8 @@ class ProtocolsDatatable < CustomDatatable
end
def modules_html(record)
- "" \
+ " " \
"#{record.nr_of_linked_children}" \
" "
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 97d77bbe4..0a7886316 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -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?
diff --git a/app/helpers/bootstrap_form_helper.rb b/app/helpers/bootstrap_form_helper.rb
index c09ed7ac3..debb53bdb 100644
--- a/app/helpers/bootstrap_form_helper.rb
+++ b/app/helpers/bootstrap_form_helper.rb
@@ -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
diff --git a/app/helpers/global_activities_helper.rb b/app/helpers/global_activities_helper.rb
index decc6a13c..e9e4f374d 100644
--- a/app/helpers/global_activities_helper.rb
+++ b/app/helpers/global_activities_helper.rb
@@ -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
diff --git a/app/helpers/my_modules_helper.rb b/app/helpers/my_modules_helper.rb
index bb31c0e8a..b04d2cb31 100644
--- a/app/helpers/my_modules_helper.rb
+++ b/app/helpers/my_modules_helper.rb
@@ -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
diff --git a/app/helpers/protocol_status_helper.rb b/app/helpers/protocol_status_helper.rb
index 71cdb8fce..3bb8bbb75 100644
--- a/app/helpers/protocol_status_helper.rb
+++ b/app/helpers/protocol_status_helper.rb
@@ -1,22 +1,5 @@
module ProtocolStatusHelper
- def protocol_status_href(protocol)
- parent = protocol.parent
- res = ''
- res << '' + protocol_name(parent).truncate(Constants::NAME_TRUNCATION_LENGTH) + ' '
- 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 << " "
- elsif protocol.in_repository_private?
- res << " "
- end
- res << " "
- if can_read_protocol_in_repository?(protocol)
- res << "" + protocol_name(protocol) + " "
- else
- res << "" + protocol_name(protocol) + " "
- end
- res << " - "
- res << "" + I18n.t("my_modules.protocols.protocol_status_bar.added_by") + " "
- res << "" +
- escape_input(protocol.added_by.full_name) + ' '
- res
- end
+ private
- def protocol_status_popover_content(protocol)
- if protocol_private_for_current_user?(protocol)
- res = '' + I18n.t('my_modules.protocols.protocol_status_bar.private_protocol_desc') + '
'
- else
- res = '' + I18n.t('my_modules.protocols.protocol_status_bar.keywords') + ': '
- if protocol.protocol_keywords.size.positive?
- protocol.protocol_keywords.each do |kw|
- res << kw.name + ', '
- end
- res = res[0..-3]
- else
- res << '' + I18n.t('my_modules.protocols.protocol_status_bar.no_keywords') + ' '
- end
- res << '
'
- end
- escape_input(res)
+ def protocol_private_for_current_user?(protocol)
+ protocol.in_repository_private? && protocol.added_by != current_user
end
end
diff --git a/app/helpers/reports_helper.rb b/app/helpers/reports_helper.rb
index 07957a7c0..b0c1dea55 100644
--- a/app/helpers/reports_helper.rb
+++ b/app/helpers/reports_helper.rb
@@ -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'
diff --git a/app/helpers/repository_datatable_helper.rb b/app/helpers/repository_datatable_helper.rb
index a26ba5925..c82815b6a 100644
--- a/app/helpers/repository_datatable_helper.rb
+++ b/app/helpers/repository_datatable_helper.rb
@@ -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?
- " "
- else
- " "
- 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)
-
- ""\
- "#{record.assigned_my_modules_count}
"
- else
- "0
"
- 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
diff --git a/app/helpers/sidebar_helper.rb b/app/helpers/sidebar_helper.rb
index eafb8f930..989ed119d 100644
--- a/app/helpers/sidebar_helper.rb
+++ b/app/helpers/sidebar_helper.rb
@@ -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
diff --git a/app/helpers/teams_helper.rb b/app/helpers/teams_helper.rb
index 7fa0070a8..9c5d116bb 100644
--- a/app/helpers/teams_helper.rb
+++ b/app/helpers/teams_helper.rb
@@ -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
diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb
new file mode 100644
index 000000000..43fd60b59
--- /dev/null
+++ b/app/jobs/application_job.rb
@@ -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
diff --git a/app/jobs/repository_snapshot_provisioning_job.rb b/app/jobs/repository_snapshot_provisioning_job.rb
new file mode 100644
index 000000000..724053931
--- /dev/null
+++ b/app/jobs/repository_snapshot_provisioning_job.rb
@@ -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
diff --git a/app/models/activity.rb b/app/models/activity.rb
index 28d3f3f6e..09538c687 100644
--- a/app/models/activity.rb
+++ b/app/models/activity.rb
@@ -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 {
diff --git a/app/models/concerns/team_by_subject_model.rb b/app/models/concerns/team_by_subject_model.rb
index e1eb7c023..b002f6979 100644
--- a/app/models/concerns/team_by_subject_model.rb
+++ b/app/models/concerns/team_by_subject_model.rb
@@ -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]
diff --git a/app/models/my_module.rb b/app/models/my_module.rb
index fa3264045..867c20dce 100644
--- a/app/models/my_module.rb
+++ b/app/models/my_module.rb
@@ -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,
diff --git a/app/models/my_module_repository_row.rb b/app/models/my_module_repository_row.rb
index f0d9eb674..48f096c13 100644
--- a/app/models/my_module_repository_row.rb
+++ b/app/models/my_module_repository_row.rb
@@ -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
diff --git a/app/models/protocol.rb b/app/models/protocol.rb
index d88da07a2..e7db69345 100644
--- a/app/models/protocol.rb
+++ b/app/models/protocol.rb
@@ -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 }
diff --git a/app/models/report_element.rb b/app/models/report_element.rb
index e110e92a8..81904195f 100644
--- a/app/models/report_element.rb
+++ b/app/models/report_element.rb
@@ -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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 2fa75675b..76e36d5f9 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -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
diff --git a/app/models/repository_asset_value.rb b/app/models/repository_asset_value.rb
index d64ffc07b..2e7cbb981 100644
--- a/app/models/repository_asset_value.rb
+++ b/app/models/repository_asset_value.rb
@@ -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
diff --git a/app/models/repository_base.rb b/app/models/repository_base.rb
new file mode 100644
index 000000000..e8e65f1e3
--- /dev/null
+++ b/app/models/repository_base.rb
@@ -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
diff --git a/app/models/repository_cell.rb b/app/models/repository_cell.rb
index cd6988725..cd9ef6fea 100644
--- a/app/models/repository_cell.rb
+++ b/app/models/repository_cell.rb
@@ -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
diff --git a/app/models/repository_checklist_item.rb b/app/models/repository_checklist_item.rb
index a754342bb..1d8f142f0 100644
--- a/app/models/repository_checklist_item.rb
+++ b/app/models/repository_checklist_item.rb
@@ -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
diff --git a/app/models/repository_checklist_value.rb b/app/models/repository_checklist_value.rb
index bfde69c25..cded875af 100644
--- a/app/models/repository_checklist_value.rb
+++ b/app/models/repository_checklist_value.rb
@@ -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
diff --git a/app/models/repository_column.rb b/app/models/repository_column.rb
index 6b9205c15..20f9b95c0 100644
--- a/app/models/repository_column.rb
+++ b/app/models/repository_column.rb
@@ -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
diff --git a/app/models/repository_date_time_range_value_base.rb b/app/models/repository_date_time_range_value_base.rb
index 698c2e6a9..206bcef74 100644
--- a/app/models/repository_date_time_range_value_base.rb
+++ b/app/models/repository_date_time_range_value_base.rb
@@ -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
diff --git a/app/models/repository_date_time_value_base.rb b/app/models/repository_date_time_value_base.rb
index d16b0fc31..5f32e7c50 100644
--- a/app/models/repository_date_time_value_base.rb
+++ b/app/models/repository_date_time_value_base.rb
@@ -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
diff --git a/app/models/repository_list_item.rb b/app/models/repository_list_item.rb
index 8c6e4e2f8..94edb408c 100644
--- a/app/models/repository_list_item.rb
+++ b/app/models/repository_list_item.rb
@@ -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'
diff --git a/app/models/repository_list_value.rb b/app/models/repository_list_value.rb
index 9923dda5f..b99b08345 100644
--- a/app/models/repository_list_value.rb
+++ b/app/models/repository_list_value.rb
@@ -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
diff --git a/app/models/repository_number_value.rb b/app/models/repository_number_value.rb
index 9ee3eac7d..c5f8393d4 100644
--- a/app/models/repository_number_value.rb
+++ b/app/models/repository_number_value.rb
@@ -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)
diff --git a/app/models/repository_row.rb b/app/models/repository_row.rb
index eb87cbf7b..1de8b9830 100644
--- a/app/models/repository_row.rb
+++ b/app/models/repository_row.rb
@@ -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
diff --git a/app/models/repository_snapshot.rb b/app/models/repository_snapshot.rb
new file mode 100644
index 000000000..d7383a557
--- /dev/null
+++ b/app/models/repository_snapshot.rb
@@ -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
diff --git a/app/models/repository_status_item.rb b/app/models/repository_status_item.rb
index dc8233d4c..546f42841 100644
--- a/app/models/repository_status_item.rb
+++ b/app/models/repository_status_item.rb
@@ -1,14 +1,17 @@
# frozen_string_literal: true
class RepositoryStatusItem < ApplicationRecord
- validates :repository, :repository_column, :icon, presence: true
+ validates :repository_column, :icon, presence: true
validates :status, presence: true, length: { minimum: Constants::NAME_MIN_LENGTH,
maximum: Constants::NAME_MAX_LENGTH }
- belongs_to :repository
belongs_to :repository_column
belongs_to :created_by, foreign_key: 'created_by_id', class_name: 'User', optional: true,
inverse_of: :created_repository_status_types
belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User', optional: true,
inverse_of: :modified_repository_status_types
has_many :repository_status_values, inverse_of: :repository_status_item, dependent: :destroy
+
+ def data
+ "#{icon} #{status}"
+ end
end
diff --git a/app/models/repository_status_value.rb b/app/models/repository_status_value.rb
index 7398a42aa..4ed93e1bd 100644
--- a/app/models/repository_status_value.rb
+++ b/app/models/repository_status_value.rb
@@ -29,10 +29,24 @@ class RepositoryStatusValue < ApplicationRecord
save!
end
+ def snapshot!(cell_snapshot)
+ value_snapshot = dup
+ status_item = cell_snapshot.repository_column
+ .repository_status_items
+ .find { |item| item.data == repository_status_item.data }
+ value_snapshot.assign_attributes(
+ repository_cell: cell_snapshot,
+ repository_status_item: status_item,
+ created_at: created_at,
+ updated_at: updated_at
+ )
+ value_snapshot.save!
+ end
+
def data
return nil unless repository_status_item
- "#{repository_status_item.icon} #{repository_status_item.status}"
+ repository_status_item.data
end
def self.new_with_payload(payload, attributes)
@@ -46,7 +60,9 @@ class RepositoryStatusValue < ApplicationRecord
def self.import_from_text(text, attributes, _options = {})
icon = text[0]
- status = text[1..-1].strip
+ status = text[1..-1]&.strip
+ return nil if status.nil? || icon.nil?
+
value = new(attributes)
column = attributes.dig(:repository_cell_attributes, :repository_column)
status_item = column.repository_status_items.find { |item| item.status == status }
@@ -55,8 +71,7 @@ class RepositoryStatusValue < ApplicationRecord
status_item = column.repository_status_items.new(icon: icon,
status: status,
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 status_item.save
end
diff --git a/app/models/repository_table_state.rb b/app/models/repository_table_state.rb
index 1f187adca..7e3286459 100644
--- a/app/models/repository_table_state.rb
+++ b/app/models/repository_table_state.rb
@@ -2,7 +2,7 @@
class RepositoryTableState < ApplicationRecord
belongs_to :user, inverse_of: :repository_table_states
- belongs_to :repository, inverse_of: :repository_table_states
+ belongs_to :repository, class_name: 'RepositoryBase', inverse_of: :repository_table_states
validates :user, :repository, presence: true
end
diff --git a/app/models/repository_text_value.rb b/app/models/repository_text_value.rb
index 154006bfc..ba10d2538 100644
--- a/app/models/repository_text_value.rb
+++ b/app/models/repository_text_value.rb
@@ -31,6 +31,16 @@ class RepositoryTextValue < 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 = payload
diff --git a/app/models/user.rb b/app/models/user.rb
index ddeb1e5fc..45907fcf0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -43,8 +43,7 @@ class User < ApplicationRecord
recent: true,
recent_email: false,
system_message_email: false
- },
- tooltips_enabled: true
+ }
)
store_accessor :variables, :export_vars
diff --git a/app/permissions/experiment.rb b/app/permissions/experiment.rb
index d404d1528..ec4909f27 100644
--- a/app/permissions/experiment.rb
+++ b/app/permissions/experiment.rb
@@ -61,7 +61,9 @@ Canaid::Permissions.register_for(MyModule) do
assign_repository_rows_to_module
assign_sample_to_module
complete_module
- create_comments_in_module)
+ create_comments_in_module
+ create_my_module_repository_snapshot
+ manage_my_module_repository_snapshots)
.each do |perm|
can perm do |_, my_module|
my_module.active? &&
@@ -112,6 +114,16 @@ Canaid::Permissions.register_for(MyModule) do
can :create_comments_in_module do |user, my_module|
can_create_comments_in_project?(user, my_module.experiment.project)
end
+
+ # module: create a snapshot of repository item
+ can :create_my_module_repository_snapshot do |user, my_module|
+ user.is_technician_or_higher_of_project?(my_module.experiment.project)
+ end
+
+ # module: make a repository snapshot selected
+ can :manage_my_module_repository_snapshots do |user, my_module|
+ user.is_technician_or_higher_of_project?(my_module.experiment.project)
+ end
end
Canaid::Permissions.register_for(Protocol) do
diff --git a/app/permissions/repository.rb b/app/permissions/repository.rb
index 47b78d9c8..8b578e3e2 100644
--- a/app/permissions/repository.rb
+++ b/app/permissions/repository.rb
@@ -1,9 +1,28 @@
# frozen_string_literal: true
-Canaid::Permissions.register_for(Repository) do
+Canaid::Permissions.register_for(RepositoryBase) do
# repository: read/export
can :read_repository do |user, repository|
- user.teams.include?(repository.team) || repository.shared_with?(user.current_team)
+ if repository.is_a?(RepositorySnapshot)
+ user.teams.include?(repository.team)
+ else
+ user.teams.include?(repository.team) || repository.shared_with?(user.current_team)
+ end
+ end
+end
+
+Canaid::Permissions.register_for(Repository) do
+ # Should be no provisioning snapshots for repository for all the specified permissions
+ %i(manage_repository
+ create_repository_rows
+ manage_repository_rows
+ update_repository_rows
+ delete_repository_rows
+ create_repository_columns)
+ .each do |perm|
+ can perm do |_, repository|
+ repository.repository_snapshots.provisioning.none?
+ end
end
# repository: update, delete
@@ -16,6 +35,16 @@ Canaid::Permissions.register_for(Repository) do
user.is_admin_of_team?(repository.team) unless repository.shared_with?(user.current_team)
end
+ # repository: make a snapshot with assigned rows
+ can :create_repository_snapshot do |user, repository|
+ user.is_normal_user_or_admin_of_team?(repository.team)
+ end
+
+ # repository: delete a snapshot with assigned rows
+ can :delete_repository_snapshot do |user, repository|
+ user.is_normal_user_or_admin_of_team?(repository.team)
+ end
+
# repository: create/import record
can :create_repository_rows do |user, repository|
if repository.shared_with?(user.current_team)
diff --git a/app/permissions/repository_column.rb b/app/permissions/repository_column.rb
index b1af4ecf3..3669014cb 100644
--- a/app/permissions/repository_column.rb
+++ b/app/permissions/repository_column.rb
@@ -4,6 +4,7 @@ Canaid::Permissions.register_for(RepositoryColumn) do
# repository: update/delete field
# Tested in scope of RepositoryPermissions spec
can :manage_repository_column do |user, repository_column|
- can_create_repository_columns?(user, repository_column.repository)
+ repository_column.repository.repository_snapshots.provisioning.none? &&
+ can_create_repository_columns?(user, repository_column.repository)
end
end
diff --git a/app/serializers/api/v1/task_serializer.rb b/app/serializers/api/v1/task_serializer.rb
index 20660805c..eaef1521b 100644
--- a/app/serializers/api/v1/task_serializer.rb
+++ b/app/serializers/api/v1/task_serializer.rb
@@ -4,7 +4,7 @@ module Api
module V1
class TaskSerializer < ActiveModel::Serializer
type :tasks
- attributes :id, :name, :due_date, :description, :state, :archived
+ attributes :id, :name, :started_on, :due_date, :description, :state, :archived
has_many :output_tasks, key: :outputs,
serializer: TaskSerializer,
class_name: 'MyModule'
diff --git a/app/services/create_my_module_service.rb b/app/services/create_my_module_service.rb
index 80a305508..ac4324045 100644
--- a/app/services/create_my_module_service.rb
+++ b/app/services/create_my_module_service.rb
@@ -29,6 +29,8 @@ class CreateMyModuleService
new_pos = @my_module.get_new_position
@my_module.x = new_pos[:x]
@my_module.y = new_pos[:y]
+ @my_module.created_by = @user
+ @my_module.last_modified_by = @user
@my_module.save!
create_my_module_activity
diff --git a/app/services/dashboard/recent_work_service.rb b/app/services/dashboard/recent_work_service.rb
index dbf41d6d6..699319224 100644
--- a/app/services/dashboard/recent_work_service.rb
+++ b/app/services/dashboard/recent_work_service.rb
@@ -113,7 +113,7 @@ module Dashboard
project_path(object_id)
when 'Protocol'
edit_protocol_path(object_id)
- when 'Repository'
+ when 'RepositoryBase'
repository_path(object_id)
when 'Report'
edit_project_report_path(recent_object[:report_project_id], object_id) if recent_object[:report_project_id]
@@ -130,7 +130,7 @@ module Dashboard
elsif recent_object[:group_id].include?('prt')
'Protocol'
elsif recent_object[:group_id].include?('inv')
- 'Repository'
+ 'RepositoryBase'
elsif recent_object[:group_id].include?('rpt')
'Report'
end
diff --git a/app/services/reports/docx/draw_my_module_repository.rb b/app/services/reports/docx/draw_my_module_repository.rb
index f79118f87..9ac872445 100644
--- a/app/services/reports/docx/draw_my_module_repository.rb
+++ b/app/services/reports/docx/draw_my_module_repository.rb
@@ -6,10 +6,11 @@ module Reports::Docx::DrawMyModuleRepository
return unless my_module
repository_id = subject['id']['repository_id']
- repository_data = my_module.repository_docx_json(repository_id)
+ repository = ::RepositoryBase.find(repository_id)
+ repository_data = my_module.repository_docx_json(repository)
+
return false unless repository_data[:rows].any?
- repository = ::Repository.find(repository_id)
table = prepare_row_columns(repository_data)
@docx.p
diff --git a/app/services/repositories/snapshot_provisioning_service.rb b/app/services/repositories/snapshot_provisioning_service.rb
new file mode 100644
index 000000000..5d37672db
--- /dev/null
+++ b/app/services/repositories/snapshot_provisioning_service.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Repositories
+ class SnapshotProvisioningService
+ extend Service
+
+ attr_reader :repository_snapshot, :errors
+
+ def initialize(repository_snapshot:)
+ @repository_snapshot = repository_snapshot
+ @errors = {}
+ end
+
+ def call
+ return self unless valid?
+
+ ActiveRecord::Base.transaction do
+ repository = @repository_snapshot.original_repository
+
+ repository.repository_columns.each do |column|
+ column.snapshot!(@repository_snapshot)
+ end
+
+ repository_rows = repository.repository_rows
+ .joins(:my_module_repository_rows)
+ .where(my_module_repository_rows: { my_module: @repository_snapshot.my_module })
+
+ repository_rows.find_each do |original_row|
+ original_row.snapshot!(@repository_snapshot)
+ end
+
+ @repository_snapshot.ready!
+ rescue ActiveRecord::RecordInvalid => e
+ @errors[e.record.class.name.underscore] = e.record.errors.full_messages
+ Rails.logger.error e.message
+ raise ActiveRecord::Rollback
+ end
+
+ self
+ end
+
+ def succeed?
+ @errors.none?
+ end
+
+ private
+
+ def valid?
+ unless @repository_snapshot
+ @errors[:invalid_arguments] =
+ { 'repository_snapshot': @repository_snapshot }
+ .map do |key, value|
+ if value.nil?
+ I18n.t('repositories.my_module_assigned_snapshot_service.invalid_arguments', key: key.capitalize)
+ end
+ end.compact
+ return false
+ end
+ true
+ end
+ end
+end
diff --git a/app/services/repository_columns/create_column_service.rb b/app/services/repository_columns/create_column_service.rb
index cea9f3cb1..f2de883bf 100644
--- a/app/services/repository_columns/create_column_service.rb
+++ b/app/services/repository_columns/create_column_service.rb
@@ -26,15 +26,15 @@ module RepositoryColumns
def column_attributes
@params[:repository_status_items_attributes]&.map do |m|
- m.merge!(repository_id: @repository.id, created_by_id: @user.id, last_modified_by_id: @user.id)
+ m.merge!(created_by_id: @user.id, last_modified_by_id: @user.id)
end
@params[:repository_list_items_attributes]&.map do |m|
- m.merge!(repository_id: @repository.id, created_by_id: @user.id, last_modified_by_id: @user.id)
+ m.merge!(created_by_id: @user.id, last_modified_by_id: @user.id)
end
@params[:repository_checklist_items_attributes]&.map do |m|
- m.merge!(repository_id: @repository.id, created_by_id: @user.id, last_modified_by_id: @user.id)
+ m.merge!(created_by_id: @user.id, last_modified_by_id: @user.id)
end
@params.merge(repository_id: @repository.id, created_by_id: @user.id, data_type: @column_type)
diff --git a/app/services/repository_columns/update_checklist_column_service.rb b/app/services/repository_columns/update_checklist_column_service.rb
index 7fbee82bb..062309d8f 100644
--- a/app/services/repository_columns/update_checklist_column_service.rb
+++ b/app/services/repository_columns/update_checklist_column_service.rb
@@ -18,7 +18,7 @@ module RepositoryColumns
to_be_deleted = existing_items_names - updating_items_names
to_be_created = updating_items_names - existing_items_names
- if @column.repository_list_items.size - to_be_deleted.size + to_be_created.size >=
+ if @column.repository_list_items.size - to_be_deleted.size + to_be_created.size >
Constants::REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN
@errors[:repository_column] = { repository_checklist_items: 'too many items' }
@@ -35,7 +35,6 @@ module RepositoryColumns
to_be_created.each do |item|
RepositoryChecklistItem.create!(
- repository: @repository,
repository_column: @column,
data: item,
created_by: @user,
diff --git a/app/services/repository_columns/update_column_service.rb b/app/services/repository_columns/update_column_service.rb
index 1b1f3d194..69dc9d78d 100644
--- a/app/services/repository_columns/update_column_service.rb
+++ b/app/services/repository_columns/update_column_service.rb
@@ -25,7 +25,7 @@ module RepositoryColumns
def column_attributes
@params[:repository_status_items_attributes]&.map do |m|
# assign for new records only
- m.merge!(repository_id: @repository.id, created_by_id: @user.id, last_modified_by_id: @user.id) unless m[:id]
+ m.merge!(created_by_id: @user.id, last_modified_by_id: @user.id) unless m[:id]
end
@params
diff --git a/app/services/repository_columns/update_list_column_service.rb b/app/services/repository_columns/update_list_column_service.rb
index 6dceb9315..a992878a0 100644
--- a/app/services/repository_columns/update_list_column_service.rb
+++ b/app/services/repository_columns/update_list_column_service.rb
@@ -18,7 +18,7 @@ module RepositoryColumns
to_be_deleted = existing_items_names - updating_items_names
to_be_created = updating_items_names - existing_items_names
- if @column.repository_list_items.size - to_be_deleted.size + to_be_created.size >=
+ if @column.repository_list_items.size - to_be_deleted.size + to_be_created.size >
Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN
@errors[:repository_column] = { repository_list_items: 'too many items' }
@@ -35,7 +35,6 @@ module RepositoryColumns
to_be_created.each do |item|
RepositoryListItem.create!(
- repository: @repository,
repository_column: @column,
data: item,
created_by: @user,
diff --git a/app/services/repository_datatable_service.rb b/app/services/repository_datatable_service.rb
index 415f6ecf6..e9f7b73c5 100644
--- a/app/services/repository_datatable_service.rb
+++ b/app/services/repository_datatable_service.rb
@@ -18,7 +18,7 @@ class RepositoryDatatableService
def create_columns_mappings
# Make mappings of custom columns, so we have same id for every
# column
- index = 6
+ index = @repository.default_columns_count
@mappings = {}
@repository.repository_columns.order(:id).each do |column|
@mappings[column.id] = index.to_s
@@ -42,16 +42,15 @@ class RepositoryDatatableService
'LEFT OUTER JOIN "my_module_repository_rows" '\
'ON "my_module_repository_rows"."repository_row_id" = "repository_rows"."id" '\
'AND "my_module_repository_rows"."my_module_id" = ' + @my_module.id.to_s
- )
+ ).select('CASE WHEN my_module_repository_rows.id IS NOT NULL '\
+ 'THEN true ELSE false END as row_assigned').group('my_module_repository_rows.id')
end
- repository_rows = repository_rows.select('COUNT(my_module_repository_rows.id) AS "assigned_my_modules_count"')
- else
- repository_rows = repository_rows
- .left_outer_joins(my_module_repository_rows: { my_module: :experiment })
- .select('COUNT(my_module_repository_rows.id) AS "assigned_my_modules_count"')
- .select('COUNT(DISTINCT my_modules.experiment_id) AS "assigned_experiments_count"')
- .select('COUNT(DISTINCT experiments.project_id) AS "assigned_projects_count"')
end
+ repository_rows = repository_rows
+ .left_outer_joins(my_module_repository_rows: { my_module: :experiment })
+ .select('COUNT(my_module_repository_rows.id) AS "assigned_my_modules_count"')
+ .select('COUNT(DISTINCT my_modules.experiment_id) AS "assigned_experiments_count"')
+ .select('COUNT(DISTINCT experiments.project_id) AS "assigned_projects_count"')
repository_rows = repository_rows.preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
@repository_rows = sort_rows(order_obj, repository_rows)
@@ -135,8 +134,8 @@ class RepositoryDatatableService
sorting_data_type = sorting_column.data_type.constantize
- if sorting_column.repository_checklist_value?
- cells = RepositoryCell.joins(sorting_data_type::SORTABLE_VALUE_INCLUDE)
+ cells = if sorting_column.repository_checklist_value?
+ RepositoryCell.joins(sorting_data_type::SORTABLE_VALUE_INCLUDE)
.where('repository_cells.repository_column_id': sorting_column.id)
.select("repository_cells.repository_row_id,
STRING_AGG(
@@ -144,12 +143,12 @@ class RepositoryDatatableService
ORDER BY #{sorting_data_type::SORTABLE_COLUMN_NAME}) AS value")
.group('repository_cells.repository_row_id')
- else
- cells = RepositoryCell.joins(sorting_data_type::SORTABLE_VALUE_INCLUDE)
+ else
+ RepositoryCell.joins(sorting_data_type::SORTABLE_VALUE_INCLUDE)
.where('repository_cells.repository_column_id': sorting_column.id)
.select("repository_cells.repository_row_id,
#{sorting_data_type::SORTABLE_COLUMN_NAME} AS value")
- end
+ end
records.joins("LEFT OUTER JOIN (#{cells.to_sql}) AS values ON values.repository_row_id = repository_rows.id")
.group('values.value')
diff --git a/app/services/repository_rows/my_module_assign_unassign_service.rb b/app/services/repository_rows/my_module_assign_unassign_service.rb
new file mode 100644
index 000000000..f0a3caa64
--- /dev/null
+++ b/app/services/repository_rows/my_module_assign_unassign_service.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+module RepositoryRows
+ class MyModuleAssignUnassignService
+ extend Service
+
+ attr_reader :repository,
+ :my_module,
+ :user,
+ :params,
+ :assigned_rows_count,
+ :unassigned_rows_count,
+ :errors
+
+ def initialize(my_module:, repository:, user:, params:)
+ @my_module = my_module
+ @repository = repository
+ @user = user
+ @params = params
+ @assigned_rows_count = 0
+ @unassigned_rows_count = 0
+ @errors = {}
+ end
+
+ def call
+ return self unless valid?
+
+ ActiveRecord::Base.transaction do
+ if params[:downstream] == 'true'
+ @my_module.downstream_modules.each do |downstream_module|
+ unassign_repository_rows_from_my_module(downstream_module)
+ assign_repository_rows_to_my_module(downstream_module)
+ end
+ else
+ unassign_repository_rows_from_my_module(@my_module)
+ assign_repository_rows_to_my_module(@my_module)
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ @errors[e.record.class.name.underscore] = e.record.errors.full_messages
+ raise ActiveRecord::Rollback
+ end
+
+ self
+ end
+
+ def succeed?
+ @errors.none?
+ end
+
+ private
+
+ def unassign_repository_rows_from_my_module(my_module)
+ return [] unless params[:rows_to_unassign]
+
+ unassigned_names = my_module.my_module_repository_rows
+ .joins(:repository_row)
+ .where(repository_rows: { repository: @repository, id: params[:rows_to_unassign] })
+ .select('my_module_repository_rows.*, repository_rows.name AS name')
+ .destroy_all
+ .pluck(:name)
+
+ return [] if unassigned_names.blank?
+
+ # update row last_modified_by
+ my_module.repository_rows
+ .where(repository: @repository, id: params[:rows_to_unassign])
+ .update_all(last_modified_by_id: @user.id)
+
+ Activities::CreateActivityService.call(activity_type: :unassign_repository_record,
+ owner: @user,
+ team: my_module.experiment.project.team,
+ project: my_module.experiment.project,
+ subject: my_module,
+ message_items: { my_module: my_module.id,
+ repository: @repository.id,
+ record_names: unassigned_names.join(', ') })
+
+ @unassigned_rows_count += unassigned_names.count
+ end
+
+ def assign_repository_rows_to_my_module(my_module)
+ assigned_names = []
+
+ return [] unless params[:rows_to_assign]
+
+ unassigned_rows = @repository.repository_rows
+ .joins("LEFT OUTER JOIN my_module_repository_rows "\
+ "ON repository_rows.id = my_module_repository_rows.repository_row_id "\
+ "AND my_module_repository_rows.my_module_id = #{my_module.id.to_i}")
+ .where(my_module_repository_rows: { id: nil })
+ .where(id: @params[:rows_to_assign])
+
+ return [] unless unassigned_rows.any?
+
+ unassigned_rows.find_each do |repository_row|
+ MyModuleRepositoryRow.create!(my_module: my_module,
+ repository_row: repository_row,
+ assigned_by: @user)
+ assigned_names << repository_row.name
+ end
+
+ return [] if assigned_names.blank?
+
+ Activities::CreateActivityService.call(activity_type: :assign_repository_record,
+ owner: @user,
+ team: my_module.experiment.project.team,
+ project: my_module.experiment.project,
+ subject: my_module,
+ message_items: { my_module: my_module.id,
+ repository: @repository.id,
+ record_names: assigned_names.join(', ') })
+ @assigned_rows_count += assigned_names.count
+ end
+
+ def valid?
+ unless @my_module && @repository && @user && @params
+ @errors[:invalid_arguments] =
+ { 'my_module': @my_module,
+ 'repository': @repository,
+ 'params': @params,
+ 'user': @user }
+ .map do |key, value|
+ I18n.t('repositories.my_module_update_row.invalid_arguments', key: key.capitalize) if value.nil?
+ end.compact
+ return false
+ end
+ true
+ end
+ end
+end
diff --git a/app/services/repository_snapshot_datatable_service.rb b/app/services/repository_snapshot_datatable_service.rb
new file mode 100644
index 000000000..abe58b313
--- /dev/null
+++ b/app/services/repository_snapshot_datatable_service.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+class RepositorySnapshotDatatableService < RepositoryDatatableService
+ private
+
+ def create_columns_mappings
+ index = @repository.default_columns_count
+ @mappings = {}
+ @repository.repository_columns.order(:parent_id).each do |column|
+ @mappings[column.id] = index.to_s
+ index += 1
+ end
+ end
+
+ def process_query
+ search_value = build_conditions(@params)[:search_value]
+ order_obj = build_conditions(@params)[:order_by_column]
+
+ repository_rows = fetch_rows(search_value)
+
+ repository_rows = repository_rows.preload(Extends::REPOSITORY_ROWS_PRELOAD_RELATIONS)
+
+ @repository_rows = sort_rows(order_obj, repository_rows)
+ end
+
+ def fetch_rows(search_value)
+ repository_rows = @repository.repository_rows
+
+ @all_count = repository_rows.count
+
+ if search_value.present?
+ matched_by_user = repository_rows.joins(:created_by).where_attributes_like('users.full_name', search_value)
+
+ repository_row_matches = repository_rows
+ .where_attributes_like(['repository_rows.name', 'repository_rows.id'], search_value)
+ results = repository_rows.where(id: repository_row_matches)
+ results = results.or(repository_rows.where(id: matched_by_user))
+
+ Extends::REPOSITORY_EXTRA_SEARCH_ATTR.each do |field, include_hash|
+ custom_cell_matches = repository_rows.joins(repository_cells: include_hash)
+ .where_attributes_like(field, search_value)
+ results = results.or(repository_rows.where(id: custom_cell_matches))
+ end
+
+ repository_rows = results
+ end
+
+ repository_rows.left_outer_joins(:created_by)
+ .select('repository_rows.*')
+ .select('COUNT("repository_rows"."id") OVER() AS filtered_count')
+ .group('repository_rows.id')
+ end
+
+ def build_sortable_columns
+ array = [
+ 'repository_rows.parent_id',
+ 'repository_rows.name',
+ 'repository_rows.created_at',
+ 'users.full_name'
+ ]
+ @repository.repository_columns.count.times do
+ array << 'repository_cell.value'
+ end
+ array
+ end
+end
diff --git a/app/services/smart_annotations/html_preview.rb b/app/services/smart_annotations/html_preview.rb
index 1650db644..c9a390d82 100644
--- a/app/services/smart_annotations/html_preview.rb
+++ b/app/services/smart_annotations/html_preview.rb
@@ -48,7 +48,7 @@ module SmartAnnotations
repository_name = fetch_repository_name(object)
return "" \
"#{trim_repository_name(repository_name)} " \
- "#{object.name} "
end
"Inv " \
diff --git a/app/services/spreadsheet_parser.rb b/app/services/spreadsheet_parser.rb
index 31a001a85..aedd01b0a 100644
--- a/app/services/spreadsheet_parser.rb
+++ b/app/services/spreadsheet_parser.rb
@@ -39,21 +39,27 @@ class SpreadsheetParser
rows = spreadsheet_enumerator(sheet)
header = []
columns = []
- i = 1
- rows.each do |row_values|
- # Creek XLSX parser returns Hash of the row, Roo - Array
- row = parse_row(row_values, sheet)
- header = row if i == 1 && row
- columns = row if i == 2 && row
- i += 1
- break if i > 2
+ rows.take(2).each_with_index do |row_values, i|
+ row = parse_row(row_values, sheet, header: i.zero?)
+ if row && i.zero?
+ header = row
+ else
+ columns = row
+ end
end
+
return header, columns
end
- def self.parse_row(row, sheet)
- if sheet.is_a?(Roo::Excelx)
- row.map { |cell| cell&.formatted_value }
+ def self.parse_row(row, sheet, header: false)
+ if sheet.is_a?(Roo::Excelx) && !header
+ row.map do |cell|
+ if cell.is_a?(Roo::Excelx::Cell::Number) && cell.format == 'General'
+ cell&.value&.to_d
+ else
+ cell&.formatted_value
+ end
+ end
else
row.map(&:to_s)
end
diff --git a/app/services/tasks/samples_to_repository_migration_service.rb b/app/services/tasks/samples_to_repository_migration_service.rb
index 0b15a6b65..4b2a3a2f8 100644
--- a/app/services/tasks/samples_to_repository_migration_service.rb
+++ b/app/services/tasks/samples_to_repository_migration_service.rb
@@ -83,7 +83,6 @@ module Tasks
last_modified_by = item['last_modified_by_id'] || team.created_by_id
timestamp = conn.quote(Time.now.to_s(:db))
values = [
- repository.id,
sample_group.id,
conn.quote(item.fetch('name') { "sample group item (#{index})" }),
created_by,
@@ -93,8 +92,7 @@ module Tasks
]
list_item_sql = <<-SQL
INSERT INTO repository_list_items
- (repository_id,
- repository_column_id,
+ (repository_column_id,
data,
created_by_id,
last_modified_by_id,
@@ -109,7 +107,6 @@ module Tasks
last_modified_by = item['last_modified_by_id'] || team.created_by_id
timestamp = conn.quote(Time.now.to_s(:db))
values = [
- repository.id,
sample_type.id,
conn.quote(item.fetch('name') { "sample type item (#{index})" }),
created_by,
@@ -119,8 +116,7 @@ module Tasks
]
list_item_sql = <<-SQL
INSERT INTO repository_list_items
- (repository_id,
- repository_column_id,
+ (repository_column_id,
data,
created_by_id,
last_modified_by_id,
diff --git a/app/services/team_importer.rb b/app/services/team_importer.rb
index 16c9453e4..c5572fdcb 100644
--- a/app/services/team_importer.rb
+++ b/app/services/team_importer.rb
@@ -313,6 +313,8 @@ class TeamImporter
if activity.subject_id.present?
if activity.subject_type == 'Team'
activity.subject_id = team.id
+ elsif activity.subject_type == 'RepositoryBase'
+ activity.subject_id = @repository_mappings[activity.subject_id]
else
mappings = instance_variable_get("@#{activity.subject_type.underscore}_mappings")
activity.subject_id = mappings[activity.subject_id]
diff --git a/app/utilities/first_time_data_generator.rb b/app/utilities/first_time_data_generator.rb
index 16a623d6b..50d0cf2c0 100644
--- a/app/utilities/first_time_data_generator.rb
+++ b/app/utilities/first_time_data_generator.rb
@@ -75,8 +75,7 @@ module FirstTimeDataGenerator
data: name,
created_by: user,
last_modified_by: user,
- repository_column: repository_column_sample_types,
- repository: repository
+ repository_column: repository_column_sample_types
)
# Check if it already exists
@@ -96,8 +95,7 @@ module FirstTimeDataGenerator
data: name,
created_by: user,
last_modified_by: user,
- repository_column: repository_column_sample_groups,
- repository: repository
+ repository_column: repository_column_sample_groups
)
# Check if it already exists
diff --git a/app/utilities/protocols_exporter.rb b/app/utilities/protocols_exporter.rb
index 1c05dfa02..94b87d3e2 100644
--- a/app/utilities/protocols_exporter.rb
+++ b/app/utilities/protocols_exporter.rb
@@ -116,10 +116,9 @@ module ProtocolsExporter
if step.tables.count > 0
step_xml << "\n"
step.tables.order(:id).each do |table|
- table_xml = "\n"
- table_xml << "#{table.contents.unpack1('H*')}" \
- " \n"
+ table_xml = "\n"
+ table_xml << "#{table.name} \n"
+ table_xml << "#{table.contents.unpack1('H*')} \n"
table_xml << " \n"
step_xml << table_xml
end
diff --git a/app/utilities/repository_import_parser/importer.rb b/app/utilities/repository_import_parser/importer.rb
index f016bf2cc..09445fa66 100644
--- a/app/utilities/repository_import_parser/importer.rb
+++ b/app/utilities/repository_import_parser/importer.rb
@@ -75,7 +75,10 @@ module RepositoryImportParser
SpreadsheetParser.parse_row(row, @sheet).each_with_index do |value, index|
if index == @name_index
new_row =
- RepositoryRow.new(name: value, repository: @repository, created_by: @user, last_modified_by: @user)
+ RepositoryRow.new(name: try_decimal_to_string(value),
+ repository: @repository,
+ created_by: @user,
+ last_modified_by: @user)
unless new_row.valid?
errors = true
break
@@ -128,6 +131,9 @@ module RepositoryImportParser
row.reject { |k| k == :repository_row }.each do |index, value|
column = @columns[index]
+ value = try_decimal_to_string(value) unless column.repository_number_value?
+ next if value.nil?
+
cell_value_attributes = { created_by: @user,
last_modified_by: @user,
repository_cell_attributes: { repository_row: row[:repository_row],
@@ -150,5 +156,13 @@ module RepositoryImportParser
data_type.to_s.constantize.import(cell_values, recursive: true, validate: false)
end
end
+
+ def try_decimal_to_string(value)
+ if value.is_a?(BigDecimal)
+ value.frac.zero? ? value.to_i.to_s : value.to_s
+ else
+ value
+ end
+ end
end
end
diff --git a/app/views/canvas/_edit.html.erb b/app/views/canvas/_edit.html.erb
index 987f971a5..e0d346eba 100644
--- a/app/views/canvas/_edit.html.erb
+++ b/app/views/canvas/_edit.html.erb
@@ -10,9 +10,7 @@
>
<%= bootstrap_form_tag url: canvas_experiment_url, method: "post", html: {class: "canvas-header"} do |f| %>
<% if can_manage_experiment?(@experiment) %>
- <%=link_to "", type: "button", class: "btn btn-primary help_tooltips", id: "canvas-new-module",
- data: { tooltiplink: I18n.t('tooltips.link.task.new'),
- tooltipcontent: I18n.t('tooltips.text.task.new') } do %>
+ <%=link_to "", type: "button", class: "btn btn-primary", id: "canvas-new-module" do %>
<%= t("experiments.canvas.edit.new_module") %>
diff --git a/app/views/canvas/_tags.html.erb b/app/views/canvas/_tags.html.erb
index d87b3258c..a2c17466b 100644
--- a/app/views/canvas/_tags.html.erb
+++ b/app/views/canvas/_tags.html.erb
@@ -1,8 +1,4 @@
-