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: - '
' - + '
' + textData + '
' - + '


' - + '' - + '
' - }) - .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 @@ -
+
<% tags2 = my_module.tags[0..3] %> <% tags2.each do |tag| %>
<% if can_manage_module?(my_module) %> @@ -31,11 +31,7 @@
<% if !my_module.completed? && can_manage_module?(my_module) %> <%= link_to due_date_my_module_path(my_module, format: :json), remote: true, - class: "due-date-link due-date-refresh help_tooltips", - data: { - tooltiplink: I18n.t('tooltips.link.task.due_date'), - tooltipcontent: I18n.t('tooltips.text.task.due_date') - } do %> + class: "due-date-link due-date-refresh" do %> <%= render partial: "my_modules/card_due_date_label.html.erb", locals: { my_module: my_module, format: :full_date } %> <% end %> <% else %> @@ -47,7 +43,7 @@
diff --git a/app/views/experiments/_dropdown_actions.html.erb b/app/views/experiments/_dropdown_actions.html.erb index 9203c425e..9919ffecd 100644 --- a/app/views/experiments/_dropdown_actions.html.erb +++ b/app/views/experiments/_dropdown_actions.html.erb @@ -15,21 +15,15 @@
  • <%= link_to t('experiments.clone.label_title'), clone_modal_experiment_url(experiment), remote: true, type: 'button', - class: 'clone-experiment help_tooltips', - data: { - tooltiplink: I18n.t('tooltips.link.experiment.copy'), - tooltipcontent: I18n.t('tooltips.text.experiment.copy') - } %>
  • + class: 'clone-experiment' %> + <% end %> <% if can_move_experiment?(experiment) %>
  • <%= link_to t('experiments.move.label_title'), move_modal_experiment_url(experiment), remote: true, type: 'button', - class: 'move-experiment help_tooltips', - data: { - tooltiplink: I18n.t('tooltips.link.experiment.move'), - tooltipcontent: I18n.t('tooltips.text.experiment.move') - } %>
  • + class: 'move-experiment'%> + <% end %> <% if can_archive_experiment?(experiment) %>
  • <%= link_to t('experiments.archive.label_title'), diff --git a/app/views/global_activities/_date_picker.html.erb b/app/views/global_activities/_date_picker.html.erb index b77add78a..b300965a2 100644 --- a/app/views/global_activities/_date_picker.html.erb +++ b/app/views/global_activities/_date_picker.html.erb @@ -4,20 +4,11 @@ <% end %> - <% - 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') - %> +
  • diff --git a/app/views/global_activities/references/_my_module.html.erb b/app/views/global_activities/references/_my_module.html.erb index c29f133c9..574f46e8c 100644 --- a/app/views/global_activities/references/_my_module.html.erb +++ b/app/views/global_activities/references/_my_module.html.erb @@ -6,11 +6,7 @@ <% if subject.archived? path = module_archive_experiment_path(subject.experiment) else - if (['assign_repository_record','unassign_repository_record'].include? type_of) - path = repository_my_module_path(subject,values["message_items"]["repository"]["id"]) - else - path = protocols_my_module_path(subject) - end + path = protocols_my_module_path(subject) end %> <%= route_to_other_team(path, team, diff --git a/app/views/global_activities/references/_report.html.erb b/app/views/global_activities/references/_report.html.erb index ede016a46..0abdcf90c 100644 --- a/app/views/global_activities/references/_report.html.erb +++ b/app/views/global_activities/references/_report.html.erb @@ -3,7 +3,7 @@
    <% if subject %> - <%= route_to_other_team(reports_path(subject), + <%= route_to_other_team(reports_path(subject, team: subject.team.id), team, subject.name&.truncate(Constants::NAME_TRUNCATION_LENGTH), title: subject.name) %> diff --git a/app/views/global_activities/references/_repository.html.erb b/app/views/global_activities/references/_repository_base.html.erb similarity index 88% rename from app/views/global_activities/references/_repository.html.erb rename to app/views/global_activities/references/_repository_base.html.erb index 13580ce4d..8c43fab04 100644 --- a/app/views/global_activities/references/_repository.html.erb +++ b/app/views/global_activities/references/_repository_base.html.erb @@ -3,7 +3,7 @@
    <% if subject %> - <%= route_to_other_team(repository_path(subject.id), + <%= route_to_other_team(repository_path(subject.id, team: subject.team.id), team, subject.name&.truncate(Constants::NAME_TRUNCATION_LENGTH), title: subject.name) %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index e9722c5b9..1256f75b9 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -37,7 +37,6 @@ data-atwho-repositories-url="<%= atwho_repositories_team_path(current_team) %>" data-atwho-rep-items-url="<%= atwho_rep_items_team_path(current_team) %>" data-atwho-menu-items="<%= atwho_menu_items_team_path(current_team) %>" - data-tooltips-enabled="<%= current_user.settings[:tooltips_enabled] %>" <% end %> > diff --git a/app/views/my_module_tags/_index_edit.html.erb b/app/views/my_module_tags/_index_edit.html.erb index acb7c1e48..d871f2f71 100644 --- a/app/views/my_module_tags/_index_edit.html.erb +++ b/app/views/my_module_tags/_index_edit.html.erb @@ -7,7 +7,7 @@
  • -
    +

    <%= tag.name %>

    @@ -62,7 +62,7 @@ {}, { class: 'selectpicker' }) %> <%= f.button class: 'btn btn-primary' do %> - + <%= t("experiments.canvas.modal_manage_tags.create") %> <% end %>
    diff --git a/app/views/my_modules/_description_form.html.erb b/app/views/my_modules/_description_form.html.erb index 358dde9e1..ac10307d5 100644 --- a/app/views/my_modules/_description_form.html.erb +++ b/app/views/my_modules/_description_form.html.erb @@ -2,7 +2,7 @@ <%= render partial: 'shared/tiny_mce_extra_buttons.html.erb' %>
    <%= custom_auto_link(@my_module.tinymce_render(:description), simple_format: false, @@ -11,7 +11,7 @@ <%= f.tiny_mce_editor(:description, id: :my_module_description_textarea, class: 'hidden', - placeholder: t('my_modules.module_header.empty_description_edit_label'), + placeholder: t('my_modules.notes.empty_description_edit_label'), hide_label: true, value: sanitize_input(@my_module.tinymce_render(:description)), autocomplete: 'off', diff --git a/app/views/my_modules/_due_date_label.html.erb b/app/views/my_modules/_due_date_label.html.erb index 01bd84101..d8f1835d2 100644 --- a/app/views/my_modules/_due_date_label.html.erb +++ b/app/views/my_modules/_due_date_label.html.erb @@ -14,6 +14,8 @@ <% elsif my_module.due_date %> <%= l(my_module.due_date, format: :full) %> <% else %> - <%= t('experiments.canvas.full_zoom.no_due_date') %> + + <%= t('my_modules.details.no_due_date_placeholder') %> + <% end %> diff --git a/app/views/my_modules/_module_header.html.erb b/app/views/my_modules/_module_header.html.erb deleted file mode 100644 index 29ce458c9..000000000 --- a/app/views/my_modules/_module_header.html.erb +++ /dev/null @@ -1,65 +0,0 @@ -
    -
    -
    - - -
    - <%= l(@my_module.created_at, format: :full) %> -
    - -
    - - -
    - <%= render partial: "module_header_due_date.html.erb", - locals: { my_module: @my_module } %> -
    -
    - - - -
    -
    - - <%= t('my_modules.states.state_label') %> -
    - - <%= render partial: "module_state_label.html.erb", - locals: { my_module: @my_module } %> - -
    -
    - -
    - <%= render partial: "my_modules/state_buttons.html.erb" %> -
    - -
    -
    - - - <%= render partial: "my_modules/tags", locals: { my_module: @my_module, editable: can_manage_module?(@my_module) } %> -
    -
    - - -
    -
    - <%= t('my_modules.module_header.description_label') %> -
    -
    - <% if can_manage_module?(@my_module) %> - <%= render partial: "description_form" %> - <% elsif @my_module.description.present? %> - <%= custom_auto_link(@my_module.tinymce_render(:description), - simple_format: false, - tags: %w(img), - team: current_team) %> - <% else %> - <%= t('my_modules.module_header.no_description') %> - <% end %> -
    -
    - - -<%= render partial: "my_modules/modals/manage_module_tags_modal", locals: { my_module: @my_module } %> diff --git a/app/views/my_modules/_module_header_details_popover.html.erb b/app/views/my_modules/_module_header_details_popover.html.erb new file mode 100644 index 000000000..ec48d2bb6 --- /dev/null +++ b/app/views/my_modules/_module_header_details_popover.html.erb @@ -0,0 +1,50 @@ +
    +
    +
    + <%= t('my_modules.details.info_popover.project_label') %> +
    +
    + <%= @my_module.experiment.project.name.truncate(Constants::MAX_NAME_TRUNCATION) %> +
    +
    +
    +
    + <%= t('my_modules.details.info_popover.experiment_label') %> +
    +
    + <%= @my_module.experiment.name.truncate(Constants::MAX_NAME_TRUNCATION) %> +
    +
    +
    +
    + <%= t('my_modules.details.info_popover.creator_label') %> +
    +
    + <%= @my_module.created_by&.full_name&.truncate(Constants::MAX_NAME_TRUNCATION) %> + <%= t('my_modules.details.info_popover.creator_same_user_label') if current_user == @my_module.created_by %> +
    +
    +
    +
    + <%= t('my_modules.details.info_popover.created_label') %> +
    +
    + <%= l(@my_module.created_at, format: :full_date) %> +
    +
    +
    +
    + <%= t('my_modules.details.info_popover.modified_label') %> +
    +
    + <% if @my_module.last_modified_by.present? %> + <%= t('my_modules.details.info_popover.modified_value', + date: l(@my_module.updated_at, format: :full_date), + full_name: @my_module.last_modified_by.full_name.truncate(Constants::MAX_NAME_TRUNCATION)) %> + <% else %> + <%= t('my_modules.details.info_popover.modified_value_without_user', + date: l(@my_module.updated_at, format: :full_date)) %> + <% end %> +
    +
    +
    diff --git a/app/views/my_modules/_module_header_due_date.html.erb b/app/views/my_modules/_module_header_due_date.html.erb index 39c5c5199..326e1cf0d 100644 --- a/app/views/my_modules/_module_header_due_date.html.erb +++ b/app/views/my_modules/_module_header_due_date.html.erb @@ -1,33 +1,24 @@ - - +<% my_module_editable = can_manage_module?(my_module) %> + + + <%= render partial: "due_date_label.html.erb" , locals: { my_module: my_module } %> - <% if can_manage_module?(my_module) %> + <% if my_module_editable %>
    - <% - 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 << ' HH:mm' - %> - -
    - + <% end %>
    diff --git a/app/views/my_modules/_module_header_start_date.html.erb b/app/views/my_modules/_module_header_start_date.html.erb new file mode 100644 index 000000000..6c5653c08 --- /dev/null +++ b/app/views/my_modules/_module_header_start_date.html.erb @@ -0,0 +1,23 @@ +<% my_module_editable = can_manage_module?(my_module) %> + + + + <%= render partial: "start_date_label.html.erb" , locals: { my_module: my_module } %> + + <% if my_module_editable %> +
    + + +
    + + <% end %> +
    diff --git a/app/views/my_modules/_my_module_details.html.erb b/app/views/my_modules/_my_module_details.html.erb new file mode 100644 index 000000000..e6d6b57aa --- /dev/null +++ b/app/views/my_modules/_my_module_details.html.erb @@ -0,0 +1,54 @@ +<% my_module_editable = can_manage_module?(@my_module) %> + +
    +
    + + +
    +
    + <%= render partial: "module_header_start_date.html.erb", locals: { my_module: @my_module } %> +
    +
    + +
    +
    + + +
    +
    + <%= render partial: "module_header_due_date.html.erb", locals: { my_module: @my_module } %> +
    +
    + +
    +
    + + <%= t('my_modules.states.state_label') %> +
    + + <%= render partial: "module_state_label.html.erb", + locals: { my_module: @my_module } %> + +
    + +
    +
    + + +
    + <%= render partial: "user_my_modules/index" %> +
    + +
    +
    + + + <%= render partial: "my_modules/tags", locals: { my_module: @my_module, editable: my_module_editable } %> +
    +
    + + +<%= render partial: "my_modules/modals/manage_users_modal" %> + + +<%= render partial: "my_modules/modals/manage_module_tags_modal", locals: { my_module: @my_module } %> diff --git a/app/views/my_modules/_my_module_notes.html.erb b/app/views/my_modules/_my_module_notes.html.erb new file mode 100644 index 000000000..a38dfa62f --- /dev/null +++ b/app/views/my_modules/_my_module_notes.html.erb @@ -0,0 +1,14 @@ +
    +
    + <% if can_manage_module?(@my_module) %> + <%= render partial: "description_form" %> + <% elsif @my_module.description.present? %> + <%= custom_auto_link(@my_module.tinymce_render(:description), + simple_format: false, + tags: %w(img), + team: current_team) %> + <% else %> + <%= t('my_modules.notes.no_description') %> + <% end %> +
    +
    diff --git a/app/views/my_modules/_my_module_protocol.html.erb b/app/views/my_modules/_my_module_protocol.html.erb new file mode 100644 index 000000000..d4b6cac2e --- /dev/null +++ b/app/views/my_modules/_my_module_protocol.html.erb @@ -0,0 +1,25 @@ +
    +
    +
    + <% if can_manage_module?(@my_module) %> + <%= render partial: "my_modules/protocols/protocol_description_form", locals: + { + protocol: @my_module.protocol, + update_url: update_protocol_description_my_module_path(@my_module, format: :json) + } + %> + <% elsif @my_module.protocol.description.present? %> + <%= custom_auto_link(@my_module.protocol.tinymce_render(:description), + simple_format: false, + tags: %w(img), + team: current_team) %> + <% else %> + <%= t('my_modules.protocols.protocol_status_bar.no_description') %> + <% end %> +
    +
    +
    + +
    + <%= render partial: "protocols/steps.html.erb" %> +
    diff --git a/app/views/my_modules/_recent_protocol_dropdown.html.erb b/app/views/my_modules/_recent_protocol_dropdown.html.erb index 1a24d943c..302bce80e 100644 --- a/app/views/my_modules/_recent_protocol_dropdown.html.erb +++ b/app/views/my_modules/_recent_protocol_dropdown.html.erb @@ -5,7 +5,7 @@ data-update-url="<%= load_from_repository_protocol_path(protocol) %>" >
    -
    <%= t('my_modules.module_header.recent_protocols_from_repository') %>
    +
    <%= t('my_modules.details.recent_protocols_from_repository') %>
    -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/my_modules/_repositories_dropdown.html.erb b/app/views/my_modules/_repositories_dropdown.html.erb index b6bfec4c5..baa9fd2d8 100644 --- a/app/views/my_modules/_repositories_dropdown.html.erb +++ b/app/views/my_modules/_repositories_dropdown.html.erb @@ -1,8 +1,6 @@ <% Repository.accessible_by_teams(current_team).order(created_at: :asc).each do |repository| %>
  • - help_tooltips" - data-tooltiplink="<%= I18n.t('tooltips.link.protocol.inventories') %>" - data-tooltipcontent="<%= I18n.t('tooltips.text.protocol.inventories') %>" + " href="<%= repository_my_module_url(id: @my_module, repository_id: repository) %>" title="<%= repository.name %>"> <% if enable_counters %> @@ -22,8 +20,7 @@ <% Repository.used_on_task_but_unshared(@my_module, current_team).each do |repository| %>
  • - diff --git a/app/views/my_modules/_result.html.erb b/app/views/my_modules/_result.html.erb index fa726b337..da3152e62 100644 --- a/app/views/my_modules/_result.html.erb +++ b/app/views/my_modules/_result.html.erb @@ -14,13 +14,13 @@
    <% if result.editable? && can_manage_module?(result.my_module) %> - + <% end %> <% if can_manage_result?(result) %> + <% if my_module.started_on.present? %> + <%= l(my_module.started_on, format: :full) %> + <% else %> + + <%= t('my_modules.details.no_start_date_placeholder') %> + + <% end %> + diff --git a/app/views/my_modules/_state_buttons.html.erb b/app/views/my_modules/_state_buttons.html.erb index e39fa1846..b00ea460e 100644 --- a/app/views/my_modules/_state_buttons.html.erb +++ b/app/views/my_modules/_state_buttons.html.erb @@ -1,8 +1,6 @@
    <% if can_complete_module?(@my_module) %> -
    +
    <% if !@my_module.completed? %>
    <%= render 'my_modules/state_button_complete.html.erb' %> @@ -14,5 +12,5 @@ <% end %>
    <% end %> - +
    diff --git a/app/views/my_modules/_tags.html.erb b/app/views/my_modules/_tags.html.erb index b4b1ab14d..158f26f17 100644 --- a/app/views/my_modules/_tags.html.erb +++ b/app/views/my_modules/_tags.html.erb @@ -2,7 +2,7 @@
    <%= select_tag "activity", @@ -17,7 +17,7 @@ id: 'module-tags-selector', 'data-module-id': my_module.id, 'data-project-id': my_module.experiment.project_id, - 'data-placeholder': t("my_modules.module_header.no_tags"), + 'data-placeholder': t("my_modules.details.no_tags"), 'data-tags-create-url': project_tags_path(project_id: my_module.experiment.project_id), 'data-ajax-url': search_tags_my_module_my_module_tags_path(@my_module), 'data-update-module-tags-url': my_module_my_module_tags_path(@my_module), diff --git a/app/views/my_modules/modals/_assign_repository_records_modal.html.erb b/app/views/my_modules/modals/_assign_repository_records_modal.html.erb deleted file mode 100644 index 6983e5af5..000000000 --- a/app/views/my_modules/modals/_assign_repository_records_modal.html.erb +++ /dev/null @@ -1,37 +0,0 @@ - diff --git a/app/views/my_modules/modals/_assign_repository_records_modal_content.html.erb b/app/views/my_modules/modals/_assign_repository_records_modal_content.html.erb new file mode 100644 index 000000000..6596c9d06 --- /dev/null +++ b/app/views/my_modules/modals/_assign_repository_records_modal_content.html.erb @@ -0,0 +1,25 @@ + + + diff --git a/app/views/my_modules/modals/_unassign_repository_records_modal.html.erb b/app/views/my_modules/modals/_unassign_repository_records_modal.html.erb deleted file mode 100644 index c186a8f58..000000000 --- a/app/views/my_modules/modals/_unassign_repository_records_modal.html.erb +++ /dev/null @@ -1,36 +0,0 @@ -
  • + <% if repository.shared_with?(current_team) %> + + + + <% end %> + <%= repository.name %> + <% if assigned_items_count.positive? %> + + + <%= assigned_items_count %> + + <% end %> +
  • +<% end %> diff --git a/app/views/my_modules/repositories/_repositories_list.html.erb b/app/views/my_modules/repositories/_repositories_list.html.erb new file mode 100644 index 000000000..287ea5042 --- /dev/null +++ b/app/views/my_modules/repositories/_repositories_list.html.erb @@ -0,0 +1,43 @@ +<% @assigned_repositories.each do |repository| %> + +<% end %> + + diff --git a/app/views/my_modules/repository.html.erb b/app/views/my_modules/repository.html.erb index 5ae682ea0..f1bfdb29c 100644 --- a/app/views/my_modules/repository.html.erb +++ b/app/views/my_modules/repository.html.erb @@ -18,11 +18,9 @@
    -
    diff --git a/app/views/my_modules/results.html.erb b/app/views/my_modules/results.html.erb index 4263875d0..74405527d 100644 --- a/app/views/my_modules/results.html.erb +++ b/app/views/my_modules/results.html.erb @@ -18,9 +18,7 @@ <% if can_manage_module?(@my_module) %>
    - diff --git a/app/views/projects/show/_experiment.html.erb b/app/views/projects/show/_experiment.html.erb index a1e18e30d..a43f6d7fe 100644 --- a/app/views/projects/show/_experiment.html.erb +++ b/app/views/projects/show/_experiment.html.erb @@ -20,11 +20,7 @@ <% if can_clone_experiment?(experiment) %> <%= link_to clone_modal_experiment_url(experiment), remote: true, type: 'button', - class: 'clone-experiment help_tooltips pull-right', - data: { - tooltiplink: I18n.t('tooltips.link.experiment.copy'), - tooltipcontent: I18n.t('tooltips.text.experiment.copy') - } do %> + class: 'clone-experiment pull-right' do %>   <%= t('experiments.clone.label_title') %> <% end %> @@ -33,7 +29,7 @@
    <%= link_to experiment.name, canvas_experiment_path(experiment) %>
    - + <%= l(experiment.created_at, format: :full_date) %> - <%= l(experiment.updated_at, format: :full_date) %> diff --git a/app/views/protocols/_steps.html.erb b/app/views/protocols/_steps.html.erb index e03b9f9d3..87f2d3fb6 100644 --- a/app/views/protocols/_steps.html.erb +++ b/app/views/protocols/_steps.html.erb @@ -1,6 +1,17 @@ <% if can_manage_protocol_in_module?(@protocol) || can_manage_protocol_in_repository?(@protocol) %>
    + <% end %> <% end %>
    <% @protocol.steps.order(:position).each do |step| %> diff --git a/app/views/protocols/edit.html.erb b/app/views/protocols/edit.html.erb index e0870f0ab..114eaa314 100644 --- a/app/views/protocols/edit.html.erb +++ b/app/views/protocols/edit.html.erb @@ -11,4 +11,4 @@ <%= javascript_include_tag "protocols/edit" %> -<%= render partial: 'assets/wopi/create_wopi_file_modal.html.erb' %> \ No newline at end of file +<%= render partial: 'assets/wopi/create_wopi_file_modal.html.erb' %> diff --git a/app/views/reports/elements/_my_module_element.html.erb b/app/views/reports/elements/_my_module_element.html.erb index 777fc46db..88144b42d 100644 --- a/app/views/reports/elements/_my_module_element.html.erb +++ b/app/views/reports/elements/_my_module_element.html.erb @@ -24,6 +24,13 @@ <% end %>
    +
    + <% if my_module.started_on.present? %> + <%= t('projects.reports.elements.module.started_on', started_on: l(my_module.started_on, format: :full)) %> + <% else %> + <%=t "projects.reports.elements.module.no_start_date" %> + <% end %> +
    <% if my_module.due_date.present? %> <%=t "projects.reports.elements.module.due_date", due_date: l(my_module.due_date, format: :full) %> diff --git a/app/views/reports/elements/_my_module_repository_element.html.erb b/app/views/reports/elements/_my_module_repository_element.html.erb index 3badd3ede..0ae35153f 100644 --- a/app/views/reports/elements/_my_module_repository_element.html.erb +++ b/app/views/reports/elements/_my_module_repository_element.html.erb @@ -1,45 +1,58 @@ -<% repository ||= Repository.find(element_id) %> <% if my_module.blank? and @my_module.present? then my_module = @my_module end %> -<% rows = my_module.repository_rows.where(repository: repository) %> +<% element_id ||= nil %> +<% repository ||= nil %> +<% repository_snapshot ||= nil %> +<% repository = assign_repository_or_snapshot(my_module, element_id, repository, repository_snapshot) %> <% if order.blank? and @order.present? then order = @order end %> <% timestamp = Time.current + 1.year - 1.days %> -<% rows_json = my_module.repository_json_hot(repository.id, order) %> -
    " data-order="<%= order == :asc ? "asc" : "desc" %>" data-name="<%= repository.name %>" data-icon-class="fas fa-list-alt"> -
    -
    -
    - -
    -
    - <%=t "projects.reports.elements.module_repository.name", repository: repository.name, my_module: my_module.name %> -
    - <% if defined? export_all and export_all %> -
    - - <%=t "projects.reports.elements.module_repository.table_name", - name: filename %> - +<% if repository.present? %> + <% rows_json = my_module.repository_json_hot(repository, order) %> +
    " + data-order="<%= order == :asc ? "asc" : "desc" %>" + data-name="<%= repository.name %>" + data-icon-class="fas fa-list-alt"> +
    +
    +
    + +
    +
    + <%=t "projects.reports.elements.module_repository.name", repository: repository.name, my_module: my_module.name %> + <%= t('projects.reports.index.deleted') if repository.is_a?(RepositorySnapshot) && !repository.original_repository %> +
    + <% if defined? export_all and export_all %> + + <% end %> +
    + <%= render partial: "reports/elements/element_controls.html.erb", locals: { show_sort: true } %>
    - <% end %> -
    - <%= render partial: "reports/elements/element_controls.html.erb", locals: { show_sort: true } %>
    -
    -
    - <% if rows_json[:data].count > 0 %> - -
    -
    - <% else %> -
    -
    - <%=t "projects.reports.elements.module_repository.no_items" %> +
    + <% if rows_json[:data].count > 0 %> + +
    +
    + <% else %> +
    +
    + <%=t "projects.reports.elements.module_repository.no_items" %> +
    -
    - <% end %> + <% end %> +
    +
    + <%= children if (defined? children and children.present?) %> +
    -
    - <%= children if (defined? children and children.present?) %> -
    -
    +<% end %> diff --git a/app/views/reports/new/modal/_module_contents_inner.html.erb b/app/views/reports/new/modal/_module_contents_inner.html.erb index c0b218c3c..d131d7783 100644 --- a/app/views/reports/new/modal/_module_contents_inner.html.erb +++ b/app/views/reports/new/modal/_module_contents_inner.html.erb @@ -74,6 +74,11 @@ <%= form.check_box "module_repository_#{repository.id}", label: repository.name.capitalize, data: { id: repository.id } %> <% end %> + <% RepositorySnapshot.with_deleted_parent_by_team(@project.team).each do |repository| %> +
  • + <%= form.check_box "module_repository_#{repository.id}", label: repository.name.capitalize + " #{t('projects.reports.index.deleted')}", data: { id: repository.id } %> +
  • + <% end %> diff --git a/app/views/repositories/_create_repository_modal.html.erb b/app/views/repositories/_create_repository_modal.html.erb index ccc283198..902e08b9f 100644 --- a/app/views/repositories/_create_repository_modal.html.erb +++ b/app/views/repositories/_create_repository_modal.html.erb @@ -1,5 +1,5 @@