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 `
- ${truncateLongString(name, maxLength)}
- ${name}
-
`;
- }
- 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 @@
+
+
+
+ You can add any custom html here or render params like this:
+
+ {{ params }}
+
+
+
+
+
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 @@
+
+
+
+ All steps have access to shared params
+
+
+
+
+
+
+
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 @@
+
+
+
+ Our params - {{ params }}
+
+ If you want emit action use wizardComponent
+
+
+
+
+
+
+
+
+
+
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">
-
+
@@ -75,6 +76,7 @@
@update="selectedItemValues = $event"
@reached-end="() => fetchInventoryItems(selectedInventoryValue)"
:disabled="!this.selectedInventoryValue"
+ data-e2e="e2e-DC-repoItemRelationshipsMD-item"
>
@@ -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 }">
diff --git a/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryStockValue.vue b/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryStockValue.vue
index 964007bf8..be8a4541d 100644
--- a/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryStockValue.vue
+++ b/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryStockValue.vue
@@ -26,6 +26,7 @@
@click="enableEditing"
:data-manage-stock-url="values?.stock_url"
:data-repository-row-id="repositoryId"
+ :data-e2e="'e2e-BT-repoItemSBcustomColumns-input' + colId"
>
diff --git a/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryTextValue.vue b/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryTextValue.vue
index cf2e5737d..1127813d9 100644
--- a/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryTextValue.vue
+++ b/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryTextValue.vue
@@ -25,7 +25,9 @@
:collapsed="collapsed"
@toggleExpandableState="toggleExpandableState"
@update="update"
- className="px-3" />
+ className="px-3"
+ :data-e2e="'e2e-IF-repoItemSBcustomColumns-input' + colId"
+ />
+ :class="{ 'text-sn-science-blue': navigationItemsStatus[index] }"
+ :data-e2e="'e2e-BT-repoItemSB-' + navigationItem.labelAlias"
+ >
{{ i18n.t(`repositories.highlight_component.${navigationItem.labelAlias}`) }}
diff --git a/app/javascript/vue/repository_print_modal/container.vue b/app/javascript/vue/repository_print_modal/container.vue
index f06449624..0e2969c8a 100644
--- a/app/javascript/vue/repository_print_modal/container.vue
+++ b/app/javascript/vue/repository_print_modal/container.vue
@@ -91,6 +91,8 @@
diff --git a/app/javascript/vue/shared/content/attachments/thumbnail.vue b/app/javascript/vue/shared/content/attachments/thumbnail.vue
index cabea06c6..490c2f56a 100644
--- a/app/javascript/vue/shared/content/attachments/thumbnail.vue
+++ b/app/javascript/vue/shared/content/attachments/thumbnail.vue
@@ -1,8 +1,9 @@
-
+
+ @keyup.enter="addItem(checklistItems[checklistItems.length - 1]?.id)"
+ @click="addItem(checklistItems[checklistItems.length - 1]?.id)">
{{ i18n.t('protocols.steps.insert.checklist_item') }}
@@ -81,6 +81,9 @@
diff --git a/app/javascript/vue/shared/content/modal/no_predefined_app_modal.vue b/app/javascript/vue/shared/content/modal/no_predefined_app_modal.vue
new file mode 100644
index 000000000..6ae1922ac
--- /dev/null
+++ b/app/javascript/vue/shared/content/modal/no_predefined_app_modal.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
diff --git a/app/javascript/vue/shared/content/modal/update_version_modal.vue b/app/javascript/vue/shared/content/modal/update_version_modal.vue
new file mode 100644
index 000000000..803f9a2c9
--- /dev/null
+++ b/app/javascript/vue/shared/content/modal/update_version_modal.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
diff --git a/app/javascript/vue/shared/inline_edit.vue b/app/javascript/vue/shared/inline_edit.vue
index 893f117d4..4fe025149 100644
--- a/app/javascript/vue/shared/inline_edit.vue
+++ b/app/javascript/vue/shared/inline_edit.vue
@@ -270,8 +270,8 @@ export default {
this.newValue = this.$refs.input.value.trim(); // Fix for smart annotation
this.editing = false;
- this.$emit('editingDisabled');
this.$emit('update', this.newValue, withKey);
+ this.$emit('editingDisabled');
},
refreshTexareaHeight() {
if (this.editing && !this.singleLine) {
diff --git a/app/javascript/vue/shared/menu_dropdown.vue b/app/javascript/vue/shared/menu_dropdown.vue
index bec8ed99f..61df17ca9 100644
--- a/app/javascript/vue/shared/menu_dropdown.vue
+++ b/app/javascript/vue/shared/menu_dropdown.vue
@@ -1,5 +1,5 @@
-
+