diff --git a/app/assets/javascripts/protocols/index.js b/app/assets/javascripts/protocols/index.js index 73a88c9d6..0cf06128f 100644 --- a/app/assets/javascripts/protocols/index.js +++ b/app/assets/javascripts/protocols/index.js @@ -1,7 +1,8 @@ //= require protocols/import_export/import /* eslint-disable no-use-before-define, no-underscore-dangle, max-len, no-param-reassign */ -/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile _ PerfectSb protocolsIO - protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */ +/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile + protocolFileImportModal PerfectSb protocolsIO + protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */ // Global variables var ProtocolsIndex = (function() { @@ -27,10 +28,10 @@ var ProtocolsIndex = (function() { * Initializes page */ function init() { - window.initActionToolbar(); - window.actionToolbarComponent.setReloadCallback(reloadTable); + // window.initActionToolbar(); + // window.actionToolbarComponent.setReloadCallback(reloadTable); // make room for pagination - window.actionToolbarComponent.setBottomOffset(68); + // window.actionToolbarComponent.setBottomOffset(68); updateButtons(); initProtocolsTable(); initKeywordFiltering(); @@ -38,6 +39,7 @@ var ProtocolsIndex = (function() { initLinkedChildrenModal(); initModals(); initVersionsModal(); + initLocalFileImport(); } function reloadTable() { @@ -267,7 +269,6 @@ var ProtocolsIndex = (function() { let protocolFilters = $($('#protocolFilters').html()); $(protocolFilters).appendTo('.protocols-container .protocol-filters'); - initLocalFileImport(); initProtocolsFilters(); initRowSelection(); }, @@ -649,8 +650,6 @@ var ProtocolsIndex = (function() { } function updateButtons() { - window.actionToolbarComponent.fetchActions({ protocol_ids: rowsSelected }); - $('.dataTables_scrollBody').css('margin-bottom', `${rowsSelected.length > 0 ? 46 : 0}px`); } function initLocalFileImport() { @@ -673,7 +672,6 @@ var ProtocolsIndex = (function() { var importUrl = fileInput.attr('data-import-url'); var teamId = fileInput.attr('data-team-id'); var type = fileInput.attr('data-type'); - if(ev.target.files[0].name.split('.').pop() === 'eln') { importProtocolFromFile( ev.target.files[0], @@ -691,14 +689,14 @@ var ProtocolsIndex = (function() { if (nrSuccessful) { HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success'); - reloadTable(); + window.protocolsTable.$refs.table.updateTable(); } else { HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger'); } } ); } else { - protocolFileImportModal.init(ev.target.files, reloadTable); + protocolFileImportModal.init(ev.target.files, window.protocolsTable.$refs.table.updateTable()); } // $(this).val(''); }); diff --git a/app/assets/javascripts/protocols/protocolsio.js b/app/assets/javascripts/protocols/protocolsio.js index bc6726dc3..abac00fac 100644 --- a/app/assets/javascripts/protocols/protocolsio.js +++ b/app/assets/javascripts/protocols/protocolsio.js @@ -215,7 +215,7 @@ var protocolsIO = function() { animateSpinner(modal, false); modal.modal('hide'); HelperModule.flashAlertMsg(data.message, 'success'); - ProtocolsIndex.reloadTable(); + window.protocolsTable.$refs.table.updateTable(); }, error: function(data) { showFormErrors(modal, data.responseJSON.validation_errors); diff --git a/app/controllers/access_permissions/protocols_controller.rb b/app/controllers/access_permissions/protocols_controller.rb index 9510fdbc7..c086f50f5 100644 --- a/app/controllers/access_permissions/protocols_controller.rb +++ b/app/controllers/access_permissions/protocols_controller.rb @@ -2,18 +2,20 @@ module AccessPermissions class ProtocolsController < ApplicationController + include InputSanitizeHelper + before_action :set_protocol before_action :check_read_permissions, only: %i(show) before_action :check_manage_permissions, except: %i(show) + before_action :available_users, only: %i(new create) - def show; end + def show + render json: @protocol.user_assignments.includes(:user_role, :user).order('users.full_name ASC'), + each_serializer: UserAssignmentSerializer + end def new - @user_assignment = UserAssignment.new( - assignable: @protocol, - assigned_by: current_user, - team: current_team - ) + render json: @available_users, each_serializer: UserSerializer end def edit; end @@ -21,38 +23,34 @@ module AccessPermissions def create ActiveRecord::Base.transaction do created_count = 0 - permitted_create_params[:resource_members].each do |_k, user_assignment_params| - next unless user_assignment_params[:assign] == '1' - - if user_assignment_params[:user_id] == 'all' - @protocol.update!(default_public_user_role_id: user_assignment_params[:user_role_id]) - log_activity(:protocol_template_access_granted_all_team_members, + if permitted_create_params[:user_id] == 'all' + @protocol.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id]) + log_activity(:protocol_template_access_granted_all_team_members, { team: @protocol.team.id, role: @protocol.default_public_user_role&.name }) - else - user_assignment = UserAssignment.find_or_initialize_by( - assignable: @protocol, - user_id: user_assignment_params[:user_id], - team: current_team - ) + else + user_assignment = UserAssignment.find_or_initialize_by( + assignable: @protocol, + user_id: permitted_create_params[:user_id], + team: current_team + ) - user_assignment.update!( - user_role_id: user_assignment_params[:user_role_id], - assigned_by: current_user, - assigned: :manually - ) + user_assignment.update!( + user_role_id: permitted_create_params[:user_role_id], + assigned_by: current_user, + assigned: :manually + ) - created_count += 1 - log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id, + log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id, role: user_assignment.user_role.name }) - end + created_count += 1 end @message = if created_count.zero? - t('access_permissions.create.success', count: t('access_permissions.all_team')) + t('access_permissions.create.success', member_name: t('access_permissions.all_team')) else - t('access_permissions.create.success', count: created_count) + t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name)) end - render :edit + render json: { message: @message } rescue ActiveRecord::RecordInvalid => e Rails.logger.error e.message errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message @@ -146,8 +144,17 @@ module AccessPermissions end def permitted_create_params - params.require(:access_permissions_new_user_form) - .permit(resource_members: %i(assign user_id user_role_id)) + params.require(:user_assignment) + .permit(%i(user_id user_role_id)) + end + + def available_users + # automatically assigned or not assigned to project + @available_users = current_team.users.where( + id: @protocol.user_assignments.automatically_assigned.select(:user_id) + ).or( + current_team.users.where.not(id: @protocol.users.select(:id)) + ).order('users.full_name ASC') end def set_protocol diff --git a/app/controllers/protocols_controller.rb b/app/controllers/protocols_controller.rb index 42d9fd3d0..afd2b0a73 100644 --- a/app/controllers/protocols_controller.rb +++ b/app/controllers/protocols_controller.rb @@ -19,6 +19,7 @@ class ProtocolsController < ApplicationController protocol_status_bar linked_children linked_children_datatable + versions_list permissions ) before_action :switch_team_with_param, only: %i(index protocolsio_index) @@ -75,7 +76,20 @@ class ProtocolsController < ApplicationController layout 'fluid' - def index; end + def index + respond_to do |format| + format.json do + protocols = Lists::ProtocolsService.new(Protocol.latest_available_versions(@current_team), params).call + render json: protocols, + each_serializer: Lists::ProtocolSerializer, + user: current_user, + meta: pagination_dict(protocols) + end + format.html do + render 'index' + end + end + end def datatable render json: ::ProtocolsDatatable.new( @@ -90,8 +104,17 @@ class ProtocolsController < ApplicationController return render_403 unless @protocol.in_repository_published_original? || @protocol.initial_draft? @published_versions = @protocol.published_versions_with_original.order(version_number: :desc) + + if @protocol.draft.present? + draft = @protocol.initial_draft? ? @protocol : @protocol.draft + draft_hash = ProtocolDraftSerializer.new(draft, scope: current_user).as_json + end + render json: { - html: render_to_string(partial: 'protocols/index/protocol_versions_modal') + draft: draft_hash, + versions: @published_versions.map do |version| + ProtocolVersionSerializer.new(version, scope: current_user).as_json + end } end @@ -100,14 +123,35 @@ class ProtocolsController < ApplicationController end def linked_children - render json: { - title: I18n.t('protocols.index.linked_children.title', - protocol: escape_input(@protocol.name)), - html: render_to_string(partial: 'protocols/index/linked_children_modal_body', - locals: { protocol: @protocol }) + if params[:version].present? + records = @protocol.published_versions_with_original + .find_by!(version_number: params[:version]) + .linked_children + else + records = Protocol.where(protocol_type: Protocol.protocol_types[:linked]) + records = records.where(parent_id: @protocol.published_versions) + .or(records.where(parent_id: @protocol.id)) + end + records.preload(my_module: { experiment: :project }).distinct + + render json: records.map { |record| + { + my_module_name: record.my_module.name, + experiment_name: record.my_module.experiment.name, + project_name: record.my_module.experiment.project.name, + my_module_url: protocols_my_module_path(record.my_module), + experiment_url: my_modules_path(experiment_id: record.my_module.experiment.id), + project_url: experiments_path(project_id: record.my_module.experiment.project.id) + } } end + def versions_list + render json: { versions: (@protocol.parent || @protocol).published_versions_with_original + .order(version_number: :desc) + .map(&:version_number) } + end + def linked_children_datatable render json: ::ProtocolLinkedChildrenDatatable.new( view_context, @@ -155,8 +199,12 @@ class ProtocolsController < ApplicationController nil, protocol: @protocol.id) - flash[:success] = I18n.t('protocols.delete_draft_modal.success') - redirect_to protocols_path + if params[:version_modal] + render json: { message: I18n.t('protocols.delete_draft_modal.success') } + else + flash[:success] = I18n.t('protocols.delete_draft_modal.success') + redirect_to protocols_path + end rescue ActiveRecord::RecordNotDestroyed => e Rails.logger.error e.message render json: { message: e.message }, status: :unprocessable_entity @@ -328,17 +376,15 @@ class ProtocolsController < ApplicationController draft = @protocol.save_as_draft(current_user) if draft.invalid? - flash[:error] = draft.errors.full_messages.join(', ') - redirect_to protocols_path + render json: { error: draft.errors.messages.map { |_, value| value }.join(' ') }, status: :unprocessable_entity else log_activity(:protocol_template_draft_created, nil, protocol: @protocol.id) - redirect_to protocol_path(draft) + render json: { url: protocol_path(draft) } end rescue StandardError => e Rails.logger.error(e.message) Rails.logger.error(e.backtrace.join("\n")) - flash[:error] = I18n.t('errors.general') - redirect_to protocols_path + render json: { error: I18n.t('errors.general') }, status: :unprocessable_entity raise ActiveRecord::Rollback end end @@ -823,7 +869,7 @@ class ProtocolsController < ApplicationController actions: Toolbars::ProtocolsService.new( current_user, - protocol_ids: params[:protocol_ids].split(',') + protocol_ids: JSON.parse(params[:items]).map { |i| i['id'] } ).actions } end diff --git a/app/javascript/packs/vue/protocols_list.js b/app/javascript/packs/vue/protocols_list.js new file mode 100644 index 000000000..5768aaafa --- /dev/null +++ b/app/javascript/packs/vue/protocols_list.js @@ -0,0 +1,12 @@ +import { createApp } from 'vue/dist/vue.esm-bundler.js'; +import PerfectScrollbar from 'vue3-perfect-scrollbar'; +import ProtocolsTable from '../../vue/protocols/table.vue'; +import { mountWithTurbolinks } from './helpers/turbolinks.js'; + +const app = createApp(); +app.component('ProtocolsTable', ProtocolsTable); +app.config.globalProperties.i18n = window.I18n; +app.use(PerfectScrollbar); +window.protocolsTable = mountWithTurbolinks(app, '#ProtocolsTable', () => { + delete window.protocolsTable; +}); diff --git a/app/javascript/vue/my_modules/modals/tags.vue b/app/javascript/vue/my_modules/modals/tags.vue index a59cbb792..ceb39647d 100644 --- a/app/javascript/vue/my_modules/modals/tags.vue +++ b/app/javascript/vue/my_modules/modals/tags.vue @@ -61,8 +61,8 @@ py-2.5 hover:bg-sn-super-light-grey" @click="createTag()" > -