From 4671754d5266e69907349e21dd2b152843e661fe Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 27 Dec 2023 20:09:36 +0100 Subject: [PATCH] Migrate protocols table [SCI-9802] --- app/assets/javascripts/protocols/index.js | 20 +- .../javascripts/protocols/protocolsio.js | 2 +- .../protocols_controller.rb | 69 ++-- app/controllers/protocols_controller.rb | 76 ++++- app/javascript/packs/vue/protocols_list.js | 12 + app/javascript/vue/my_modules/modals/tags.vue | 4 +- app/javascript/vue/projects/list.vue | 4 +- .../vue/protocol/protocolMetadata.vue | 164 ++++++---- .../protocols/modals/linked_my_modules.vue | 104 ++++++ app/javascript/vue/protocols/modals/new.vue | 90 ++++++ .../vue/protocols/modals/versions.vue | 181 +++++++++++ .../vue/protocols/renderers/keywords.vue | 23 ++ .../protocols/renderers/linked_my_modules.vue | 22 ++ .../vue/protocols/renderers/versions.vue | 33 ++ app/javascript/vue/protocols/table.vue | 298 ++++++++++++++++++ .../vue/shared/datatable/toolbar.vue | 36 ++- app/models/protocol.rb | 1 + app/serializers/lists/protocol_serializer.rb | 104 ++++++ app/serializers/protocol_draft_serializer.rb | 43 +++ app/serializers/protocol_serializer.rb | 4 +- .../protocol_version_serializer.rb | 36 +++ app/serializers/user_assignment_serializer.rb | 2 + app/services/lists/protocols_service.rb | 88 ++++++ app/services/toolbars/protocols_service.rb | 28 +- app/views/protocols/index.html.erb | 58 ++-- config/routes.rb | 1 + config/webpack/webpack.config.js | 5 +- 27 files changed, 1321 insertions(+), 187 deletions(-) create mode 100644 app/javascript/packs/vue/protocols_list.js create mode 100644 app/javascript/vue/protocols/modals/linked_my_modules.vue create mode 100644 app/javascript/vue/protocols/modals/new.vue create mode 100644 app/javascript/vue/protocols/modals/versions.vue create mode 100644 app/javascript/vue/protocols/renderers/keywords.vue create mode 100644 app/javascript/vue/protocols/renderers/linked_my_modules.vue create mode 100644 app/javascript/vue/protocols/renderers/versions.vue create mode 100644 app/javascript/vue/protocols/table.vue create mode 100644 app/serializers/lists/protocol_serializer.rb create mode 100644 app/serializers/protocol_draft_serializer.rb create mode 100644 app/serializers/protocol_version_serializer.rb create mode 100644 app/services/lists/protocols_service.rb 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()" > -
-
+
+
{{ i18n.t('experiments.canvas.modal_manage_tags.create_new') }}
diff --git a/app/javascript/vue/projects/list.vue b/app/javascript/vue/projects/list.vue index eaaf5d9dd..804c80692 100644 --- a/app/javascript/vue/projects/list.vue +++ b/app/javascript/vue/projects/list.vue @@ -332,8 +332,8 @@ export default { }, move(event, rows) { this.objectsToMove = rows; - }, - }, + } + } }; diff --git a/app/javascript/vue/protocol/protocolMetadata.vue b/app/javascript/vue/protocol/protocolMetadata.vue index 751263520..08d00def9 100644 --- a/app/javascript/vue/protocol/protocolMetadata.vue +++ b/app/javascript/vue/protocol/protocolMetadata.vue @@ -2,7 +2,8 @@
- + - - - + + +
@@ -87,77 +97,95 @@
+ + + diff --git a/app/javascript/vue/protocols/modals/linked_my_modules.vue b/app/javascript/vue/protocols/modals/linked_my_modules.vue new file mode 100644 index 000000000..1a07a4628 --- /dev/null +++ b/app/javascript/vue/protocols/modals/linked_my_modules.vue @@ -0,0 +1,104 @@ + + + diff --git a/app/javascript/vue/protocols/modals/new.vue b/app/javascript/vue/protocols/modals/new.vue new file mode 100644 index 000000000..d9ccd297b --- /dev/null +++ b/app/javascript/vue/protocols/modals/new.vue @@ -0,0 +1,90 @@ + + + diff --git a/app/javascript/vue/protocols/modals/versions.vue b/app/javascript/vue/protocols/modals/versions.vue new file mode 100644 index 000000000..30f9dc8d4 --- /dev/null +++ b/app/javascript/vue/protocols/modals/versions.vue @@ -0,0 +1,181 @@ + + + diff --git a/app/javascript/vue/protocols/renderers/keywords.vue b/app/javascript/vue/protocols/renderers/keywords.vue new file mode 100644 index 000000000..84ecdad40 --- /dev/null +++ b/app/javascript/vue/protocols/renderers/keywords.vue @@ -0,0 +1,23 @@ + + + diff --git a/app/javascript/vue/protocols/renderers/linked_my_modules.vue b/app/javascript/vue/protocols/renderers/linked_my_modules.vue new file mode 100644 index 000000000..cf9ea6642 --- /dev/null +++ b/app/javascript/vue/protocols/renderers/linked_my_modules.vue @@ -0,0 +1,22 @@ + + + diff --git a/app/javascript/vue/protocols/renderers/versions.vue b/app/javascript/vue/protocols/renderers/versions.vue new file mode 100644 index 000000000..fcc932bd0 --- /dev/null +++ b/app/javascript/vue/protocols/renderers/versions.vue @@ -0,0 +1,33 @@ + + + diff --git a/app/javascript/vue/protocols/table.vue b/app/javascript/vue/protocols/table.vue new file mode 100644 index 000000000..0d93ec01d --- /dev/null +++ b/app/javascript/vue/protocols/table.vue @@ -0,0 +1,298 @@ + + + diff --git a/app/javascript/vue/shared/datatable/toolbar.vue b/app/javascript/vue/shared/datatable/toolbar.vue index d889cb01f..d119eeb55 100644 --- a/app/javascript/vue/shared/datatable/toolbar.vue +++ b/app/javascript/vue/shared/datatable/toolbar.vue @@ -1,18 +1,30 @@