diff --git a/Gemfile.lock b/Gemfile.lock index a18313ab5..8e3e10cb3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,40 +68,40 @@ GIT GEM remote: http://rubygems.org/ specs: - actioncable (6.1.7.1) - actionpack (= 6.1.7.1) - activesupport (= 6.1.7.1) + actioncable (6.1.7.3) + actionpack (= 6.1.7.3) + activesupport (= 6.1.7.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.1) - actionpack (= 6.1.7.1) - activejob (= 6.1.7.1) - activerecord (= 6.1.7.1) - activestorage (= 6.1.7.1) - activesupport (= 6.1.7.1) + actionmailbox (6.1.7.3) + actionpack (= 6.1.7.3) + activejob (= 6.1.7.3) + activerecord (= 6.1.7.3) + activestorage (= 6.1.7.3) + activesupport (= 6.1.7.3) mail (>= 2.7.1) - actionmailer (6.1.7.1) - actionpack (= 6.1.7.1) - actionview (= 6.1.7.1) - activejob (= 6.1.7.1) - activesupport (= 6.1.7.1) + actionmailer (6.1.7.3) + actionpack (= 6.1.7.3) + actionview (= 6.1.7.3) + activejob (= 6.1.7.3) + activesupport (= 6.1.7.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.1) - actionview (= 6.1.7.1) - activesupport (= 6.1.7.1) + actionpack (6.1.7.3) + actionview (= 6.1.7.3) + activesupport (= 6.1.7.3) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.1) - actionpack (= 6.1.7.1) - activerecord (= 6.1.7.1) - activestorage (= 6.1.7.1) - activesupport (= 6.1.7.1) + actiontext (6.1.7.3) + actionpack (= 6.1.7.3) + activerecord (= 6.1.7.3) + activestorage (= 6.1.7.3) + activesupport (= 6.1.7.3) nokogiri (>= 1.8.5) - actionview (6.1.7.1) - activesupport (= 6.1.7.1) + actionview (6.1.7.3) + activesupport (= 6.1.7.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -111,24 +111,24 @@ GEM activemodel (>= 4.1, < 6.2) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.7.1) - activesupport (= 6.1.7.1) + activejob (6.1.7.3) + activesupport (= 6.1.7.3) globalid (>= 0.3.6) - activemodel (6.1.7.1) - activesupport (= 6.1.7.1) - activerecord (6.1.7.1) - activemodel (= 6.1.7.1) - activesupport (= 6.1.7.1) + activemodel (6.1.7.3) + activesupport (= 6.1.7.3) + activerecord (6.1.7.3) + activemodel (= 6.1.7.3) + activesupport (= 6.1.7.3) activerecord-import (1.0.7) activerecord (>= 3.2) - activestorage (6.1.7.1) - actionpack (= 6.1.7.1) - activejob (= 6.1.7.1) - activerecord (= 6.1.7.1) - activesupport (= 6.1.7.1) + activestorage (6.1.7.3) + actionpack (= 6.1.7.3) + activejob (= 6.1.7.3) + activerecord (= 6.1.7.3) + activesupport (= 6.1.7.3) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.1) + activesupport (6.1.7.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -235,7 +235,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.2) crack (0.4.5) rexml crass (1.0.6) @@ -293,7 +293,7 @@ GEM discard (1.2.0) activerecord (>= 4.2, < 7) docile (1.3.5) - doorkeeper (5.4.0) + doorkeeper (5.6.6) railties (>= 5) down (5.2.0) addressable (~> 2.5) @@ -321,7 +321,7 @@ GEM raabro (~> 1.4) generator (0.0.1) gherkin (5.1.0) - globalid (1.0.1) + globalid (1.1.0) activesupport (>= 5.0) graphviz (1.2.1) process-pipeline @@ -331,7 +331,7 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.12.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) i18n-js (3.8.0) i18n (>= 0.6.6) @@ -382,10 +382,10 @@ GEM logging (2.0.0) little-plugger (~> 1.1) multi_json (~> 1.10) - loofah (2.19.1) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.8.0.1) + nokogiri (>= 1.12.0) + mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop @@ -397,7 +397,8 @@ GEM mime-types-data (3.2022.0105) mini_magick (4.11.0) mini_mime (1.1.2) - minitest (5.17.0) + mini_portile2 (2.8.2) + minitest (5.18.0) momentjs-rails (2.17.1) railties (>= 3.1) msgpack (1.4.2) @@ -418,8 +419,11 @@ GEM net-smtp (0.3.3) net-protocol newrelic_rpm (8.16.0) - nio4r (2.5.8) - nokogiri (1.14.3-x86_64-linux) + nio4r (2.5.9) + nokogiri (1.14.5) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) + nokogiri (1.14.5-x86_64-linux) racc (~> 1.4) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) @@ -473,30 +477,30 @@ GEM puma (5.6.4) nio4r (~> 2.0) raabro (1.4.0) - racc (1.6.2) - rack (2.2.6.4) + racc (1.7.0) + rack (2.2.7) rack-attack (6.4.0) rack (>= 1.0, < 3) rack-cors (1.1.1) rack (>= 2.0.0) rack-protection (3.0.1) rack - rack-test (2.0.2) + rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.1) - actioncable (= 6.1.7.1) - actionmailbox (= 6.1.7.1) - actionmailer (= 6.1.7.1) - actionpack (= 6.1.7.1) - actiontext (= 6.1.7.1) - actionview (= 6.1.7.1) - activejob (= 6.1.7.1) - activemodel (= 6.1.7.1) - activerecord (= 6.1.7.1) - activestorage (= 6.1.7.1) - activesupport (= 6.1.7.1) + rails (6.1.7.3) + actioncable (= 6.1.7.3) + actionmailbox (= 6.1.7.3) + actionmailer (= 6.1.7.3) + actionpack (= 6.1.7.3) + actiontext (= 6.1.7.3) + actionview (= 6.1.7.3) + activejob (= 6.1.7.3) + activemodel (= 6.1.7.3) + activerecord (= 6.1.7.3) + activestorage (= 6.1.7.3) + activesupport (= 6.1.7.3) bundler (>= 1.15.0) - railties (= 6.1.7.1) + railties (= 6.1.7.3) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -505,8 +509,9 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) - loofah (~> 2.19, >= 2.19.1) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) rails_12factor (0.0.3) rails_serve_static_assets rails_stdout_logging @@ -514,9 +519,9 @@ GEM rails (> 3.1) rails_serve_static_assets (0.0.5) rails_stdout_logging (0.0.5) - railties (6.1.7.1) - actionpack (= 6.1.7.1) - activesupport (= 6.1.7.1) + railties (6.1.7.3) + actionpack (= 6.1.7.3) + activesupport (= 6.1.7.3) method_source rake (>= 12.2) thor (~> 1.0) @@ -625,14 +630,16 @@ GEM generator tailwindcss-rails (2.0.27) railties (>= 6.0.0) - thor (1.2.1) + tailwindcss-rails (2.0.27-x86_64-linux) + railties (>= 6.0.0) + thor (1.2.2) tilt (2.0.10) timecop (0.9.2) - timeout (0.3.1) + timeout (0.3.2) turbolinks (5.1.1) turbolinks-source (~> 5.1) turbolinks-source (5.2.0) - tzinfo (2.0.5) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) @@ -656,11 +663,12 @@ GEM activesupport xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.6) + zeitwerk (2.6.8) zip-zip (0.3) rubyzip (>= 1.0.0) PLATFORMS + ruby x86_64-linux DEPENDENCIES diff --git a/VERSION b/VERSION index 5ae955a48..457f03854 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.27.1.2 +1.27.2 diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index adeae341c..d0d01b904 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -43,9 +43,6 @@ // forms with clicking on links outside form in cases when other than // GET method is used. -// eslint-disable-next-line no-unused-vars -var formatJS = $('body').data('datetime-picker-format-date-only'); - function initFormSubmitLinks(el) { el = el || $(document.body); diff --git a/app/assets/javascripts/dashboard/calendar.js b/app/assets/javascripts/dashboard/calendar.js index a6eb2984c..8a34ab9f4 100644 --- a/app/assets/javascripts/dashboard/calendar.js +++ b/app/assets/javascripts/dashboard/calendar.js @@ -83,6 +83,8 @@ var DasboardCalendarWidget = (function() { }; }()); +var formatJS; $(document).on('turbolinks:load', function() { DasboardCalendarWidget.init(); + formatJS = $('body').data('datetime-picker-format'); }); diff --git a/app/assets/javascripts/global_activities/date_picker.js b/app/assets/javascripts/global_activities/date_picker.js index 1c9db9b03..7f55977ab 100644 --- a/app/assets/javascripts/global_activities/date_picker.js +++ b/app/assets/javascripts/global_activities/date_picker.js @@ -1,10 +1,11 @@ -/* global I18n formatJS */ +/* global I18n */ (function() { $('.datetime-picker-container').each(function() { const id = $(this).data('id'); if (id) { const dt = $(`#calendar-${id}`); const useCurrent = $(this).data('use-current'); + const formatJS = $(this).data('datetime-picker-format'); dt.datetimepicker({ useCurrent, ignoreReadonly: true, locale: I18n.locale, format: formatJS }); diff --git a/app/assets/javascripts/my_modules/repositories.js b/app/assets/javascripts/my_modules/repositories.js index 531631d45..915475148 100644 --- a/app/assets/javascripts/my_modules/repositories.js +++ b/app/assets/javascripts/my_modules/repositories.js @@ -783,10 +783,9 @@ var MyModuleRepositories = (function() { 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, true); updateFullViewRowsCount(data.rows_count); - renderFullViewAssignButtons(); + FULL_VIEW_MODAL.modal('hide'); }, error: function(response) { if (response.status === 403) { diff --git a/app/assets/javascripts/projects/index.js b/app/assets/javascripts/projects/index.js index cc6762ecc..41fc02529 100644 --- a/app/assets/javascripts/projects/index.js +++ b/app/assets/javascripts/projects/index.js @@ -76,17 +76,25 @@ var ProjectsIndex = (function() { // Modal's submit handler function $(projectsWrapper) - .on('ajax:success', newProjectModal, function(ev, data) { + .on('submit', '#new_project', function() { + $('#project_name').prop('disabled', true); + $('#new-project-modal button[type="submit"]').prop('disabled', true); + }) + .on('ajax:complete', '#new_project', function() { + $('#project_name').prop('disabled', false); + $('#new-project-modal button[type="submit"]').prop('disabled', false); + }) + .on('ajax:success', newProjectModal, function(_ev, data) { $(newProjectModal).modal('hide'); HelperModule.flashAlertMsg(data.message, 'success'); refreshCurrentView(); }) - .on('ajax:error', newProjectModal, function(ev, data) { + .on('ajax:error', newProjectModal, function(_ev, data) { $(this).renderFormErrors('project', data.responseJSON); }); $(projectsWrapper) - .on('ajax:success', '.new-project-btn', function(ev, data) { + .on('ajax:success', '.new-project-btn', function(_ev, data) { // Add and show modal $(projectsWrapper).append($.parseHTML(data.html)); $(newProjectModal).modal('show'); diff --git a/app/assets/javascripts/reports/new.js b/app/assets/javascripts/reports/new.js index 2abb9c260..0ec2bc788 100644 --- a/app/assets/javascripts/reports/new.js +++ b/app/assets/javascripts/reports/new.js @@ -1065,9 +1065,15 @@ function reportHandsonTableConverter() { type: 'POST', data: JSON.stringify(getReportData()), contentType: 'application/json; charset=utf-8', + beforeSend: function() { + $('.generate-button').prop('disabled', true); + }, success: function() {}, error: function(jqxhr) { HelperModule.flashAlertMsg(jqxhr.responseJSON.join(' '), 'danger'); + }, + ajaxComplete: function() { + $('.generate-button').prop('disabled', false); } }); }); diff --git a/app/assets/javascripts/repositories/renderers/edit_renderers.js b/app/assets/javascripts/repositories/renderers/edit_renderers.js index e61065bc3..ab9884915 100644 --- a/app/assets/javascripts/repositories/renderers/edit_renderers.js +++ b/app/assets/javascripts/repositories/renderers/edit_renderers.js @@ -115,25 +115,35 @@ $.fn.dataTable.render.editRepositoryChecklistValue = function(formId, columnId, }; $.fn.dataTable.render.editRepositoryNumberValue = function(formId, columnId, cell, $header) { - let $cell = $(cell.node()); - let decimals = $header.data('metadata-decimals'); + const $cell = $(cell.node()); + const decimals = $header.data('metadata-decimals'); let number = $cell.find('.number-value').data('value'); - if (!number) number = ''; - $cell.html(` -
- -
`); + let $input = $('', { + class: 'sci-input-field', + form: formId, + type: 'text', + name: 'repository_cells[' + columnId + ']', + placeholder: I18n.t('repositories.table.number.enter_number'), + value: number, + 'data-type': 'RepositoryNumberValue' + }); + + $input.on('input', function() { + const regexp = decimals === 0 ? /[^0-9]/g : /[^0-9.]/g; + const decimalsRegex = new RegExp(`^\\d*(\\.\\d{0,${decimals}})?`); + let value = this.value; + value = value.replace(regexp, ''); + value = value.match(decimalsRegex)[0]; + this.value = value; + }); + + let $div = $('
', { + class: 'sci-input-container text-field error-icon' + }).append($input); + + $cell.html($div); }; $.fn.dataTable.render.editRepositoryStockValue = function(formId, columnId, cell) { diff --git a/app/assets/javascripts/repositories/renderers/new_renderers.js b/app/assets/javascripts/repositories/renderers/new_renderers.js index a860e5428..c4f834c04 100644 --- a/app/assets/javascripts/repositories/renderers/new_renderers.js +++ b/app/assets/javascripts/repositories/renderers/new_renderers.js @@ -50,20 +50,31 @@ $.fn.dataTable.render.newRepositoryChecklistValue = function(formId, columnId, $ }; $.fn.dataTable.render.newRepositoryNumberValue = function(formId, columnId, $cell, $header) { - let decimals = $header.data('metadata-decimals'); + const decimals = $header.data('metadata-decimals'); - $cell.html(` -
- -
`); + let $input = $('', { + class: 'sci-input-field', + form: formId, + type: 'text', + name: 'repository_cells[' + columnId + ']', + value: '', + placeholder: I18n.t('repositories.table.number.enter_number'), + 'data-type': 'RepositoryNumberValue' + }); + + $input.on('input', function() { + const decimalsRegex = new RegExp(`^\\d*(\\.\\d{0,${decimals}})?`); + let value = this.value; + value = value.replace(/[^0-9.]/g, ''); + value = value.match(decimalsRegex)[0]; + this.value = value; + }); + + let $div = $('
', { + class: 'sci-input-container text-field error-icon' + }).append($input); + + $cell.html($div); }; $.fn.dataTable.render.newRepositoryDateTimeValue = function(formId, columnId, $cell) { diff --git a/app/assets/javascripts/shared/remote_modal.js b/app/assets/javascripts/shared/remote_modal.js index 7478c304d..a13bb85f7 100644 --- a/app/assets/javascripts/shared/remote_modal.js +++ b/app/assets/javascripts/shared/remote_modal.js @@ -26,6 +26,19 @@ animateSpinner(null, false); ShareModal.init(); } + if (['rename-repo-modal', 'copy-repo-modal'].includes($(this).attr('id'))) { + $(this).find('form') + .on('ajax:success', function(_e, data) { + if (data.url) { + window.location = data.url; + } else { + window.location.reload(); + } + }) + .on('ajax:error', function(_e, data) { + $(this).renderFormErrors('repository', data.responseJSON); + }); + } $(this).find('.selectpicker').selectpicker(); }) .on('hidden.bs.modal', function() { diff --git a/app/assets/javascripts/sitewide/dropdown_selector.js b/app/assets/javascripts/sitewide/dropdown_selector.js index d15f216b4..ba32eebf4 100644 --- a/app/assets/javascripts/sitewide/dropdown_selector.js +++ b/app/assets/javascripts/sitewide/dropdown_selector.js @@ -716,7 +716,9 @@ var dropdownSelector = (function() { } else { // Or delete specific one deleteValue(selector, container, tagLabel.data('ds-tag-id'), tagLabel.data('ds-tag-group')); - removeOptionFromSelector(selector, tagLabel.data('ds-tag-id')); + if (selector.data('config').tagClass) { + removeOptionFromSelector(selector, tagLabel.data('ds-tag-id')); + } } }, 350); } @@ -1010,7 +1012,9 @@ var dropdownSelector = (function() { currentData = getCurrentData($(selector).next()); currentData.push(value); setData($(selector), currentData, skip_event); - appendOptionToSelector(selector, value); + if (selector.data('config').tagClass) { + appendOptionToSelector(selector, value); + } return this; }, diff --git a/app/assets/stylesheets/navigation/navigator.scss b/app/assets/stylesheets/navigation/navigator.scss index 4eca5b903..1b595fd38 100644 --- a/app/assets/stylesheets/navigation/navigator.scss +++ b/app/assets/stylesheets/navigation/navigator.scss @@ -2,22 +2,34 @@ .menu-item:not(.active):hover { background-color: var(--sn-super-light-grey); - a:not(.no-hover) { + :not(.no-hover) { color: var(--sn-blue); } - a.no-hover { + .no-hover { color: var(--sn-science-blue-hover); } } .menu-item.active:hover { - a:not(.no-hover) { + :not(.no-hover) { color: var(--sn-blue); } - a.no-hover { + .no-hover { color: var(--sn-science-blue-hover); } } + + .menu-item, + .menu-item:not(.active):hover, + .menu-item.active:hover { + .disabled-link, + .disabled-link:not(.no-hover):hover, + .disabled-link.no-hover:hover { + color: var(--sn-grey); + pointer-events: none; + text-decoration: none; + } + } } diff --git a/app/assets/stylesheets/projects.scss b/app/assets/stylesheets/projects.scss index e4f8ee403..75d1beb73 100644 --- a/app/assets/stylesheets/projects.scss +++ b/app/assets/stylesheets/projects.scss @@ -747,6 +747,16 @@ li.module-hover { a { color: inherit; + + &.disabled-link { + color: var(--sn-grey); + pointer-events: none; + text-decoration: none; + + .name { + color: var(--sn-grey); + } + } } .name { @@ -872,7 +882,7 @@ li.module-hover { animation-duration: 2s; animation-iteration-count: infinite; animation-name: placeholder-pulsing; - background-color: $color-alto; + background-color: var(--sn-sleepy-grey); border-radius: $border-radius-default; height: 18px; diff --git a/app/controllers/experiments_controller.rb b/app/controllers/experiments_controller.rb index 43733d2d4..1331ec058 100644 --- a/app/controllers/experiments_controller.rb +++ b/app/controllers/experiments_controller.rb @@ -447,22 +447,22 @@ class ExperimentsController < ApplicationController end def inventory_assigning_experiment_filter - readable_experiments = Experiment.readable_by_user(current_user) + viewable_experiments = Experiment.viewable_by_user(current_user, current_team) assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user) - project = Project.readable_by_user(current_user) + project = Project.viewable_by_user(current_user, current_team) .joins(experiments: :my_modules) - .where(experiments: { id: readable_experiments }) + .where(experiments: { id: viewable_experiments }) .where(my_modules: { id: assignable_my_modules }) .find_by(id: params[:project_id]) return render_404 if project.blank? experiments = project.experiments + .active .joins(:my_modules) - .where(experiments: { id: readable_experiments }) + .where(experiments: { id: viewable_experiments }) .where(my_modules: { id: assignable_my_modules }) - .search(current_user, false, params[:query], 1, current_team) .distinct .pluck(:id, :name) diff --git a/app/controllers/my_modules_controller.rb b/app/controllers/my_modules_controller.rb index 3eddeb756..683224c2b 100644 --- a/app/controllers/my_modules_controller.rb +++ b/app/controllers/my_modules_controller.rb @@ -452,12 +452,12 @@ class MyModulesController < ApplicationController end def inventory_assigning_my_module_filter - readable_experiments = Experiment.readable_by_user(current_user) + viewable_experiments = Experiment.viewable_by_user(current_user, current_team) assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user) - experiment = Experiment.readable_by_user(current_user) + experiment = Experiment.viewable_by_user(current_user, current_team) .joins(:my_modules) - .where(experiments: { id: readable_experiments }) + .where(experiments: { id: viewable_experiments }) .where(my_modules: { id: assignable_my_modules }) .find_by(id: params[:experiment_id]) @@ -465,8 +465,6 @@ class MyModulesController < ApplicationController my_modules = experiment.my_modules .where(my_modules: { id: assignable_my_modules }) - .distinct - .search(current_user, false, params[:query], 1, current_team) .pluck(:id, :name) return render plain: [].to_json if my_modules.blank? diff --git a/app/controllers/navigator/base_controller.rb b/app/controllers/navigator/base_controller.rb index ecc161fd9..61bc14e2f 100644 --- a/app/controllers/navigator/base_controller.rb +++ b/app/controllers/navigator/base_controller.rb @@ -12,7 +12,8 @@ module Navigator archived: project.archived, type: :project, has_children: project.has_children, - children_url: navigator_project_path(project) + children_url: navigator_project_path(project), + disabled: project.disabled } end @@ -69,25 +70,35 @@ module Navigator (projects.archived IS TRUE AND experiments.id IS NOT NULL) THEN 1 ELSE 0 END) > 0 AS has_children' end + disabled_sql = 'SUM(CASE WHEN project_user_roles IS NULL THEN 0 ELSE 1 END) < 1 AS disabled' + current_team.projects .where(project_folder_id: folder) .visible_to(current_user, current_team) .with_children_viewable_by_user(current_user) - .where(' - projects.archived = :archived OR - ( - ( - experiments.archived = :archived OR - my_modules.archived = :archived - ) AND - :archived IS TRUE - ) OR - projects.id = :project_id - ', archived: archived, project_id: project&.id || -1) + .joins("LEFT OUTER JOIN user_assignments project_user_assignments + ON project_user_assignments.assignable_type = 'Project' + AND project_user_assignments.assignable_id = projects.id + AND project_user_assignments.user_id = #{current_user.id} + LEFT OUTER JOIN user_roles project_user_roles + ON project_user_roles.id = project_user_assignments.user_role_id + AND project_user_roles.permissions @> ARRAY['#{ProjectPermissions::READ}']::varchar[]") + .where('projects.archived = :archived OR + ( + ( + experiments.archived = :archived OR + my_modules.archived = :archived + ) AND + :archived IS TRUE + ) OR + projects.id = :project_id', + archived: archived, + project_id: project&.id || -1) .select( 'projects.id', 'projects.name', 'projects.archived', + disabled_sql, has_children_sql ).group('projects.id') end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 66d8a51a1..c64511770 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -107,14 +107,14 @@ class ProjectsController < ApplicationController end def inventory_assigning_project_filter - readable_experiments = Experiment.readable_by_user(current_user) + viewable_experiments = Experiment.viewable_by_user(current_user, current_team) assignable_my_modules = MyModule.repository_row_assignable_by_user(current_user) - projects = Project.readable_by_user(current_user) + projects = Project.viewable_by_user(current_user, current_team) + .active .joins(experiments: :my_modules) - .where(experiments: { id: readable_experiments }) + .where(experiments: { id: viewable_experiments }) .where(my_modules: { id: assignable_my_modules }) - .search(current_user, false, params[:query], 1, current_team) .distinct .pluck(:id, :name) @@ -167,64 +167,70 @@ class ProjectsController < ApplicationController end def update + @project.assign_attributes(project_update_params) return_error = false flash_error = t('projects.update.error_flash', name: escape_input(@project.name)) + return render_403 unless can_manage_project?(@project) || @project.archived_changed? + # Check archive permissions if archiving/restoring - if project_params.include? :archived - if (project_params[:archived] == 'true' && - !can_archive_project?(@project)) || - (project_params[:archived] == 'false' && - !can_restore_project?(@project)) + if @project.archived_changed? && + ((@project.archived == 'true' && !can_archive_project?(@project)) || + (@project.archived == 'false' && !can_restore_project?(@project))) return_error = true - is_archive = project_params[:archived] == 'true' ? 'archive' : 'restore' + is_archive = @project.archived? ? 'archive' : 'restore' flash_error = t("projects.#{is_archive}.error_flash", name: escape_input(@project.name)) - end - elsif !can_manage_project?(@project) - render_403 && return end - message_renamed = nil - message_visibility = nil - if (project_params.include? :name) && - (project_params[:name] != @project.name) - message_renamed = true - end - if (project_params.include? :visibility) && - (project_params[:visibility] != @project.visibility) - message_visibility = if project_params[:visibility] == 'visible' - t('projects.activity.visibility_visible') - else - t('projects.activity.visibility_hidden') - end + message_renamed = @project.name_changed? + message_visibility = if @project.visibility_changed? + nil + elsif @project.visible? + t('projects.activity.visibility_visible') + else + t('projects.activity.visibility_hidden') + end + + message_archived = if !@project.archived_changed? + nil + elsif @project.archived? + 'archive' + else + 'restore' + end + + default_public_user_name = nil + if @project.visibility_changed? && @project.default_public_user_role_id_changed? + default_public_user_name = UserRole.find(project_params[:default_public_user_role_id])&.name end @project.last_modified_by = current_user - if !return_error && @project.update(project_params) - # Add activities if needed + if !return_error && @project.save + # Add activities if needed log_activity(:change_project_visibility, @project, visibility: message_visibility) if message_visibility.present? log_activity(:rename_project) if message_renamed.present? - log_activity(:archive_project) if project_params[:archived] == 'true' - log_activity(:restore_project) if project_params[:archived] == 'false' + log_activity(:archive_project) if message_archived == 'archive' + log_activity(:restore_project) if message_archived == 'restore' + + if default_public_user_name.present? + log_activity(:project_access_changed_all_team_members, + @project, + { team: @project.team.id, role: default_public_user_name }) + end flash_success = t('projects.update.success_flash', name: escape_input(@project.name)) - if project_params[:archived] == 'true' + if message_archived == 'archive' flash_success = t('projects.archive.success_flash', name: escape_input(@project.name)) - elsif project_params[:archived] == 'false' + elsif message_archived == 'restore' flash_success = t('projects.restore.success_flash', name: escape_input(@project.name)) end respond_to do |format| format.html do - # Redirect URL for archive view is different as for other views. - if project_params[:archived] == 'false' - # The project should be restored - @project.restore(current_user) unless @project.archived - elsif @project.archived - # The project should be archived - @project.archive(current_user) - end + @project.restore(current_user) if message_archived == 'restore' + @project.archive(current_user) if message_archived == 'archive' + redirect_to projects_path flash[:success] = flash_success end @@ -404,12 +410,17 @@ class ProjectsController < ApplicationController def project_params params.require(:project) .permit( - :name, :team_id, :visibility, + :name, :visibility, :archived, :project_folder_id, :default_public_user_role_id ) end + def project_update_params + params.require(:project) + .permit(:name, :visibility, :archived, :default_public_user_role_id) + end + def view_type_params params.require(:project).require(:view_type) end diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 83caeafa0..43040bf55 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -382,6 +382,16 @@ class RepositoriesController < ApplicationController end end + def export_repositories + repositories = Repository.viewable_by_user(current_user, current_team).where(id: params[:repository_ids]) + if repositories.present? + RepositoriesExportJob.perform_later(repositories.pluck(:id), current_user, current_team) + render json: { message: t('zip_export.export_request_success') } + else + render json: { message: t('zip_export.export_error') }, status: :unprocessable_entity + end + end + def assigned_my_modules my_modules = MyModule.joins(:repository_rows).where(repository_rows: { repository: @repository }) .readable_by_user(current_user).distinct diff --git a/app/javascript/packs/vue/assign_items_to_task_modal.js b/app/javascript/packs/vue/assign_items_to_task_modal.js index 2cc783622..ab7ddad48 100644 --- a/app/javascript/packs/vue/assign_items_to_task_modal.js +++ b/app/javascript/packs/vue/assign_items_to_task_modal.js @@ -1,8 +1,11 @@ import TurbolinksAdapter from 'vue-turbolinks'; import Vue from 'vue/dist/vue.esm'; import AssignItemsToTaskModalContainer from '../../vue/assign_items_to_tasks_modal/container.vue'; +import PerfectScrollbar from 'vue2-perfect-scrollbar'; +import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css'; Vue.use(TurbolinksAdapter); +Vue.use(PerfectScrollbar); Vue.prototype.i18n = window.I18n; function initAssignItemsToTaskModalComponent() { diff --git a/app/javascript/vue/assign_items_to_tasks_modal/container.vue b/app/javascript/vue/assign_items_to_tasks_modal/container.vue index d5d54f371..b17ae20bf 100644 --- a/app/javascript/vue/assign_items_to_tasks_modal/container.vue +++ b/app/javascript/vue/assign_items_to_tasks_modal/container.vue @@ -43,6 +43,7 @@ ref="projectsSelector" @change="changeProject" :options="projects" + :isLoading="projectsLoading" :placeholder=" i18n.t( 'repositories.modal_assign_items_to_task.body.project_select.placeholder' @@ -76,6 +77,7 @@ ref="experimentsSelector" @change="changeExperiment" :options="experiments" + :isLoading="experimentsLoading" :placeholder="experimentsSelectorPlaceholder" :no-options-placeholder=" i18n.t( @@ -105,6 +107,7 @@ ref="tasksSelector" @change="changeTask" :options="tasks" + :isLoading="tasksLoading" :placeholder="tasksSelectorPlaceholder" :no-options-placeholder=" i18n.t( @@ -153,6 +156,9 @@ export default { selectedProject: null, selectedExperiment: null, selectedTask: null, + projectsLoading: null, + experimentsLoading: null, + tasksLoading: null, showCallback: null }; }, @@ -164,12 +170,16 @@ export default { }, mounted() { $(this.$refs.modal).on("shown.bs.modal", () => { + this.projectsLoading = true; + $.get(this.projectURL, data => { if (Array.isArray(data)) { this.projects = data; return false; } this.projects = []; + }).always(() => { + this.projectsLoading = false; }); }); @@ -240,24 +250,30 @@ export default { this.resetExperimentSelector(); this.resetTaskSelector(); + this.experimentsLoading = true; $.get(this.experimentURL, data => { if (Array.isArray(data)) { this.experiments = data; return false; } this.experiments = []; + }).always(() => { + this.experimentsLoading = false; }); }, changeExperiment(value) { this.selectedExperiment = value; this.resetTaskSelector(); + this.tasksLoading = true; $.get(this.taskURL, data => { if (Array.isArray(data)) { this.tasks = data; return false; } this.tasks = []; + }).always(() => { + this.tasksLoading = false; }); }, changeTask(value) { diff --git a/app/javascript/vue/components/action_toolbar.vue b/app/javascript/vue/components/action_toolbar.vue index 42ac75926..ffd0b513c 100644 --- a/app/javascript/vue/components/action_toolbar.vue +++ b/app/javascript/vue/components/action_toolbar.vue @@ -92,7 +92,10 @@ this.buttonOverflow = false; this.$nextTick(() => { - if (!this.$el.getBoundingClientRect) return; + if ( + !(this.$el.getBoundingClientRect && + document.querySelector('.sn-action-toolbar__action:last-child')) + ) return; let containerRect = this.$el.getBoundingClientRect(); let lastActionRect = document.querySelector('.sn-action-toolbar__action:last-child').getBoundingClientRect(); diff --git a/app/javascript/vue/navigation/navigator_item.vue b/app/javascript/vue/navigation/navigator_item.vue index 4ba4e7f31..2c29c7632 100644 --- a/app/javascript/vue/navigation/navigator_item.vue +++ b/app/javascript/vue/navigation/navigator_item.vue @@ -17,7 +17,8 @@ class="text-ellipsis overflow-hidden hover:no-underline pr-3" :class="{ 'text-sn-science-blue-hover': (!item.archived && archived), - 'no-hover': (!item.archived && archived) + 'no-hover': (!item.archived && archived), + 'disabled-link': item.disabled }"> {{ item.name }} @@ -62,7 +63,7 @@ export default { }, computed: { hasChildren: function() { - return this.item.has_children || this.children.length > 0; + return !this.item.disabled && (this.item.has_children || this.children.length > 0); }, sortedMenuItems: function() { return this.children.sort((a, b) => { diff --git a/app/javascript/vue/protocol/step_elements/table.vue b/app/javascript/vue/protocol/step_elements/table.vue index 4e87d377d..e19e80a4f 100644 --- a/app/javascript/vue/protocol/step_elements/table.vue +++ b/app/javascript/vue/protocol/step_elements/table.vue @@ -95,7 +95,8 @@ editingTable: false, tableObject: null, nameModalOpen: false, - reloadHeader: 0 + reloadHeader: 0, + updatingTableData: false } }, computed: { @@ -104,10 +105,10 @@ } }, updated() { - this.loadTableData(); + if(!this.updatingTableData) this.loadTableData(); }, beforeUpdate() { - this.tableObject.destroy(); + if(!this.updatingTableData) this.tableObject.destroy(); }, mounted() { this.loadTableData(); @@ -130,6 +131,7 @@ }, disableTableEdit() { this.editingTable = false; + this.updatingTableData = false; }, enableNameEdit() { this.editingName = true; @@ -203,6 +205,7 @@ } this.$emit('update', this.element) this.ajax_update_url() + this.updatingTableData = false; }, ajax_update_url() { $.ajax({ @@ -229,7 +232,10 @@ formulas: formulasEnabled, preventOverflow: 'horizontal', readOnly: !this.editingTable, - afterUnlisten: () => setTimeout(this.updateTable, 100) // delay makes cancel button work + afterUnlisten: () => { + this.updatingTableData = true; + setTimeout(this.updateTable, 100) // delay makes cancel button work + } }); } } diff --git a/app/javascript/vue/shared/select.vue b/app/javascript/vue/shared/select.vue index 83788fcf3..9d11dd0b5 100644 --- a/app/javascript/vue/shared/select.vue +++ b/app/javascript/vue/shared/select.vue @@ -14,7 +14,7 @@