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 ``; } 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 @@ + + + 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..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">