diff --git a/.rubocop.yml b/.rubocop.yml index 463f027db..81760341f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -9,7 +9,7 @@ AllCops: - "spec/**/*" NewCops: enable UseCache: false - TargetRubyVersion: 3.0 + TargetRubyVersion: 3.1 ##################### Style #################################### diff --git a/Gemfile b/Gemfile index 6559d7435..d4dae0a1d 100644 --- a/Gemfile +++ b/Gemfile @@ -56,7 +56,7 @@ gem 'i18n-js', '~> 3.6' # Localization in javascript files gem 'jbuilder' # JSON structures via a Builder-style DSL gem 'logging', '~> 2.0.0' gem 'nested_form_fields' -gem 'nokogiri', '~> 1.14.3' # HTML/XML parser +gem 'nokogiri', '~> 1.16.2' # HTML/XML parser gem 'noticed' gem 'rails_autolink', '~> 1.1', '>= 1.1.6' gem 'rgl' # Graph framework for project diagram calculations diff --git a/Gemfile.lock b/Gemfile.lock index 3ee29feb9..2e40a4959 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -422,7 +422,7 @@ GEM mime-types-data (3.2023.0218.1) mini_magick (4.12.0) mini_mime (1.1.2) - mini_portile2 (2.8.4) + mini_portile2 (2.8.5) minitest (5.20.0) msgpack (1.7.1) multi_json (1.15.0) @@ -443,12 +443,12 @@ GEM net-protocol newrelic_rpm (9.2.2) nio4r (2.7.0) - nokogiri (1.14.5) - mini_portile2 (~> 2.8.0) + nokogiri (1.16.2) + mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.14.5-arm64-darwin) + nokogiri (1.16.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.5-x86_64-linux) + nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) noticed (1.6.3) http (>= 4.0.0) @@ -508,7 +508,7 @@ GEM puma (6.4.2) nio4r (~> 2.0) raabro (1.4.0) - racc (1.7.1) + racc (1.7.3) rack (2.2.7) rack-attack (6.6.1) rack (>= 1.0, < 3) @@ -769,7 +769,7 @@ DEPENDENCIES logging (~> 2.0.0) nested_form_fields newrelic_rpm - nokogiri (~> 1.14.3) + nokogiri (~> 1.16.2) noticed omniauth (~> 2.1) omniauth-azure-activedirectory-v2 diff --git a/app/assets/javascripts/access_permissions/user_assignments.js b/app/assets/javascripts/access_permissions/user_assignments.js index a4d6bda35..fc9348ca0 100644 --- a/app/assets/javascripts/access_permissions/user_assignments.js +++ b/app/assets/javascripts/access_permissions/user_assignments.js @@ -45,6 +45,10 @@ $(document).on('ajax:success', 'form#new-user-assignment-form', function(_e, data) { $('#user_assignments_modal').replaceWith($(data.html).find('#user_assignments_modal')); HelperModule.flashAlertMsg(data.flash, 'success'); + + if (window.actionToolbarComponent?.reloadCallback) { + window.actionToolbarComponent.reloadCallback(); + } }); $(document).on('ajax:error', 'form#new-user-assignment-form', function(_e, data) { @@ -60,6 +64,10 @@ if (data.flash) { HelperModule.flashAlertMsg(data.flash, 'success'); } + + if (window.actionToolbarComponent?.reloadCallback) { + window.actionToolbarComponent.reloadCallback(); + } }); $(document).on('click', '.user-assignment-dropdown .user-role-selector', function() { diff --git a/app/assets/javascripts/my_modules/protocols.js b/app/assets/javascripts/my_modules/protocols.js index fdc20f140..9832fcda4 100644 --- a/app/assets/javascripts/my_modules/protocols.js +++ b/app/assets/javascripts/my_modules/protocols.js @@ -287,48 +287,6 @@ function refreshProtocolStatusBar() { }); } -function initImport() { - var fileInput = $("[data-action='load-from-file']"); - - // Make sure multiple selections of same file - // always prompt new modal - fileInput.find("input[type='file']").on('click', function() { - this.value = null; - }); - - // Hack to hide "No file chosen" tooltip - fileInput.attr('title', window.URL ? ' ' : ''); - - fileInput.on('change', function(ev) { - var importUrl = fileInput.attr('data-import-url'); - importProtocolFromFile( - ev.target.files[0], - importUrl, - null, - true, - function(datas) { - var data = datas[0]; - if (data.status === 'ok') { - // Simply reload page - location.reload(); - } else if (data.status === 'locked') { - alert(I18n.t('my_modules.protocols.load_from_file_error_locked')); - } else { - if (data.status === 'size_too_large') { - alert(I18n.t('my_modules.protocols.load_from_file_size_error', - { size: GLOBAL_CONSTANTS.FILE_MAX_SIZE_MB })); - } else { - alert(I18n.t('my_modules.protocols.load_from_file_error')); - } - animateSpinner(null, false); - } - } - ); - // Clear input on self - $(this).val(''); - }); -} - function initDetailsDropdown() { $('#task-details .task-section-caret').on('click', function() { if (!$('.task-details').hasClass('collapsing')) { @@ -354,7 +312,6 @@ function init() { initEditProtocolDescription(); initLinkUpdate(); initLoadFromRepository(); - initImport(); initProtocolSectionOpenEvent(); initDetailsDropdown(); } diff --git a/app/assets/javascripts/projects/canvas.js.erb b/app/assets/javascripts/projects/canvas.js.erb index 5cbcfa281..b18f66542 100644 --- a/app/assets/javascripts/projects/canvas.js.erb +++ b/app/assets/javascripts/projects/canvas.js.erb @@ -1372,6 +1372,9 @@ function bindNewModuleAction(gridDistX, gridDistY) { function handleNewNameConfirm(ev) { var input = $("#new-module-name-input"); + + input.parent().removeClass("has-error"); + input.next("span.help-block").remove(); // Validate module name var moduleNameValid = textValidator(ev, input, <%= Constants::NAME_MIN_LENGTH %>, <%= Constants::NAME_MAX_LENGTH %>, diff --git a/app/assets/javascripts/projects/show.js b/app/assets/javascripts/projects/show.js index 887c1bd2a..56f4369e8 100644 --- a/app/assets/javascripts/projects/show.js +++ b/app/assets/javascripts/projects/show.js @@ -335,10 +335,9 @@ if (data.path) { window.location.replace(data.path); } - refreshCurrentView(); }) .on('ajax:error', '.experiment-action-form', function(ev, data) { - $(this).renderFormErrors('experiment', data.responseJSON); + HelperModule.flashAlertMsg(data.responseJSON.message, 'danger'); }); window.initActionToolbar(); diff --git a/app/assets/javascripts/repositories/repository_datatable.js b/app/assets/javascripts/repositories/repository_datatable.js index 0f7285c4c..d7a8e6bf5 100644 --- a/app/assets/javascripts/repositories/repository_datatable.js +++ b/app/assets/javascripts/repositories/repository_datatable.js @@ -179,7 +179,7 @@ var RepositoryDatatable = (function(global) { }); } - function changeToViewMode() { + function changeToViewMode(restoreSizes = true) { currentMode = 'viewMode'; // Table specific stuff TABLE.button(0).enable(true); @@ -192,7 +192,7 @@ var RepositoryDatatable = (function(global) { updateButtons(); disableCheckboxToggleOnCheckboxPreview(); - restoreColumnSizes(); + if (restoreSizes) restoreColumnSizes(); } function changeToEditMode() { @@ -626,7 +626,6 @@ var RepositoryDatatable = (function(global) { return JSON.stringify(d); }, - complete: restoreColumnSizes, global: false, type: 'POST' }, @@ -686,6 +685,11 @@ var RepositoryDatatable = (function(global) { targets: 5, class: 'added-on', visible: true + },{ + // Added by column + targets: 6, + class: 'added-by', + visible: true }, { targets: '_all', render: function(data) { @@ -743,7 +747,7 @@ var RepositoryDatatable = (function(global) { var archivedOnIndex = TABLE.column('#archived-on').index(); var archivedByIndex = TABLE.column('#archived-by').index(); animateSpinner(this, false); - changeToViewMode(); + changeToViewMode(false); updateDataTableSelectAllCtrl(); // Prevent row toggling when selecting user smart annotation link @@ -776,8 +780,8 @@ var RepositoryDatatable = (function(global) { var state = localStorage.getItem(`datatables_repositories_state/${repositoryId}/${viewType}`); json.state.start = state !== null ? JSON.parse(state).start : 0; - if (json.state.columns[6]) json.state.columns[6].visible = archived; if (json.state.columns[7]) json.state.columns[7].visible = archived; + if (json.state.columns[8]) json.state.columns[8].visible = archived; if (json.state.search) delete json.state.search; if (json.state.ColSizes && json.state.ColSizes.length > 0) { @@ -860,8 +864,6 @@ var RepositoryDatatable = (function(global) { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(restoreColumnSizes, 200); }); - - restoreColumnSizes(); } }); diff --git a/app/assets/javascripts/repositories/show.js b/app/assets/javascripts/repositories/show.js index 2ad89ca61..091d42d76 100644 --- a/app/assets/javascripts/repositories/show.js +++ b/app/assets/javascripts/repositories/show.js @@ -17,6 +17,8 @@ var formGroup = $('#form-records-file').find('.form-group'); formGroup.addClass('has-error'); formGroup.find('.help-block').remove(); + $('#form-records-file input[type="submit"]').removeAttr('disabled'); + $('#parse-sheet-loader').addClass('hidden'); formGroup.append( '' + XHR.responseJSON.message + '' ); @@ -24,6 +26,8 @@ } function handleSuccessfulSubmit(data) { + $('#form-records-file input[type="submit"]').removeAttr('disabled'); + $('#parse-sheet-loader').addClass('hidden'); $('#modal-import-records').modal('hide'); $(data.html).appendTo('body').promise().done(function() { $('#parse-records-modal').modal('show'); @@ -54,6 +58,8 @@ submitBtn.on('click', function(event) { var data = new FormData(); + submitBtn.attr('disabled', true); + $('#parse-sheet-loader').removeClass('hidden'); event.preventDefault(); event.stopPropagation(); data.append('file', document.getElementById('file').files[0]); diff --git a/app/assets/javascripts/repository_columns/index.js b/app/assets/javascripts/repository_columns/index.js index 6b86ffebc..d20a124df 100644 --- a/app/assets/javascripts/repository_columns/index.js +++ b/app/assets/javascripts/repository_columns/index.js @@ -224,17 +224,6 @@ var RepositoryColumns = (function() { }); } - function generateColumnNameTooltip(name) { - var maxLength = $(TABLE_ID).data('max-dropdown-length'); - if ($.trim(name).length > maxLength) { - return ``; - } - return name; - } - function toggleColumnVisibility() { $(columnsList).find('.vis').on('click', function(event) { const $this = $(this); @@ -323,8 +312,8 @@ var RepositoryColumns = (function() { - ${generateColumnNameTooltip(thederName)} - ${ +
${thederName}
+ ${ getColumnTypeText(el, colId) || '' } diff --git a/app/assets/javascripts/sitewide/atwho_res.js b/app/assets/javascripts/sitewide/atwho_res.js index 9e3414c72..c200a3169 100644 --- a/app/assets/javascripts/sitewide/atwho_res.js +++ b/app/assets/javascripts/sitewide/atwho_res.js @@ -56,11 +56,22 @@ var SmartAnnotation = (function() { at: at, callbacks: { remoteFilter: function(query, callback) { + + // show loader after .25 seconds and block other tab clicks + var loaderTimeout = setTimeout(function() { + $('.atwho-scroll-container').css({ height: '100px' }); + $('.atwho-scroll-container').html('
'); + $('.atwho-header-res').css({ 'pointer-events': 'none' }); + }, 250); + var $currentAtWho = $(`.atwho-view[data-at-who-id=${$(field).attr('data-smart-annotation')}]`); var filterType; var params = { query: query }; filterType = FilterTypeEnum[$currentAtWho.find('.tab-pane.active').data('object-type')]; if (!filterType) { + clearTimeout(loaderTimeout); + $('.atwho-header-res').css({ 'pointer-events': '' }); + callback([{ name: '' }]); return false; } @@ -73,6 +84,9 @@ var SmartAnnotation = (function() { } } $.getJSON(filterType.dataUrl, params, function(data) { + clearTimeout(loaderTimeout); + $('.atwho-header-res').css({ 'pointer-events': '' }); + localStorage.setItem('smart_annotation_states/teams/' + data.team, JSON.stringify({ tag: filterType.tag, repository: data.repository diff --git a/app/assets/javascripts/sitewide/constants.js.erb b/app/assets/javascripts/sitewide/constants.js.erb index 7f473c423..1b9f18059 100644 --- a/app/assets/javascripts/sitewide/constants.js.erb +++ b/app/assets/javascripts/sitewide/constants.js.erb @@ -14,4 +14,6 @@ const GLOBAL_CONSTANTS = { FILENAME_MAX_LENGTH: <%= Constants::FILENAME_MAX_LENGTH %>, FAST_STATUS_POLLING_INTERVAL: <%= Constants::FAST_STATUS_POLLING_INTERVAL %>, SLOW_STATUS_POLLING_INTERVAL: <%= Constants::SLOW_STATUS_POLLING_INTERVAL %>, + ASSET_POLLING_INTERVAL: <%= Constants::ASSET_POLLING_INTERVAL %>, + ASSET_SYNC_URL: '<%= Constants::ASSET_SYNC_URL %>', }; diff --git a/app/assets/javascripts/sitewide/filter_dropdown.js b/app/assets/javascripts/sitewide/filter_dropdown.js index cd6fd8f5b..9798b2acd 100644 --- a/app/assets/javascripts/sitewide/filter_dropdown.js +++ b/app/assets/javascripts/sitewide/filter_dropdown.js @@ -148,7 +148,6 @@ var filterDropdown = (function() { initCloseButton(); initDateTimePickerComponent(); initSearchField(filtersEnabledFunction); - this.toggleFilterMark($filterContainer, filtersEnabled) return $filterContainer; }, toggleFilterMark: function(filterContainer, filtersEnabledArg) { diff --git a/app/assets/javascripts/sitewide/zebra_print.js b/app/assets/javascripts/sitewide/zebra_print.js index 25d94b5d1..b5dc1d20b 100644 --- a/app/assets/javascripts/sitewide/zebra_print.js +++ b/app/assets/javascripts/sitewide/zebra_print.js @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ /* eslint-disable no-undef */ -/* global I18n */ +/* global HelperModule I18n */ /* eslint-disable no-unused-vars, no-use-before-define */ /* config = { @@ -218,6 +218,8 @@ var zebraPrint = (function() { updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR); } }); + }).fail(() => { + HelperModule.flashAlertMsg(I18n.t('repository_row.modal_print_label.general_error'), 'danger'); }); } }; diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index 703ee0481..9836d0532 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -26,6 +26,15 @@ html { opacity: 1; } +.scroll-container.ps-transparent .ps__rail-y{ + background-color: transparent; + + &::after, + &::before { + background-color: transparent; + } +} + .scroll-container .ps__thumb-y{ background-color: var(--sn-grey); opacity: 1; diff --git a/app/assets/stylesheets/repositories.scss b/app/assets/stylesheets/repositories.scss index 713198989..8bc1ff652 100644 --- a/app/assets/stylesheets/repositories.scss +++ b/app/assets/stylesheets/repositories.scss @@ -335,8 +335,8 @@ } &::before { - @include font-small; - bottom: -15px; + font-size: x-small; + bottom: -18px; color: $brand-danger; content: attr(data-error-text); left: 0; diff --git a/app/assets/stylesheets/repository_columns/index.scss b/app/assets/stylesheets/repository_columns/index.scss index c0b59c927..16fd7d409 100644 --- a/app/assets/stylesheets/repository_columns/index.scss +++ b/app/assets/stylesheets/repository_columns/index.scss @@ -108,6 +108,12 @@ z-index: 99999999; } + .modal-tooltip > span:first-child { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .col-list-el { align-items: center; background: $color-white; @@ -120,6 +126,7 @@ .manage-controls { display: none; + white-space: nowrap; } .text { diff --git a/app/assets/stylesheets/shared/smart_annotation.scss b/app/assets/stylesheets/shared/smart_annotation.scss index 5fdcd6c5c..1c7e77785 100644 --- a/app/assets/stylesheets/shared/smart_annotation.scss +++ b/app/assets/stylesheets/shared/smart_annotation.scss @@ -236,3 +236,7 @@ .sa-link { pointer-events: initial; } + +.atwho-inserted { + line-height: 16px; +} diff --git a/app/controllers/api/v1/assets_controller.rb b/app/controllers/api/v1/assets_controller.rb index e758e741c..24219cae7 100644 --- a/app/controllers/api/v1/assets_controller.rb +++ b/app/controllers/api/v1/assets_controller.rb @@ -34,7 +34,7 @@ module Api end asset.save!(context: :on_api_upload) - asset.post_process_file(@team) + asset.post_process_file render jsonapi: asset, serializer: AssetSerializer, diff --git a/app/controllers/api/v1/results_controller.rb b/app/controllers/api/v1/results_controller.rb index 084347588..c26c2ef02 100644 --- a/app/controllers/api/v1/results_controller.rb +++ b/app/controllers/api/v1/results_controller.rb @@ -113,7 +113,7 @@ module Api blob = create_blob_from_params asset = Asset.create!(file: blob, team: @team) end - asset.post_process_file(@team) + asset.post_process_file ResultAsset.create!(asset: asset, result: @result) end end @@ -129,7 +129,7 @@ module Api blob = create_blob_from_params asset.update!(file: blob) end - asset.post_process_file(@team) + asset.post_process_file new_checksum = asset.file.blob.checksum end @asset_result_updated = old_checksum != new_checksum diff --git a/app/controllers/asset_sync_controller.rb b/app/controllers/asset_sync_controller.rb new file mode 100644 index 000000000..855bfba53 --- /dev/null +++ b/app/controllers/asset_sync_controller.rb @@ -0,0 +1,227 @@ +# frozen_string_literal: true + +class AssetSyncController < ApplicationController + include FileIconsHelper + + skip_before_action :authenticate_user!, only: %i(update download) + skip_before_action :verify_authenticity_token, only: %i(update download) + before_action :authenticate_asset_sync_token!, only: %i(update download) + before_action :check_asset_sync + + def show + asset = Asset.find_by(id: params[:asset_id]) + + render_error(:forbidden) and return unless asset && can_manage_asset?(asset) + + asset_sync_token = current_user.asset_sync_tokens.find_or_create_by(asset_id: params[:asset_id]) + + unless asset_sync_token.token_valid? + asset_sync_token = current_user.asset_sync_tokens.create(asset_id: params[:asset_id]) + end + + render json: AssetSyncTokenSerializer.new(asset_sync_token).as_json + end + + def download + redirect_to(@asset.file.url, allow_other_host: true) + end + + def update + if @asset_sync_token.conflicts?(request.headers['VersionToken']) + ActiveRecord::Base.transaction do + conflict_response = AssetSyncTokenSerializer.new(conflicting_asset_copy_token).as_json + error_message = { message: I18n.t('assets.conflict_error', filename: @asset.file.filename) } + log_activity(:create) + render json: conflict_response.merge(error_message), status: :conflict + end + + return + end + + orig_file_size = @asset.file_size + + ActiveRecord::Base.transaction do + @asset.update(last_modified_by: current_user) + if wopi_file?(@asset) + @asset.update_contents(request.body) + else + @asset.file.attach(io: request.body, filename: @asset.file.filename) + @asset.touch + end + + @asset.team.release_space(orig_file_size) + @asset.post_process_file + + log_activity(:edit) + end + + render json: AssetSyncTokenSerializer.new(@asset_sync_token).as_json + end + + def api_url + render plain: Constants::ASSET_SYNC_URL + end + + def log_activity(action) + case action + when :edit + log_edit_activity + when :create + log_create_activity + end + end + + private + + def render_error(status, filename = nil, message = nil) + message ||= if filename.present? + I18n.t('assets.default_error_with_filename', filename: filename) + else + I18n.t('assets.default_error') + end + + render json: { message: message }, status: status + end + + def conflicting_asset_copy_token + Asset.transaction do + new_asset = @asset.dup + new_asset.save + + blob = ActiveStorage::Blob.create_and_upload!( + io: request.body, + filename: "#{@asset.file.filename.base} (#{t('general.copy')}).#{@asset.file.filename.extension}", + metadata: @asset.blob.metadata + ) + + new_asset.file.attach(blob) + + case @asset.parent + when Step + StepAsset.create!(step: @asset.step, asset: new_asset) + when Result + ResultAsset.create!(result: @asset.result, asset: new_asset) + end + + @asset = new_asset.reload + + new_asset.post_process_file + + current_user.asset_sync_tokens.create!(asset_id: new_asset.id) + end + end + + def authenticate_asset_sync_token! + @asset_sync_token = AssetSyncToken.find_by(token: request.headers['Authentication']) + + render_error(:unauthorized) and return unless @asset_sync_token&.token_valid? + + @asset = @asset_sync_token.asset + @current_user = @asset_sync_token.user + + render_error(:forbidden, @asset.file.filename) and return unless can_manage_asset?(@asset) + end + + def log_create_activity + assoc = @asset.step + assoc ||= @asset.result + + case assoc + when Step + type_of = assoc.protocol.in_module? ? :task_step_file_added : :protocol_step_file_added + message_items = { + step: assoc.id, + step_position: { id: assoc.id, + value_for: 'position_plus_one' }, + file: @asset.file_name, + my_module: assoc.protocol.in_module? ? assoc.my_module.id : nil, + protocol: assoc.protocol.in_module? ? nil : assoc.protocol.id + }.compact + project = assoc.protocol.in_module? ? assoc.my_module.project : nil + when Result + type_of = :result_file_added + message_items = { result: assoc } + project = assoc.my_module.project + end + + Activities::CreateActivityService.call( + activity_type: type_of, + owner: current_user, + team: assoc.team, + subject: @asset, + project: project, + message_items: message_items + ) + end + + def log_edit_activity + assoc ||= @asset.step + assoc ||= @asset.result + + case assoc + when Step + if assoc.protocol.in_module? + log_step_edit_activity( + :edit_task_step_file_locally, + assoc, + assoc.my_module.project, + my_module: assoc.my_module.id, + file: @asset.file_name, + user: current_user.id, + step_position_original: @asset.step.position + 1, + step: assoc.id + ) + else + log_step_edit_activity( + :edit_protocol_template_file_locally, + assoc, + nil, + { + file: @asset.file_name, + user: current_user.id, + step_position_original: @asset.step.position + 1, + step: assoc.id + } + ) + end + when Result + log_result_edit_activity( + :edit_task_result_file_locally, + assoc, + file: @asset.file_name, + user: current_user.id, + result: Result.first.id + ) + end + end + + def log_step_edit_activity(type_of, step, project = nil, message_items = {}) + default_items = { step: step.id, + step_position: { id: step.id, value_for: 'position_plus_one' } } + message_items = default_items.merge(message_items) + + Activities::CreateActivityService + .call(activity_type: type_of, + owner: current_user, + subject: step.protocol, + team: step.protocol.team, + project: project, + message_items: message_items) + end + + def log_result_edit_activity(type_of, result, message_items) + Activities::CreateActivityService + .call(activity_type: type_of, + owner: current_user, + subject: result, + team: result.my_module.team, + project: result.my_module.project, + message_items: { + result: result.id + }.merge(message_items)) + end + + def check_asset_sync + render_404 if ENV['ASSET_SYNC_URL'].blank? + end +end diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb index 1586da11c..a07f34e0e 100644 --- a/app/controllers/assets_controller.rb +++ b/app/controllers/assets_controller.rb @@ -145,6 +145,14 @@ class AssetsController < ApplicationController redirect_to rails_blob_path(@asset.file, disposition: 'attachment') end + def show + if @asset + render json: @asset, serializer: AssetSerializer, user: current_user + else + render json: { error: 'Asset not found' }, status: :not_found + end + end + def edit action = @asset.file_size.zero? && !@asset.locked? ? 'editnew' : 'edit' @action_url = append_wd_params(@asset.get_action_url(current_user, action, false)) @@ -186,13 +194,14 @@ class AssetsController < ApplicationController orig_file_name = @asset.file_name return render_403 unless can_read_team?(@asset.team) + @asset.last_modified_by = current_user @asset.file.attach(io: params.require(:image), filename: orig_file_name) @asset.save! create_edit_image_activity(@asset, current_user, :finish_editing) # release previous image space @asset.team.release_space(orig_file_size) # Post process file here - @asset.post_process_file(@asset.team) + @asset.post_process_file @asset.step&.protocol&.update(updated_at: Time.zone.now) render_html = if [Result, Step].include?(@assoc.class) @@ -302,6 +311,10 @@ class AssetsController < ApplicationController end end + def checksum + render json: { checksum: @asset.file.blob.checksum } + end + private def load_vars diff --git a/app/controllers/experiments_controller.rb b/app/controllers/experiments_controller.rb index 662e55f43..81c1132df 100644 --- a/app/controllers/experiments_controller.rb +++ b/app/controllers/experiments_controller.rb @@ -8,19 +8,21 @@ class ExperimentsController < ApplicationController include Rails.application.routes.url_helpers include Breadcrumbs - before_action :load_project, only: %i(new create archive_group restore_group) + before_action :load_project, only: %i(new create archive_group restore_group move) before_action :load_experiment, except: %i(new create archive_group restore_group - inventory_assigning_experiment_filter actions_toolbar) - before_action :check_read_permissions, except: %i(edit archive clone move new + inventory_assigning_experiment_filter actions_toolbar + move move_modal) + before_action :load_experiments, only: %i(move_modal move) + before_action :check_move_permissions, only: %i(move_modal move) + before_action :check_read_permissions, except: %i(edit archive clone move move_modal new create archive_group restore_group inventory_assigning_experiment_filter actions_toolbar) before_action :check_canvas_read_permissions, only: %i(canvas) - before_action :check_create_permissions, only: %i(new create) + before_action :check_create_permissions, only: %i(new create move) before_action :check_manage_permissions, only: %i(edit batch_clone_my_modules) before_action :check_update_permissions, only: %i(update) before_action :check_archive_permissions, only: :archive before_action :check_clone_permissions, only: %i(clone_modal clone) - before_action :check_move_permissions, only: %i(move_modal move) before_action :set_inline_name_editing, only: %i(canvas table module_archive) before_action :set_breadcrumbs_items, only: %i(canvas table module_archive) before_action :set_navigator, only: %i(canvas module_archive table) @@ -254,11 +256,11 @@ class ExperimentsController < ApplicationController # POST: clone_experiment(id) def clone - project = current_team.projects.find(move_experiment_param) - return render_403 unless can_create_project_experiments?(project) + @project = current_team.projects.find(move_experiment_param) + return render_403 unless can_create_project_experiments?(@project) service = Experiments::CopyExperimentAsTemplateService.call(experiment: @experiment, - project: project, + project: @project, user: current_user) if service.succeed? @@ -274,7 +276,7 @@ class ExperimentsController < ApplicationController # GET: move_modal_experiment_path(id) def move_modal - @projects = @experiment.movable_projects(current_user) + @projects = @experiments.first.movable_projects(current_user) render json: { html: render_to_string(partial: 'move_modal', formats: :html) } @@ -297,23 +299,28 @@ class ExperimentsController < ApplicationController # POST: move_experiment(id) def move - service = Experiments::MoveToProjectService - .call(experiment_id: @experiment.id, - project_id: move_experiment_param, - user_id: current_user.id) - if service.succeed? - flash[:success] = t('experiments.move.success_flash', - experiment: @experiment.name) - status = :ok - view_state = @experiment.current_view_state(current_user) - view_type = view_state.state['my_modules']['view_type'] || 'canvas' - path = view_mode_redirect_url(view_type) - else - message = "#{service.errors.values.join('. ')}." - status = :unprocessable_entity - end + @project.transaction do + @experiments.each do |experiment| + service = Experiments::MoveToProjectService + .call(experiment_id: experiment.id, + project_id: params[:project_id], + user_id: current_user.id) + raise StandardError unless service.succeed? + end - render json: { message: message, path: path }, status: status + flash[:success] = t('experiments.table.move_success_flash', project: escape_input(@project.name)) + render json: { message: t('experiments.table.move_success_flash', + project: escape_input(@project.name)), path: project_path(@project) } + rescue StandardError => e + Rails.logger.error(e.message) + Rails.logger.error(e.backtrace.join("\n")) + render json: { + message: t('experiments.table.move_error_flash', project: escape_input(@project.name)) + }, status: :unprocessable_entity + raise ActiveRecord::Rollback + end + rescue ActiveRecord::RecordNotFound + render_404 end def move_modules_modal @@ -532,6 +539,11 @@ class ExperimentsController < ApplicationController render_404 unless @experiment end + def load_experiments + @experiments = Experiment.preload(user_assignments: %i(user user_role)).where(id: params[:ids]) + render_404 unless @experiments + end + def load_project @project = Project.find_by(id: params[:project_id]) render_404 unless @project @@ -584,7 +596,7 @@ class ExperimentsController < ApplicationController end def check_move_permissions - render_403 unless can_move_experiment?(@experiment) + render_403 unless @experiments.all? { |e| can_move_experiment?(e) } end def set_inline_name_editing diff --git a/app/controllers/gene_sequence_assets_controller.rb b/app/controllers/gene_sequence_assets_controller.rb index 5562a50d6..a706a200f 100644 --- a/app/controllers/gene_sequence_assets_controller.rb +++ b/app/controllers/gene_sequence_assets_controller.rb @@ -91,6 +91,7 @@ class GeneSequenceAssetsController < ApplicationController file.blob.metadata['name'] = params[:sequence_name] file.save! @asset.view_mode = view_mode || @parent.assets_view_mode + @asset.last_modified_by = current_user @asset.save! end end diff --git a/app/controllers/my_module_repositories_controller.rb b/app/controllers/my_module_repositories_controller.rb index e32d8cf4d..b2a201544 100644 --- a/app/controllers/my_module_repositories_controller.rb +++ b/app/controllers/my_module_repositories_controller.rb @@ -130,7 +130,7 @@ class MyModuleRepositoriesController < ApplicationController render json: { html: render_to_string( partial: 'my_modules/repositories/full_view_table', - locals: { include_stock_consumption: params[:include_stock_consumption] } + locals: { include_stock_consumption: params[:include_stock_consumption] == 'true' } ) } end diff --git a/app/controllers/my_module_repository_snapshots_controller.rb b/app/controllers/my_module_repository_snapshots_controller.rb index 112e76537..2d0c6a128 100644 --- a/app/controllers/my_module_repository_snapshots_controller.rb +++ b/app/controllers/my_module_repository_snapshots_controller.rb @@ -34,7 +34,7 @@ class MyModuleRepositorySnapshotsController < ApplicationController end def create - repository_snapshot = RepositorySnapshot.create_preliminary(@repository, @my_module, current_user) + repository_snapshot = RepositorySnapshot.create_preliminary!(@repository, @my_module, current_user) RepositorySnapshotProvisioningJob.perform_later(repository_snapshot) render json: { diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index 42d9fd3d0..a6204470d 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -53,9 +53,6 @@ class ProtocolsController < ApplicationController before_action :check_load_from_repository_permissions, only: [ :load_from_repository ] - before_action :check_load_from_file_permissions, only: [ - :load_from_file - ] before_action :check_copy_to_repository_permissions, only: %i( copy_to_repository ) @@ -69,7 +66,7 @@ class ProtocolsController < ApplicationController before_action :check_protocolsio_import_permissions, only: %i(protocolsio_import_create protocolsio_import_save) - before_action :set_importer, only: %i(load_from_file import) + before_action :set_importer, only: :import before_action :set_inline_name_editing, only: :show before_action :set_breadcrumbs_items, only: %i(index show) @@ -472,41 +469,6 @@ class ProtocolsController < ApplicationController end end - def load_from_file - # This is actually very similar to import - if @protocol.can_destroy? - transaction_error = false - Protocol.transaction do - @importer.import_into_existing( - @protocol, @protocol_json - ) - rescue StandardError => e - transaction_error = true - Rails.logger.error(e.message) - Rails.logger.error(e.backtrace.join("\n")) - raise ActiveRecord::Rollback - end - - if transaction_error - format.json do - render json: { status: :error }, status: :bad_request - end - else - # Everything good, record activity, display flash & render 200 - log_activity(:load_protocol_to_task_from_file, - @protocol.my_module.experiment.project, - my_module: @my_module.id) - flash[:success] = t( - 'my_modules.protocols.load_from_file_flash' - ) - flash.keep(:success) - render json: { status: :ok }, status: :ok - end - else - render json: { status: :locked }, status: :bad_request - end - end - def protocolsio_index render json: { html: render_to_string({ partial: 'protocols/index/protocolsio_modal_body', formats: :html }) @@ -1015,19 +977,6 @@ class ProtocolsController < ApplicationController can_read_protocol_in_repository?(@source)) end - def check_load_from_file_permissions - @protocol_json = params[:protocol] - @protocol = Protocol.find_by_id(params[:id]) - @my_module = @protocol.my_module - - if @protocol_json.blank? || - @protocol.blank? || - @my_module.blank? || - !can_manage_protocol_in_module?(@protocol) - render_403 - end - end - def check_copy_to_repository_permissions @protocol = Protocol.find_by(id: params[:id]) @my_module = @protocol.my_module diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 46df52191..37a5228fb 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -155,7 +155,7 @@ class ReportsController < ApplicationController end def status - docx = @report.docx_file.attached? ? document_preview_report_path(@report, report_type: :docx) : nil + docx = @report.docx_preview_file.attached? ? document_preview_report_path(@report, report_type: :docx) : nil pdf = @report.pdf_file.attached? ? document_preview_report_path(@report, report_type: :pdf) : nil render json: { diff --git a/app/controllers/result_assets_controller.rb b/app/controllers/result_assets_controller.rb index 228f12a92..e2c429260 100644 --- a/app/controllers/result_assets_controller.rb +++ b/app/controllers/result_assets_controller.rb @@ -60,7 +60,7 @@ class ResultAssetsController < ApplicationController team.save # Post process new file if neccesary - @result.asset.post_process_file(team) if asset_changed && @result.asset.present? + @result.asset.post_process_file if asset_changed && @result.asset.present? log_activity(:edit_result) end @@ -133,7 +133,7 @@ class ResultAssetsController < ApplicationController last_modified_by: current_user) results << result # Post process file here - asset.post_process_file(@my_module.experiment.project.team) + asset.post_process_file log_activity(:add_result, result) end diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index e2e38ef45..a01c41244 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -90,7 +90,7 @@ class ResultsController < ApplicationController view_mode: @result.assets_view_mode ) @asset.file.attach(params[:signed_blob_id]) - @asset.post_process_file(@my_module.team) + @asset.post_process_file end log_activity(:result_file_added, { file: @asset.file_name, result: @result }) diff --git a/app/controllers/step_elements/checklist_items_controller.rb b/app/controllers/step_elements/checklist_items_controller.rb index 9889d6f51..6870ce8fe 100644 --- a/app/controllers/step_elements/checklist_items_controller.rb +++ b/app/controllers/step_elements/checklist_items_controller.rb @@ -18,7 +18,7 @@ module StepElements checklist_item = @checklist.checklist_items.new(checklist_item_params.merge!(created_by: current_user)) new_items = [] ActiveRecord::Base.transaction do - new_items = checklist_item.save_multiline! + new_items = checklist_item.save_multiline!(after_id: params[:after_id]) new_items.each do |item| log_activity( "#{@step.protocol.in_module? ? :task : :protocol}_step_checklist_item_added", @@ -102,9 +102,10 @@ module StepElements end def reorder - checklist_item = @checklist.checklist_items.find(checklist_item_params[:id]) + checklist_item = @checklist.checklist_items.find(params[:id]) ActiveRecord::Base.transaction do - checklist_item.insert_at(checklist_item_params[:position]) + insert_at = (@checklist.checklist_items.find_by(id: params[:after_id])&.position || 0) + checklist_item.insert_at(insert_at) end render json: params[:checklist_item_positions], status: :ok rescue ActiveRecord::RecordInvalid diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb index cd4d699de..c2b1c0764 100644 --- a/app/controllers/steps_controller.rb +++ b/app/controllers/steps_controller.rb @@ -42,7 +42,7 @@ class StepsController < ApplicationController view_mode: @step.assets_view_mode ) @asset.file.attach(params[:signed_blob_id]) - @asset.post_process_file(@protocol.team) + @asset.post_process_file default_message_items = { step: @step.id, diff --git a/app/controllers/users/settings/account/addons_controller.rb b/app/controllers/users/settings/account/addons_controller.rb index c1777ba3c..1c89c96d4 100644 --- a/app/controllers/users/settings/account/addons_controller.rb +++ b/app/controllers/users/settings/account/addons_controller.rb @@ -9,6 +9,7 @@ module Users def index @label_printer_any = LabelPrinter.any? + @user_agent = request.user_agent end private diff --git a/app/controllers/wopi_controller.rb b/app/controllers/wopi_controller.rb index 949b15c59..6dbe4051e 100644 --- a/app/controllers/wopi_controller.rb +++ b/app/controllers/wopi_controller.rb @@ -201,8 +201,8 @@ class WopiController < ActionController::Base logger.warn 'WOPI: replacing file' @team.release_space(@asset.estimated_size) - @asset.update_contents(request.body) @asset.last_modified_by = @user + @asset.update_contents(request.body) @asset.save @team.take_space(@asset.estimated_size) diff --git a/app/datatables/protocols_datatable.rb b/app/datatables/protocols_datatable.rb index 5ce0b5075..81c5cc7ec 100644 --- a/app/datatables/protocols_datatable.rb +++ b/app/datatables/protocols_datatable.rb @@ -146,7 +146,22 @@ class ProtocolsDatatable < CustomDatatable end def get_raw_records_base - records = Protocol.latest_available_versions(@team) + team_protocols = Protocol.where(team: @team) + original_without_versions = team_protocols + .left_outer_joins(:published_versions) + .in_repository_published_original + .where(published_versions: { id: nil }) + .select(:id) + published_versions = team_protocols + .in_repository_published_version + .order(:parent_id, version_number: :desc) + .select('DISTINCT ON (parent_id) id') + new_drafts = team_protocols + .where(protocol_type: Protocol.protocol_types[:in_repository_draft], parent_id: nil) + .select(:id) + + records = Protocol.where('protocols.id IN (?) OR protocols.id IN (?) OR protocols.id IN (?)', + original_without_versions, published_versions, new_drafts) records = @type == :archived ? records.archived : records.active @@ -162,16 +177,14 @@ class ProtocolsDatatable < CustomDatatable .joins("LEFT OUTER JOIN protocols protocol_versions " \ "ON protocol_versions.protocol_type = #{Protocol.protocol_types[:in_repository_published_version]} " \ "AND protocol_versions.parent_id = protocols.parent_id") - .joins("LEFT OUTER JOIN protocols self_linked_task_protocols " \ - "ON self_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \ - "AND self_linked_task_protocols.parent_id = protocols.id") - .joins("LEFT OUTER JOIN protocols parent_linked_task_protocols " \ - "ON parent_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \ - "AND parent_linked_task_protocols.parent_id = protocols.parent_id") - .joins("LEFT OUTER JOIN protocols version_linked_task_protocols " \ - "ON version_linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \ - "AND version_linked_task_protocols.parent_id = protocol_versions.id " \ - "AND version_linked_task_protocols.parent_id != protocols.id") + .joins("LEFT OUTER JOIN protocols protocol_originals " \ + "ON protocol_originals.protocol_type = #{Protocol.protocol_types[:in_repository_published_original]} " \ + "AND protocol_originals.id = protocols.parent_id OR " \ + "(protocols.id = protocol_originals.id AND protocols.parent_id IS NULL)") + .joins("LEFT OUTER JOIN protocols linked_task_protocols " \ + "ON linked_task_protocols.protocol_type = #{Protocol.protocol_types[:linked]} " \ + "AND (linked_task_protocols.parent_id = protocol_versions.id OR " \ + "linked_task_protocols.parent_id = protocol_originals.id)") .joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \ 'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"') .joins('LEFT OUTER JOIN "protocol_keywords" ' \ @@ -191,9 +204,7 @@ class ProtocolsDatatable < CustomDatatable "CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \ "THEN 0 ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \ "END AS nr_of_versions", - '(COUNT(DISTINCT("self_linked_task_protocols"."id")) + ' \ - 'COUNT(DISTINCT("parent_linked_task_protocols"."id")) + ' \ - 'COUNT(DISTINCT("version_linked_task_protocols"."id"))) AS nr_of_linked_tasks', + 'COUNT(DISTINCT("linked_task_protocols"."id")) AS nr_of_linked_tasks', 'COUNT(DISTINCT("all_user_assignments"."id")) AS "nr_of_assigned_users"', 'MAX("users"."full_name") AS "full_username_str"', # "Hack" to get single username 'MAX("archived_users"."full_name") AS "archived_full_username_str"' diff --git a/app/datatables/report_datatable.rb b/app/datatables/report_datatable.rb index 74f061134..59da7c0de 100644 --- a/app/datatables/report_datatable.rb +++ b/app/datatables/report_datatable.rb @@ -76,7 +76,7 @@ class ReportDatatable < CustomDatatable end def docx_file(report) - docx = document_preview_report_path(report, report_type: :docx) if report.docx_file.attached? + docx = document_preview_report_path(report, report_type: :docx) if report.docx_preview_file.attached? { processing: report.docx_processing?, preview_url: docx, diff --git a/app/helpers/repository_datatable_helper.rb b/app/helpers/repository_datatable_helper.rb index f721f0a9b..34c9b336b 100644 --- a/app/helpers/repository_datatable_helper.rb +++ b/app/helpers/repository_datatable_helper.rb @@ -52,6 +52,11 @@ module RepositoryDatatableHelper serialize_repository_cell_value(cell, team, repository, reminders_enabled: reminders_enabled) end + if repository.repository_columns.stock_type.exists? + stock_cell = record.repository_cells.find { |cell| cell.value_type == 'RepositoryStockValue' } + row['stock'] = serialize_repository_cell_value(record.repository_stock_cell, team, repository) if stock_cell.present? + end + if has_stock_management stock_cell = record.repository_cells.find { |cell| cell.value_type == 'RepositoryStockValue' } diff --git a/app/javascript/packs/vue/design_system/modals.js b/app/javascript/packs/vue/design_system/modals.js new file mode 100644 index 000000000..66f138567 --- /dev/null +++ b/app/javascript/packs/vue/design_system/modals.js @@ -0,0 +1,56 @@ +import { createApp } from 'vue/dist/vue.esm-bundler.js'; +import { shallowRef } from 'vue'; + +import WizardModal from '../../../vue/shared/wizard_modal.vue'; +import Step1 from './wizard_steps/step_1.vue'; +import Step2 from './wizard_steps/step_2.vue'; +import Step3 from './wizard_steps/step_3.vue'; +import { mountWithTurbolinks } from '../helpers/turbolinks.js'; + +const app = createApp({ + components: { + Step1, + Step2, + Step3 + }, + methods: { + fireAlert() { + alert('Fired!'); + } + }, + data() { + return { + wizardConfig: { + title: 'Wizard steps', + subtitle: 'Wizard subtitle description', + steps: [ + { + id: 'step1', + icon: 'sn-icon sn-icon-open', + label: 'Step 1', + component: shallowRef(Step1) + }, + { + id: 'step2', + icon: 'sn-icon sn-icon-edit', + label: 'Step 2', + component: shallowRef(Step2) + }, + { + id: 'step3', + icon: 'sn-icon sn-icon-inventory', + label: 'Step 3', + component: shallowRef(Step3) + } + ] + }, + wizardParams: { + text: 'Some text' + }, + showWizard: false + }; + } +}); +app.component('WizardModal', WizardModal); +app.config.globalProperties.i18n = window.I18n; +mountWithTurbolinks(app, '#modals'); diff --git a/app/javascript/packs/vue/design_system/wizard_steps/step_1.vue b/app/javascript/packs/vue/design_system/wizard_steps/step_1.vue new file mode 100644 index 000000000..bd7f8851c --- /dev/null +++ b/app/javascript/packs/vue/design_system/wizard_steps/step_1.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/javascript/packs/vue/design_system/wizard_steps/step_2.vue b/app/javascript/packs/vue/design_system/wizard_steps/step_2.vue new file mode 100644 index 000000000..590836b8c --- /dev/null +++ b/app/javascript/packs/vue/design_system/wizard_steps/step_2.vue @@ -0,0 +1,41 @@ + + + diff --git a/app/javascript/packs/vue/design_system/wizard_steps/step_3.vue b/app/javascript/packs/vue/design_system/wizard_steps/step_3.vue new file mode 100644 index 000000000..c8571e599 --- /dev/null +++ b/app/javascript/packs/vue/design_system/wizard_steps/step_3.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/javascript/packs/vue/open_locally_menu.js b/app/javascript/packs/vue/open_locally_menu.js new file mode 100644 index 000000000..d7e6d6c2b --- /dev/null +++ b/app/javascript/packs/vue/open_locally_menu.js @@ -0,0 +1,8 @@ +import { createApp } from 'vue/dist/vue.esm-bundler.js'; +import OpenLocallyMenu from '../../vue/shared/content/attachments/open_locally_menu.vue'; +import { mountWithTurbolinks } from './helpers/turbolinks.js'; + +const app = createApp({}); +app.component('OpenLocallyMenu', OpenLocallyMenu); +app.config.globalProperties.i18n = window.I18n; +mountWithTurbolinks(app, '#openLocallyMenu'); diff --git a/app/javascript/packs/vue/scinote_edit_download.js b/app/javascript/packs/vue/scinote_edit_download.js new file mode 100644 index 000000000..85640284a --- /dev/null +++ b/app/javascript/packs/vue/scinote_edit_download.js @@ -0,0 +1,8 @@ +import { createApp } from 'vue/dist/vue.esm-bundler.js'; +import ScinoteEditDownload from '../../vue/shared/scinote_edit_download.vue'; +import { mountWithTurbolinks } from './helpers/turbolinks.js'; + +const app = createApp({}); +app.component('ScinoteEditDownload', ScinoteEditDownload); +app.config.globalProperties.i18n = window.I18n; +mountWithTurbolinks(app, '#scinoteEditDownload'); diff --git a/app/javascript/vue/item_relationships/RepositoryItemRelationshipsModal.vue b/app/javascript/vue/item_relationships/RepositoryItemRelationshipsModal.vue index 24c2ad97c..6f0210ddf 100644 --- a/app/javascript/vue/item_relationships/RepositoryItemRelationshipsModal.vue +++ b/app/javascript/vue/item_relationships/RepositoryItemRelationshipsModal.vue @@ -6,9 +6,9 @@ id="repositoryItemRelationshipsModal" tabindex="-1" role="dialog" - class="modal "> + class="modal"> @@ -92,6 +94,7 @@ :value="selectedRelationshipValue" :options="[['parent', 'Parent'], ['child', 'Child']]" :placeholder="i18n.t('repositories.item_card.repository_item_relationships_modal.select_relationship_placeholder')" + data-e2e="e2e-DD-repoItemRelationshipsMD-relationship" > @@ -112,11 +115,11 @@ @@ -91,7 +92,7 @@ {{ i18n.t('repositories.item_card.default_columns.added_by') }} - + {{ defaultColumns?.added_by }} @@ -102,7 +103,7 @@ {{ i18n.t('repositories.item_card.default_columns.archived_on') }} - + {{ defaultColumns.archived_on }} @@ -113,7 +114,7 @@ {{ i18n.t('repositories.item_card.default_columns.archived_by') }} - + {{ defaultColumns.archived_by.full_name }} @@ -147,13 +148,15 @@
- @@ -193,13 +196,15 @@
- @@ -244,6 +249,7 @@ class="flex flex-row text-lg font-semibold w-[350px] mb-6 leading-7 items-center justify-between transition-colors duration-300" ref="assigned-label" id="assigned-label" + data-e2e="e2e-TX-repoItemSB-assigned" > {{ i18n.t('repositories.item_card.section.assigned', { count: assignedModules ? @@ -255,7 +261,7 @@ 'disabled': actions?.assign_repository_row && actions.assign_repository_row.disabled }" :data-assign-url="actions?.assign_repository_row ? actions.assign_repository_row.assign_url : ''" - :data-repository-row-id="repositoryRowId" @click="showRepositoryAssignModal"> + :data-repository-row-id="repositoryRowId" @click="showRepositoryAssignModal" data-e2e="e2e-TL-repoItemSBassigned-assignToTask"> {{ i18n.t('repositories.item_card.assigned.assign') }}
@@ -314,7 +320,7 @@ :class="{ 'pb-6': customColumns?.length }">
- + +
+
+ + +<%= javascript_include_tag 'vue_design_system_modals' %> diff --git a/app/views/design_elements/index.html.erb b/app/views/design_elements/index.html.erb index e69de29bb..b9138c5ca 100644 --- a/app/views/design_elements/index.html.erb +++ b/app/views/design_elements/index.html.erb @@ -0,0 +1 @@ +<%= render partial: 'modals' %> diff --git a/app/views/experiments/_dropdown_actions.html.erb b/app/views/experiments/_dropdown_actions.html.erb index 48d102df6..9032fb333 100644 --- a/app/views/experiments/_dropdown_actions.html.erb +++ b/app/views/experiments/_dropdown_actions.html.erb @@ -32,7 +32,7 @@ <% end %> <% if can_move_experiment?(experiment) && !archived %>
  • - <%= link_to move_modal_experiment_url(experiment), + <%= link_to move_modal_experiments_path(ids: experiment.id), remote: true, type: 'button', class: 'move-experiment' do %> diff --git a/app/views/experiments/_move_modal.html.erb b/app/views/experiments/_move_modal.html.erb index 68d45ee4f..bce372516 100644 --- a/app/views/experiments/_move_modal.html.erb +++ b/app/views/experiments/_move_modal.html.erb @@ -1,10 +1,10 @@