diff --git a/VERSION b/VERSION
index f5519b88a..034552a83 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.29.6
+1.30.0
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..8553b581a 100644
--- a/app/assets/javascripts/repositories/repository_datatable.js
+++ b/app/assets/javascripts/repositories/repository_datatable.js
@@ -686,6 +686,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) {
@@ -776,8 +781,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) {
diff --git a/app/assets/javascripts/repository_columns/index.js b/app/assets/javascripts/repository_columns/index.js
index 6b86ffebc..4e65596f2 100644
--- a/app/assets/javascripts/repository_columns/index.js
+++ b/app/assets/javascripts/repository_columns/index.js
@@ -228,7 +228,7 @@ var RepositoryColumns = (function() {
var maxLength = $(TABLE_ID).data('max-dropdown-length');
if ($.trim(name).length > maxLength) {
return `
- ${truncateLongString(name, maxLength)}
+ ${truncateLongString(name, maxLength)}
${name}
`;
}
diff --git a/app/assets/javascripts/sitewide/constants.js.erb b/app/assets/javascripts/sitewide/constants.js.erb
index 7f473c423..8e37b3abc 100644
--- a/app/assets/javascripts/sitewide/constants.js.erb
+++ b/app/assets/javascripts/sitewide/constants.js.erb
@@ -14,4 +14,5 @@ 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_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/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 24219cae7..e758e741c 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
+ asset.post_process_file(@team)
render jsonapi: asset,
serializer: AssetSerializer,
diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb
index 457a67380..f1fdc5d96 100644
--- a/app/controllers/api/v1/base_controller.rb
+++ b/app/controllers/api/v1/base_controller.rb
@@ -11,15 +11,6 @@ module Api
class FilterParamError < StandardError; end
- class MutuallyExclusiveParamsError < StandardError
- attr_reader :first_param, :second_param
-
- def initialize(first_param, second_param)
- @first_param = first_param
- @second_param = second_param
- end
- end
-
class PermissionError < StandardError
attr_reader :klass, :mode
@@ -37,14 +28,6 @@ module Api
:bad_request)
end
- rescue_from MutuallyExclusiveParamsError do |e|
- render_error(I18n.t('api.core.errors.mutually_exclusive_params_error.title'),
- I18n.t('api.core.errors.mutually_exclusive_params_error.detail',
- first_param: e.first_param,
- second_param: e.second_param),
- :bad_request)
- end
-
rescue_from FilterParamError do |e|
logger.error e.message
logger.error e.backtrace.join("\n")
diff --git a/app/controllers/api/v1/results_controller.rb b/app/controllers/api/v1/results_controller.rb
index 2566f5d45..084347588 100644
--- a/app/controllers/api/v1/results_controller.rb
+++ b/app/controllers/api/v1/results_controller.rb
@@ -113,6 +113,7 @@ module Api
blob = create_blob_from_params
asset = Asset.create!(file: blob, team: @team)
end
+ asset.post_process_file(@team)
ResultAsset.create!(asset: asset, result: @result)
end
end
@@ -128,6 +129,7 @@ module Api
blob = create_blob_from_params
asset.update!(file: blob)
end
+ asset.post_process_file(@team)
new_checksum = asset.file.blob.checksum
end
@asset_result_updated = old_checksum != new_checksum
diff --git a/app/controllers/api/v2/inventory_item_child_relationships_controller.rb b/app/controllers/api/v2/inventory_item_child_relationships_controller.rb
new file mode 100644
index 000000000..8d6043753
--- /dev/null
+++ b/app/controllers/api/v2/inventory_item_child_relationships_controller.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ class InventoryItemChildRelationshipsController < BaseController
+ before_action :load_team, :load_inventory, :load_inventory_item
+ before_action :load_child_connection, only: %w(show destroy)
+ before_action :check_manage_permission, only: %w(create destroy)
+
+ def index
+ child_connections = timestamps_filter(@inventory_item.child_connections).page(params.dig(:page, :number))
+ .per(params.dig(:page, :size))
+ render jsonapi: child_connections, each_serializer: InventoryItemRelationshipSerializer, include: include_params
+ end
+
+ def show
+ render jsonapi: @child_connection, serializer: InventoryItemRelationshipSerializer, include: include_params
+ end
+
+ def create
+ inventory_item_to_link = RepositoryRow.where(repository: Repository.accessible_by_teams(@team))
+ .find(connection_params[:child_id])
+ child_connection = @inventory_item.child_connections.create!(
+ child: inventory_item_to_link,
+ created_by: current_user,
+ last_modified_by: current_user
+ )
+
+ render jsonapi: child_connection, serializer: InventoryItemRelationshipSerializer, status: :created
+ end
+
+ def destroy
+ @child_connection.destroy!
+ render body: nil
+ end
+
+ private
+
+ def load_child_connection
+ @child_connection = @inventory_item.child_connections.find(params.require(:id))
+ end
+
+ def check_manage_permission
+ raise PermissionError.new(Repository, :manage) unless can_connect_repository_rows?(@inventory)
+ end
+
+ def connection_params
+ raise TypeError unless params.require(:data).require(:type) == 'inventory_item_relationships'
+
+ params.require(:data).require(:attributes).permit(:child_id)
+ end
+
+ def permitted_includes
+ %w(child)
+ end
+ end
+ end
+end
diff --git a/app/controllers/api/v2/inventory_item_parent_relationships_controller.rb b/app/controllers/api/v2/inventory_item_parent_relationships_controller.rb
new file mode 100644
index 000000000..dec716a7d
--- /dev/null
+++ b/app/controllers/api/v2/inventory_item_parent_relationships_controller.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ class InventoryItemParentRelationshipsController < BaseController
+ before_action :load_team, :load_inventory, :load_inventory_item
+ before_action :load_parent_connection, only: %w(show destroy)
+ before_action :check_manage_permission, only: %w(create destroy)
+
+ def index
+ parent_connections = timestamps_filter(@inventory_item.parent_connections).page(params.dig(:page, :number))
+ .per(params.dig(:page, :size))
+ render jsonapi: parent_connections,
+ each_serializer: InventoryItemRelationshipSerializer,
+ include: include_params
+ end
+
+ def show
+ render jsonapi: @parent_connection, serializer: InventoryItemRelationshipSerializer, include: include_params
+ end
+
+ def create
+ inventory_item_to_link = RepositoryRow.where(repository: Repository.accessible_by_teams(@team))
+ .find(connection_params[:parent_id])
+ parent_connection = @inventory_item.parent_connections.create!(
+ parent: inventory_item_to_link,
+ created_by: current_user,
+ last_modified_by: current_user
+ )
+
+ render jsonapi: parent_connection, serializer: InventoryItemRelationshipSerializer, status: :created
+ end
+
+ def destroy
+ @parent_connection.destroy!
+ render body: nil
+ end
+
+ private
+
+ def load_parent_connection
+ @parent_connection = @inventory_item.parent_connections.find(params.require(:id))
+ end
+
+ def check_manage_permission
+ raise PermissionError.new(Repository, :manage) unless can_connect_repository_rows?(@inventory)
+ end
+
+ def connection_params
+ raise TypeError unless params.require(:data).require(:type) == 'inventory_item_relationships'
+
+ params.require(:data).require(:attributes).permit(:parent_id)
+ end
+
+ def permitted_includes
+ %w(parent)
+ end
+ end
+ end
+end
diff --git a/app/controllers/api/v2/inventory_item_relationships_controller.rb b/app/controllers/api/v2/inventory_item_relationships_controller.rb
deleted file mode 100644
index 714147bf1..000000000
--- a/app/controllers/api/v2/inventory_item_relationships_controller.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-module Api
- module V2
- class InventoryItemRelationshipsController < BaseController
- before_action :load_team, :load_inventory, :load_inventory_item
- before_action :check_manage_permission, only: %w(create destroy)
- before_action :load_create_params, only: :create
-
- def create
- parent = @relation == :parent ? @inventory_item : @inventory_item_to_link
- child = @relation == :child ? @inventory_item : @inventory_item_to_link
-
- @connection = RepositoryRowConnection.create!(
- parent_id: parent,
- child_id: child,
- created_by: current_user,
- last_modified_by: current_user
- )
-
- render jsonapi: @connection, serializer: InventoryItemRelationshipSerializer, status: :created
- end
-
- def destroy
- @connection = @inventory_item.parent_connections
- .or(@inventory_item.child_connections)
- .find(params.require(:id))
- @connection.destroy!
- render body: nil
- end
-
- private
-
- def check_manage_permission
- raise PermissionError.new(Repository, :manage) unless can_manage_repository?(@inventory)
- end
-
- def load_create_params
- if connection_params[:parent_id].present? && connection_params[:child_id].present?
- raise MutuallyExclusiveParamsError.new(:parent_id, :child_id)
- end
-
- if connection_params[:parent_id].present?
- @relation = :parent
- @inventory_item_to_link = RepositoryRow.find(connection_params[:parent_id])
- elsif connection_params[:child_id].present?
- @relation = :child
- @inventory_item_to_link = RepositoryRow.find(connection_params[:child_id])
- end
-
- raise ActiveRecord::RecordNotFound unless @inventory_item_to_link
- end
-
- def connection_params
- raise TypeError unless params.require(:data).require(:type) == 'inventory_item_relationships'
-
- params.require(:data).require(:attributes).permit(%i(parent_id child_id))
- end
-
- def permitted_includes
- %w(parent child)
- end
- end
- end
-end
diff --git a/app/controllers/asset_sync_controller.rb b/app/controllers/asset_sync_controller.rb
new file mode 100644
index 000000000..5336babc9
--- /dev/null
+++ b/app/controllers/asset_sync_controller.rb
@@ -0,0 +1,160 @@
+# frozen_string_literal: true
+
+class AssetSyncController < ApplicationController
+ 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'])
+ conflict_response = AssetSyncTokenSerializer.new(conflicting_asset_copy_token).as_json
+ error_message = { message: I18n.t('assets.conflict_error', filename: @asset.file.filename) }
+ render json: conflict_response.merge(error_message), status: :conflict
+ return
+ end
+
+ @asset.file.attach(io: request.body, filename: @asset.file.filename)
+ @asset.update(last_modified_by: current_user)
+
+ log_activity
+
+ render json: AssetSyncTokenSerializer.new(@asset_sync_token).as_json
+ end
+
+ def api_url
+ render plain: Constants::ASSET_SYNC_URL
+ end
+
+ def log_activity
+ assoc ||= @asset.step
+ assoc ||= @asset.result
+
+ case assoc
+ when Step
+ if assoc.protocol.in_module?
+ log_step_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_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_activity(
+ :edit_task_result_file_locally,
+ assoc,
+ file: @asset.file_name,
+ user: current_user.id,
+ result: Result.first.id
+ )
+ 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
+ new_asset.file.attach(
+ io: request.body,
+ filename: "#{@asset.file.filename.base} (#{t('general.copy')}).#{@asset.file.filename.extension}"
+ )
+
+ case @asset.parent
+ when Step
+ StepAsset.create!(step: @asset.step, asset: new_asset)
+ when Result
+ ResultAsset.create!(result: @asset.result, asset: new_asset)
+ end
+
+ 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_step_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_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..03dde54ea 100644
--- a/app/controllers/assets_controller.rb
+++ b/app/controllers/assets_controller.rb
@@ -187,6 +187,7 @@ class AssetsController < ApplicationController
return render_403 unless can_read_team?(@asset.team)
@asset.file.attach(io: params.require(:image), filename: orig_file_name)
+ @asset.last_modified_by = current_user
@asset.save!
create_edit_image_activity(@asset, current_user, :finish_editing)
# release previous image space
diff --git a/app/controllers/design_elements_controller.rb b/app/controllers/design_elements_controller.rb
new file mode 100644
index 000000000..075f85974
--- /dev/null
+++ b/app/controllers/design_elements_controller.rb
@@ -0,0 +1,4 @@
+class DesignElementsController < ApplicationController
+ def index
+ end
+end
diff --git a/app/controllers/experiments_controller.rb b/app/controllers/experiments_controller.rb
index dab2cb19e..f7ded1a67 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,7 +256,7 @@ class ExperimentsController < ApplicationController
# POST: clone_experiment(id)
def clone
- project = current_team.projects.find(move_experiment_param)
+ @project = current_team.projects.find(move_experiment_param)
return render_403 unless can_create_project_experiments?(project)
service = Experiments::CopyExperimentAsTemplateService.call(experiment: @experiment,
@@ -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
@@ -531,6 +538,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
@@ -583,7 +595,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/result_orderable_elements_controller.rb b/app/controllers/result_orderable_elements_controller.rb
index 7f516f7b0..c129836cb 100644
--- a/app/controllers/result_orderable_elements_controller.rb
+++ b/app/controllers/result_orderable_elements_controller.rb
@@ -5,9 +5,9 @@ class ResultOrderableElementsController < ApplicationController
before_action :check_manage_permissions
def reorder
- params[:result_orderable_element_positions].each do |id, position|
- result_element = @result.result_orderable_elements.find(id)
- ActiveRecord::Base.transaction do
+ ActiveRecord::Base.transaction do
+ params[:result_orderable_element_positions].each do |id, position|
+ result_element = @result.result_orderable_elements.find(id)
result_element.insert_at(position)
end
end
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/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/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..7acba00b4 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">